1.如何查看宏展開
在開始之前,先簡單的介紹下,我們自己定義的一個宏,在引用的代碼中是如何被展開的。我用的是Microsoft Visual C++ 6.0,project menu -> settings,C/C++選項卡,在Project Options加上/P, 1 1所示。rebuild all,會產生與.cpp同名的.i檔案。開啟.i檔案,在這裡就可以看到代碼中宏是如何被展開的。
圖 1 1 添加“/P”編譯選項
比如:
#define CONS(a,b) int(a##e##b)
int n = CONS(2, 3); // 被展開為int n = int(2e3);即n = 2000;
2.預先處理操作符
以前,我經常看到一些巧妙的宏定義,裡面有用到#、##、等符號,但不懂這些#是幹麼用的,今天突然想起這事,決定把它們徹底搞明白。
開啟MSDN查看相關說明,如下表格所示,注意這些預先處理操作符只能用在#define的宏定義中
。
| Preprocessor Operators |
Action |
| Stringizing operator (#) |
字串化操作符,給對應的實參加上雙引號。 |
| Charizing operator (#@) |
字元化操作符,給對應的實參加上單引號。 |
| Token-pasting operator (##) |
符號串連操作符,串聯兩個符號。 |
2.1. Stringizing operator (#)
字串化操作符,作用:將宏定義中的傳入參數名轉換成用一對雙引號括起來,即字串化。我們測試看看(樣本來自MSDN)。
#define stringer( x ) printf( #x "/n" )
void main()
{
stringer( In quotes in the printf function call/n );
stringer( "In quotes when printed to the screen"/n );
stringer( "This: /" prints an escaped double quote" );
}
用上面介紹的方法查看宏展開(在.i檔案中),以上代碼先行編譯時展開成如下代碼,可以發現用字串化(Stringizing)操作時,如果實參中有引號、反斜線字元時,宏展開時會在這些符號錢自動加上“/”以便將這些字元也字串化。
void main()
{
printf( "In quotes in the printf function call/n" "/n" );
printf( "/"In quotes when printed to the screen/"/n" "/n" );
printf( "/"This: ///" prints an escaped double quote/"" "/n" );
}
最終輸入如下:
In quotes in the printf function call
"In quotes when printed to the screen"
"This: /" prints an escaped double quote"
注意:
◆逸出字元
◇某些形式的傳入參數名中,若存在特殊字元,編譯器會自動為其添加逸出字元號'/'。
◆對空格的處理
◇忽略傳入參數名前面和後面的空格。
如:stringer( ABC ); // 宏展開為printf( "ABC" "/n" );
◇當傳入參數名間存在空格時,編譯器將會自動連接各個子字串,用每個子字串中只以一個空格串連,忽略其中多餘一個的空格。
如:stringer( ABC DEF ); // 宏展開為printf( "ABC DEF" "/n" );
2.2. Charizing operator (#@)
字元化操作符,作用:將宏定義中的傳入參數名轉換成用一對單引號括起來,即字元化。如:
#define MAKECHAR(ch) #@ch
char a = MAKECHAR (b); // 被宏展開為char a = 'b';
注意:
在預設的類型轉換中,傳入的參數如果不是字元型,會被截斷成char型。
如:
char a = MAKECHAR (abcd); 將會截斷成 a='d'。
2.3. Token-pasting operator (##)
符號串連操作符,作用:將兩個符號串聯成一個符號使用。Token-Pasting Operator也成為merging operator,即合併作業。TEXT這個宏我想搞Windows編程的人都不陌生,我們看看它的定義:
#ifdef UNICODE
#define __TEXT(quote) L##quote
#else
#define __TEXT(quote) quote
#endif
#define TEXT(quote) __TEXT(quote)
用實際例子看看怎麼個串連法,在UNICODE環境中:
TEXT(ABC); // 宏展開為LABC;
TEXT("ABC"); // 宏展開為L"ABC";
再比如:
#define CONS(a,b) int(a##e##b)
int n = CONS(2, 3); // 被展開為int n = int(2e3);即n = 2000;
3.宏嵌套
宏定義是允許嵌套的,但要注意:宏定義裡有用’#'、’#@’、'##'的地方宏參數是不會再展開
。為了說明問題,讓我們來看看一個簡單例子。
#define MAX_VAL 100
#define _STR(arg) #arg
#define STR(arg) _STR(arg)
int main()
{
puts(_STR(MAX_VAL));
puts(STR(MAX_VAL));
return 0 ;
}
先看看.i檔案中的宏展開是如何的:
int main()
{
puts("MAX_VAL");
puts("100");
return 0 ;
}
輸出:
MAX_VAL
100
從輸出結果就可以看出其差別所在,之所以會這樣,就是因為:宏定義裡有用’#'、’#@’、'##'的地方宏參數是不會再展開
。然而解決這個問題的方法很簡單,加多一層中間轉換宏,中間轉換宏的作用就是對宏參數進行宏展開。在許多大型項目、MFC代碼、微軟代碼的標頭檔中這樣的定義隨處可見。就像上面介紹的TEXT宏嵌套定義亦是如此。
4.總結
宏的設計很巧妙,只要你能想得出,能大大的強化你的代碼,在學習和工作的過程中,要善於總結、歸納、收藏巧妙的宏應用。僅供參考,如有誤,請賜教。如果我寫的東西對你有用,不要忘了頂下。