有没有一种方法可以在C语言中指定函数的默认参数?
当前回答
我偶尔会使用一个技巧,它从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宏实现自动化。但这样做也会进一步降低可读性。
其他回答
为什么我们不能这样做呢?
给可选参数一个默认值。这样,函数的调用者就不一定需要传递实参的值。参数接受默认值。 很容易,这个参数对客户端来说是可选的。
如。
Void foo(int a, int b = 0);
这里b是可选参数。
不,但你可以考虑使用一组函数(或宏)来近似使用默认参数:
// 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
}
No.
即使是最新的C99标准也不支持这一点。
是的。:-)但不是你所期望的那样。
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。这是一个“不要重复自己”的解决方案,它可以帮助您避免复制/粘贴现有代码。
我们可以为默认值创建(仅)使用命名形参的函数。这是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