C默认参数

有没有一种方法可以在C语言中指定函数的默认参数?

278447 次浏览

不,那是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++编写的粒子物理代码中找到这种方法(例如)。它的主要优点是可以几乎无限期地扩展,同时保持向后兼容性。

不。

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

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

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

一般不会,但在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) );
}

做到这一点的最好方法(根据你的情况,这可能也可能不可能)可能是转移到c++,并将其作为“更好的C”使用。你可以在不使用类、模板、操作符重载或其他高级特性的情况下使用c++。

这将为您提供一个具有函数重载和默认参数(以及您选择使用的任何其他特性)的C变体。如果你真的想只使用c++的一个受限子集,你就必须稍微自律一些。

很多人会说以这种方式使用c++是一个糟糕的想法,他们可能是有道理的。但这只是一种观点;我认为使用c++中你觉得舒服的特性是合理的,而不必购买整个东西。我认为c++成功的一个重要原因是它在早期被大量的程序员以这种方式使用。

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

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

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

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

这并不简单:到最后,我们将拥有核心函数,支持结构,包装函数和宏 围绕包装器函数。在我的工作中,我有一组宏来自动化所有这些;一次 你理解了流程,你也会很容易做到。

我已经在其他地方写过了,所以这里有一个详细的外部链接来补充这里的摘要: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,并定义一个设置默认值和调用的包装器函数 基础:< / p >
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),因为我们不能区分一个缺失的值和 零。根据我的经验,这是需要注意的事情,但可以作为照顾 这就有必要了——有一半的时候你的默认值真的是0 我费了好大劲才写出来,因为我认为命名参数和默认值 确实让用C编写代码变得更简单,更有趣。和 C语言太棒了,因为它如此简单,而且仍然有足够的内容使这一切成为可能
是的,对于C99的功能,你可以这样做。这种工作方式不需要定义新的数据结构,也不需要函数在运行时决定如何调用 没有任何计算开销

有关详细的解释,请参阅我的帖子

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

延斯

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

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

usage example

为什么我们不能这样做呢?

给可选参数一个默认值。这样,函数的调用者就不一定需要传递实参的值。参数接受默认值。 这个参数对于客户端来说是可选的

如。

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

这里b是可选参数。

我改进了Jens Gustedt的回答,这样:

  1. 不使用内联函数
  2. 默认值在预处理期间计算
  3. 模块化可重用宏
  4. 可以设置编译器错误,使其与允许的默认值参数不足的情况有意义地匹配
  5. 如果实参类型保持无二义性,则不需要默认值来形成形参列表的尾部
  6. 与C11 _Generic互操作
  7. 根据参数的数量来改变函数名!

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

是的

通过宏

三个参数:

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

唯一的例外是浮动变量不能被赋予默认值(除非它是3形参情况下的最后一个参数),因为它们需要句号('.'),这在宏参数中是不被接受的。但可以找到一个工作,如my_func2宏(4个参数的情况)所示

程序

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

使用宏的另一个技巧:

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

你不需要只在c上使用VARARGS,下面是一个例子。

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


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

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

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;
}
我知道如何以更好的方式做到这一点。 您只需将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

我偶尔会使用一个技巧,它从C99开始就可用了,使用可变宏、复合字面量和指定初始化式。与任何宏解决方案一样,它很麻烦,通常不推荐使用,除非是最后的手段……

我的方法是这样构建的:

  • 将实际函数封装在一个类函数的可变变量宏中:

    void myfunc (int x, int y)         // actual function
    #define myfunc(...) myfunc(params) // wrapper macro
    
  • 通过使用复合字面值,将传递到临时对象中的参数复制下来。该对象应该是一个私有结构体,直接对应于函数的期望形参列表。例子:

    typedef struct
    {
    int x;
    int y;
    } myfunc_t;
    
    
    #define PASSED_ARGS(...) (myfunc_t){__VA_ARGS__}
    

    这意味着将参数传递给函数时使用的类型安全(“as per assignment"”)规则也用于初始化该结构体时。我们不会失去任何类型安全性。类似地,这将自动防止提供太多参数。

  • 然而,上述不包括空参数列表的情况。为了解决这个问题,添加一个虚拟参数,这样初始化器列表就永远不会为空:

    typedef struct
    {
    int dummy;
    int x;
    int y;
    } myfunc_t;
    
    
    #define PASSED_ARGS(...) (myfunc_t){0,__VA_ARGS__}
    
  • 类似地,我们可以计算传递的参数数量,假设传递的每个参数都可以隐式转换为int:

    #define COUNT_ARGS(...) (sizeof(int[]){0,__VA_ARGS__} / sizeof(int) - 1)

  • 我们为默认参数#define DEFAULT_ARGS (myfunc_t){0,1,2}定义了一个宏,其中0是虚拟参数,1,2是默认参数。

  • 将所有这些包装在一起,最外层的包装器宏可能看起来像:

    #define myfunc(...) myfunc( MYFUNC_INIT(__VA_ARGS__).x, MYFUNC_INIT(__VA_ARGS__).y )

    这假设内部宏MYFUNC_INIT返回一个myfunc_t结构体。

  • 内部宏根据参数列表的大小有条件地选择结构初始化式。如果参数列表很短,则会用默认参数填充。

    #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

正如你可以从-O3分解中看到的,复合字面量的开销为零。


我注意到我的方法让人想起了目前投票最多的答案。与其他解决方案的比较:

优点:

  • 纯粹的、可移植的标准ISO C,没有脏的gcc扩展,没有定义不好的行为。
  • 可以处理空参数列表。
  • 高效,零开销,不依赖于函数内联按预期进行。
  • 在调用端没有模糊的指定初始化式。

缺点:

  • 依赖于每个参数都隐式转换为int,但通常情况并非如此。例如,严格C不允许从指针到int的隐式转换——这种隐式转换是一个不符合(但流行)的编译器扩展。
  • 默认参数和结构必须为每个函数生成。虽然这个答案没有涉及到,但这可以通过X宏实现自动化。但这样做也会进一步降低可读性。