C++ 是一個編譯式語言,也就是程式碼要先經過編譯,變成執行檔才能執行。不過「編譯」的過程其實不光只有編譯這麼簡單,例如這個程式碼:
1 |
這種以 #
為開頭的程式碼是前置處理器的指令,在編譯之前有一個階段就叫作前置處理,前置處理做的事情就是根據你的指令修改程式碼,然後再拿去編譯。
舉例來說,#include
會在編譯前將指定檔案裡的文字完全複製貼上到你 include 的那個地方,它可以用在任何地方,例如:
你開了一個檔案叫做 hello.txt
,內容只有 "hello"
,然後你在同目錄有另一個檔案 test.cpp
,內容是: 1
2
3
4
5
6
7
8
9
using namespace std;
int main(){
cout <<
<< "\n";
return 0;
}hello
,在編譯之前,#include "hello.txt"
這一行就會被替換成 "hello"
。
還有一個常見的指令是 #define
,寫法是 #define identifier 替換字串
,也被稱為「巨集」或「宏」(macro),有些人可能會說這是「常數」,然後你就會開始懷疑它跟 const
的差別,其實 #define
並不是在宣告一個常數,而是它會把整份程式碼中的一段特定文字替換,例如:
1 |
|
在編譯之前,hello
就會被替換成 "hello"
,接著才進行編譯,所以輸出會是 hello
。它也有 function 的用法:
1 |
|
這樣會輸出 hello
,在編譯前,say("hello")
會被替換成 cout << a << "\n"
。
要特別注意的一點是,因為這是把文字原封不動貼上,所以 #define
替換的部分不會先做運算,而是在執行期按照前置處理器處理完的程式碼運算,例如: 1
2
3
4
5
6
7
8
using namespace std;
int main(){
cout << (plus(1, 2) * 3) << "\n";
return 0;
}9
,而是 7
,因為替換完後的程式碼會是 1 + 2 * 3
,2 * 3
會先被計算。所以建議都用個括號把它包起來。
以上是 #define
的「巨集」用法,而 #define
還有另一個功用,前置處理器也有 if 的語法,它們都和 #define
有關。
最單純的是 #if
、#elif
、#else
、#endif
,用法例如: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using namespace std;
int main(){
cout << "test\n";
cout << "hello\n";
cout << "QQ\n";
return 0;
}#endif
結束區塊,要巢狀結構也可以。如果判斷結果是 true
,那個區塊才會被留下來,否則會被刪掉,上述的範例中,只有 A==3
這個判斷會是 true
,因此只有第 7 行會留下來,第 9 和 11 行不會。前置處理器的判斷只能判 #define
定義的東西,可以判等於、比大小等等。如果把第 2 行改成 #define A 2
,那被留下的就會是第 9 行,如果 A
不是 2
也不是 3
,那被留下的就會是第 11 行。
除了可以判斷值之外,也可以判斷一個 identifier 有無被定義,用 defined(identifier)
就可以得到指定的 identifier 有沒有 define 過,例如: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
using namespace std;
int main(){
cout << "test\n";
cout << "hi\n";
return 0;
}#if defined(A)
可以簡寫為 #ifdef A
,而 #if !defined(A)
可以簡寫為 #ifndef A
。至於已經 define 過的東西可以用 #undef
移除。
這通常會用來避免標頭檔被重複 include,至於競賽用途,如果你打了一大段測試用的程式碼,覺得要在 submit 前註解掉、WA 了又要取消註解很麻煩,那就可以用個 #ifdef
來處理,會方便許多。
其實通常也只會用到 define 而已 (?),define 在競賽中最大的功能就是把常用的東西變得比較好寫,例如這是一個常見的 pair 模板:
1 |
|
除了普通的把東西替換掉之外,function macro 還有一種特別的寫法:
1 |
|
#a
就是把你在 a
打的東西變成字串替換上去,所以 err(num);
會被替換成:
1 | cerr << "num" << ": " << a << "\n"; |