C/C++中宏總結C程序的源代碼中可包括各種編譯指令,這些指令稱為預處理命令。雖然它們實際上不是C語言的一部分,但卻擴展了C程序設計的環境。
ANSI標准定義的C語言預處理程序包括下列命令:
#define,#error,#include,#if,#else,#elif,#endif,#ifdef,#ifndef,#undef,#line,#pragma等。所有的預處理命令都已符號#開頭。
1、#define
命令#define定義了一個標識符及一個串。在源程序中每次遇到該標識符時,均以定義的串代換它。ANSI標准將標識符定義為宏名,將替換過程稱為宏替換。命令的一般形式為:
#define identifier string
- 該語句沒有分號。標識符和字符串之間可以有任意個空格,但是一旦字符串開始,只能由新的一行結束。
- 宏名稱定義后,即可成為其他宏名定義的一部分。
- 宏替換僅僅是以字符串代替標識符,前提是宏標識符必須獨立的識別出來,否則不進行替換。比如#define 123 a number就不能獨立識別。
- 若字符串長於一行,可以在行末尾使用‘\’續行。
2、#error
處理器命令#error強迫編譯程序停止編譯,主要用於程序調試。
3、#include
命令#include使編譯程序將另一源文件嵌入帶有#include的源文件,被讀入的源文件必須用雙引號或尖括號括起來。
- 如果顯式路徑名為文件標識符的一部分,則僅在子目錄中搜索被嵌入文件。即包含路徑只搜索路徑內。
- 如果文件名用雙引號括起來,則首先檢索當前工作目錄。如果未發現文件,則在命令行中說明的所有目錄中搜索。如果仍未發現文件,則搜索實現時定義的標准目錄。
- 如果沒有顯式路徑名且文件名被尖括號括起來,則首先在編譯命令行中的目錄內檢索。
4、條件編譯指令
有幾個命令可對程序源代碼的各部分有選擇地進行編譯,該過程稱為條件編譯。商業軟件公司廣泛應用條件編譯來提供和維護某一程序的許多顧客版本。
#if、#else,#elif及#endif #if的一般含義是如果#if后面的常量表達式為true,則編譯它與#endif之間的代碼,否則跳過這些代碼。命令#endif標識一個#if塊的 結束。
#if constant-expression
statement sequence
#endif
跟在#if后面的表達式在編譯時求值,因此它必須僅含常量及已定義過的標識符,不可使用變量。表達式不許含有操作符sizeof(sizeof也是編譯時求值)。
if、else、elif(elseif)、endif與代碼中的條件判斷類似。
# ifdef 和# ifndef
條件編譯的另一種方法是用#ifdef與#ifndef命令,它們分別表示"如果有定義"及"如果無定義"。
# ifdef的一般形式是:
# ifdef macroname
statement sequence
#endif
#ifdef與#ifndef可以用於#if、#else,#elif語句中,但必須與一個#endif。
5、undef
命令#undef 取消其后那個前面已定義過的宏名定義。一般形式為:
#undef macroname
6、#line
命令# line改變__LINE__與__FILE__的內容,它們是在編譯程序中預先定義的標識符。命令的基本形式如下:
# line number["filename"]
其中的數字為任何正整數,可選的文件名為任意有效文件標識符。行號為源程序中當前行號,文件名為源文件的名字。命令# line主要用於調試及其它特殊應用。
7、預定義的宏名
ANSI標准說明了C中的五個預定義的宏名。它們是:
__LINE__
__FILE__
__DATE__
__TIME__
__STDC__
如果編譯不是標准的,則可能僅支持以上宏名中的幾個,或根本不支持。記住編譯程序也許還提供其它預定義的宏名。
__LINE__及__FILE__宏指令在有關# line的部分中已討論,這里討論其余的宏名。
__DATE__宏指令含有形式為月/日/年的串,表示源文件被翻譯到代碼時的日期。
源代碼翻譯到目標代碼的時間作為串包含在__TIME__中。串形式為時:分:秒。
如果實現是標准的,則宏__STDC__含有十進制常量1。如果它含有任何其它數,則實現是非標准的。編譯C++程序時,編譯器自動定義了一個預處理名
字__cplusplus,而編譯標准C時,自動定義名字__STDC__。
注意:宏名的書寫由標識符與兩邊各二條下划線構成。
8、C、C++宏體中出現的#,#@,##
宏體中,#的功能是將其后面的宏參數進行字符串化操作(Stringfication),簡單說就是在對它所引用的宏變量通過替換后在其左右各加上一個
雙引號。
而##被稱為連接符(concatenator),用來將兩個Token連接為一個Token。注意這里連接的對象是Token就行,而不一定是宏的變
量。比如你要做一個菜單項命令名和函數指針組成的結構體的數組,並且希望在函數名和菜單項命令名之間有直觀的、名字上的關系。那就可以使用:宏參數##
固定部分。當然還可以n個##符號連接 n+1個Token,這個特性也是#符號所不具備的。
#@的功能是將其后面的宏參數進行字符化。
9、C宏中的變參...
...在C宏中稱為Variadic Macro,也就是變參宏。比如:
#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)
或者#define myprintf(templt,args...) fprintf(stderr,templt,args)
第一個宏中由於沒有對變參起名,我們用默認的宏__VA_ARGS__來替代它。第二個宏中,我們顯式地命名變參為args,那么我們在宏定義中就可以
用args來代指變參了。同C語言的stdcall一樣,變參必須作為參數表的最后有一項出現。當上面的宏中我們只能提供第一個參數templt時,C
標准要求我們必須寫成: myprintf(templt,);的形式。這時的替換過程為:myprintf("Error!\n",);替換為:
fprintf(stderr,"Error!\n",).
這是一個語法錯誤,不能正常編譯。這個問題一般有兩個解決方法。首先,GNU CPP提供的解決方法允許上面的宏調用寫成:
myprintf(templt);而它將會被通過替換變成: fprintf(stderr,"Error!\n",);
很明顯,這里仍然會產生編譯錯誤(非本例的某些情況下不會產生編譯錯誤)。除了這種方式外,c99和GNU CPP都支持下面的宏定義方式:
#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)
這時,##這個連接符號充當的作用就是當__VAR_ARGS__為空的時候,消除前面的那個逗號。那么此時的翻譯過程如下:
myprintf(templt);被轉化為: fprintf(stderr,templt);
這樣如果templt合法,將不會產生編譯錯誤。