C默认参数


279

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


6
只想要更好的C语言,而不是C ++语言。认为C +。C ++带来了许多小的改进,但不是一团糟。而且,请不要使用其他链接加载器。应该只是另一个类似于预处理程序的步骤。标准化。到处都是…
ivo Welch

我没有看到的相关问题在侧栏中列出。
谢尔比·摩尔三世

我会说停止成为野蛮人,并学会使用C ++(11,...)-jk!/我扑灭了火焰...但是...你会爱上它的...哈哈哈,我忍不住了,抱歉。
moodboom

Answers:


147

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


22
我讨厌使用varargs时缺乏检查。
dmckee ---前主持人小猫,

16
您也应该;我实际上不建议这样做。我只是想传达这是可能的。
Eli Courtwright 09年

4
但是,您如何检查调用方是否传递了参数?我认为要使此方法有效,您是否没有来电者告诉您他没有通过?我认为这会使整个方法的可用性降低-调用者还可以使用其他名称调用函数。
Johannes Schaub-litb

3
open(2)系统调用使用此为可选参数取决于所需的参数可能存在,并且printf(3)读取格式字符串指定多少个参数会有。两者都非常安全有效地使用了varargs,尽管您当然可以固定它们,但printf()似乎很受欢迎。
克里斯·卢茨

4
@Eli:并非所有的C编译器都是gcc。当您的printf()参数与格式字符串不匹配时,会发出一些高级编译器魔术警告它。而且我认为您自己的可变参数函数不可能收到类似的警告(除非它们使用相同样式的格式字符串)。
tomlogic 2010年

280

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

这并非无关紧要:最后,我们将拥有核心功能,支持结构,包装器功能以及包装器功能周围的宏。在我的工作中,我有一组宏可以自动完成所有这些操作。一旦了解了流程,您将很容易做到这一点。

我已经在其他地方进行了编写,因此这里有一个详细的外部链接以补充此处的摘要: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非常简单,而且仍然有足够的能力使所有这些成为可能。


16
+1广告!它有其局限性,但也将命名参数引入表中。请注意,{}(空初始化程序)是错误C99。
u0b34a0f6ae 2011年

28
但是,这对您来说很重要:该标准允许多次指定命名成员,以后再覆盖。因此,仅对于命名参数,您可以解决默认问题并允许空调用。#define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
u0b34a0f6ae 2011年

3
我希望编译器错误是可读的,但这是一个很棒的技术!几乎看起来像python kwargs。
totowtwo

6
@RunHolt虽然肯定更简单,但客观上并没有更好。命名参数具有诸如便于调用的可读性(以牺牲源代码的可读性为代价)等优点。一个对源代码的开发者来说更​​好,另一种对函数的用户来说更好。仅仅抛出“这个更好”是有点草率的。
爱丽丝

3
@DawidPi:C11 6.7.9(19),在初始化聚合时:“所有未显式初始化的子对象都应与具有静态存储持续时间的对象隐式地初始化”,并且您知道,静态持续时间元素被初始化为零| NULL | \ 0。[在c99中也是如此。]
bk。

161

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

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


24
FWIW,我更喜欢使用函数末尾的数字来表示所需的args数。比仅使用任意数字更容易。:)
Macke

4
到目前为止,这是最好的答案,因为它演示了实现相同目标的简单方法。我有一个函数是我不想更改的固定API的一部分,但我需要使用它来采用新的参数。当然,它是如此令人
目眩的

4
f2也可以是预处理程序宏
osvein

41

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

2
与Wim十Brink或BK解决方案相比,这似乎是一个更容易,更直接的实现。此实施方案是否有其他方面没有的缺点?
stephenmm 2014年

24

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

如果用户不知道自己应该写什么,此技巧可能会有所帮助:

使用范例


19

没有。

甚至最新的C99标准也不支持此功能。


一个简单的否定甚至会更好;)
KevinDTimm

21
@kevindtimm:这是不可能的,所以SO要求答案的长度最小。我试过了。:)
放松

2
请参考我的答案。:)
混乱


14

简短答案:不可以。

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

int f(int arg1, double arg2, char* name, char *opt);

其中opt可能包含“名称=值”对或其他内容,您可能会这样称呼

n = f(2,3.0,"foo","plot=yes save=no");

显然,这只是偶尔有用。通常,当您想要一个接口到一系列功能时。


您仍然可以在由c ++专业程序编写的粒子物理学代码中找到这种方法(例如ROOT)。它的主要优点是,在保持向后兼容性的同时,可以无限期地扩展它。


2
将其与varargs结合使用,您将获得各种乐趣!
David Thornley 2009年

5
我将使用一个自定义方法,struct并让调用者创建一个自定义方法,为不同的选项填写字段,然后按地址传递它,或者传递NULL默认选项。
克里斯·卢兹

从ROOT复制代码模式是一个糟糕的主意!
innisfree,2017年

13

做到这一点的最佳方法(根据您的情况,可能会或可能不会)可能是转到C ++并将其用作“更好的C”。您可以使用C ++,而无需使用类,模板,运算符重载或其他高级功能。

这将为您提供C语言的变体,其中包含函数重载和默认参数(以及您选择使用的任何其他功能)。如果您真的很认真地仅使用C ++的受限子集,则只需稍加训练即可。

很多人会说以这种方式使用C ++是一个糟糕的主意,他们可能会有一点建议。但这只是一种意见;我认为使用您习惯使用的C ++功能而不必全盘考虑是有效的。我认为,成功使用C ++的重要原因之一是,在早期,很多程序员都以这种方式使用了C ++。


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

9

没有。


2
解决方法是什么?我可以看到它是20202020十六进制的,但是我怎么输入呢?
Lazer

5

使用宏的另一个技巧:

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


4

不,但是您可能会考虑使用一函数(或宏)来使用默认参数进行近似:

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


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

1

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

1

通过宏

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宏(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  

0

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

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

-6

我们为什么不能这样做。

给可选参数一个默认值。这样,函数的调用者不必传递参数的值。该参数采用默认值。而且,该参数很容易成为客户端的可选参数。

例如

无效foo(int a,int b = 0);

b是可选参数。


10
令人惊讶的见解是,问题在于C不支持可选参数或重载函数,因此直接解决方案无法编译。
托马斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.