2009-09-24 6 views
216

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

+32

@pmg - И пока мы на нем, мы может исправить все недостатки C, которые нам не нравятся. И тогда мы можем добавить несколько функций здесь и там, а затем почему бы нам не добавить перегрузку функций, а затем мы сделали хакерский язык, который никто не будет использовать, и это смутит почти всех, кто должен поддерживать ваше приложение. Если вы хотите сделать язык, во что бы то ни стало, сделайте язык, но не создавайте новый язык только для определенной задачи. Это просто ужасно. –

+56

@ Крис - ты не говоришь о Бьярне С, ты? :) –

+2

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

ответ

110

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

+15

Я ненавижу отсутствие проверки при использовании varargs. – dmckee

+10

Также вы должны; Я на самом деле не рекомендую это; Я просто хотел сказать, что это возможно. –

+3

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

17

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

12

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

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

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, и у вас есть все виды развлечений! –

+4

Я бы использовал пользовательскую 'struct' и позвонил, чтобы заполнить поля для разных параметров, а затем передать их по адресу или передать' NULL' для параметров по умолчанию. –

+3

Кто-то читал слишком много кода FORTRAN! –

18

No.

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

+0

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

+16

@kevindtimm: Это невозможно, SO накладывает минимальную длину на ответы. Я пытался. :) – unwind

+0

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

8

Номер

+2

То же обходное решение, что и для комментариев? – dmckee

+1

Какое обходное решение? Я вижу, что это «20202020» в шестнадцатеричном виде, но как его ввести? – Lazer

+4

Проходящие пространства. Он больше не работает. – chaos

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

+19

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

+1

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

+2

f2 также может быть макросом препроцессора – Spookbuster

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

Что я сказал .... –

3

как правило, нет, но в НКУ Вы можете сделать последний параметр 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)); 
} 
11

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

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

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

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

Любые причины для downvote? Это второй анонимный нисходящий поток за два дня. –

0

Да, вы можете сделать Somthing 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; 
    } 
} 
219

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

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

Я написал это в другом месте, так что здесь подробно внешняя ссылка дополнить резюме здесь: 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); 
} 

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

+12

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

+22

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

+3

Я надеюсь, что ошибки компилятора читабельны, но это отличный техника! Почти похоже на python kwargs. – totowtwo

3

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

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

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

Jens

+0

Упомянуте также мой [ответ] (http://stackoverflow.com/questions/1472138/c-default-arguments#33786937), который получен из вашего. –

+0

Извините, моя связь сегодня слишком плохая. Надеюсь, завтра будет лучше. –

35

Мы можем создать функции, которые используют именованные параметры (только) для значений по умолчанию. Это продолжение ответа 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

14

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

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

usage example

+0

Это на самом деле довольно приятная интеграция IDE для C! Btw, это «default», а не «defualt». – Thomas

-4

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

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

См., Например,

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

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

+6

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

1

я улучшил Jens Gustedt-х answer так что:

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

variadic.ч:

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

И с VARIADIC выбора имени функции, которая не может быть объединено с _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__) 
Смежные вопросы