C++ 支援 void f(int a = 10);,C 必須通過宏、結構體、_Generic (C11) 或設計模式變通。
#include <stdio.h>
int _func(int a, int b) { return a + b; }
// 宏展開:f1() → _func(0, 1) ; f1(2) → _func(2, 1)
#define f1(arg0) _func(DEFARG(arg0, 0), 1)
// 萬能宏:參數為空則用默認值
#define DEFARG(name, defval) ((#name[0]) ? (name) : (defval))
int main(void) {
printf("%d\n", f1()); // 0+1=1
printf("%d\n", f1(5)); // 5+1=6
return 0;
}
原理:#name 字符串化,name[0] 判斷是否為空 token。
typedef struct { int a; int b; } Args;
int func_impl(Args args) { return args.a + args.b; }
#define func(...) func_impl((Args){ .a = 0, .b = 1, __VA_ARGS__ })
int main(void) {
printf("%d\n", func()); // 1
printf("%d\n", func(.a=5)); // 6
printf("%d\n", func(.b=3)); // 3
printf("%d\n", func(.a=2,.b=4)); // 6
return 0;
}
優勢:類型安全、參數順序無關、可擴展多參數。
#define func(...) _Generic((__VA_ARGS__), \
int: func_int, \
default: func_default \
)(__VA_ARGS__)
較複雜,適用於嚴格類型分發場景。
// api.h
void _getStr(const char *filename);
#define getStr() _getStr("default.txt")
// api.c
void _getStr(const char *filename) { /* ... */ }
// main.c
#include "api.h"
int main() { getStr(); getStr("custom.txt"); }
編譯單元分離,二進制兼容,適合庫開發。
| 方案 | 標準要求 | 類型安全 | 可讀性 | 適用場景 |
|---|---|---|---|---|
| 宏重定向 | C89 | 弱 | 中 | 簡單 1-2 參數 |
| 結構體初始化器 | C99 | 強 | 高 | 多參數、現代代碼庫 |
| _Generic | C11 | 強 | 低 | 類型分發、泛型模擬 |
| 內部函數+宏 | C89 | 中 | 高 | 庫接口、二進制兼容 |
__VA_ARGS__ 行為在 C99/C11/GCC/Clang/MSVC 間有差異,C23 標準化了 __VA_OPT__。#name 字符串化會保留空白,需配合 #name[0] 判斷非空。