12.1 萬用標頭檔 bits/stdc++.h
先看你現在的程式開頭。這一年下來,#include 越積越多:
#include <iostream>
#include <iomanip>
#include <cmath>
#include <cctype>
#include <cstring>
#include <vector>
#include <string>
#include <utility>
#include <algorithm>
每用一個新工具就要想「它住在哪個標頭檔」,忘了就編譯錯誤。競程選手的偷懶方法是一行換掉整串:
#include <bits/stdc++.h>
bits/stdc++.h 是 GNU 編譯器(g++)自家提供的「萬用包」:它把所有標準函式庫的標頭檔一口氣全部包進來(只有極少數 GNU 專屬的進階工具不在裡面,Level 0 用不到)。iostream、cmath、vector、algorithm……從此通通不用背。
¶優點與缺點
| 優點 | 缺點 |
|---|---|
| 包含所有標準函式庫標頭檔 | 只有 GNU 編譯器能用(程式不可移植) |
| 不用背每個工具住在哪 | 編譯時間變長 |
| 減少寫程式的時間 | 宣告變數時,撞名機率增加 |
三個缺點都值得展開講一下。
不可移植:bits/stdc++.h 不是 C++ 標準的一部分,是 g++ 自己附贈的。用其他編譯器(例如微軟的 MSVC)編譯,這行直接找不到檔案。
編譯變慢:包進來的東西多,編譯器要讀的東西就多。本站實測:同一份 Hello World 等級的程式,只 #include <iostream> 編譯約 0.3 秒,換成 bits/stdc++.h 約 1.2 秒。對一天編譯幾百次的人來說有感,但單次還在可接受範圍。
撞名機率增加:標準函式庫裡有幾千個名字,全部倒進來之後,搭配你每份程式都寫的 using namespace std;,你的變數就可能跟它們撞名:
✘ 錯誤寫法:
#include <bits/stdc++.h>
using namespace std;
int count = 0; // 全域變數想叫 count
int main() {
count++; // 編譯錯誤:reference to 'count' is ambiguous
cout << count << '\n';
return 0;
}
count 恰好也是 <algorithm> 裡一個函式的名字。只 #include <iostream> 時這份程式編譯得過;換成 bits/stdc++.h 之後 <algorithm> 也被包進來,編譯器分不清你說的 count 是哪一個,直接罷工。遇到「reference to ‘XXX’ is ambiguous」這種錯誤訊息,幫變數換個名字就解決(count、data、size 這類常見英文單字最容易中獎;宣告在函式裡面的區域變數則不會有這個問題)。
¶開頭的另一半:using namespace std; 與 std:: 前綴
上面那個撞名,其實不是 bits/stdc++.h 一個人的責任——另一半責任在你從第一支程式就在寫、卻從沒解釋過的那行:using namespace std;。
標準函式庫的所有名字(cout、vector、sort、string……)都住在一個叫 std 的命名空間(namespace)裡,全名其實是 std::cout、std::vector——:: 表示「裡面的」(10.5 見過)。using namespace std; 的意思就是「把 std 整包攤開,之後我寫 cout 你就當作 std::cout」。
所以你會看到有人不寫那一行,改成每個名字都掛前綴:
#include <bits/stdc++.h>
int count = 0; // 全域變數叫 count,這裡完全沒問題
int main() {
count++;
std::vector<int> v = {3, 1, 2};
std::sort(v.begin(), v.end());
std::cout << count << ' ' << v[0] << '\n';
return 0;
}
執行結果:
1 1
同一個 int count = 0;,本節開頭那份程式編譯不過,這份卻正常執行——因為沒有攤開 std,你的 count 和 std::count 從頭到尾就是兩個不相干的名字。這是 std:: 前綴真正的好處,而且還附贈一個:看到 std:: 開頭就知道那是內建工具,不是這份程式自己寫的函式——讀長程式時很省力。
還有折衷寫法:只攤開你要的幾個名字。
using std::cin;
using std::cout;
using std::vector;
那本書為什麼還是用 using namespace std;? 因為競程是限時比賽,少打 std:: 五個字元 × 幾十次是真的省時間,而且解題程式短、變數少,撞名機率低——撞到了就照上面說的換個變數名。工程專案(程式碼幾萬行、好幾個函式庫混用)才是 std:: 前綴真正發揮的場合。本書繼續寫 using namespace std;,但你必須看得懂 std:: 版本——網路上的參考解、cppreference 的範例,很多都是那樣寫的。
動手試試看:把你最近一份 AC 的程式開頭換成 #include <bits/stdc++.h> 重新編譯、重新提交,確認行為完全一樣。再故意宣告一個叫 count 的全域變數,親眼看一次「ambiguous」錯誤長什麼樣子——之後在自己的程式裡撞到才認得它。