15.5 print 除錯法
15.1 說「模擬自己的程式」,測資小可以手動跑;但步驟一多,手動模擬又慢又容易抄錯。最實在的辦法是讓程式自己報告:在關鍵步驟印出中間變數,對照你的預期,找出第一個分岔點。這招叫 print 除錯法——不用學任何新工具,人人第一天就會,資深選手也天天在用。
¶用 cerr,不要用 cout
印除錯資訊不要用 cout,用 cerr:
cerr << "i = " << i << ", sum = " << sum << '\n';
cerr 把東西送到標準錯誤(stderr)——它是與標準輸出(stdout)分開的另一條輸出通道,專門給錯誤與診斷訊息走。好處立刻見效:
- 不會混進答案:OJ 只把 stdout 當你的答案比對,cerr 印的東西 OJ 不看——就算忘了刪,也不會因為多印東西而 WA(但印太多還是會拖慢程式,交之前仍應清掉)。
- 重導向時不搗亂:14.7 的
./a.out > out.txt只把 stdout 寫進檔案,cerr 照樣顯示在螢幕上——答案進檔案、除錯訊息給你看,各走各的。
¶實際辦一個案子
題意:讀入 n 個成績,輸出平均(答案與正確值誤差在 10^{-4} 內都算對,9.7 的講法)。有人寫出:
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int sum = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
sum += x;
}
double avg = sum / n; // 兇手藏在這行
cout << avg << '\n';
return 0;
}
執行結果(輸入 3 與 80 85 91):
85
手算是 256 \div 3 = 85.33\ldots,程式印 85——找到會錯的測資了(15.1 步驟 3 完成)。接著模擬:在每個關鍵步驟裝上探針,印出「這一步算完的所有狀態」:
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int sum = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
sum += x;
cerr << "i = " << i << ", x = " << x << ", sum = " << sum << '\n';
}
double avg = sum / n;
cerr << "avg = " << avg << '\n';
cout << avg << '\n';
return 0;
}
執行結果(輸入同上,終端機上兩條通道都會顯示):
i = 0, x = 80, sum = 80
i = 1, x = 85, sum = 165
i = 2, x = 91, sum = 256
avg = 85
(最後還有一行 stdout 的 85。)現在逐行對照預期:三圈的 sum 是 80、165、256——全對,讀入和累加清白;avg 卻是 85 而不是預期的 85.33\ldots——第一個分岔點就在 sum / n 那行。範圍縮到一行,答案呼之欲出:int / int 是整數除法(15.3 清單第 5 項),小數在存進 double 之前就被丟了。修法是上冊 3.7 的轉型:double avg = (double)sum / n;(修正版印出 85.3333)。
print 除錯的兩條守則,這個案例都示範了:
- 印之前先寫下預期值。「i = 1 時 sum 應該是 165」——有預期才叫對照,沒預期只是印心安的。
- 每個值都帶標籤。
cerr << sum印出一個孤零零的256,過三行你就忘了它是誰;sum = 256才是給人看的。
¶查完要清乾淨
除錯輸出是鷹架,房子蓋好要拆。兩種拆法:
- 手動刪:提交前把
cerr行全部刪掉或註解——順手 Ctrl+F 搜一次cerr,確保沒有漏網之魚。 - 讓前置處理器代勞:用 12.9 提過的
#ifdef把除錯輸出包起來,只在本機出聲:
#ifdef LOCAL
cerr << "sum = " << sum << '\n';
#endif
本機編譯時加參數 -DLOCAL(等於在程式開頭補一行 #define LOCAL),這段就存在;OJ 沒有這個定義,這段在編譯前就整段消失——同一份程式碼,本機話癆、OJ 沉默,永遠不會忘記刪。本站實測:加 -DLOCAL 編譯印出 sum = 256 加答案,不加參數只印答案。
動手試試看:回到 15.1 的「最大值減最小值」錯誤程式,這次不用手動模擬——在迴圈裡放一行 cerr 印出每圈的 x、mx、mn,餵進全正數測資,親眼看著 mn 一動也不動。