C默认参数

363

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


14
只想要一个稍微好一点的 C,不是 C++。认为 C+ 是从 C++ 中提取了各种小改进,但没有大混乱。而且,请不要使用不同的链接加载器。应该只是另一个类似预处理器的步骤。标准化。无处不在... - ivo Welch
在侧边栏中我没有看到的相关问题 - Shelby Moore III
我想说的是,别再像野蛮人一样了,好好学习使用C++(11,...)- 开个玩笑! /我熄灭了火焰...但是...你会爱上它的...哈哈哈,我忍不住了,抱歉。 - moodboom
23个回答

15

还有一种选项是使用 struct

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

9

编号


3
什么是解决方法?我可以看到它用十六进制表示为 20202020,但我该如何输入? - Lazer
1
@Lazer 那应该是 ASCII 空格,对吧? - Gustavo6046

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
}

5

请查看我的答案,它是从你的答案中得出的。 - Shelby Moore III
4
将示例内联。 - user877329

4

我改进了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__)

3

通常不需要,但在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) );
}

2

通过宏

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

如果您需要第四个参数,则需要添加一个额外的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 变量不能被赋默认值(除非它是最后一个参数,例如在三个参数的情况下),因为它们需要句点('.'),而这在宏参数中不被接受。但可以通过类似于 my_func2 宏(四个参数的情况)中所见的方法解决。

程序

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  

1

我偶尔使用的一个技巧,自 C99 以来就已经可用,使用可变参宏、复合字面量和指定初始化程序。与任何宏解决方案一样,它很繁琐,通常不建议除非作为最后的手段…

我的方法是以下方式构建的:

  • 将实际函数包装在类似函数的可变参数宏中:

    void myfunc (int x, int y)         // 实际函数
    #define myfunc(...) myfunc(params) // 包装器宏
    
  • 通过使用复合字面量,将传递的参数复制到临时对象中。该对象应为与函数预期参数列表直接对应的私有结构体。例如:

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

    这意味着传递参数给函数时使用的相同类型安全性(“按分配”)规则也用于初始化此结构。我们不会失去任何类型安全性。同样,这自动防止提供过多的参数。

  • 然而,上述方法并不涵盖空参数列表的情况。为了解决这个问题,添加一个虚拟参数,以便初始化程序列表永远不为空:

    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

Godbolt: https://godbolt.org/z/4ns1zPW16 从 -O3 反汇编结果可以看出,复合字面量没有任何开销。


我注意到我的方法有点像当前得票最高的答案。与此处的其他解决方案进行比较:

优点:

  • 纯净、可移植的标准 ISO C,没有脏的 gcc 扩展,没有定义不清的行为。
  • 可以处理空参数列表。
  • 高效、零开销,不依赖于函数内联按预期执行。
  • 调用方没有晦涩的指定初始化器。

缺点:

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

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


在你的第一个例子中,只有当 x 已经是 0 时,x == NULL 才为真,因此代码不会给 x 赋默认值。第二个例子可能有效,但如果输入参数可以是完整的值范围,则很容易无效。 - user904963
如果你只有一个参数可以保存默认值,那么添加一个布尔类型的参数标志是可以的,但是如果你需要所有参数都有这样的标志,那么它会变得笨重!使用表示默认值的NULL更好... - Andrew

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接