巨集定義\字串 多行書寫時換行
無引數的巨集定義的一般形式為
# define 識別符號 字元序列
其中# define之後的識別符號稱為巨集定義名(簡稱巨集名),要求巨集名與字元序列之間用空格符分隔。這種巨集定義要求編譯預處理程式將源程式中隨後所有的定名的出現(註釋與字串常量中的除外)均用字元序列替換之。前面經常使用的定義符號常量是巨集定義的最簡單應用。如有:
# define TRUE 1
# define FALSE 0
則在定義它們的源程式檔案中,凡定義之後出現的單詞TRUE將用1替代之;出現單詞FALSE將用0替代之。
在巨集定義的#之前可以有若干個空格、製表符,但不允許有其它字元。巨集定義在源程式中單獨另起一行,換行符是巨集定義的結束標誌。如果一個巨集定義太長,一行不夠時,可採用續行的方法。續行是在鍵人回車符之前先鍵入符號""。注意回車要緊接在符號""之後,中間不能插入其它符號。
巨集定義的有效範圍稱為巨集定義名的轄域,轄域從巨集定義的定義結束處開始到其所在的源程式檔案末尾。巨集定義名的轄域不受分程式結構的影響。可以用預處理命令#undef終止巨集定義名的轄域。
在新的巨集定義中,可以使用前面已定義的巨集名。例如,
# define R 2.5
# define PI 3.1415926
# define Circle 2*PI*R
# define Area PI* R * R
程式中的Circle被展開為2*3.1415926* 2.5, Area被展開為3.1415926*2.5*2.5。
如有必要,巨集名可被重複定義。被重複定義後,巨集名原先的意義被新意義所代替。
通常,無引數的巨集定義多用於定義常量。程式中統一用巨集名錶示常量值,便於程式前後統一,不易出錯,也便於修改,能提高程式的可讀性和可移植性。特別是給陣列元素個數一個巨集定義,並用巨集名定義陣列元素個數能部分彌補陣列元素個數固定的不足。
注意:預處理程式在處理巨集定義時
# define PI 3.1415926;
原希望用PI求圓的周長的語句
c=2*PI*r;
經巨集展開後,變成
c=2*3.1415926*r;
這就不能達到希望的要求。
帶引數巨集定義進一步擴充了無引數巨集定義的能力,在字元序列替換同時還能進行引數替換。帶引數定定義的一般形式為
# define 識別符號(引數表)字元序列
其中引數表中的引數之間用逗號分隔,字元序列中應包含引數表中的引數。在定義帶引數的巨集時,巨集名識別符號與左圓括號之間不允許有空白符,應緊接在一起,否則變成了無引數的巨集定義。
# define MAX(A,B) ((A) > (B)?(A):(B))
則程式碼 y= MAX( p+q, u+v)將被替換成 y=((p+q) >(u+v)?(p+q):(u+v)。
程式中的巨集呼叫是這樣被替換展開的,分別用巨集呼叫中的實在引數字元序列(如p+q和u+V) 替換巨集定義字元序列中對應所有出現的形式引數(如用p+q替代所有形式引數A,用u+V替代所有形式引數B),而巨集定義字元序列中的不是形式引數的其它字元則保留。這樣形成的字元序列,即為巨集呼叫的展開替換結果。巨集呼叫提供的實在引數個數必須與巨集定義中的形式引數個數相同。
注意:巨集呼叫與函式呼叫的區別。函式呼叫在程式執行時實行,而巨集展開是在編譯的預處理階段進行;函式呼叫佔用程式執行時間,巨集呼叫只佔編譯時間;函式呼叫對實參有型別要求,而巨集呼叫實在引數與巨集定義形式引數之間沒有型別的概念,只有字元序列的對應關係。函式呼叫可返回一個值,巨集呼叫獲得希望的C程式碼。另外,函式呼叫時,實參表示式分別獨立求值在前,執行函式體在後。巨集呼叫是實在引數字元序列替換形式引數。替換後,實在引數字元序列就與相鄰的字元自然連線,實在引數的獨立性就不一定依舊存在。如下面的巨集定義:
# define SQR(x) x*x
希望實現表示式的平方計算。對於巨集呼叫
P=SQR(y)
能得到希望的巨集展開p= y*y。但對於巨集呼叫q=SQR(u+v)得到的巨集展開是q=u+V*u+V。顯然,後者的展開結果不是程式設計者所希望的。為能保持實在引數替換後的獨立性,應在巨集定義中給形式引數加上括號。進一步,為了保證巨集呼叫的獨立性,作為算式的巨集定義也應加括
號。如 SQR巨集定義改寫成:
# define SQR((x)*(x))
才是正確的巨集定義。
對於簡短的表示式計算函式,或為了提高程式的執行效率、避免函式呼叫時的分配儲存單元、保留現場、引數值傳遞、釋放儲存單元等工作。可將函式定義改寫成巨集定義。所以合理使用巨集定義,可以使程式更簡潔。
使用一些巨集跟蹤除錯
A N S I標準說明了五個預定義的巨集名。它們是:
_ L I N E _ (兩個下劃線),對應%d
_ F I L E _ 對應%s
_ D A T E _ 對應%s
_ T I M E _ 對應%s
_ S T D C _
如果編譯不是標準的,則可能僅支援以上巨集名中的幾個,或根本不支援。記住編譯程式
也許還提供其它預定義的巨集名。
_ L I N E _及_ F I L E _巨集指令在有關# l i n e的部分中已討論,這裡討論其餘的巨集名。
_ D AT E _巨集指令含有形式為月/日/年的串,表示原始檔被翻譯到程式碼時的日期。
原始碼翻譯到目的碼的時間作為串包含在_ T I M E _中。串形式為時:分:秒。
如果實現是標準的,則巨集_ S T D C _含有十進位制常量1。如果它含有任何其它數,則實現是
非標準的。
可以定義巨集,例如:
當定義了_DEBUG,輸出資料資訊和所在檔案所在行
#ifdef _DEBUG
#define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%s”,date,_LINE_,_FILE_)
#else
#define DEBUGMSG(msg,date)
#endif
20,巨集定義防止使用是錯誤
用小括號包含。
例如:#define ADD(a,b) (a+b)
用do{}while(0)語句包含多語句防止錯誤
例如:#difne DO(a,b) a+b;\
a++;
應用時:if(….)
DO(a,b); //產生錯誤
else
解決方法: #difne DO(a,b) do{a+b;\
a++;}while(0)
巨集中"#"和"##"的用法
一、一般用法
我們使用#把巨集引數變為一個字串,用##把兩個巨集引數貼合在一起(這裡說的是在預處理是對原始檔的操作).
用法:
#include<cstdio>
#include<climits>
using namespace std;
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
int main()
{
printf(STR(vck)); // 輸出字串"vck"
printf("%d\n", CONS(2,3)); // 2e3 輸出:2000
return 0;
}
二、當巨集引數是另一個巨集的時候
需要注意的是凡巨集定義裡有用''#''或''##''的地方巨集引數是不會再展開.
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 #include<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型的最大值,為一個變數 #include<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];