Pages

2016年1月28日星期四

C/C++ 宏中的單/雙井號( # and ## )

 
 

一、一般用法
# 的功能是將其後面的宏參數進行字符串化操作(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;
二、當宏參數是另一個宏的時候
需要注意的是凡宏定義裡有用'#' 或'##' 的地方宏參數是不會再展開.
1, 非'#' 和'##' 的情況
#define TOW (2)
#define MUL(a,b) (a*b)
printf("%d*%d=%d\n", TOW, TOW, MUL(TOW,TOW));
這行的宏會被展開為:
printf("%d*%d=%d\n" , (2), (2), ((2)*(2)));
MUL 裡的參數TOW 會被展開為(2).
2, 當有'#' 或'##' 的時候
#define A (2)
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
printf("int max: %s\n", STR(INT_MAX)); // INT_MAX #i nclude<climits>
這行會被展開為:
printf("int max: %s\n", "INT_MAX") ;
printf("%s\n", CONS(A, A)); // compile error
這一行則是:
printf("%s\n", int(AeA));
INT_MAX 和A 都不會再被展開, 然而解決這個問題的方法很簡單. 加多一層中間轉換宏.
加這層宏的用意是把所有宏的參數在這層裡全部展開, 那麼在轉換宏裡的那一個宏(_STR) 就能得到正確的宏參數.
#define A (2)
#define _STR(s) #s
#define STR(s) _STR(s) // 轉換宏
#define _CONS(a,b) int(a##e##b)
#define CONS(a, b) _CONS(a,b) // 轉換宏
printf("int max: %s\n", STR(INT_MAX)); // INT_MAX,int 型的最大值,為一個變量 #i nclude<climits>
輸出為: int max: 0x7fffffff
STR(INT_MAX) -- > _STR(0x7fffffff) 然後再轉換成字符串;
printf("%d\n", CONS(A, A));
輸出為:200
CONS(A, A) --> _CONS((2), (2)) --> int((2)e( 2))
三、'#' '##' 的一些應用特例

1 、合併匿名變量名
#define ___ANONYMOUS1(type, var, line) type var##line
#define __ANONYMOUS0(type, line) ___ANONYMOUS1(type, _anonymous, line)
#define ANONYMOUS(type) __ANONYMOUS0(type, __LINE__)
例:ANONYMOUS(static int);  即: static int _anonymous70; 70 表示該行行號;
第一層:ANONYMOUS(static int); --> __ANONYMOUS0(static int, __LINE__);
第二層:                        --> ___ANONYMOUS1( static int, _anonymous, 70);
第三層:                        --> static int _anonymous70;
即每次只能解開當前層的宏,所以__LINE__ 在第二層才能被解開;
2 、填充結構
#define FILL(a) {a, #a}
enum IDD{OPEN, CLOSE};
typedef struct MSG{
  IDD id;
  const char * msg;
}MSG;
MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相當於:
MSG _msg[] = {{OPEN, "OPEN"},
              {CLOSE, "CLOSE"}};
3 、記錄文件名
​​#define _GET_FILE_NAME(f) #f
#define GET_FILE_NAME(f) _GET_FILE_NAME(f)
static char FILE_NAME[] = GET_FILE_NAME(__FILE__);
4 、得到一個數值類型所對應的字符串緩衝大小
#define _TYPE_BUF_SIZE(type) sizeof #type
#define TYPE_BUF_SIZE(type) _TYPE_BUF_SIZE(type)
char buf[TYPE_BUF_SIZE(INT_MAX)];
     --> char buf[_TYPE_BUF_SIZE(0x7fffffff )];
     --> char buf[sizeof "0x7fffffff"];
這里相當於:
char buf[11];