15.8 assert:讓程式自我檢查(延伸知識)
最後一件小工具。寫程式時腦中隨時有一堆「這裡一定成立」的信念:讀進來的 n 一定在題目給的範圍內、算到這裡 sum 一定不是負數、陣列索引一定沒越界……bug 的本質,就是其中某個信念其實是錯的。assert 讓你把信念寫成程式,交給電腦逐一查核:
#include <cassert>
assert(條件);
程式執行到這行時檢查條件:成立就當作沒事繼續跑;不成立就立刻終止,並印出是哪個檔案、哪一行、哪個條件陣亡。
¶範例程式碼
把「我認定 n 在範圍內」寫成檢查:
#include <iostream>
#include <cassert>
using namespace std;
int main() {
int n;
cin >> n;
assert(1 <= n && n <= 100); // 我認定 n 一定在題目給的範圍內
long long sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
cout << sum << '\n';
return 0;
}
執行結果(輸入 100):
5050
條件成立,assert 一聲不吭。換餵一個範圍外的 0:
執行結果(輸入 0):
a.out: main.cpp:8: int main(): Assertion `1 <= n && n <= 100' failed.
Aborted (core dumped)
程式當場終止,訊息把案發地點(main.cpp 第 8 行)和陣亡的條件原文全部報給你。
¶什麼時候用
- 讀入後檢查範圍:像上面那樣,把題目宣稱的範圍 assert 起來。如果它爆了,代表你把範圍看錯了(15.1 心法三的自動化版本)——這比默默算出錯誤答案好一萬倍。
- 檢查演算法的中間狀態:例如「分完這一步,
cnt一定介於 0 和 n 之間」。中間狀態一爆,你立刻知道錯誤發生在這行之前,辦案範圍瞬間砍半。 - 給對拍的暴力解上保險:在 15.7 的
brute開頭 assert 輸入合法(例如assert(2 <= n && n <= 6);),萬一產生器寫錯生出非法測資,brute 會當場異常終止、對拍主程式回報「有程式執行失敗」——連產生器的 bug 都逃不掉(本站實測:把產生器上限偷偷改大,對拍第二輪就攔下來了)。
兩件收尾小事。第一,本機想暫時關掉所有檢查時,編譯加上 -DNDEBUG(定義 NDEBUG 巨集)就能讓整份程式的 assert 全部消失、零執行成本。第二,本站 OJ 評測時不會定義 NDEBUG——你提交的 assert 在評測時是活的,失敗會顯示為 RE。那提交前要不要拔?通常不必:檢查本身的成本極小,而且萬一真的在 OJ 上爆掉,那個 RE 反而在告訴你「程式有 bug,而且就是這一行的信念倒了」——比默默算出錯誤答案的 WA 有價值得多。
動手試試看:照「什麼時候用」的第三點,親手把 15.7 的 brute 加上 assert、把 gen 的 n 上限從 6 改成 8,跑 ./duipai 看它怎麼回報。改完記得把產生器改回來。