C 默认参数

Posted

技术标签:

【中文标题】C 默认参数【英文标题】:C default arguments 【发布时间】:2010-12-01 02:58:32 【问题描述】:

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

【问题讨论】:

只想要一个稍微好一点的 C,而不是 C++。想想 C+ 。从 C++ 中提升了各种小的改进,但不是大混乱。而且,请不要使用不同的链接加载器。应该只是另一个类似预处理器的步骤。标准化。无处不在... 相关question,我没有在侧栏中看到。 我想说不要再做野蛮人了,好好学习使用 C++(11, ...) - jk! /me 扑灭火焰……但是……你会爱上它的……哈哈哈我忍不住,对不起。 Default values on arguments in C functions and function overloading in C的可能重复 【参考方案1】:

哇,这里的每个人都是如此悲观的人。答案是肯定的。

这不是微不足道的:到最后,我们将拥有核心函数、支持结构、包装函数和宏 围绕包装函数。在我的工作中,我有一组宏来自动化这一切;一次 您了解流程,您也很容易做到这一点。

我已经在其他地方写过这个,所以这里有一个详细的外部链接来补充这里的摘要: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 语言非常简单,因为它仍然有足够的能力让这一切成为可能。

【讨论】:

+1 创意!它有其局限性,但也将命名参数带到了表中。请注意,(空初始化程序)是错误 C99。 但是,这里有一些对您来说很棒的东西:该标准允许多次指定命名成员,稍后会覆盖。因此,对于命名参数,您只能解决默认问题并允许空调用。 #define vrange(...) CALL(range,(param).from=1, .to=100, .step=1, __VA_ARGS__) 我希望编译器错误是可读的,但这是一个很棒的技术!几乎看起来像 python kwargs。 @RunHolt 虽然肯定更简单,但客观上并不是更好;命名参数具有诸如易于调用的可读性(以牺牲源代码的可读性为代价)的好处。一个更适合源代码的开发者,另一个更适合功能的用户。直接抛出“这个更好!”有点草率! @DawidPi: C11 6.7.9(19),关于初始化聚合:“所有未显式初始化的子对象都应隐式初始化,与具有静态存储持续时间的对象相同”如您所知,静态持续时间元素被初始化为零|NULL|\0。 [这也在 c99 中。]【参考方案2】:

是的。 :-) 但不是你所期望的那样。

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。这是一个“不要重复自己”的解决方案,可帮助您避免复制/粘贴现有​​代码。

【讨论】:

FWIW,我更喜欢使用函数末尾的数字来表示它需要的参数数量。比使用任意数字更容易。 :) 这是迄今为止最好的答案,因为它展示了实现相同目标的简单方法。我有一个函数,它是我不想更改的固定 API 的一部分,但我需要它来获取新参数。当然,这太明显了,以至于我错过了(一直想着默认参数!) f2 也可以是预处理宏【参考方案3】:

不是真的。唯一的方法是 write a varargs function 并手动填写调用者未传递的参数的默认值。

【讨论】:

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

我们可以创建将命名参数(仅)用于默认值的函数。这是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

【讨论】:

这似乎比 Wim 10 Brink 或 BK 解决方案更简单、更直接。这个实现有没有其他人没有的缺点?【参考方案5】:

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));

如果用户不知道自己应该写什么,这个技巧会很有帮助:

【讨论】:

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

不,那是 C++ 语言的特性。

【讨论】:

【参考方案7】:

没有。

即使是最新的 C99 标准也不支持这一点。

【讨论】:

一个简单的不会更好;) @kevindtimm:这是不可能的,所以要求答案的最小长度。我试过了。 :) 请参考我的回答。 :)【参考方案8】:

执行此操作的最佳方法(根据您的情况可能会也可能不会)可能是迁移到 C++ 并将其用作“更好的 C”。您可以在不使用类、模板、运算符重载或其他高级功能的情况下使用 C++。

这将为您提供具有函数重载和默认参数(以及您选择使用的任何其他功能)的 C 变体。如果您真的很想只使用 C++ 的受限子集,那么您只需要有点自律。

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

【讨论】:

【参考方案9】:

简答:不。

答案稍长: 有一个旧的old 解决方法,您可以传递一个字符串,解析 可选参数:

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 作为默认选项。 从 ROOT 复制代码模式是个糟糕的主意!【参考方案10】:

另一个选项使用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);

【讨论】:

【参考方案11】:

另一个使用宏的技巧:

#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)

【讨论】:

在 FUNC(VA_ARGS, 15, 0) 处,最后一个参数是强制性的,那个“0”?我试过没有它,似乎工作正常。 @ungalcrys 如果您不使用 gcc 编译,则它是强制性的,gcc 允许包含 ... 并仅传递 2 个参数(在这种情况下)作为扩展名,使用 -pedantic 编译并查看会发生什么。【参考方案12】:

没有。

【讨论】:

解决方法是什么?我可以看到它是十六进制的20202020,但是我该如何输入呢? @Lazer 那些应该是 ASCII 空格,不是吗?【参考方案13】:

是的,使用 C99 的功能,您可以这样做。这无需定义新的数据结构等即可工作,也无需函数在运行时决定如何调用它,并且 没有任何计算开销。

详细解释见我的帖子

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

詹斯

【讨论】:

另请参阅我的answer,它源自您的。 内联一个例子。【参考方案14】:

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

// 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

【讨论】:

【参考方案15】:

我改进了 Jens Gustedt 的 answer 以便:

    未使用内联函数 在预处理期间计算默认值 模块化可重用宏 可以设置编译器错误,以有意义地匹配允许的默认值的参数不足的情况 如果参数类型保持明确,则不需要默认值来形成参数列表的尾部 与 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__)

【讨论】:

【参考方案16】:

一般不会,但在 gcc 中,你可以通过宏将 funcA() 的最后一个参数设为可选。

在 funcB() 中,我使用一个特殊值 (-1) 来表示我需要 'b' 参数的默认值。

#include <stdio.h> 

int funcA( int a, int b, ... ) return a+b; 
#define funcA( a, ... ) funcA( a, ##__VA_ARGS__, 8 ) 


int funcB( int a, int b )
  if( b == -1 ) b = 8;
  return a+b;


int main(void)
  printf("funcA(1,2): %i\n", funcA(1,2) );
  printf("funcA(1):   %i\n", funcA(1)   );

  printf("funcB(1, 2): %i\n", funcB(1, 2) );
  printf("funcB(1,-1): %i\n", funcB(1,-1) );

【讨论】:

【参考方案17】:

是的

通过宏

3 个参数:

#define my_func2(...) my_func3(__VA_ARGS__, 0.5)
#define my_func1(...) my_func2(__VA_ARGS__, 10)
#define VAR_FUNC(_1, _2, _3, NAME, ...) NAME
#define my_func(...) VAR_FUNC(__VA_ARGS__, my_func3, my_func2, my_func1)(__VA_ARGS__)

void my_func3(char a, int b, float c) // b=10, c=0.5

    printf("a=%c; b=%d; c=%f\n", a, b, c);

如果您想要第 4 个参数,则需要添加一个额外的 my_func3。注意 VAR_FUNC、my_func2 和 my_func 的变化

4个参数:

#define my_func3(...) my_func4(__VA_ARGS__, "default") // <== New function added
#define my_func2(...) my_func3(__VA_ARGS__, (float)1/2)
#define my_func1(...) my_func2(__VA_ARGS__, 10)
#define VAR_FUNC(_1, _2, _3, _4, NAME, ...) NAME
#define my_func(...) VAR_FUNC(__VA_ARGS__, my_func4, my_func3, my_func2, my_func1)(__VA_ARGS__)

void my_func4(char a, int b, float c, const char* d) // b=10, c=0.5, d="default"

    printf("a=%c; b=%d; c=%f; d=%s\n", a, b, c, d);

float 变量不能被赋予默认值的唯一例外(除非它是最后一个参数,如 3 个参数的情况),因为它们需要句点 ('. '),在宏参数中不被接受。但是可以找出一个解决方法,如 my_func2 宏中所见(of 4 parameters case

程序

int main(void)

    my_func('a');
    my_func('b', 20);
    my_func('c', 200, 10.5);
    my_func('d', 2000, 100.5, "hello");

    return 0;

输出:

a=a; b=10; c=0.500000; d=default                                                                                                                                                  
a=b; b=20; c=0.500000; d=default                                                                                                                                                  
a=c; b=200; c=10.500000; d=default                                                                                                                                                
a=d; b=2000; c=100.500000; d=hello  

【讨论】:

【参考方案18】:

是的,你可以做一些类似的事情,在这里你必须知道你可以获得的不同参数列表,但你有相同的函数来处理。

typedef enum  my_input_set1 = 0, my_input_set2, my_input_set3 INPUT_SET;

typedef struct
    INPUT_SET type;
    char* text;
 input_set1;

typedef struct
    INPUT_SET type;
    char* text;
    int var;
 input_set2;

typedef struct
    INPUT_SET type;
    int text;
 input_set3;

typedef union

    INPUT_SET type;
    input_set1 set1;
    input_set2 set2;
    input_set3 set3;
 MY_INPUT;

void my_func(MY_INPUT input)

    switch(input.type)
    
        case my_input_set1:
        break;
        case my_input_set2:
        break;
        case my_input_set3:
        break;
        default:
        // unknown input
        break;
    

【讨论】:

【参考方案19】:

您不需要仅对 C 使用 VARARGS。这是一个示例。

int funcA_12(int a1, int a2)  ... 

#define funcA(a1) funcA_12(a1, 0)

这个答案与上面的两个函数方法非常相似,但在这种情况下,您使用宏作为定义参数的函数名称。

【讨论】:

【参考方案20】:

https://github.com/cindRoberta/C/blob/master/structure/function/default_parameter.c

#include<stdio.h>

void f_impl(int a, float b) 
  printf("%d %g\n", a, b);


#define f_impl(...) f_macro(__VA_ARGS__, 3.7)
#define f_macro(a, b, ...) f_impl(a, b)

int main(void) 
  f_impl(1);
  f_impl(1, 2, 3, 4);

  return 0;

【讨论】:

【参考方案21】:

我知道如何以更好的方式做到这一点。 您只需将 NULL 分配给参数,因此您将没有任何价值。然后检查参数值是否为NULL,将其更改为默认值。

void func(int x)
if(x == NULL)
  x = 2;
....

但是,它会导致警告。更好的选择是分配一个值,如果参数值是这样的,则不会执行任何操作:

void func(int x)
if(x == 1)
  x = 2;
....

在上面的示例中,如果x1,则函数将其更改为2

感谢@user904963,编辑: 如果你必须覆盖所有数字范围,添加另一个参数只是告诉函数是否会将参数设置为默认值并不难

void func(int x, bool useDefault)
if(useDefault) //useDefault == true
  x = 2;
....

但是,请记住包含stdbool.h

【讨论】:

在您的第一个示例中,x == NULL 仅在x 已经为0 时才为真,因此代码没有为x 提供默认值。第二个例子可以工作,但如果传入的参数可以是完整的值范围,它很容易就不行了。 我已编辑,谢谢。 添加一个布尔参数标志是可以的,如果你只有一个可以保存默认值的参数,但如果你需要所有参数都有这样一个标志,它就会变得笨拙! NULL 表示默认值更好...【参考方案22】:

为什么我们不能这样做。

给可选参数一个默认值。这样,函数的调用者不一定需要传递参数的值。该参数采用默认值。 并且很容易该参数对客户来说是可选的。

例如

void foo(int a, int b = 0);

这里的 b 是一个可选参数。

【讨论】:

惊人的洞察力,问题是C不支持可选参数或重载函数,因此直接解决方案无法编译。

以上是关于C 默认参数的主要内容,如果未能解决你的问题,请参考以下文章

C++核心编程中的函数-占位参数和默认参数

C/C++ Python的函数默认参数

C 默认参数

python中定义函数时如何书写可变参数和默认参数

面向对象程序设计-C++_课时17函数重载和默认参数

在函数c ++中将变量作为默认参数传递[重复]