最近有興致端研了一下C++模板源編程,demo裏面用了大量boost庫的mpl類庫。不少東西單看使用方法感受很容易很方便,但這是怎麼實現的呢?一時手賤,打開了demo中包含的boost庫頭文件,鬱悶的是搜索了半天也沒找到相對應的調用關鍵字。這下我就好奇了,這是咋整的?一堆宏定義就搞定了? 編程
仔細研究了一下代碼,發現裏面宏定義佔了好大一部分代碼,尤爲#、##的使用。當時被整蒙了,代碼還能夠這麼寫。順便搜索了一下資料,發現這兩個貨功能確實挺多。 c#
在C/C++語言的宏中,#的功能是將其後面的宏參數進行字符串化操做(Stringfication),簡單說就是在對它所引用的宏變量經過替換後在其左右各加上一個雙引號。好比下面代碼中的宏: 數組
#define WARN_IF(EXP) \
do{ if (EXP) \
fprintf(stderr, "Warning: " #EXP "\n"); } \
while(0) ide
若是在實際中使用: WARN_IF (divider == 0);則該行代碼會被擴展爲: 函數
do { if (divider == 0) fprintf(stderr, "Warning" "divider == 0" "\n"); } while(0); ui
##鏈接符號由兩個井號組成,其功能是在帶參數的宏定義中將兩個子串(token)聯接起來, 從而造成一個新的子串。但它不能夠是第一個或者最後一個子串。所謂的子串 (token)就是指編譯器可以識別的最小語法單元。具體的定義在編譯原理裏有詳盡的解釋,但不知道也無所謂。同時值得注意的是#符是把傳遞過來的參數當 成字符串進行替代。下面來看看它們是怎樣工做的。這是MSDN上的一個例子。
假設程序中已經定義了這樣一個帶參數的宏:
#define paster( n ) printf( "token" #n " = %d", token##n )
同時又定義了一個整形變 量:
int token9 = 9;
如今在主程序中如下面的方式調用這個宏:
paster( 9 );
那 麼在編譯時,上面的這句話被擴展爲:
printf( "token" "9" " = %d", token9 );
注意到 在這個例子中,paster(9);中的這個」9」被原封不動的當成了一個字符串,與」token」鏈接在了一塊兒,從而成爲了token9。而#n也 被」9」所替代。 可想而知,上面程序運行的結果就是在屏幕上打印出token9=9 lua
再有你要作一個菜單項命令名和函數指針組成的結構體的數組,而且但願在函數名和菜單項命令名之間有直觀的名字上的關係。那麼下面的代碼就很是實用 翻譯
struct command {
char * name;
void (*function) (void);
};
#define COMMAND(NAME) { NAME, NAME##_command }
// 而後你就用一些預先定義好的命令來方便的初始化一個command結構的數組了 指針
struct command commands[] = {
COMMAND(quit),
COMMAND(help),
...
}; token
COMMAND宏在這裏充當一個代碼生成器的做用,這樣能夠在必定程度上減小代碼密度,間接地也可 以減小不留心所形成的錯誤。
咱們還能夠n個##符號鏈接 n+1個Token,這個特性也是#符號所不具有的。好比:
#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d
typedef struct_record_type LINK_MULTIPLE(name,company,position,salary);
// 這裏這個語句將展開爲:typedef struct _record_type name_company_position_salary;
關於...的使用
...在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合法,將不會產生 編譯錯誤。 這裏列出了一些宏使用中容易出錯的地方,以及合適的使用方式。
由 操做符優先級引發的問題-Operator Precedence Problem
因爲宏只是簡單的替換,宏的參數若是是複合結構,那麼 經過替換以後可能因爲各個參數之間的操做符優先級高於單個參數內部各部分之間相互做用的操做符優先級,若是咱們不用括號保護各個宏參數,可能會產生預想 到的情形。好比:
#define ceil_div(x, y) (x + y - 1) / y
那麼
a = ceil_div( b & c, sizeof(int) );
將被轉化爲:
a = ( b & c + sizeof(int) - 1) / sizeof(int);
// 因爲+/-的優先級高於&的優先級,那麼上面式子等同於:
a = ( b & (c + sizeof(int) - 1)) / sizeof(int);
這顯然不是調用者的初衷。爲了不這種狀況發生,應當多寫幾個括號:#define ceil_div(x, y) (((x) + (y) - 1) / (y)) Duplication of Side Effects 這裏的Side Effect是指宏在展開的時候對其參數可能進行屢次Evaluation(也就是取值),可是若是這個宏參數是一個函數,那麼就有可能被調用屢次從而達 到不一致的結果,甚至會發生更嚴重的錯誤。好比: #define min(X,Y) ((X) > (Y) ? (Y) : (X)) //以下調用 c = min(a,foo(b)); 這 時foo()函數就被調用了兩次。爲了解決這個潛在的問題,咱們應當這樣寫min(X,Y)這個宏: #define min(X,Y) ({ \ typeof (X) x_ = (X); \ typeof (Y) y_ = (Y); \ (x_ < y_) ? x_ : y_; }) ({...})的做用是將內部的幾條語句中最後一條的值返回,它也容許在內部聲明變量(由於它經過大括號組成了一個局部 Scope)。 == #define display(name) printf(""#name"") int main() { display(name); } 運行結果是name,爲何不是"#name"呢? --------------------------------------------------------------- #在這裏是字符串化的意思 printf(""#name"") 至關於 printf("" "name" "")