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


279

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


6
просто хочу немного лучше C, а не C ++. думаю С +. с множеством мелких улучшений, взятых из C ++, но не большой беспорядок. И, пожалуйста, никакой другой линк-загрузчик. должен быть просто еще один шаг, подобный препроцессору. стандартизированы. везде ...
Иво Уэлч

Связанный вопрос, который я не видел в списке на боковой панели.
Шелби Мур III

Я бы сказал, перестать быть варваром и научиться хорошо использовать C ++ (11, ...) - jk! / я тушит пламя ... но ... ты полюбишь это ... хахаха, я не могу с собой поделать, извини.
настроение

Ответы:


147

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


22
Я ненавижу отсутствие проверки при использовании varargs.
dmckee --- котенок экс-модератора

16
Как хорошо, вы должны; Я на самом деле не рекомендую это; Я просто хотел донести, что это возможно.
Эли Кортрайт

4
Однако, как вы хотите проверить, передает ли вызывающая сторона аргумент или нет? Я думаю, чтобы это сработало, разве у вас нет звонящего, чтобы сказать, что он не прошел? Я думаю, что это делает весь подход несколько менее применимым - вызывающий может также вызвать функцию с другим именем.
Йоханнес Шауб -

3
open(2)Системный вызов использует это для необязательного аргумента , который может присутствовать в зависимости от требуемых параметров, и printf(3)считывает строку формата , которая определяет , сколько аргументов там будет. Оба используют varargs довольно безопасно и эффективно, и, хотя вы, конечно, можете их испортить, printf()особенно это кажется довольно популярным.
Крис Латс

4
@Eli: не все компиляторы C являются gcc. Существует некоторая продвинутая магия компилятора, которая предупреждает, когда ваши аргументы printf () не соответствуют вашей строке формата. И я не думаю, что возможно получить аналогичные предупреждения для ваших собственных функций переменной (если они не используют тот же стиль строки формата).
Tomlogic

280

Вау, все здесь такие пессимисты. Ответ - да.

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

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

Мы хотели бы включить

double f(int i, double x)

в функцию, которая принимает значения по умолчанию (я = 8, х = 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);
}

Теперь добавьте макрос, используя переменные макросы Си. Таким образом, пользователям не нужно знать, что они на самом деле 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 проще и еще веселее. И Си потрясающе, потому что он такой простой и все еще имеет достаточно, чтобы сделать все это возможным.


16
+1 креатив! Он имеет свои ограничения, но также приносит именованные параметры в таблицу. Обратите внимание, что {}(пустой инициализатор) является ошибкой C99.
u0b34a0f6ae

28
Тем не менее, здесь есть кое-что отличное для вас: стандарт позволяет указывать именованные элементы несколько раз, позднее переопределение. Таким образом, только для именованных параметров можно решить проблему значений по умолчанию и разрешить пустой вызов. #define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
u0b34a0f6ae

3
Я надеюсь, что ошибки компилятора читабельны, но это отличная техника! Почти выглядит как питонские кварги.
totowtwo два

6
@RunHolt Хотя, конечно, проще, объективно не лучше; Именованные параметры имеют такие преимущества, как удобство чтения вызовов (за счет удобства чтения исходного кода). Один лучше для разработчиков исходного кода, другой лучше для пользователей функции. Немного поспешно просто выбросить "это лучше!"
Алиса

3
@DawidPi: C11 6.7.9 (19), при инициализации агрегатов: «все подобъекты, которые не инициализированы явно, должны быть неявно инициализированы так же, как объекты, имеющие статическую длительность хранения» | NULL | \ 0. [Это было в c99 тоже.]
БК.

161

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

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 со значением по умолчанию. Это решение «Не повторяй себя», которое поможет вам избежать копирования / вставки существующего кода.


24
Кстати, я бы предпочел использовать число в конце функции, чтобы указать количество аргументов, которые он принимает. Делает это проще, чем просто использовать любое произвольное число. :)
Macke

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

4
f2 также может быть макросом препроцессора
osvein

41

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

#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. Есть ли какие-либо недостатки в этой реализации, которые другие также не имеют?
Стивенм

24

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));

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

пример использования


19

Нет.

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


просто нет было бы еще лучше;)
KevinDTimm

21
@kevindtimm: Это невозможно, ТАК требует минимальной длины ответов. Я попытался. :)
раскручиваемся

2
Пожалуйста, обратитесь к моему ответу. :)
хаос


14

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

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

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

где opt может включать в себя пару «имя = значение» или что-то, что вы бы назвали как

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

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


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


2
Объедините это с varargs, и вы получите массу удовольствия!
Дэвид Торнли

5
Я бы использовал обычай struct, чтобы вызывающий сделал его, заполнил поля для разных опций, а затем передал его по адресу или передал NULLопции по умолчанию.
Крис Латс

Копирование шаблонов кода из ROOT - ужасная идея!
Innisfree

13

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

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

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


13

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

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);

9

Нет.


2
какой обходной путь? Я вижу, что это 20202020в шестнадцатеричном формате, но как мне его напечатать?
Лазер

5

Еще один трюк с использованием макросов:

#include <stdio.h>

#define func(...) FUNC(__VA_ARGS__, 15, 0)
#define FUNC(a, b, ...) func(a, b)

int (func)(int a, int b)
{
    return a + b;
}

int main(void)
{
    printf("%d\n", func(1));
    printf("%d\n", func(1, 2));
    return 0;
}

Если передается только один аргумент, bполучает значение по умолчанию (в данном случае 15)


4

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

// 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
}

4

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

Для подробного объяснения смотрите мой пост на

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

Jens


Прочтите также мой ответ, полученный от вашего.
Шелби Мур III

1
Приведите пример.
user877329

3

Обычно нет, но в 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

Я улучшил ответ Йенса Гастедта так, чтобы:

  1. встроенные функции не используются
  2. значения по умолчанию вычисляются во время предварительной обработки
  3. модульные многоразовые макросы
  4. возможно установить ошибку компилятора, которая значимо соответствует случаю недостаточных аргументов для допустимых значений по умолчанию
  5. по умолчанию не требуется формировать хвост списка параметров, если типы аргументов останутся однозначными
  6. взаимодействует с C11 _Generic
  7. измените имя функции на количество аргументов!

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__)

1

ДА

Через макросы

3 параметра:

#define my_func2(...) my_func3(__VA_ARGS__, 0.5)
#define my_func1(...) my_func2(__VA_ARGS__, 10)
#define VAR_FUNC(_1, _2, _3, NAME, ...) NAME
#define my_func(...) VAR_FUNC(__VA_ARGS__, my_func3, my_func2, my_func1)(__VA_ARGS__)

void my_func3(char a, int b, float c) // b=10, c=0.5
{
    printf("a=%c; b=%d; c=%f\n", a, b, c);
}

Если вы хотите 4-й аргумент, то нужно добавить дополнительный my_func3. Обратите внимание на изменения в VAR_FUNC, my_func2 и my_func

4 параметра:

#define my_func3(...) my_func4(__VA_ARGS__, "default") // <== New function added
#define my_func2(...) my_func3(__VA_ARGS__, (float)1/2)
#define my_func1(...) my_func2(__VA_ARGS__, 10)
#define VAR_FUNC(_1, _2, _3, _4, NAME, ...) NAME
#define my_func(...) VAR_FUNC(__VA_ARGS__, my_func4, my_func3, my_func2, my_func1)(__VA_ARGS__)

void my_func4(char a, int b, float c, const char* d) // b=10, c=0.5, d="default"
{
    printf("a=%c; b=%d; c=%f; d=%s\n", a, b, c, d);
}

Исключение составляет только то, что переменным с плавающей запятой не могут быть заданы значения по умолчанию ( если только это не последний аргумент, как в случае с 3 параметрами ), потому что им нужен период ('.'), Который не принимается внутри аргументов макроса. Но можно найти обходной путь, как показано в макросе my_func2 ( в случае с 4 параметрами )

программа

int main(void)
{
    my_func('a');
    my_func('b', 20);
    my_func('c', 200, 10.5);
    my_func('d', 2000, 100.5, "hello");

    return 0;
}

Вывод:

a=a; b=10; c=0.500000; d=default                                                                                                                                                  
a=b; b=20; c=0.500000; d=default                                                                                                                                                  
a=c; b=200; c=10.500000; d=default                                                                                                                                                
a=d; b=2000; c=100.500000; d=hello  

0

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

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;
    }
}

-6

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

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

Например,

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

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


10
Проблема в том, что C не поддерживает необязательные аргументы или перегруженные функции, поэтому прямое решение не компилируется.
Томас
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.