預編譯是整個編譯過程的第一步,是g++ -E選項輸出的結果。
這個步驟處理的是源文件/頭文件中的宏,宏指令常用的有以下幾類:
文件包含:#include
宏定義:#define、#undef
條件編譯:#ifdef、#ifndef、#if、#elif、#else、#endif
1. 文件包含 #include
預處理會把要包含的文件的內容全部包含進來,比如下面這個文件prepro.cpp:
[cpp]
#include "prehead.h"
int main(){
add(1, 2);
}
引入了頭文件prehead.h:
[cpp]
#ifndef _PREHEAD_H
#define _PREHEAD_H
/* declaration of a function that add two integer */
int add(int, int);
#endif
使用命令g++ -E prepro.cpp預編譯,輸出結果如下:
[plain]
# 1 "prepro.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "prepro.cpp"
# 1 "prehead.h" 1
int add(int, int);
# 2 "prepro.cpp" 2
int main(){
add(1, 2);
}
可以看到結果中兩個文件的宏定義都被刪除了,文件prehead.h中注釋也被刪除了,而剩下的內容被引入了prepro.h中。至於#ifndef的意思最後介紹。
2. 宏定義:#define、#undef
#define宏定義的一種用法是聲明一個宏,用來實現條件編譯,這種用法後面和#ifdef等宏命令一起介紹。
#define宏定義的另一種用法是用來定義常量,定義准inline的函數。這種用法不好,應該避免使用。
使用#define定義的變量,比如:#define PI 3.14,預編譯以後程序中所有的PI都會被3.14替代。再比如上次談g++那篇博客的這個例子:
[cpp]
#define ONE 1
#define TWO 2
int add_one_two(){
return ONE + TWO;
}
預編譯後結果如下:
[plain]
# 1 "add.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "add.cpp"
int add_one_two(){
return 1 + 2;
}
可以看到定義的ONE被替換成了1,定義的TWO被替換成了2。
使用常量是好的,但是使用#define這種方法的不好是調試時候變量名都被替換了,失去了常量的效果。最好用下面這種方式定義變量:
[plain]
const TYPE VAR_NAME=value
使用#define定義准inline函數,比如下面這個的例子(引自《effective C++》):
[cpp]
#define max(a, b) ( (a) > (b) ? (a) : (b) )
一般也沒什麼問題,但是:
[cpp]
int a = 5, b = 0;
max(++a, b); /* a自增了兩次 */
max(++a, b + 10); /* b自增了一次*/
所以這樣定義是會出問題的。使用#define定義函數的初衷是為了提高程序運行速度,但是這種方式提高速度有限,根本上應該從改進數據結構,算法方面入手。這種#define,果斷寫成inline就OK了,那麼多括號寫著累,看著也累,沒必要。
3. 條件編譯:#ifdef、#ifndef、#if、#elif、#else、#endif
條件編譯算是宏最精髓的應用吧。先來說說這幾個指令的意思,如果你看過我前面的博客,而且堅持在USACO上做題,那麼想必你已經熟悉了C++最基本的語法,那這幾個指令看一眼也能基本知道大概意思。下面用注釋方式解釋:
[plain]
#ifdef //if define,如果定義了。。。
#ifndef //if not define,如果沒有定義。。。
#if // 如果。。。
#elif // 或者如果。。。
#else // 或者。。。
#endif // 結束if
第一種條件編譯
用來防止一個頭文件引入兩次。比如開始那個例子裡面的頭文件prehead.h:
[cpp]
#ifndef _PREHEAD_H /* 如果沒有定義_PREHEAD_H */
#define _PREHEAD_H /* 定義_PREHEAD_H */
/* declaration of a function that add two integer */
int add(int, int);
#endif /* 結束上面對應的#ifndef */
然後更改下我們的prepro.cpp文件,增加一行#include "prehead.h"
[cpp]
#include "prehead.h"
#include "prehead.h"
int main(){
add(1, 2);
}
這裡我手工展開,變成下面這個樣子,然後在注釋裡面分析一下:
[cpp]
#ifndef _PREHEAD_H /* 如果沒有定義_PREHEAD_H */ /* 的確沒有定義 */
#define _PREHEAD_H /* 定義_PREHEAD_H */ /* 那麼就定義之 */
/* declaration of a function that add two integer */
int add(int, int);
#endif /* 結束上面對應的#ifndef */
#ifndef _PREHEAD_H /* 如果沒有定義_PREHEAD_H */ /* 上面已經定義了,條件為false */
#define _PREHEAD_H /* 定義_PREHEAD_H */ /* 所以從這裡開始到endif都會被預處理器刪除 */
/* declaration of a function that add two integer */
int add(int, int);
#endif /* 結束上面對應的#ifndef */ /* 後面的編譯獨立於第二個#ifndef _PREHEAD_H */
int main(){
add(1, 2);
}
使用命令g++ -E prepro.cpp,輸出和第一個例子裡面一樣,如下:
[plain]
# 1 "prepro.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "prepro.cpp"
# 1 "prehead.h" 1
int add(int, int);
# 2 "prepro.cpp" 2
int main(){
add(1, 2);
}
或許你會說,這個SB,一個頭文件include兩次,哈哈哈哈。但是,這只是個例子,實際項目中文件引入比較復雜,比如上面的prepro.cpp引入了第三個文件,然後第三個文件引入prehead.h,那麼沒有條件編譯的話prepro.cpp裡面就會有兩個prehead.h。
使用#ifndef... #define... #endif可以保證一個文件只引入一次,這裡的...可以是任何內容,只要你能保證定義的這個東西其他地方沒定義,但是慣例是定義文件名的大寫然後加幾個下劃線,像上面那個例子一樣。
第二種條件編譯
用來實現跨平台編譯。比如下面這樣:
[cpp]
#ifdef _linux_
// linux平台相關代碼。。。
#endif
#ifdef _windows_
// windows平台相關代碼。。。
#endif
#ifdef _macos_
// mac os平台相關代碼
#endif
然後在某個include的配置文件config.h裡面,如果有如下定義:#define _linux_,那麼編譯器就只編譯linux平台相關的代碼;如果有如下定義:#define _windows_,那麼只編譯windows平台相關的代碼。
最後要說明的是,配置文件config.h不是手動寫的,而是腳本自動生成的。至於如何編寫腳本或者使用工具生成腳本,算是另一個話題了,以後再介紹吧。
宏指令還有其他一些,但是用的很少,我就不寫了,想了解的朋友可以參考這個網址:點擊進入。這個網站C++的文檔,手冊,幫助都一流,學習C++可以多看看。
參考文獻:
Effective C++, third edition. Scott Meyers. 2005
摘自 程序猿