有没有一种方法可以在C语言中指定函数的默认参数?
当前回答
为什么我们不能这样做呢?
给可选参数一个默认值。这样,函数的调用者就不一定需要传递实参的值。参数接受默认值。 很容易,这个参数对客户端来说是可选的。
如。
Void foo(int a, int b = 0);
这里b是可选参数。
其他回答
哇,这里的每个人都这么悲观。答案是肯定的。
这并不简单:到最后,我们将拥有核心函数、支持结构、包装器函数和宏 围绕包装器函数。在我的工作中,我有一组宏来自动化所有这些;一次 你了解流程,你也会很容易做到。
我已经在其他地方写过了,所以这里有一个详细的外部链接来补充这里的摘要: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语言太棒了,因为它如此简单,而且仍然有足够的内容使这一切成为可能。
我偶尔会使用一个技巧,它从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++语言的特性。
简单的回答:不。
稍微长一点的回答:有一个很老很老的解决方法,你传递一个字符串来解析可选参数:
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)。它的主要优点是可以几乎无限期地扩展,同时保持向后兼容性。