C 語言原生不支持默認參數,但通過宏重載、_Generic (C11)、可變參數結構體等技巧,可實現類似效果。本文對比四種主流方案的優劣、適用場景與代碼示例。
// 內部實現
int _fun(int a, int b) { return a + b; }
// 公開宏:一個參數時默認 b=10
#define fun1(a) _fun(a, 10)
// 兩個參數正常調用
#define fun2(a, b) _fun(a, b)
// 使用
fun1(5); // 15
fun2(5, 20); // 25
缺點:需為每種參數組合寫宏,無法「默認參數在前,必填在後」。
#define DEFARG(name, defval) ((#name[0]) ? (name) : (defval))
int _f2(int a, int b) { return a + b; }
#define f2(a, b) _f2(DEFARG(a, 0), DEFARG(b, 1))
// 使用:必須保留逗號
f2(,); // 0+1=1
f2(2,); // 2+1=3
f2(, 3); // 0+3=3
f2(4, 5); // 9
原理:#name 字符串化參數,空參數產生空字符串 "",[0] == '\0' 判斷為假。僅 GCC/Clang 支持,MSVC 不支持空宏參數。
#define DEFAULT_VAL 40
#define FUN(A) _fun(#A)
int _fun(const char *s) {
int n = (s[0] == '-' || s[0] == '\0') ? DEFAULT_VAL : atoi(s);
return printf("%d\n", n);
}
// 使用
FUN(-); // 40 (傳 "-" 觸發默認)
FUN(67); // 67
缺點:運行時字符串解析開銷,非類型安全。
#include <stdio.h>
typedef struct {
int a;
int b;
int c;
} Config;
// 默認值構造器
#define CONFIG_DEFAULTS { .a = 1, .b = 2, .c = 3 }
// 類型安全的重載入口
#define config_init(...) _config_init((Config){CONFIG_DEFAULTS, __VA_ARGS__})
void _config_init(Config cfg) {
printf("a=%d, b=%d, c=%d\n", cfg.a, cfg.b, cfg.c);
}
// 使用
config_init(); // a=1, b=2, c=3
config_init(.a = 10); // a=10, b=2, c=3
config_init(.b = 20, .c = 30); // a=1, b=20, c=30
config_init(.a = 5, .b = 6, .c = 7); // 全覆蓋
優勢:
void log_msg(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
#define LOG(level, fmt, ...) log_msg("[%s] " fmt, level_str(level), ##__VA_ARGS__)
LOG(INFO, "User %s login", name); // 自動拼接級別前綴
| 方案 | 標準 | 類型安全 | 命名參數 | 默認值位置 | 調試友好 | 推薦場景 |
|---|---|---|---|---|---|---|
| 宏重載 | C89 | ✅ | ❌ | 僅尾部 | ✅ | 簡單 1-2 參數、舊標準項目 |
| 空參佔位 | GNU | ✅ | ❌ | 任意 | ⚠️ | GCC/Clang 專用、參數少 |
| 字符串解析 | C89 | ❌ | ❌ | 任意 | ❌ | 極簡腳本、非性能敏感 |
| _Generic+結構體 | C11 | ✅✅ | ✅ | 任意 | ✅✅ | 現代項目、配置對象、公共 API |
| VA_ARGS+fmt | C99 | ⚠️ | ❌ | N/A | ✅ | 日志、格式化輸出 |
_impl 後綴,宏包裝對外保持兼容。@param b default 10,配合 Doxygen 生成文檔。C 語言模擬默認參數的本質是在調用點展開時注入默認值。選對技巧,能在保持 C 極簡語義的同時,獲得類似高級語言的調用便利性。