C аргументи за замовчуванням


279

Чи є спосіб вказати аргументи за замовчуванням для функції в C?


6
просто хочу трохи краще C, а не C ++. подумайте C +. з різноманітними дрібними поліпшеннями, які вивели з C ++, але не з великим безладом І, будь ласка, немає різного завантажувача посилань. має бути лише черговим кроком, схожим на препроцесор. стандартизовані. скрізь ...
ivo Welch

Пов'язане питання, яке я не бачив у списку в бічній панелі
Шелбі Мур III,

Я б сказав, перестань бути варваром і навчись добре використовувати C ++ (11, ...) - jk! / мене гасить полум’я ... але ... вам сподобається це ... ха-ха, я не можу собі допомогти, вибачте.
moodboom

Відповіді:


147

Не зовсім. Єдиним способом було б написати функцію varargs та вручну заповнити значення за замовчуванням для аргументів, які абонент не передає.


22
Я ненавиджу відсутність перевірки під час використання varargs.
dmckee --- кошеня колишнього модератора

16
Як і слід; Я насправді цього не рекомендую; Я просто хотів донести, що це можливо.
Eli Courtwright

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

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

4
@Eli: Не всі компілятори C - це gcc. Існує деяка вдосконалена магія компілятора, щоб він попереджав, коли аргументи printf () не відповідають вашому рядку формату. І я не думаю, що можливо отримати подібні попередження для ваших власних різноманітних функцій (якщо вони не використовують той самий стиль рядка формату).
tomlogic

280

Ух, всі тут такі песимісти. Відповідь - так.

Це не тривіально: до кінця ми матимемо основну функцію, підтримуючу структуру, функцію обгортки та макрос навколо функції обгортки. У своїй роботі я маю набір макросів для автоматизації всього цього; як тільки ви зрозумієте потік, вам буде легко зробити те ж саме.

Я написав це деінде, тому ось детальне зовнішнє посилання на доповнення резюме тут: 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. Таким чином, користувачі не повинні знати, що вони насправді 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 дивовижний тим, що він такий простий і все ще має достатньо для того, щоб зробити все це можливим.


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
FWIW, я вважаю за краще використовувати число в кінці функції, щоб вказати кількість аргументів, які він займає. Це простіше, ніж просто використовувати будь-яке довільне число. :)
Макке

4
Це, безумовно, найкраща відповідь, оскільки це демонструє простий спосіб досягти тієї ж мети. У мене є функція, яка є частиною фіксованого API, яку я не хочу змінювати, але мені потрібна, щоб взяти новий параметр. Звичайно, це настільки сліпо очевидно, що я його пропустив (застряг на думці про параметр за замовчуванням!)
RunHolt

4
f2 також може бути макросом препроцесора
osvein

41

Ми можемо створити функції, які використовують іменовані параметри (лише) для значень за замовчуванням. Це продовження відповіді 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. Чи є якісь недоліки цієї реалізації, якими інші також не володіють?
stephenmm

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 може включати пару "name = value" або щось таке, і що ви б хотіли назвати

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/

Єнс


Дивіться також мою відповідь, яка походить від вашої.
Шелбі Мур III,

1
Наведемо приклад.
користувач877329

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

Я поліпшив Jens Gustedt в відповідь так , що:

  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 ( з чотирьох випадків параметрів )

Програма

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.