經典面試題:輸出 1, -2 後為何停止?
題目代碼
#include <stdio.h>
int main() {
int x = 3;
do {
printf("%d\n", x -= 2);
} while (!(--x));
return 0;
}
// 輸出: 1, -2
逐步執行追蹤表
| 輪次 | x 初值 | x -= 2 | printf 輸出 | --x (前置遞減) | ! (邏輯非) | while 判斷 | 動作 |
| 1 | 3 | 1 | 1 | 0 | 1 (真) | true | 繼續循環 |
| 2 | 0 | -2 | -2 | -3 | 0 (假) | false | 退出循環 |
關鍵運算符優先級與結合性
while (!(--x))
// 解析順序(由高到低):
// 1. --x (前置遞減,右結合)
// 2. ! (邏輯非,右結合)
// 3. while 判斷整個表達式值
// 等價展開:
temp = --x; // x = x - 1,取新值
cond = !temp; // 邏輯非:0→1,非0→0
if (cond) continue; else break;
核心邏輯解析
- do-while 至少執行一次:無條件進入循環體
- x -= 2 先執行:x = 3 → 1,輸出 1
- --x 後執行:x = 1 → 0
- !0 = 1 (真):條件為真,繼續循環
- 第二輪:x = 0 → -2 (輸出),x = -2 → -3
- !(-3) = 0 (假):條件為假,退出循環
邏輯非 ! 運算符真值表
| 運算元 | 結果 | 說明 |
| 0 | 1 | 假變真 |
| 非 0 (含負數) | 0 | 真變假 |
變體練習
// 變體 1:去掉邏輯非
do { printf("%d\n", x -= 2); } while (--x);
// x: 3→1(輸出1)→0(假)停止。輸出: 1
// 變體 2:後置遞減
do { printf("%d\n", x -= 2); } while (!(x--));
// x: 3→1(輸出1)→1(真)→ -1(輸出-1)→-2(真)→-3(輸出-3)→-4(真)...無限循環!
// 變體 3:分離條件判斷(更清晰)
do {
x -= 2;
printf("%d\n", x);
x--; // 等價於 --x 在條件中
} while (x != 0); // 明確語義:x 不為 0 繼續
最佳實踐建議
- 避免在循環條件中做複雜副作用操作:將狀態變更移到循環體內,條件只做純判斷
- 優先用 while/for 替代 do-while:除非確確實實需要「至少執行一次」
- 複雜條件拆解為中間變量:可讀性 >> 炫技
- 單元測試覆蓋邊界:x 初值為 0、1、2、3、負數等情況
等價重構版(推薦寫法)
int x = 3;
while (1) { // 明確無限循環
x -= 2;
printf("%d\n", x);
x--; // 明確狀態變更
if (x == 0) break; // 明確退出條件
}
// 輸出相同:1, -2
// 但邏輯清晰、易維護、易測試