C аргументы по умолчанию

202

Есть ли способ указать аргументы по умолчанию для функции в C?

  • 4
    просто хочу немного лучше C, а не C ++. думаю С +. с множеством мелких улучшений, взятых из C ++, но не большой беспорядок. И, пожалуйста, никакой другой линк-загрузчик. должен быть просто еще один шаг, подобный препроцессору. стандартизированы. везде...
  • 0
    Связанный вопрос, который я не видел в списке на боковой панели.
Показать ещё 2 комментария
Теги:
arguments
default-value

17 ответов

111
Лучший ответ

Не совсем. Единственный способ - написать функцию varargs и вручную заполнить значения по умолчанию для аргументов, которые вызывающий не передает.

  • 21
    Я ненавижу отсутствие проверки при использовании varargs.
  • 14
    Как хорошо, вы должны; Я на самом деле не рекомендую это; Я просто хотел донести, что это возможно.
Показать ещё 7 комментариев
222

Ничего себе, здесь все такие пессимисты. Ответ - да.

Это не тривиально: к концу мы будем иметь основную функцию, поддерживающую структуру, функцию-оболочку и макрос вокруг функции обертки. В моей работе у меня есть набор макросов для автоматизации всего этого; один раз вы понимаете поток, вам будет легко сделать то же самое.

Я написал это в другом месте, поэтому здесь приведена подробная внешняя ссылка, чтобы дополнить резюме здесь: http://modelingwithdata.org/arch/00000022.htm

Мы хотели бы повернуть

double f(int i, double x)

в функцию, которая принимает значения по умолчанию (i = 8, x = 3.14). Определите сопутствующую структуру:

typedef struct {
    int i;
    double x;
} f_args;

Переименуйте свою функцию f_base и определите функцию обертки, которая устанавливает значения по умолчанию и вызовы база:

double var_f(f_args in){
    int i_out = in.i ? in.i : 8;
    double x_out = in.x ? in.x : 3.14;
    return f_base(i_out, x_out);
}

Теперь добавьте макрос, используя C variadic макросы. Таким образом, пользователи не должны знать, что они фактически заполняя структуру f_args и думая, что они делают обычное:

#define f(...) var_f((f_args){__VA_ARGS__});

ОК, теперь все следующее будет работать:

f(3, 8);      //i=3, x=8
f(.i=1, 2.3); //i=1, x=2.3
f(2);         //i=2, x=3.14
f(.x=9.2);    //i=8, x=9.2

Проверьте правила о том, как составные инициализаторы устанавливают значения по умолчанию для точных правил.

Одна вещь, которая не сработает: f(0), потому что мы не можем различать недостающее значение и нуль. По моему опыту, это то, на что нужно следить, но можно позаботиться о возникает необходимость - половина времени вашего по умолчанию действительно равна нулю.

Я столкнулся с трудностями в написании этого, потому что я думаю, что именованные аргументы и значения по умолчанию  действительно сделать кодирование на C проще и веселее. А также C является удивительным для того, чтобы быть таким простым и до сих пор достаточно, чтобы сделать все возможное.

  • 14
    +1 креатив! Он имеет свои ограничения, но также приносит именованные параметры в таблицу. Обратите внимание, что {} (пустой инициализатор) является ошибкой C99.
  • 27
    Тем не менее, здесь есть кое-что отличное для вас: стандарт позволяет указывать именованные элементы несколько раз, позднее переопределение. Таким образом, только для именованных параметров можно решить проблему значений по умолчанию и разрешить пустой вызов. #define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
Показать ещё 8 комментариев
141

Да.:-) Но не так, как вы ожидали.

int f1(int arg1, double arg2, char* name, char *opt);

int f2(int arg1, double arg2, char* name)
{
  return f1(arg1, arg2, name, "Some option");
}

К сожалению, C не позволяет вам перегружать методы, поэтому вы получите две разные функции. Тем не менее, вызывая f2, вы на самом деле вызываете f1 со значением по умолчанию. Это решение "Не повторяй себя", которое помогает избежать копирования/вставки существующего кода.

  • 21
    Кстати, я бы предпочел использовать число в конце функции, чтобы указать количество аргументов, которые он принимает. Делает это проще, чем просто использовать любое произвольное число. :)
  • 4
    Это, безусловно, лучший ответ, потому что он демонстрирует простой способ достижения той же цели. У меня есть функция, которая является частью фиксированного API, которую я не хочу менять, но мне нужно, чтобы она приняла новый параметр. Конечно, это настолько ослепительно очевидно, что я пропустил это (застрял при мысли о параметре по умолчанию!)
Показать ещё 1 комментарий
33

Мы можем создавать функции, которые используют именованные параметры (только) для значений по умолчанию. Это продолжение bk. Ответ.

#include <stdio.h>                                                               

struct range { int from; int to; int step; };
#define range(...) range((struct range){.from=1,.to=10,.step=1, __VA_ARGS__})   

/* use parentheses to avoid macro subst */             
void (range)(struct range r) {                                                     
    for (int i = r.from; i <= r.to; i += r.step)                                 
        printf("%d ", i);                                                        
    puts("");                                                                    
}                                                                                

int main() {                                                                     
    range();                                                                    
    range(.from=2, .to=4);                                                      
    range(.step=2);                                                             
}    

Стандарт C99 определяет, что более поздние имена в инициализации переопределяют предыдущие элементы. У нас также могут быть некоторые стандартные позиционные параметры, просто измените подпись макроса и функции. Параметры значения по умолчанию могут использоваться только в стиле именованных параметров.

Выход программы:

1 2 3 4 5 6 7 8 9 10 
2 3 4 
1 3 5 7 9
  • 2
    Это кажется более простой и понятной реализацией, чем решение Wim ten Brink или BK. Есть ли какие-либо недостатки в этой реализации, которые другие также не имеют?
18

Нет.

Не поддерживается даже самый последний стандарт C99.

  • 0
    просто нет было бы еще лучше;)
  • 17
    @kevindtimm: Это невозможно, ТАК требует минимальной длины ответов. Я старался. :)
Показать ещё 1 комментарий
15

Нет, это функция языка С++.

12

OpenCV использует что-то вроде:

/* in the header file */

#ifdef __cplusplus
    /* in case the compiler is a C++ compiler */
    #define DEFAULT_VALUE(value) = value
#else
    /* otherwise, C compiler, do nothing */
    #define DEFAULT_VALUE(value)
#endif

void window_set_size(unsigned int width  DEFAULT_VALUE(640),
                     unsigned int height DEFAULT_VALUE(400));

Если пользователь не знает, что он должен написать, этот трюк может быть полезным:

Изображение 4806

12

Еще один вариант использует struct s:

struct func_opts {
  int    arg1;
  char * arg2;
  int    arg3;
};

void func(int arg, struct func_opts *opts)
{
    int arg1 = 0, arg3 = 0;
    char *arg2 = "Default";
    if(opts)
      {
        if(opts->arg1)
            arg1 = opts->arg1;
        if(opts->arg2)
            arg2 = opts->arg2;
        if(opts->arg3)
            arg3 = opts->arg3;
      }
    // do stuff
}

// call with defaults
func(3, NULL);

// also call with defaults
struct func_opts opts = {0};
func(3, &opts);

// set some arguments
opts.arg3 = 3;
opts.arg2 = "Yes";
func(3, &opts);
11

Вероятно, лучший способ сделать это (что может быть или не быть возможным в вашем случае в зависимости от вашей ситуации) - это перейти на С++ и использовать его как "лучше C". Вы можете использовать С++ без использования классов, шаблонов, перегрузки оператора или других расширенных функций.

Это даст вам вариант C с перегрузкой функций и параметрами по умолчанию (и любыми другими функциями, которые вы выбрали для использования). Вам просто нужно быть немного дисциплинированным, если вы действительно серьезно относитесь к использованию только ограниченного подмножества С++.

Многие люди скажут, что это ужасная идея использовать С++ таким образом, и у них может быть точка. Но это просто мнение; Я считаю правильным использовать возможности С++, с которыми вам удобно, без необходимости покупать все это. Я думаю, что значительная часть причины успеха С++ заключается в том, что он очень быстро использовал его в первые дни именно таким образом.

11

Короткий ответ: Нет.

Слегка более длинный ответ: Существует старый, старый метод обхода, где вы передаете строку, которую вы анализируете для необязательных аргументов:

int f(int arg1, double arg2, char* name, char *opt);

где опция может включать пару "имя = значение" или что-то еще, и которую вы бы назвали как

n = f(2,3.0,"foo","plot=yes save=no");

Очевидно, это только время от времени полезно. Как правило, если вам нужен один интерфейс для семейства функций.


Вы все еще находите этот подход в кодах физики частиц, которые написаны профессиональными программами в С++ (например, ROOT). Основное преимущество заключается в том, что он может быть расширен почти на неопределенный срок, сохраняя при этом совместимость с обратной связью.

  • 2
    Объедините это с varargs, и вы получите массу удовольствия!
  • 4
    Я хотел бы использовать пользовательскую struct и сделать так, чтобы вызывающий объект struct ее, заполнил поля для различных параметров и затем передал ее по адресу или передал NULL для параметров по умолчанию.
Показать ещё 2 комментария
8

Нет.

  • 2
    То же решение, что и для комментариев?
  • 1
    какой обходной путь? Я вижу, что это 20202020 в шестнадцатеричном 20202020 , но как мне его ввести?
Показать ещё 1 комментарий
3

Да, с функциями C99 вы можете это сделать. Это работает без определения новых структур данных или так и без функции, которая должна решать во время выполнения, как она была вызвана, и без каких-либо вычислительных затрат.

Подробное объяснение см. в моем сообщении в

http://gustedt.wordpress.com/2010/06/03/default-arguments-for-c99/

Jens

  • 0
    Прочтите также мой ответ, полученный от вашего.
  • 0
    Приведите пример.
3

Нет, но вы можете использовать набор функций (или макросов) для аппроксимации с использованием аргументов по умолчанию:

// No default args
int foo3(int a, int b, int c)
{
    return ...;
}

// Default 3rd arg
int foo2(int a, int b)
{
    return foo3(a, b, 0);  // default c
}

// Default 2nd and 3rd args
int foo1(int a)
{
    return foo3(a, 1, 0);  // default b and c
}
  • 0
    То, что я сказал....
2

Как правило, нет, но в gcc. Вы можете сделать последний параметр funcA() необязательным с макросом.

В funcB() я использую специальное значение (-1), чтобы сигнализировать, что мне нужно значение по умолчанию для параметра "b".

#include <stdio.h> 

int funcA( int a, int b, ... ){ return a+b; }
#define funcA( a, ... ) funcA( a, ##__VA_ARGS__, 8 ) 


int funcB( int a, int b ){
  if( b == -1 ) b = 8;
  return a+b;
}

int main(void){
  printf("funcA(1,2): %i\n", funcA(1,2) );
  printf("funcA(1):   %i\n", funcA(1)   );

  printf("funcB(1, 2): %i\n", funcB(1, 2) );
  printf("funcB(1,-1): %i\n", funcB(1,-1) );
}
1

Я улучшил Jens Gustedts ответ, чтобы:

  • используемые встроенные функции arent
  • значения по умолчанию вычисляются во время предварительной обработки
  • модульные повторно используемые макросы
  • можно установить ошибку компилятора, которая в значительной степени соответствует случаю недостаточных аргументов для разрешенных значений по умолчанию
  • Аргумент по умолчанию, необходимый для формирования хвоста списка параметров, если типы аргументов остаются недвусмысленными
  • взаимодействует с C11 _Generic
  • меняйте имя функции на количество аргументов!

variadic.h:

#ifndef VARIADIC

#define _NARG2(_0, _1, _2, ...) _2
#define NUMARG2(...) _NARG2(__VA_ARGS__, 2, 1, 0)
#define _NARG3(_0, _1, _2, _3, ...) _3
#define NUMARG3(...) _NARG3(__VA_ARGS__, 3, 2, 1, 0)
#define _NARG4(_0, _1, _2, _3, _4, ...) _4
#define NUMARG4(...) _NARG4(__VA_ARGS__, 4, 3, 2, 1, 0)
#define _NARG5(_0, _1, _2, _3, _4, _5, ...) _5
#define NUMARG5(...) _NARG5(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
#define _NARG6(_0, _1, _2, _3, _4, _5, _6, ...) _6
#define NUMARG6(...) _NARG6(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
#define _NARG7(_0, _1, _2, _3, _4, _5, _6, _7, ...) _7
#define NUMARG7(...) _NARG7(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)
#define _NARG8(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) _8
#define NUMARG8(...) _NARG8(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define _NARG9(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) _9
#define NUMARG9(...) _NARG9(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define __VARIADIC(name, num_args, ...) name ## _ ## num_args (__VA_ARGS__)
#define _VARIADIC(name, num_args, ...) name (__VARIADIC(name, num_args, __VA_ARGS__))
#define VARIADIC(name, num_args, ...) _VARIADIC(name, num_args, __VA_ARGS__)
#define VARIADIC2(name, num_args, ...) __VARIADIC(name, num_args, __VA_ARGS__)

// Vary function name by number of arguments supplied
#define VARIADIC_NAME(name, num_args) name ## _ ## num_args ## _name ()
#define NVARIADIC(name, num_args, ...) _VARIADIC(VARIADIC_NAME(name, num_args), num_args, __VA_ARGS__)

#endif

Упрощенный сценарий использования:

const uint32*
uint32_frombytes(uint32* out, const uint8* in, size_t bytes);

/*
The output buffer defaults to NULL if not provided.
*/

#include "variadic.h"

#define uint32_frombytes_2(   b, c) NULL, b, c
#define uint32_frombytes_3(a, b, c)    a, b, c
#define uint32_frombytes(...) VARIADIC(uint32_frombytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

И с помощью _Generic:

const uint8*
uint16_tobytes(const uint16* in, uint8* out, size_t bytes);

const uint16*
uint16_frombytes(uint16* out, const uint8* in, size_t bytes);

const uint8*
uint32_tobytes(const uint32* in, uint8* out, size_t bytes);

const uint32*
uint32_frombytes(uint32* out, const uint8* in, size_t bytes);

/*
The output buffer defaults to NULL if not provided.
Generic function name supported on the non-uint8 type, except where said type
is unavailable because the argument for output buffer was not provided.
*/

#include "variadic.h"

#define   uint16_tobytes_2(a,    c) a, NULL, c
#define   uint16_tobytes_3(a, b, c) a,    b, c
#define   uint16_tobytes(...) VARIADIC(  uint16_tobytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

#define uint16_frombytes_2(   b, c) NULL, b, c
#define uint16_frombytes_3(a, b, c)    a, b, c
#define uint16_frombytes(...) VARIADIC(uint16_frombytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

#define   uint32_tobytes_2(a,    c) a, NULL, c
#define   uint32_tobytes_3(a, b, c) a,    b, c
#define   uint32_tobytes(...) VARIADIC(  uint32_tobytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

#define uint32_frombytes_2(   b, c) NULL, b, c
#define uint32_frombytes_3(a, b, c)    a, b, c
#define uint32_frombytes(...) VARIADIC(uint32_frombytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

#define   tobytes(a, ...) _Generic((a),                                                                                                 \
                                   const uint16*: uint16_tobytes,                                                                       \
                                   const uint32*: uint32_tobytes)  (VARIADIC2(  uint32_tobytes, NUMARG3(a, __VA_ARGS__), a, __VA_ARGS__))

#define frombytes(a, ...) _Generic((a),                                                                                                 \
                                         uint16*: uint16_frombytes,                                                                     \
                                         uint32*: uint32_frombytes)(VARIADIC2(uint32_frombytes, NUMARG3(a, __VA_ARGS__), a, __VA_ARGS__))

И с выбором имени вариационной функции, который нельзя комбинировать с _Generic:

// winternitz() with 5 arguments is replaced with merkle_lamport() on those 5 arguments.

#define   merkle_lamport_5(a, b, c, d, e) a, b, c, d, e
#define   winternitz_7(a, b, c, d, e, f, g) a, b, c, d, e, f, g
#define   winternitz_5_name() merkle_lamport
#define   winternitz_7_name() winternitz
#define   winternitz(...) NVARIADIC(winternitz, NUMARG7(__VA_ARGS__), __VA_ARGS__)
0

Да, вы можете сделать что-то simulair, здесь вы должны знать разные списки аргументов, которые вы можете получить, но у вас есть одна и та же функция для обработки всех.

typedef enum { my_input_set1 = 0, my_input_set2, my_input_set3} INPUT_SET;

typedef struct{
    INPUT_SET type;
    char* text;
} input_set1;

typedef struct{
    INPUT_SET type;
    char* text;
    int var;
} input_set2;

typedef struct{
    INPUT_SET type;
    int text;
} input_set3;

typedef union
{
    INPUT_SET type;
    input_set1 set1;
    input_set2 set2;
    input_set3 set3;
} MY_INPUT;

void my_func(MY_INPUT input)
{
    switch(input.type)
    {
        case my_input_set1:
        break;
        case my_input_set2:
        break;
        case my_input_set3:
        break;
        default:
        // unknown input
        break;
    }
}
-3

Почему мы не можем этого сделать.

Дайте опциональному аргументу значение по умолчанию. Таким образом, вызывающая функция не обязательно должна передавать значение аргумента. Аргумент принимает значение по умолчанию. И легко этот аргумент становится необязательным для клиента.

Например,

void foo (int a, int b = 0);

Здесь b - необязательный аргумент.

  • 9
    Проблема в том, что C не поддерживает необязательные аргументы или перегруженные функции, поэтому прямое решение не компилируется.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню