Czy istnieje sposób określenia domyślnych argumentów funkcji w C?
Czy istnieje sposób określenia domyślnych argumentów funkcji w C?
Odpowiedzi:
Nie całkiem. Jedynym sposobem byłoby napisanie funkcji varargs i ręczne wpisanie wartości domyślnych dla argumentów, których nie wywołuje program wywołujący.
open(2)
Połączenia system wykorzystuje to dla opcjonalnego argumentu, które mogą być obecne w zależności od wymaganych argumentów i printf(3)
odczytuje ciąg formatu, który określa ile argumenty nie będzie. Oba używają varargs dość bezpiecznie i skutecznie, i chociaż z pewnością można je zepsuć, printf()
szczególnie wydaje się dość popularne.
Wow, wszyscy są tutaj takim pesymistą. Odpowiedź brzmi tak.
To nie jest trywialne: do końca będziemy mieli podstawową funkcję, strukturę wspierającą, funkcję otoki i makro wokół funkcji otoki. W mojej pracy mam zestaw makr do automatyzacji tego wszystkiego; kiedy zrozumiesz przepływ, będzie ci łatwo zrobić to samo.
Napisałem to gdzie indziej, więc tutaj jest szczegółowy zewnętrzny link do uzupełnienia podsumowania tutaj: http://modelingwithdata.org/arch/00000022.htm
Chcielibyśmy się odwrócić
double f(int i, double x)
w funkcję, która przyjmuje wartości domyślne (i = 8, x = 3,14). Zdefiniuj strukturę towarzyszącą:
typedef struct {
int i;
double x;
} f_args;
Zmień nazwę swojej funkcji f_base
i zdefiniuj funkcję otoki, która ustawia wartości domyślne i wywołuje bazę:
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);
}
Teraz dodaj makro, używając makr rozmaitości C. W ten sposób użytkownicy nie muszą wiedzieć, że faktycznie f_args
wypełniają strukturę i myślą, że robią to, co zwykle:
#define f(...) var_f((f_args){__VA_ARGS__});
OK, teraz wszystkie następujące elementy będą działać:
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
Sprawdź reguły, w jaki sposób inicjalizatory złożone ustawiają wartości domyślne dla dokładnych reguł.
Jedna rzecz, która nie zadziała: f(0)
ponieważ nie możemy odróżnić brakującej wartości od zera. Z mojego doświadczenia wynika, że jest to coś, na co należy uważać, ale można sobie z tym poradzić, gdy zajdzie taka potrzeba - połowa czasu, gdy domyślnie wynosi zero.
Zadałem sobie trud napisania tego, ponieważ myślę, że nazwane argumenty i wartości domyślne naprawdę sprawiają, że kodowanie w C jest łatwiejsze i jeszcze przyjemniejsze. A C jest niesamowity, ponieważ jest tak prosty i wciąż ma wystarczająco dużo, aby wszystko to było możliwe.
{}
(pusty inicjator) to błąd C99.
#define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
Tak. :-) Ale nie w sposób, którego można by się spodziewać.
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");
}
Niestety, C nie pozwala na przeciążanie metod, więc otrzymujesz dwie różne funkcje. Mimo to, wywołując f2, faktycznie dzwoniłbyś do f1 z wartością domyślną. Jest to rozwiązanie „Don't Repeat Yourself”, które pomaga uniknąć kopiowania / wklejania istniejącego kodu.
Możemy tworzyć funkcje, które używają nazwanych parametrów (tylko) dla wartości domyślnych. To kontynuacja odpowiedzi 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);
}
Standard C99 określa, że późniejsze nazwy w inicjalizacji zastępują poprzednie elementy. Możemy również mieć pewne standardowe parametry pozycyjne, wystarczy odpowiednio zmienić makro i podpis funkcji. Domyślnych parametrów wartości można używać tylko w nazwanym stylu parametrów.
Wyjście programu:
1 2 3 4 5 6 7 8 9 10
2 3 4
1 3 5 7 9
OpenCV używa czegoś takiego:
/* 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));
Jeśli użytkownik nie wie, co powinien napisać, ta sztuczka może być pomocna:
Nie.
Nie obsługuje tego nawet najnowszy standard C99.
Krótka odpowiedź: Nie.
Nieco dłuższa odpowiedź: istnieje stare, stare obejście, w którym przekazujesz ciąg, który analizujesz pod kątem opcjonalnych argumentów:
int f(int arg1, double arg2, char* name, char *opt);
gdzie opt może zawierać parę „nazwa = wartość” lub coś, i jak byś nazwał
n = f(2,3.0,"foo","plot=yes save=no");
Oczywiście jest to przydatne tylko czasami. Zasadniczo, gdy chcesz mieć pojedynczy interfejs z rodziną funkcji.
Wciąż znajdziesz to podejście w kodach fizyki cząstek, które są pisane przez profesjonalne programy w c ++ (jak na przykład ROOT ). Jego główną zaletą jest to, że można go rozszerzać prawie bez końca, zachowując kompatybilność wsteczną.
struct
i kazałbym wywołującemu go utworzyć, wypełnić pola dla różnych opcji, a następnie przekazać je przez adres lub przekazać NULL
domyślne opcje.
Prawdopodobnie najlepszym sposobem na zrobienie tego (co może, ale nie musi być możliwe w twoim przypadku, w zależności od twojej sytuacji) jest przejście do C ++ i użycie go jako „lepszego C”. Możesz używać C ++ bez użycia klas, szablonów, przeciążania operatora lub innych zaawansowanych funkcji.
To da ci wariant C z przeciążeniem funkcji i parametrami domyślnymi (i wszystkimi innymi funkcjami, które wybierzesz). Musisz być trochę zdyscyplinowany, jeśli naprawdę poważnie podchodzisz do używania tylko ograniczonego podzbioru C ++.
Wiele osób powie, że używanie C ++ w ten sposób jest okropnym pomysłem i mogą mieć rację. Ale to tylko opinia; Myślę, że warto korzystać z funkcji C ++, z którymi czujesz się komfortowo, bez konieczności kupowania całości. Myślę, że istotną przyczyną sukcesu C ++ jest to, że okropnie wielu programistów wykorzystało go we wczesnych dniach właśnie w ten sposób.
Jeszcze inna opcja wykorzystuje 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);
Kolejna sztuczka z użyciem makr:
#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;
}
Jeśli przekazany zostanie tylko jeden argument, b
otrzymuje wartość domyślną (w tym przypadku 15)
Nie, ale możesz rozważyć użycie zestawu funkcji (lub makr) w celu przybliżenia przy użyciu domyślnych argumentów:
// 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
}
Tak, dzięki funkcjom C99 możesz to zrobić. Działa to bez definiowania nowych struktur danych lub mniej i bez konieczności decydowania przez funkcję w czasie wykonywania, jak zostanie ona wywołana, i bez narzutu obliczeniowego.
Szczegółowe wyjaśnienie znajduje się w moim poście pod adresem
http://gustedt.wordpress.com/2010/06/03/default-arguments-for-c99/
Jens
Generalnie nie, ale w gcc Możesz ustawić ostatni parametr funcA () jako opcjonalny w makrze.
W funcB () używam specjalnej wartości (-1), aby zasygnalizować, że potrzebuję wartości domyślnej parametru „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) );
}
Poprawiłem Jens Gustedt za odpowiedź , tak aby:
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
Uproszczony scenariusz użytkowania:
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__)
I z _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__))
I z wyborem nazwy funkcji variadic, której nie można łączyć z _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__)
TAK
Poprzez makra
3 parametry:
#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);
}
Jeśli chcesz czwartego argumentu, musisz dodać dodatkowy my_func3. Zwróć uwagę na zmiany w VAR_FUNC, my_func2 i my_func
4 parametry:
#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);
}
Jedynym wyjątkiem jest to, że zmienne zmiennoprzecinkowe nie mogą mieć wartości domyślnych ( chyba że jest to ostatni argument, jak w przypadku 3 parametrów ), ponieważ potrzebują kropki („.”), Co nie jest akceptowane w argumentach makr. Ale może wymyślić obejście, jak widać w makrze my_func2 ( przypadek 4 parametrów) )
Program
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;
}
Wynik:
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
Tak, możesz zrobić coś simulair, tutaj musisz znać różne listy argumentów, które możesz uzyskać, ale masz tę samą funkcję do obsługi, to wszystko.
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;
}
}
Dlaczego nie możemy tego zrobić?
Podaj opcjonalny argument wartość domyślną. W ten sposób obiekt wywołujący funkcję niekoniecznie musi przekazywać wartość argumentu. Argument przyjmuje wartość domyślną. I łatwo ten argument staje się opcjonalny dla klienta.
Na przykład
void foo (int a, int b = 0);
Tutaj b jest argumentem opcjonalnym.