宏在C/C++中有挺大的作用。
關鍵詞
1,定義常量
2,定義表達式
3,簡化繁瑣的代碼
4,作為標識符
5,可變參數
6,#和##的使用
1,定義常量
比如定義PI的值可以通過宏來定義
#define PI 3.1415927
2,定義表達式
#define MAX(a, b) (a>b?a:b)
3,簡化繁瑣的代碼
這個可以簡化一些重復的代碼,比如聲明函數,定義類的成員變量,或者是簡化多次編寫重復性高的代碼。
簡化函數聲明,在函數聲明的時候有些必要的關鍵字需要寫,但是很多時候都是一樣的,可以通過宏來簡化
定義線程函數
unsigned __stdcall ThreadFunc(void* pArguments)
可以通過宏簡化為
#define THREAD_FUNC(func) unsigned __stdcall func(void* pArguments) THREAD_FUNC(ThreadFunc) { printf("hello\n"); return 0; }
定義類的成員變量,可能需要定義成員變量的get,set函數,這時候可以通過宏來簡化這個過程。
#define PROP_DECL(ClassName, Prop) \ public: ClassName Get##Prop(void){return m_##Prop;}\ public: void Set##Prop(ClassName vl){m_##Prop = vl;}\ private: ClassName m_##Prop; class CTestObj { PROP_DECL(int, Count) public: CTestObj(); ~CTestObj(); };
上面的代碼經過預編譯之後就會產生GetCount和SetCount兩個函數和m_Count私有成員變量。
簡化繁瑣代碼,在內存釋放的時候可能需要把指針,需要兩行代碼
WA *pa = new WA(c); delete pa; pa = NULL;
可以使用宏來簡化這個過程
#define MEM_FREE(x) do \ {\ delete x;\ x = NULL;\ } while (0) WA *pa = new WA(c); MEM_FREE(pa);
在ATL,MFC中大量使用到宏來簡化代碼。
4,作為標識
作為標識的宏在大量的使用
#ifndef __TMP_H__ //判斷是否已經定義宏,如果沒有,將會執行下面代碼,用於避免包含文件的時候重復包含 #define __TMP_H__ //定義宏,這樣第二次包含這個頭文件的時候就不會執行下面的定義 //判斷是否是UNICODE,用於定義TTCHAR的字符類型。 #ifdef UNICODE typedef wchar_t TTCHAR; #else typedef char TTCHAR; #endif //用於標識的宏定義 #define IN #define OUT #endif // !__TMP_H__
4,可變參數
宏可以有參數,而且參數數量可以不定
#define LOG(format, ...) printf(format, __VA_ARGS__) LOG("hello, %d, %s\n", 10, "nihao");
5,#和##的使用
#的作用是把宏參數變成字符串
#define STR(s) #s printf(STR(hello)); // 輸出字符串"hello"
##的作用是把宏參數粘貼起來例子可以參考一下第三點的代碼。
使用宏需要注意的點
宏是在預處理過程中進行存文本替換,預處理過程中不會對宏進行任何的語法檢測,卻個括號,少個分號都不會管,所以可能會出現一些很奇怪的錯誤,所以要慎用。
1,算法優先問題
一個乘法的宏
#define MUL(x, y) (x * y)
MUL(2,3)的時候沒有問題,如果是MUL(1+2, 3)的時候就出事了,宏會替換成1+2*3,跟預想的結果就不一樣了。
這時候就需要把宏定義改成
#define MUL(x, y) (x) * (y)
2,分號吞噬問題
#define SKIP_SPACES(p, limit) \ { char *lim = (limit); \ while (p < lim) { \ if (*p++ != ' ') { \ p--; break; }}}
如果上面的代碼放在判斷語句中使用
if (*p != 0) SKIP_SPACES (p, lim); else ...
編譯器會報錯,else之前要有if,可以通過下面代碼來解決
#define SKIP_SPACES(p, limit) \ do { char *lim = (limit); \ while (p < lim) { \ if (*p++ != ' ') { \ p--; break; }}} \ while (0)
這種方式在linux中大量使用到
3,重復調用
#define min(X, Y) ((X) < (Y) ? (X) : (Y))
如果調用時這樣
int x = 1; int y = 2; min(x++, y);
展開後x++會被調用兩次,這種方式要避免。
4,對自身的遞歸引用
有如下宏定義:
#define foo (4 + foo)
按前面的理解,(4 + foo)會展開成(4 + (4 + foo),然後一直展開下去,直至內存耗盡。但是,預處理器采取的策略是只展開一次。也就是說,foo只會展開成(4 + foo),而展開之後foo的含義就要根據上下文來確定了。
對於以下的交叉引用,宏體也只會展開一次。
#define x (4 + y) #define y (2 * x)
x展開成(4 + y) -> (4 + (2 * x)),y展開成(2 * x) -> (2 * (4 + y))。
注意,這是極不推薦的寫法,程序可讀性極差。
5,宏參數預處理
宏參數中若包含另外的宏,那麼宏參數在被代入到宏體之前會做一次完全的展開,除非宏體中含有#或##。
有如下宏定義:
#define AFTERX(x) X_ ## x #define XAFTERX(x) AFTERX(x) #define TABLESIZE 1024 #define BUFSIZE TABLESIZE
AFTERX(BUFSIZE)會被展開成X_BUFSIZE。因為宏體中含有##,宏參數直接代入宏體。
XAFTERX(BUFSIZE)會被展開成X_1024。因為XAFTERX(x)的宏體是AFTERX(x),並沒有#或##,所以BUFSIZE在代入前會被完全展開成1024,然後才代入宏體,變成X_1024。
宏實在預處理過程中被替換掉的,所以在實際的編譯過程中,不會出現宏,或者宏的變量名。
參考:
http://hbprotoss.github.io/posts/cyu-yan-hong-de-te-shu-yong-fa-he-ji-ge-keng.html