語法書 / AA 競程語法書 下冊 / 第十五單元 / 新手常犯錯誤檢查清單

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.6int / 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 的人,這種錯活不過編譯那一秒。

動手試試看:清單六項,每項故意寫一個會踩雷的十行小程式,親眼看一次症狀(錯的輸出、警告訊息或當掉)。自己種過一次的雷,將來在自己程式碼裡認出它的速度會快十倍。