文章目錄
先行編譯是整個編譯過程的第一步,是g++ -E選項輸出的結果。
這個步驟處理的是源檔案/標頭檔中的宏,巨集指令常用的有以下幾類:
- 檔案包含:#include
- 宏定義:#define、#undef
- 條件編譯:#ifdef、#ifndef、#if、#elif、#else、#endif
1. 檔案包含 #include
預先處理會把要包含的檔案的內容全部包含進來,比如下面這個檔案prepro.cpp:
#include "prehead.h"int main(){ add(1, 2);}
引入了標頭檔prehead.h:
#ifndef _PREHEAD_H#define _PREHEAD_H/* declaration of a function that add two integer */int add(int, int);#endif
使用命令g++ -E prepro.cpp先行編譯,輸出結果如下:
# 1 "prepro.cpp"# 1 "<built-in>"# 1 "<command-line>"# 1 "prepro.cpp"# 1 "prehead.h" 1int add(int, int);# 2 "prepro.cpp" 2int 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++那篇部落格的這個例子:
#define ONE 1#define TWO 2int add_one_two(){ return ONE + TWO;}
先行編譯後結果如下:
# 1 "add.cpp"# 1 "<built-in>"# 1 "<command-line>"# 1 "add.cpp"int add_one_two(){ return 1 + 2;}
可以看到定義的ONE被替換成了1,定義的TWO被替換成了2。
使用常量是好的,但是使用#define這種方法的不好是調試時候變數名都被替換了,失去了常量的效果。最好用下面這種方式定義變數:
const TYPE VAR_NAME=value
使用#define定義准inline函數,比如下面這個的例子(引自《effective C++》):
#define max(a, b) ( (a) > (b) ? (a) : (b) )
一般也沒什麼問題,但是:
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++最基本的文法,那這幾個指令看一眼也能基本知道大概意思。下面用注釋方式解釋:
#ifdef//if define,如果定義了。。。#ifndef//if not define,如果沒有定義。。。#if// 如果。。。#elif// 或者如果。。。#else// 或者。。。#endif// 結束if
第一種條件編譯
用來防止一個標頭檔引入兩次。比如開始那個例子裡面的標頭檔prehead.h:
#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"
#include "prehead.h"#include "prehead.h"int main(){ add(1, 2);}
這裡我手工展開,變成下面這個樣子,然後在注釋裡面分析一下:
#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,輸出和第一個例子裡面一樣,如下:
# 1 "prepro.cpp"# 1 "<built-in>"# 1 "<command-line>"# 1 "prepro.cpp"# 1 "prehead.h" 1int add(int, int);# 2 "prepro.cpp" 2int main(){ add(1, 2);}
或許你會說,這個SB,一個標頭檔include兩次,哈哈哈哈。但是,這隻是個例子,實際項目中檔案引入比較複雜,比如上面的prepro.cpp引入了第三個檔案,然後第三個檔案引入prehead.h,那麼沒有條件編譯的話prepro.cpp裡面就會有兩個prehead.h。
使用#ifndef... #define... #endif可以保證一個檔案只引入一次,這裡的...可以是任何內容,只要你能保證定義的這個東西其他地方沒定義,但是慣例是定義檔案名稱的大寫然後加幾個底線,像上面那個例子一樣。
第二種條件編譯
用來實現跨平台編譯。比如下面這樣:
#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