1. 程式人生 > >C,C++巨集中#、##和__VA_ARGS__的理解

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手冊。