C,C++巨集中#、##和__VA_ARGS__的理解
巨集中的#的功能是將其後面的巨集引數進行字串化操作(Stringizing operator),簡單說就是在它引用的巨集變數的左右各加上一個雙引號。
如定義好#define STRING(x) #x之後,下面二條語句就等價。
char *pChar = "hello";
char *pChar = STRING(hello);
還有一個#@是加單引號(Charizing Operator)
#define makechar(x) #@x
char ch = makechar(b);與char ch = 'b';等價。
但有小問題要注意,巨集中遇到#或##時就不會再展開巨集中巢狀的巨集了。什麼意思了?比如使用char *pChar = STRING(__FILE__);雖然__FILE__本身也是一個巨集,但編譯器不會展開它,所以pChar將指向"__FILE__"而不是你要想的形如"D:\XXX.cpp"的原始檔名稱。因此要加一箇中間轉換巨集,先將__FILE__解析成"D:\XXX.cpp"字串。
定義如下所示二個巨集:
#define _STRING(x) #x
#define STRING(x) _STRING(x)
再呼叫下面語句將輸出帶""的原始檔路徑
char* pChar = STRING(__FILE__);
printf("%s %s\n", pChar, __FILE__);
可以比較下STRING(__FILE__)與__FILE__的不同,前將帶雙引號,後一個沒有雙引號。
再講下##的功能,它可以拼接符號(Token-pasting operator)。
MSDN上有個例子:
#define paster( n ) printf( "token"#n" = %d\n", token##n )
int token9 = 100;
再呼叫 paster(9);巨集展開後token##n直接合並變成了token9。整個語句變成了
printf( "token""9"" = %d", token9 );
在C語言中字串中的二個相連的雙引號會被自動忽略,於是上句等同於
printf("token9 = %d", token9);。
即輸出token9 = 100
有了上面的基礎後再來看示例1
#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
#define __WFILE__ WIDEN(__FILE__)
wchar_t *pwsz = __WFILE__;
第一個巨集中的L是將ANSI字串轉化成unicode字串。如:wchar_t *pStr = L"hello";
再來看wchar_t *pwsz = __WFILE__;
__WFILE__被首先展開成WIDEN(__FILE__),再展開成WIDEN2("__FILE__表示的字串"),再拼接成 L"__FILE__表示的字串" 即L"D:\XXX.cpp" 從而得到unicode字串並取字串地址賦值給pwsz指標。
可變引數巨集__VA_ARGS__
在GNU C中,巨集可以接受可變數目的引數,就象函式一樣,例如:
1 2 |
#define pr_debug(fmt,arg...) \
printk(KERN_DEBUG fmt, ##arg)
|
用可變引數巨集(variadic macros)傳遞可變引數表
你可能很熟悉在函式中使用可變引數表,如:
1 |
void
printf ( const
char * format, ...);
|
直到最近,可變引數表還是隻能應用在真正的函式中,不能使用在巨集中。
C99編譯器標準終於改變了這種局面,它允許你可以定義可變引數巨集(variadic macros),這樣你就可以使用擁有可以變化的引數表的巨集。可變引數巨集就像下面這個樣子:
1 |
#define debug(...) printf(__VA_ARGS__)
|
預設號代表一個可以變化的引數表。使用保留名 __VA_ARGS__ 把引數傳遞給巨集。當巨集的呼叫展開時,實際的引數就傳遞給 printf()了。例如:
1 |
Debug( "Y = %d\n" , y);
|
而處理器會把巨集的呼叫替換成:
1 |
printf ( "Y = %d\n" , y);
|
因為debug()是一個可變引數巨集,你能在每一次呼叫中傳遞不同數目的引數:
1 |
debug( "test" );
// 一個引數
|
可變引數巨集不被ANSI/ISO C++ 所正式支援。因此,你應當檢查你的編譯器,看它是否支援這項技術。
用GCC和C99的可變引數巨集, 更方便地列印除錯資訊
gcc的預處理提供的可變引數巨集定義真是好用:
1 2 3 4 5 6 |
#ifdef DEBUG
#define dbgprint(format,args...) \
fprintf (stderr, format, ##args)
#else
#define dbgprint(format,args...)
#endif
|
如此定義之後,程式碼中就可以用dbgprint了,例如dbgprint("%s", __FILE__);
下面是C99的方法:
1 |
#define dgbmsg(fmt,...) printf(fmt,__VA_ARGS__)
|
新的C99規範支援了可變引數的巨集
具體使用如下:
以下內容為程式程式碼:
1 2 3 4 5 6 7 8 |
#include <stdarg.h>
#include <stdio.h>
#define LOGSTRINGS(fm, ...) printf(fm,__VA_ARGS__)
int
main()
{
LOGSTRINGS( "hello, %d " , 10);
return
0;
}
|
但現在似乎只有gcc才支援。
可變引數的巨集裡的'##'操作說明帶有可變引數的巨集(Macros with a Variable Number of Arguments)
在1999年版本的ISO C 標準中,巨集可以象函式一樣,定義時可以帶有可變引數。巨集的語法和函式的語法類似。下面有個例子:
1 |
#define debug(format, ...) fprintf (stderr, format, __VA_ARGS__)
|
這裡,'...'指可變引數。這類巨集在被呼叫時,它(這裡指'...')被表示成零個或多個符號,包括裡面的逗號,一直到到右括弧結束為止。當被呼叫時,在巨集體(macro body)中,那些符號序列集合將代替裡面的__VA_ARGS__識別符號。更多的資訊可以參考CPP手冊。