#include<stdio.h>
數組
#define f(a,b) a##b
ide
#define g(a) #a
函數
#define h(a) g(a)
ui
int main()
lua
{
spa
printf("%s\n",h(f(1,2)));
.net
printf("%s\n",g(f(1,2)));
翻譯
return 0;
指針
}
在宏定義裏,a##b就是把a,b聯接起來,
好比f(1,2)就是12,可是是數。
#a就是把a轉化成字串,併合並。
因此 printf("%s\n",g(f(1,2)));就直接把f(1,2)轉成字串了。
#define A(x) T_##x
#define B(x) #@x
#define C(x) #x
咱們假設:x=1,則有:
A(1)------〉T_1
B(1)------〉'1'
C(1)------〉"1"
C語言中如何使用宏C(和C++)中的宏(Macro)屬於編譯器預處理的範疇,屬於編譯期概念(而非運行期概念)。下面對常遇到的宏的使用問題作了簡單總結。
在C語言的宏中,#的功能是將其後面的宏參數進行字符串化操做(Stringfication),簡單說就是在對它所引用的宏變量 經過替換後在其左右各加上一個雙引號。好比下面代碼中的宏:
#define WARN_IF(EXP) do{ if (EXP) fprintf(stderr, "Warning: " #EXP "/n"); } while(0)
那麼實際使用中會出現下面所示的替換過程:
WARN_IF (divider == 0); 被替換爲 do { if (divider == 0) fprintf(stderr, "Warning" "divider == 0" "/n"); } while(0);
這樣每次divider(除數)爲0的時候便會在標準錯誤流上輸出一個提示信息。
而##被稱爲鏈接符(concatenator),用來將兩個Token鏈接爲一個Token。注意這裏鏈接的對象是Token就行,而不必定 是宏的變量。好比你要作一個菜單項命令名和函數指針組成的結構體的數組,而且但願在函數名和菜單項命令名之間有直觀的、名字上的關係。那麼下面的代碼就很是實用:
struct command { char * name; void (*function) (void); }; #define COMMAND(NAME) { NAME, NAME ## _command } // 而後你就用一些預先定義好的命令來方便的初始化一個command結構的數組了: struct command commands[] = { COMMAND(quit), COMMAND(help), ... }
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;
## 鏈接符號由兩個井號組成,其功能是在帶參數的宏定義中將兩個子串(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
#define A(x) T_##x
則 int A(1) = 10; //等效於int T_1 = 10;
#define A(x) Tx##__
則 int A(1) = 10; //等效於int T1__ = 10;
#define B(x) #@x
則B(a)即’a’,B(1)即’1’.但B(abc)卻不甚有效.
#define C(x) #x
則C(1+1) 即 」1+1」.
...在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合法,將不會產生編譯錯誤。 這裏列出了一些宏使用中容易出錯的地方,以及合適的使用方式。
宏的定義不必定要有完整的、配對的括號,可是爲了不出錯而且提升可讀性,最好避免這樣使用。
因爲宏只是簡單的替換,宏的參數若是是複合結構,那麼經過替換以後可能因爲各個參數之間的操做符優先級高於單個參數內部各部分之間相互做用的操做符優先級,若是咱們不用括號保護各個宏參數,可能會產生預想不到的情形。好比:
#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))
一般狀況下,爲了使函數模樣的宏在表面上看起來像一個一般的C語言調用同樣,一般狀況下咱們在宏的後面加上一個分號,好比下面的帶參宏:
MY_MACRO(x);
可是若是是下面的狀況:
#define MY_MACRO(x) { /* line 1 */ /* line 2 */ /* line 3 */ } //... if (condition()) MY_MACRO(a); else {...}
這樣會因爲多出的那個分號產生編譯錯誤。爲了不這種狀況出現同時保持MY_MACRO(x);的這種寫法,咱們須要把宏定義爲這種形式:
#define MY_MACRO(x) do { /* line 1 */ /* line 2 */ /* line 3 */ } while(0)
這樣只要保證老是使用分號,就不會有任何問題。
這裏的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)。