語法書 / AA 競程語法書 下冊 / 第十二單元 / 萬用標頭檔 bits/stdc++.h

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++.h1.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」這種錯誤訊息,幫變數換個名字就解決(countdatasize 這類常見英文單字最容易中獎;宣告在函式裡面的區域變數則不會有這個問題)。

開頭的另一半:using namespace std;std:: 前綴

上面那個撞名,其實不是 bits/stdc++.h 一個人的責任——另一半責任在你從第一支程式就在寫、卻從沒解釋過的那行:using namespace std;

標準函式庫的所有名字(coutvectorsortstring……)都住在一個叫 std命名空間(namespace)裡,全名其實是 std::coutstd::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,你的 countstd::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」錯誤長什麼樣子——之後在自己的程式裡撞到才認得它。