if、switch、巢狀、最佳實踐與常見陷阱| 結構 | 適用場景 | 關鍵字 | 表達式型別 |
|---|---|---|---|
if-else | 區間判斷、複合條件、布林邏輯、指標/浮點比較 | if, else, else if | 任意標量 (整數、浮點、指標),非零為真 |
switch | 離散常數值匹配 (枚舉、字元、整數常數)、狀態機 | switch, case, default | 整數型別 (含 char, enum),編譯期常數 |
if 語句完整語法
// 1. 單分支
if (expr) statement;
// 或
if (expr) { block; }
// 2. 雙分支
if (expr) statement1; else statement2;
// 或
if (expr) { block1; } else { block2; }
// 3. 多重分支 (else-if 鏈)
if (expr1) { ... }
else if (expr2) { ... }
else if (expr3) { ... }
...
else { ... } // 兜底
if 僅管制**緊接的單一陳述式** (含空語句 ;),後續程式碼順序執行。else:else 總與**最近的未配對 if** 綁定 (就近原則)。用大括號消除歧義。== > && > || > ?: > =,複雜條件加括號。= 而非 ==
if (x = 5) { ... } // 永遠為真,x 被指派 5
// 編譯器警告: -Wparentheses / -Wassignment-in-condition
if (5 == x) (C 風格) 或啟用警告視為錯誤
double d = 0.1 + 0.2;
if (d == 0.3) ... // 可能為假 (0.30000000000000004)
fabs(a-b) < 1e-9
if (a)
if (b) foo();
else bar(); // else 配對給 if(b),非 if(a)!
if (a) {
if (b) foo();
} else bar();
switch 語句完整語法
switch (expr) {
case const1: statements1; break; // break 結束 switch
case const2: statements2; break;
case const3:
case const4: statements34; break; // 多標籤共用程式碼
default: default_stmts; break; // 兜底 (可選)
}
expr 必須為**整數型別** (int, char, short, long, enum);不可為浮點、字串、結構體。case 標籤必須為**編譯期整數常數表達式**;不可為變數、函數呼叫。break 時,執行完當前 case 繼續執行下一 case (C 語言預設行為,常用於合併處理)。default 位置任意 (慣例放最後);若無 default 且無匹配 case,整個 switch 無動作。switch 內可宣告變數 (C99),但**初始化會被跳過** (跳躍過初始化) → 需用區塊 { int x = 0; } 包裹。case 值連續/稠密 → 編譯器生成 **跳躍表** (O(1) 間接跳躍)。case 值稀疏 → 生成 **二分搜尋決策樹** (O(log n))。if-else 鏈。
// if 巢狀 switch
if (user_logged_in) {
switch (user_role) {
case ADMIN: ShowAdminPanel(); break;
case EDITOR: ShowEditorPanel(); break;
case VIEWER: ShowViewerPanel(); break;
default: ShowGuestPanel();
}
} else {
ShowLoginPage();
}
// switch 巢狀 if
switch (event_type) {
case KEY_PRESS:
if (key == 'q') Quit();
else if (key == 'h') Help();
break;
case MOUSE_CLICK:
if (btn == LEFT) Select();
else if (btn == RIGHT) ContextMenu();
break;
}
// ❌ 巢狀地獄
void Process(User *u) {
if (u) {
if (u->active) {
if (u->permission >= READ) {
DoWork(u);
}
}
}
}
// ✅ 衛語句
void Process(User *u) {
if (!u) return;
if (!u->active) return;
if (u->permission < READ) return;
DoWork(u);
}
typedef void (*Handler)(void);
Handler handlers[256] = {0};
void Init() {
handlers['a'] = HandleA;
handlers['b'] = HandleB;
// ...
}
void Dispatch(char cmd) {
if (handlers[(unsigned char)cmd]) handlers[(unsigned char)cmd]();
else DefaultHandler();
}
typedef enum { IDLE, RUNNING, PAUSED, ERROR } State;
State current = IDLE;
void EventHandler(Event e) {
switch (current) {
case IDLE:
if (e == START) current = RUNNING;
break;
case RUNNING:
if (e == PAUSE) current = PAUSED;
else if (e == STOP) current = IDLE;
else if (e == FAULT) current = ERROR;
break;
// ...
}
}
switch 擴充 (提案) — 模式匹配未來可能支援:switch (x) { case 1..10: ...; case >100: ...; } 範圍匹配。
| 結構 | 情境 | 相對耗時 |
|---|---|---|
switch (連續 case) | 跳躍表 | 1.0x (基準) |
switch (稀疏 case) | 二分搜尋 | 1.3x |
if-else 鏈 (命中首項) | 分支預測友善 | 1.1x |
if-else 鏈 (命中尾項) | 多次分支錯誤 | 2.5x |
| 查表法 (函數指標陣列) | 間接呼叫 | 1.2x |
結論:**熱路徑優先用 switch (連續值) 或查表法**;冷路徑/複雜條件用 if-else + 衛語句。
選擇結構是程式流程控制的骨架。精通 if-else 的布林邏輯與衛語句扁平化、switch 的跳躍表最佳化與貫穿語意、巢狀結構的狀態機建模、查表法替代巨大條件鏈,能寫出可讀、可維護、效能可預測的 C 程式碼。下次寫條件判斷時,先問:值是否為離散常數?是否為熱路徑?能否提前返回? — 選擇最適工具。