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; 關於...的使用 ...在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合法,將不會產生編譯錯誤。 這裏列出了一些宏使用中容易出錯的地方,以及合適的使用方式。 |
【轉】C語言中如何使用宏 包括單雙井號 可變參數
【轉】C語言中如何使用宏 包括單雙井號 可變參數
2009-05-04 11:15
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.