在 C 语言中,有没有一种方法可以指定函数的默认参数?
在 C 语言中,有没有一种方法可以指定函数的默认参数?
哇,大家在这里都是如此悲观。答案是肯定的。
这并不是一件小事:最后我们将拥有核心函数、支持结构、包装函数和一个围绕着包装函数的宏。在我的工作中,我有一组宏来自动完成所有这些工作;一旦你理解了流程,你也可以轻松地做到同样的事情。
我已经在别处写过这个内容,所以这里提供一个详细的外部链接来补充这里的总结: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中的错误。 - u0b34a0f6ae#define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
- u0b34a0f6ae可以。 :-) 但不是你所期望的方式。
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。这是一种“不要重复自己”的解决方案,帮助你避免复制/粘贴现有的代码。
不完全正确。唯一的方法是编写一个可变参数函数,并手动填充调用者未传递参数的默认值。
open(2)
系统调用使用此参数作为可选项,具体取决于所需的参数。printf(3)
读取指定有多少参数的格式字符串。两者都安全有效地使用可变参数列表,并且尽管你肯定可以出错,但printf()
似乎特别受欢迎。 - Chris Lutz我们可以创建只使用命名参数作为默认值的函数。这是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
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));
如果用户不知道该写什么,这个小技巧可能会有所帮助:
不行。
即使是最新的C99标准也不支持这个。
不,这是 C++ 语言的一个特性。
另一个使用宏的技巧:
#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;
}
如果只传递一个参数,则b
将接收默认值(在本例中为15)
...
并且只传递2个参数(在这种情况下)作为扩展,使用 -pedantic
编译并查看会发生什么。 - David Ranieri简短答案:不行。
稍微长一点的回答:有一个古老的、非常古老的解决方法,你可以传递一个字符串,然后对其进行解析以获取可选参数:
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)。其主要优点是可以无限扩展而同时保持向后兼容性。
struct
,让调用者创建一个结构体并填充不同选项的字段,然后通过地址传递它,或者传递 NULL
以获取默认选项。 - Chris Lutz也许最好的方法(根据您的情况,可能有可能不可行)是转向C++,并将其用作“更好的C语言”。 您可以在不使用类、模板、运算符重载或其他高级功能的情况下使用C++。
这将为您提供一个具有函数重载和默认参数(以及您选择使用的任何其他功能)的C语言变体。 如果您真的严格使用C++的受限子集,您只需要稍微有些自律。
很多人会说这样使用C++是个糟糕的主意,他们可能有一定的道理。但这仅仅是一种看法;我认为可以使用您熟悉的C++功能而无需完全掌握它的全部。 我认为C++之所以如此成功,其中一个重要原因就是在早期它被许多程序员用于这种方式。