C默认参数

Nat*_*ath 258 c arguments default-parameters

有没有办法在C中为函数指定默认参数?

bk.*_*bk. 261

哇,每个人都是这样的悲观主义者.答案是肯定的.

这不是微不足道的:到最后,我们将拥有核心函数,支持结构,包装函数和包装函数周围的宏.在我的工作中,我有一组宏来自动化所有这些; 一旦你理解了流程,你就可以轻松地做到这一点.

我在其他地方写过这个,所以这里有一个详细的外部链接来补充这里的摘要:http://modelingwithdata.org/arch/00000022.htm

我们想转

double f(int i, double x)
Run Code Online (Sandbox Code Playgroud)

进入一个默认值的函数(i = 8,x = 3.14).定义一个伴随结构:

typedef struct {
    int i;
    double x;
} f_args;
Run Code Online (Sandbox Code Playgroud)

重命名您的函数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);
}
Run Code Online (Sandbox Code Playgroud)

现在使用C的可变参数宏添加一个宏.这样用户不必知道他们实际上正在填充f_args结构并认为他们正在做通常的事情:

#define f(...) var_f((f_args){__VA_ARGS__});
Run Code Online (Sandbox Code Playgroud)

好的,现在以下所有方法都可以:

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
Run Code Online (Sandbox Code Playgroud)

检查复合初始化程序如何为确切规则设置默认值的规则.

有一件事是行不通的:f(0)因为我们无法区分缺失值和零.根据我的经验,这是需要注意的事项,但可以根据需要进行处理 - 有一半时间你的默认值为零.

我经历了编写这个问题的麻烦,因为我认为命名参数和默认值确实使C编码变得更容易,更有趣.C非常棒,因为它非常简单,而且还有足够的空间来实现这一切.

  • 但是,这里有一些很棒的东西:标准允许多次指定命名成员,后者覆盖.因此,对于命名参数,您只能解决默认问题并允许空调用.`#define vrange(...)CALL(range,(param){.from = 1,.to = 100,.step = 1,__ VA_ARGS __}) (27认同)
  • +1创意!它有其局限性,但也为表格提供了命名参数.注意,`{}`(空初始化器)是错误C99. (15认同)
  • @RunHolt虽然当然简单,但客观上并不好; 命名参数带来诸如易于调用的可读性(以牺牲源代码的可读性为代价).一个对于源的开发人员更好,另一个对于该功能的用户更好.抛出"这个更好!"有点仓促. (6认同)
  • 我希望编译器错误是可读的,但这是一个很棒的技术!几乎看起来像蟒蛇kwargs. (3认同)
  • @DawidPi:C11 6.7.9(19),关于初始化聚合:"未显式初始化的所有子对象应隐式初始化,与具有静态存储持续时间的对象相同"如您所知,静态持续时间元素初始化为零| NULL |\0.[这也是在c99.] (3认同)
  • 该解决方案存在一个重大缺陷,使其无法成为通用解决方案。作者也对此提出了警告,但仅限于第一个论点。事实上,每个参数都必须有一个特殊值(在本例中为 0),这可以解释为“没有传递值”。这通常是不可能的,因为该值可能是调用者有意的参数。在这种情况下,“默认”值将替换调用者定义的值。不过,这是个好主意。 (2认同)

Wim*_*ink 152

是.:-)但不是你想象的方式.

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");
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,C不允许你重载方法,所以你最终会得到两个不同的功能.仍然,通过调用f2,你实际上是用默认值调用f1.这是一个"不要重复自己"的解决方案,它可以帮助您避免复制/粘贴现有​​代码.

  • FWIW,我更喜欢使用函数末尾的数字来表示它所需的args数.比使用任意数字更容易.:) (23认同)
  • 这是迄今为止最好的答案,因为它展示了实现相同目标的简单方法.我有一个功能,它是我不想改变的固定API的一部分,但我需要它来采取一个新的参数.当然,我非常明显地错过了它(想到默认的参数!) (4认同)
  • f2也可以是预处理器宏 (3认同)

Eli*_*ght 134

并不是的.唯一的方法是编写一个varargs函数并手动填写调用者未传递的参数的默认值.

  • 我讨厌在使用varargs时缺乏检查. (21认同)
  • 你也应该这样; 我实际上不推荐这个; 我只想表达它是可能的. (15认同)
  • 但是,您如何检查调用者是否通过了参数?我觉得为了这个工作,难道你没有打电话的人告诉你他没有通过吗?我认为这会使整个方法的可用性降低 - 调用者也可以调用另一个名称的函数. (4认同)
  • @Eli:并非所有C编译器都是gcc.当你的printf()args与你的格式字符串不匹配时,它会发出一些高级编译器魔法来警告它.而且我认为不可能为你自己的可变函数得到类似的警告(除非他们使用相同类型的格式字符串). (4认同)
  • `open(2)`系统调用将此函数用于可能存在的可选参数,具体取决于所需的参数,而`printf(3)`读取一个格式字符串,该字符串指定将有多少参数.两者都非常安全有效地使用varargs,虽然你肯定可以搞砸它们,但是'printf()`似乎特别受欢迎. (3认同)

u0b*_*6ae 40

我们可以创建使用命名参数(仅)作为默认值的函数.这是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);                                                             
}    
Run Code Online (Sandbox Code Playgroud)

C99标准定义了初始化中的后续名称会覆盖以前的项目.我们也可以有一些标准的位置参数,只需相应地改变宏和函数签名.默认值参数只能在命名参数样式中使用.

节目输出:

1 2 3 4 5 6 7 8 9 10 
2 3 4 
1 3 5 7 9
Run Code Online (Sandbox Code Playgroud)

  • 这似乎比Wim ten Brink或BK解决方案更简单,更直接.这个实现有没有任何缺点,其他人也没有? (2认同)

小智 22

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));
Run Code Online (Sandbox Code Playgroud)

如果用户不知道他应该写什么,这个技巧可能会有所帮助:

用法示例

  • 这是一个 C 问题,解决方案没有提供 C 的默认值。将 C 代码编译为 C++ 并不是那么有趣,那么您不妨使用 C++ 提供的默认值。此外,C 代码并不总是有效的 C++ 代码,因此它意味着移植工作。 (4认同)

unw*_*ind 19

没有.

即使是最新的C99标准也不支持此功能.

  • @kevindtimm:那是不可能的,因此要求答案的最小长度.我试过了.:) (18认同)
  • 请参考我的回答。:) (3认同)

Ale*_*x B 18

不,这是一个C++语言功能.


dmc*_*kee 13

简答:不.

稍微长一点的回答:有一个旧的,旧的解决方法,你传递一个你为可选参数解析的字符串:

int f(int arg1, double arg2, char* name, char *opt);
Run Code Online (Sandbox Code Playgroud)

opt可能包含"name = value"对或其他东西,你可以称之为

n = f(2,3.0,"foo","plot=yes save=no");
Run Code Online (Sandbox Code Playgroud)

显然这只是偶尔有用.通常,当您需要单个接口来实现一系列功能时.


您仍然可以在粒子物理代码中找到这种方法,这些代码由c ++中的专业程序编写(例如ROOT).它的主要优点是它可以几乎无限延长,同时保持兼容性.

  • 我将使用自定义`struct`并让调用者创建一个,填写不同选项的字段,然后通过地址传递,或传递"NULL"作为默认选项. (4认同)
  • 有人一直在阅读太多的FORTRAN代码! (4认同)
  • 将此与varargs相结合,您将获得各种乐趣! (2认同)

Mic*_*urr 13

可能最好的方法(根据你的情况在你的情况下可能或不可能)是转向C++并将其用作"更好的C".您可以在不使用类,模板,运算符重载或其他高级功能的情况下使用C++.

这将为您提供具有函数重载和默认参数(以及您选择使用的任何其他功能)的C变体.如果你真的认真地只使用有限的C++子集,你就必须要有点自律.

很多人会说以这种方式使用C++是一个糟糕的主意,他们可能会有一个观点.但这只是一种观点; 我认为使用你熟悉的C++功能是有效的,而不必购买整个东西.我认为C++成功的一个重要原因在于它早期就被很多程序员用于这种方式.


Chr*_*utz 13

另一种选择使用structs:

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);
Run Code Online (Sandbox Code Playgroud)


Dav*_*eri 10

另一个使用宏的技巧:

#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;
}
Run Code Online (Sandbox Code Playgroud)

如果只传递一个参数,则b接收默认值(在本例中为 15)


cha*_*aos 9

没有.

  • 尾随空间.它不再起作用了. (4认同)
  • 解决方法是什么?我可以看到它是十六进制的“20202020”,但是我该如何输入呢? (2认同)

Dav*_*ble 6

不,但您可以考虑使用一函数(或宏)来近似使用默认参数:

// 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
}
Run Code Online (Sandbox Code Playgroud)


Jen*_*edt 5

是的,使用 C99 的功能,您可以做到这一点。这不需要定义新的数据结构,也不需要函数在运行时决定如何调用它,也没有任何计算开销。

有关详细说明,请参阅我的帖子

http://gustedt.wordpress.com/2010/06/03/default-arguments-for-c99/

延斯

  • 内联一个例子。 (3认同)