默认实参


C 本身不支持在定义函数指定默认实参,然而,可以透过结构与宏来处理,例如,若有个结构与函数如下:

typedef struct {
    int a;
    double b;
} foo_args;

void _foo(foo_args args) {
    int a = args.a ? args.a : 8;
    double b = args.b ? args.b : 3.14;
    printf("a:%d\n", a);
    printf("b:%f\n", b);
}

如果a成员没有指定,默认的成员值会是 0,在_foo中就会使用 8 作为默认值,类似地,若b成员没有指定,默认值会是 0.0,这时会使用 3.14 作为默认值。

可以如下进行调用:

_foo((foo_args) {});
_foo((foo_args) {10});
_foo((foo_args) {10, 20.0});
_foo((foo_args) {.a = 5, .b = 30});

以上创建了匿名的foo_args实例并传入,由于可以指定成员名称来设值,也就可以有具名参数指定的风格。

不过每次都得编写(foo_args){}感觉不是很方便,这可以可以定义宏来展开:

#include <stdio.h>
#define foo(...) _foo((foo_args) {__VA_ARGS__});

typedef struct {
    int a;
    double b;
} foo_args;

void _foo(foo_args args) {
    int a = args.a ? args.a : 8;
    double b = args.b ? args.b : 3.14;
    printf("a:%d\n", a);
    printf("b:%f\n", b);
}

int main(void) {
    foo();
    foo(10);
    foo(10, 20);
    foo(.a = 5, .b = 30);

    return 0;
}

宏的出发点是字符串取代(或称扩展),...代表实参的部份,后续可以使用__VA_ARGS__在指定位置展开,以foo(10, 20)为例,...捕捉了10, 20,后续的__VA_ARGS__会展开为10, 20,因此整个foo(10, 20)会被展开为_foo((foo_args) {10, 20})

基于以上,若要设置必要参数,可以如下:

#include <stdio.h>
#define foo(must, ...) _foo(must, (foo_args) {__VA_ARGS__});

typedef struct {
    int a;
    double b;
} foo_args;

void _foo(char must, foo_args args) {
    int a = args.a ? args.a : 8;
    double b = args.b ? args.b : 3.14;
    printf("must:%c\n", must);
    printf("a:%d\n", a);
    printf("b:%f\n", b);
}

int main(void) {
    foo('A');
    foo('B', 10);
    foo('C', 10, 20);
    foo('D', .a = 5, .b = 30);

    return 0;
}

如果只想模拟具名参数风格,由于结构若具名地指定成员时,重复是可以允许的,也就可以编写如下:

#include <stdio.h>
#define foo(must, ...) _foo(must, (foo_args){.a = 8, .b = 3.14, __VA_ARGS__});

typedef struct {
    int a;
    double b;
} foo_args;

void _foo(char must, foo_args args) {
    printf("must:%c\n", must);
    printf("a:%d\n", args.a);
    printf("b:%f\n", args.b);
}

int main(void) {
    foo('A');
    foo('B', .a = 5);
    foo('C', .b = 88);
    foo('D', .b = 9, .a = 2);

    return 0;
}




展开阅读全文