C 語言無原生函數重載,但可通過可變參數、GCC 內建類型檢查、宏預處理、函數指針等技巧模擬。本文系統對比四大方案原理、代碼實現、適用場景與優劣勢。
int func(); // _Z4funcv
int func(int); // _Z4funci
int func(int,float); // _Z4funcif
編譯器按參數類型修飾函數名,調用時自動選擇。C 無此機制,需手工實現分發。
va_arg(按參數個數重載)
#include <stdarg.h>
void va_overload2(int p1, int p2) { printf("2 args: %d %d\n", p1, p2); }
void va_overload3(int p1, int p2, int p3) { printf("3 args: %d %d %d\n", p1, p2, p3); }
void va_overload(int p1, int p2, ...) {
if(p2 == 3) { // 約定:p2=3 表示有第三參數
va_list v; va_start(v, p2);
int p3 = va_arg(v, int);
va_end(v);
va_overload3(p1, p2, p3);
return;
}
va_overload2(p1, p2);
}
int main() {
va_overload(1, 2); // 2 args
va_overload(1, 2, 3); // 3 args
}
缺點:需約定標記參數、類型不安全、無法區分同個數不同類型。
__builtin_types_compatible_p + __builtin_choose_expr(按類型重載)
struct s1 { int a,b; double c; };
struct s2 { long long a,b; };
void gcc_overload_s1(struct s1 s) { printf("s1: %d %d %f\n", s.a, s.b, s.c); }
void gcc_overload_s2(struct s2 s) { printf("s2: %lld %lld\n", s.a, s.b); }
// 巨集分發
#define gcc_overload(A) \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s1), \
gcc_overload_s1(*(struct s1*)&A), \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s2), \
gcc_overload_s2(*(struct s2*)&A), (void)0))
// 更優雅:輔助函數 + 類型編碼
void gcc_type_overload_aux(int typeval, ...) {
va_list v; va_start(v, typeval);
switch(typeval) {
case 1: { struct s1 s = va_arg(v, struct s1); gcc_overload_s1(s); break; }
case 2: { struct s2 s = va_arg(v, struct s2); gcc_overload_s2(s); break; }
default: printf("Invalid type\n"); exit(1);
}
va_end(v);
}
#define gcc_type_overload(A) \
gcc_type_overload_aux( \
__builtin_types_compatible_p(typeof(A), struct s1) * 1 + \
__builtin_types_compatible_p(typeof(A), struct s2) * 2, \
A)
優點:編譯期類型檢查、零運行時開銷、類型安全。
缺點:僅限 GCC/Clang、語法複雜。
#define OVERLOAD_FUNC(name, ...) name##_##__VA_ARGS__
#define func(...) OVERLOAD_FUNC(func, __VA_ARGS__)
void func_int(int a) { printf("int: %d\n", a); }
void func_float(float a) { printf("float: %f\n", a); }
// 調用需顯式指定類型標記
func(int)(5);
func(float)(3.14f);
本質是手工 name mangling,調用端需知悉內部命名規則。
typedef void (*Handler)(void*);
void handle_int(void *p) { printf("int: %d\n", *(int*)p); }
void handle_float(void *p) { printf("float: %f\n", *(float*)p); }
Handler handlers[] = { [0] = handle_int, [1] = handle_float };
void dispatch(int type, void *data) {
if(type >= 0 && type < 2) handlers[type](data);
}
適用於插件架構、狀態機、事件分發。
| 方案 | 重載依據 | 類型安全 | 編譯期 | 可移植 | 複雜度 |
|---|---|---|---|---|---|
| va_arg | 參數個數 | 低 | 否 | 高 | 低 |
| GCC builtin | 參數類型 | 高 | 是 | 低 | 高 |
| 宏名修飾 | 調用端標記 | 中 | 是 | 高 | 中 |
| 函數指針表 | 運行時類型碼 | 中 | 否 | 高 | 低 |
va_arg__builtin_types_compatible_pC 可實現重載,但需額外代碼、門檻較高,**不適合作為規範 API 設計慣用手段**。面試時可答:「視場景而定,通常用 va_arg 或 GCC 擴展模擬」。