有没有一种方法可以在C语言中指定函数的默认参数?
当前回答
我改进了Jens Gustedt的回答:
不使用内联函数 默认值在预处理期间计算 模块化可重用宏 可以设置编译器错误,使其与允许的默认值参数不足的情况有意义地匹配 如果实参类型保持无二义性,则不需要默认值来形成形参列表的尾部 与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__)
其他回答
简单的回答:不。
稍微长一点的回答:有一个很老很老的解决方法,你传递一个字符串来解析可选参数:
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)。它的主要优点是可以几乎无限期地扩展,同时保持向后兼容性。
我知道如何更好地做到这一点。 您只需将NULL赋值给一个参数,因此,您将没有值。然后检查参数值是否为NULL,将其更改为默认值。
void func(int x){
if(x == NULL)
x = 2;
....
}
尽管如此,它还是会引起警告。一个更好的选择是,如果参数值是:
void func(int x){
if(x == 1)
x = 2;
....
}
在上面的例子中,如果x是1,函数将其更改为2;
感谢@user904963,编辑: 如果必须覆盖所有的数字范围,那么添加另一个参数来告诉函数是否将形参设置为默认并不难
void func(int x, bool useDefault){
if(useDefault) //useDefault == true
x = 2;
....
}
但是,请记住包含stdboolh
我偶尔会使用一个技巧,它从C99开始就可用了,使用可变宏、复合字面量和指定初始化式。与任何宏解决方案一样,它很麻烦,通常不推荐使用,除非是最后的手段……
我的方法是这样构建的:
Wrap the actual function in a function-like, variadic macro: void myfunc (int x, int y) // actual function #define myfunc(...) myfunc(params) // wrapper macro By using compound literals, copy down the parameters passed into a temporary object. This object should be a private struct corresponding directly to the function's expected parameter list. Example: typedef struct { int x; int y; } myfunc_t; #define PASSED_ARGS(...) (myfunc_t){__VA_ARGS__} This means that the same type safety ("as per assignment") rules used when passing parameters to a function is also used when initializing this struct. We don't lose any type safety. Similarly, this automatically guards against providing too many arguments. However, the above doesn't cover the case of an empty argument list. To counter this, add a dummy argument so that the initializer list is never empty: typedef struct { int dummy; int x; int y; } myfunc_t; #define PASSED_ARGS(...) (myfunc_t){0,__VA_ARGS__} Similarly, we can count the number of arguments passed, assuming that every parameter passed can get implicitly converted to int: #define COUNT_ARGS(...) (sizeof(int[]){0,__VA_ARGS__} / sizeof(int) - 1) We define a macro for the default arguments #define DEFAULT_ARGS (myfunc_t){0,1,2}, where 0 is the dummy and 1,2 are the default ones. Wrapping all of this together, the outermost wrapper macro may look like: #define myfunc(...) myfunc( MYFUNC_INIT(__VA_ARGS__).x, MYFUNC_INIT(__VA_ARGS__).y ) This assuming that the inner macro MYFUNC_INIT returns a myfunc_t struct. The inner macro conditionally picks struct initializers based on the size of the argument list. In case the argument list is short, it fills up with default arguments. #define MYFUNC_INIT(...) \ (myfunc_t){ 0, \ .x = COUNT_ARGS(__VA_ARGS__)==0 ? DEFAULT_ARGS.x : PASSED_ARGS(__VA_ARGS__).x, \ .y = COUNT_ARGS(__VA_ARGS__)<2 ? DEFAULT_ARGS.y : PASSED_ARGS(__VA_ARGS__).y, \ }
完整的例子:
#include <stdio.h>
void myfunc (int x, int y)
{
printf("x:%d y:%d\n", x, y);
}
typedef struct
{
int dummy;
int x;
int y;
} myfunc_t;
#define DEFAULT_ARGS (myfunc_t){0,1,2}
#define PASSED_ARGS(...) (myfunc_t){0,__VA_ARGS__}
#define COUNT_ARGS(...) (sizeof(int[]){0,__VA_ARGS__} / sizeof(int) - 1)
#define MYFUNC_INIT(...) \
(myfunc_t){ 0, \
.x = COUNT_ARGS(__VA_ARGS__)==0 ? DEFAULT_ARGS.x : PASSED_ARGS(__VA_ARGS__).x, \
.y = COUNT_ARGS(__VA_ARGS__)<2 ? DEFAULT_ARGS.y : PASSED_ARGS(__VA_ARGS__).y, \
}
#define myfunc(...) myfunc( MYFUNC_INIT(__VA_ARGS__).x, MYFUNC_INIT(__VA_ARGS__).y )
int main (void)
{
myfunc(3,4);
myfunc(3);
myfunc();
}
输出:
x:3 y:4
x:3 y:2
x:1 y:2
Godbolt: https://godbolt.org/z/4ns1zPW16正如您可以从-O3分解中看到的,复合字面量的开销为零。
我注意到我的方法让人想起了目前投票最多的答案。与其他解决方案的比较:
优点:
纯粹的、可移植的标准ISO C,没有脏的gcc扩展,没有定义不好的行为。 可以处理空参数列表。 高效,零开销,不依赖于函数内联按预期进行。 在调用端没有模糊的指定初始化式。
缺点:
依赖于每个参数都隐式转换为int,但通常情况并非如此。例如,严格C不允许从指针到int的隐式转换——这种隐式转换是一个不符合(但流行)的编译器扩展。 默认参数和结构必须为每个函数生成。虽然这个答案没有涉及到,但这可以通过X宏实现自动化。但这样做也会进一步降低可读性。
我们可以为默认值创建(仅)使用命名形参的函数。这是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
你不需要只在c上使用VARARGS,下面是一个例子。
int funcA_12(int a1, int a2) { ... }
#define funcA(a1) funcA_12(a1, 0)
这个答案与上面的两个函数方法非常相似,但在这种情况下,您使用的是定义参数的函数名宏。