經典計算器程序,隨操作碼 (OpCode) 增加,switch 膨脹為數百行:
int GetOpResultBySwitch(int a, int b, OpCode op)
{
switch (op) {
case OP_ADD: return a + b;
case OP_SUB: return a - b;
case OP_MUL: return a * b;
case OP_DIV: return b ? a/b : 0;
// ... 新增 OP_MOD, OP_POW, OP_BIT_AND ...
default: return -1;
}
}
弊端:
核心思想:將「操作碼 → 處理函數」的映射關係提取為靜態數據表,運行時通過下標直接索引。
typedef int (*BinaryOp)(int, int); // 統一接口
int Add(int a, int b) { return a + b; }
int Sub(int a, int b) { return a - b; }
int Mul(int a, int b) { return a * b; }
int Div(int a, int b) { return b ? a/b : 0; }
typedef enum {
OP_ADD = 0,
OP_SUB,
OP_MUL,
OP_DIV,
OP_COUNT // 哨兵,自動等於操作數量
} OpCode;
/* 驅動表:下標 = OpCode,值 = 處理函數 */
static const BinaryOp g_OpTable[OP_COUNT] = {
[OP_ADD] = Add,
[OP_SUB] = Sub,
[OP_MUL] = Mul,
[OP_DIV] = Div
// C99 指定初始化器,順序無關、擴展無誤
};
int GetOpResultByTable(int a, int b, OpCode op)
{
if (op >= OP_COUNT) return -1; // 邊界保護
if (!g_OpTable[op]) return -1; // 空槽保護 (支持稀疏表)
return g_OpTable[op](a, b); // 直接調用
}
typedef enum { ERR_OK, ERR_DIV_ZERO, ERR_OVERFLOW, ERR_INVALID_OP } ErrCode;
typedef ErrCode (*SafeOp)(int a, int b, int* result);
ErrCode SafeDiv(int a, int b, int* res)
{
if (b == 0) return ERR_DIV_ZERO;
*res = a / b;
return ERR_OK;
}
/* 驅動表擴展為結構體,攜帶元數據 */
typedef struct {
OpCode code;
const char* name;
SafeOp handler;
const char* desc;
} OpEntry;
static const OpEntry g_OpRegistry[] = {
{OP_ADD, "ADD", SafeAdd, "加法"},
{OP_DIV, "DIV", SafeDiv, "除法(檢查除零)"},
// ...
};
/* 通用執行引擎 */
ErrCode ExecuteOp(OpCode op, int a, int b, int* result)
{
for (size_t i = 0; i < sizeof(g_OpRegistry)/sizeof(g_OpRegistry[0]); ++i) {
if (g_OpRegistry[i].code == op)
return g_OpRegistry[i].handler(a, b, result);
}
return ERR_INVALID_OP;
}
#define MAX_OPS 64
static OpEntry g_DynamicTable[MAX_OPS];
static size_t g_OpCount = 0;
BOOL RegisterOp(OpCode code, const char* name, SafeOp handler, const char* desc)
{
if (g_OpCount >= MAX_OPS) return FALSE;
// 可選:檢查 code 衝突
g_DynamicTable[g_OpCount++] = (OpEntry){code, name, handler, desc};
return TRUE;
}
這使得運行時加載共享庫 (.so/.dll) 並註冊新操作成為可能,無需重啟主程序。
| 指標 | Switch-Case (N個case) | 驅動表 (N個條目) |
|---|---|---|
| 圈復雜度 V(G) | N + 1 | 1 (查表函數) + N×1 (各handler) |
| 測試用例下限 | N + 1 | 1 (表遍歷) + Σ handler各自測試 |
| 新增操作修改點 | 核心函數 + 測試 | 僅新增 handler + 註冊調用 |
| 編譯單元耦合 | 高 (需包含所有handler頭文件) | 低 (handler可獨立編譯單元) |
typedef struct {
const char* cmdName;
void (*handler)(int argc, char* argv[]);
const char* help;
} CmdEntry;
static const CmdEntry g_CmdTable[] = {
{"reboot", Cmd_Reboot, "系統重啟"},
{"flash", Cmd_Flash, "固件升級"},
{"config", Cmd_Config, "參數配置"},
// ...
};
void Shell_Process(char* line)
{
char* argv[16]; int argc = ParseArgs(line, argv);
if (argc == 0) return;
for (size_t i = 0; i < sizeof(g_CmdTable)/sizeof(g_CmdTable[0]); ++i) {
if (strcmp(argv[0], g_CmdTable[i].cmdName) == 0) {
g_CmdTable[i].handler(argc, argv);
return;
}
}
Printf("Unknown command: %s\n", argv[0]);
}
驅動表模式將控制流轉化為數據查找,本質是「約定優於配置」的 C 語言實踐。它帶來:
下次面對膨脹的 switch/if-else 鏈時,請想起:一張表,搞定所有分支。