15.4 四種卡關情境
接下來是四個新手常見的卡關情境。它們初遇時都很嚇人,但其實各自有固定的嫌疑犯名單——對號入座,通常很快解決。
¶情境一:自己電腦編譯成功,OJ 上卻編譯錯誤
程式在本機跑得好好的,一提交就 CE(Compile Error)。常見原因:
- 本機編譯器版本較舊或較寬鬆:有些舊編譯器(例如年代久遠的 Dev C++ 內建版)會放行不合標準的寫法;OJ 用的編譯器照標準來,直接打回票。
- 用了非標準的函式或標頭檔:只有特定環境才有的東西(例如 Windows 專屬的函式),OJ 上不存在。
處理方式:把 OJ 給你的完整錯誤訊息讀完——編譯錯誤訊息會指出行號和原因,從第一條開始修(後面的錯常常只是第一條的連鎖反應)。AACPOJ 還內建了一個查錯工具:常見編譯錯誤偵測——把程式碼貼上去,它會自動指出十九種新手常見的編譯錯誤與警告,並附上對應的教學說明。
¶情境二:自己電腦上範例對,OJ 上範例就錯
最詭異的情境:一模一樣的範例輸入,本機印出正確答案,OJ 卻說 WA——連範例測資都沒過。兩大嫌疑犯:
- 未初始化變數的未定義行為:15.2 那支沒初始化
sum的程式就是活案例——本機碰巧印出正確的55,換一台機器(OJ 的評測機)就可能是垃圾值。未定義行為的可怕正在於「本機看起來完全正常」。 - 輸出格式不符:多印了一個空格、少印一個換行、題目要求每筆答案一行你卻印成同一行、或是查錯用的輸出忘了刪(15.5 的清理守則)。人眼看兩份輸出「長得一樣」,OJ 是逐字元比對的。
處理方式:開著 -O2 -Wall 重新編譯聽聽警告(15.2 說過,未初始化的警告要開著最佳化才會出現);把輸出格式跟題目敘述逐字元對一遍,特別注意行尾與行數。
¶情境三:上傳後執行結果為 RE
RE(Runtime Error)=程式執行到一半異常終止。嫌疑犯名單:
- 陣列越界:最大宗。特別注意「本機沒事、OJ 才爆」的版本——本機測的都是小測資,OJ 的大測資才會踩出「陣列開太小」(15.3 清單第 2 項)。
- 除以零、對零取餘數:檢查每個
/和%的右邊有沒有可能是 0。 stoi轉換失敗:字串不是合法數字、或超出int範圍(10.9),會直接拋出例外。.at()越界:vector和string的.at()檢查範圍,越界就拋例外終止。
在本機重現 RE 時,訊息長這樣(vector<int> v(5); 卻去讀 v.at(5),本站實測):
terminate called after throwing an instance of 'std::out_of_range'
what(): vector::_M_range_check: __n (which is 5) >= this->size() (which is 5)
看起來凶神惡煞,其實資訊滿滿:out_of_range 就是「越界」,後面連「索引 5 撞上大小 5」都告訴你了。處理方式:檢查所有中括號與除號;抓不到就用 15.2 延伸的 sanitizer,讓它直接報行號。順帶一提,[] 越界是未定義行為不保證會 RE(可能悄悄給你錯的值),所以「沒 RE」不等於「沒越界」。
¶情境四:上傳後執行結果為 TLE
TLE(Time Limit Exceeded)=時間內沒跑完。嫌疑犯:
- 演算法本身太慢:用上冊 4.9 的方法估運算量——超過約 10^8 就該換做法,這不是「查錯」能救的,是解法要重想。
- 死迴圈:
while條件永遠成立、迴圈變數忘了更新、EOF 輸入模式寫錯導致永遠讀不到結束。懷疑死迴圈時,在迴圈裡印圈數(15.5),一眼看出它停不下來。 - 輸出用
endl狂洗版:12.10 實測過,10^6 行輸出endl比'\n'慢五十倍——大輸出量的題目光這個就能 TLE。 - 大輸入量沒開加速:12.10 的兩行開場白沒寫,
cin讀 10^6 筆資料的同步成本就可能吃掉大半時限。
處理方式:先估運算量分辨「演算法慢」還是「常數慢」;前者重想解法,後者檢查 endl 與開場白。
動手試試看:故意寫一個死迴圈(while (n != 1) 但迴圈裡忘了改 n)跑跑看,感受「程式沒回應」長什麼樣子;再故意用 v.at(v.size()) 觸發一次 RE,練習讀懂錯誤訊息。