預設引數


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