15.3 新手常犯錯誤檢查清單
心法有了、編譯器隊友上線了,接下來是辦案手冊的「慣犯檔案」。多數新手的 WA 都出自同一批錯誤——與其每次從零開始瞪程式碼,不如拿著清單逐項掃過。左欄是罪名,右欄是具體的檢查方式:
| # | 常見錯誤 | 檢查方式 |
|---|---|---|
| 1 | 整數溢位:該用 long long 的變數宣告成 int |
對程式裡每一處乘法(和大量連加)都想一遍:兩個運算元最大各是多少?乘起來會不會超過 int 上限約 2.1 \times 10^9(上冊 2.9)?快篩法見下方 |
| 2 | 陣列開太小、用到負數索引 | 檢查所有出現中括號的地方:索引最大會到多少?會不會算出 -1?陣列大小是否 \ge 上限 + 1(1-base 要多開;C 字串再幫 '\0' 留一格,8.7) |
| 3 | 忘記初始化,或初始化成錯的值 | 對每個變數重新想一遍:初值應該是什麼?找最小值卻從 0 出發(15.1 的例子)?多筆測資間忘記重置?(重點展開見下方) |
| 4 | = 寫成 == |
if (x = 5) 是指派不是比較——-Wall 會警告(見下方);看到 if 條件裡孤零零的 = 就多看兩眼 |
| 5 | 整數除法丟小數 | 上冊 2.6:int / int 小數直接蒸發。算平均、比例、百分比的每個除號都檢查:兩邊是不是都是整數? |
| 6 | cin >> 與 getline 混用 |
cin >> 讀完會留下換行,getline 一來就撞上(10.10;字元版的同一個坑見 8.4)——兩種讀法交界處檢查有沒有清掉殘留換行 |
¶重點展開:多筆測資忘記重置——APCS 大殺手
清單第 3 項有個特別致命的變形。很多題目一次餵你 T 筆測試資料,如果「這一筆的狀態」(累加器、計數器、標記陣列……)沒有在每筆開始時歸零,第一筆答案是對的,從第二筆開始全錯:
¶溢位快篩:#define int long long
清單第 1 項有個快速檢驗法,用的是 12.9 的巨集。懷疑自己溢位、又一時找不到是哪個變數時,在程式最上面加一行,把整份程式的 int 暫時全變 long long:
#include <bits/stdc++.h>
using namespace std;
#define int long long // 快篩:整份程式的 int 暫時全變 long long
signed main() { // main 的 int 也被換掉了,要改寫成 signed main
int a, b;
cin >> a >> b;
cout << a * b << '\n';
return 0;
}
執行結果(輸入 100000 100000):
10000000000
沒加那兩行改動的原版,同樣輸入印出的是 1410065408——標準的溢位車禍現場。重交一次快篩版:過了,就證實兇手是溢位;接著把快篩拿掉,回頭找出真正需要 long long 的變數改型態。強調:這是檢驗手段,不是常駐寫法——12.9 說過它的代價(記憶體翻倍、速度變慢),確認病因後就該對症下藥。
¶順手驗證:-Wall 抓 = 寫成 ==
清單第 4 項交給 15.2 的隊友示範一次:
#include <iostream>
using namespace std;
int main() {
int x;
cin >> x;
if (x = 5) { // 想寫 ==,手滑寫成 =
cout << "five" << '\n';
}
return 0;
}
編譯訊息(加 -Wall):
main.cpp: In function ‘int main()’:
main.cpp:7:11: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
7 | if (x = 5) { // 想寫 ==,手滑寫成 =
| ~~^~~
x = 5 是指派,指派完的值是 5、當條件永遠成立——輸入 3 照樣印出 five,而且 x 還被改成 5,一顆 bug 兩種傷害。有開 -Wall 的人,這種錯活不過編譯那一秒。
動手試試看:清單六項,每項故意寫一個會踩雷的十行小程式,親眼看一次症狀(錯的輸出、警告訊息或當掉)。自己種過一次的雷,將來在自己程式碼裡認出它的速度會快十倍。