1:__FILE__,__LINE__,宏展開,中間層宏
__FILE__是編譯器預定義宏,表示此檔案的絕對路徑,但是ASCII字元。
假如你想将__FILE__轉換成寬字元。
或許你想這樣
#define __WFILE__ L##__FILE__
wchar_t pszFilePath[]=__WFILE__;
這樣的話編譯器會提示說,“L__FILE__”: 未聲明的辨別符。
這是為什麼?
如果##後的參數本身也是一個宏的話,##會阻止這個宏的展開,也就是隻替換一次。需要加一層中介完成代換,就想這樣。
#define __WFILE3__(x) (L##x)
#define __WFILE2__(x) __WFILE3__(x)
#define __WFILE__ __WFILE2__(__FILE__)
當宏函數的定義中調用了另一個宏函數,預處理器會将其中所有可展開的宏展開。這樣就可以了。
再看一個具有迷惑性的錯誤宏定義:
#define __WFILE1__(x) (L##x)
#define __WFILE__ __WFILE1__(__FILE__)
這樣呢,編譯器同樣會報出L__FILE__未定義,也就是說L_FILE__不是一個字元串。
讓我們一步一步看看預處理器是如何做的吧?
1.當預處理器到達wchar_t pszFilePath[]=__WFILE__;它發現宏定義,然後執行展開__WFILE1__( __FILE__ )
2.同時發現__FILE1__又是一個宏定義,那麼展開(L##__FILE1__)
為什麼預處理器沒有展開__FILE1__呢,你可能覺得,自己已經添加了中間步驟。但實際上沒有,我們單單看__WFILE1__( __FILE__ ),不關其他定義,你就會發現,這個宏定義根據前面的規則是絕對不會展開的,是以前面的__WFILE__根本沒有起到中間代換的作用。
是以總結起來的話,從__FILE__第一次出現,必須要經過中間一層宏。中間一層宏是指__FILE__必須出現在宏聲明中(宏的左側),同時宏的定義(即宏定義右側)也是一個宏。那麼這個宏才能稱為中間層。這個中間層才能起到展開__FILE__的作用。
下面看下個錯誤的字元串宏定義例子
#define SHOWLINENUMW3(x) (L##x)
#define SHOWLINENUMW2(x) SHOWLINENUMW3(#x)
#define SHOWLINENUMW(x) SHOWLINENUMW2(x)
#define __WLINE__ SHOWLINENUMW(__LINE__)
#define __WFILE__ __WFILE2__(__FILE__ )
#define __N__ (__WFILE__ __WLINE__)
TCHAR szFileName[]=__N__;
注意L##x是帶括号的。編譯器提示:項不會計算為接受 1 個參數的函數。
為什麼會出錯呢?那是因為對多個單獨的字元串連接配接時,不能用括号括起來括起任意一個。當然單獨一個可以。
如:
wchar_t szTemp[]=(L"nihao"); //第一種 可以
wchar_t szTemp[]=(L"nihao") (L"tahao");//第二種 不可以。
wchar_t szTemp[]=L"nihao" L"woyehao";//第三種 可以
而前面給出的錯誤的例子經過宏替換這後就相當于第二種,是以編譯器會報告錯誤。我們需要将(L##x)改成L##x才能正确運作。
是以正确的例子是:
#define SHOWLINENUMW3(x) L##x
#define __WFILE3__(x) L##x
2:__VA_ARGS__
__VA_ARGS__好像是c99标準加進來的宏定義功能,可能用于不變參數宏
#define MY_PRINTF( x,...) printf( x, ##__VA_ARGS__ )
其中...表示可變參數清單,__VA_ARGS__則是表示前面的可變參數清單,其中##用于在沒可變參數清單時去掉前面的逗号。編譯器會識别出##,并去掉前面的逗号。
如果沒有##:
#define MY_PRINTF( x,...) printf( x, __VA_ARGS__ )
MY_PRINTF( "Hello World" );
宏展開後:
printf( "Hello World", );
這樣的編譯器會抱怨警告,甚至編譯不過。采用##之後就可以了。而且##本身也是連接配接符。
當然有時我們可以直接:
#define MY_PRINTF(...) printf(__VA_ARGS__)
此時就不需要##,因為前面沒有逗号。
暫時總結自己知道的這麼多的,以後再補充吧。