C++中宏究竟多重要?
我記得在上學的時候,老師還有教材都教育我們,儘量不要用宏,改用內聯函數或者模板代替。
但是以我個人的使用經驗(其實我是非常喜歡使用宏的^_^),宏要比函數自由得多,尤其是涉及到要考慮作用域的範圍或者參數類型的時候,你會發現宏所施行的簡單粗暴的替換政策是多麼的可愛。(其實這種情況在c11中可以使用能捕獲所有變量的Lambda表達式…)
當然,首先要提個醒,使用宏,一定要在對宏有充分了解的情況下,不然有時候本來用起來很爽的它簡單粗暴的替換也會讓你很蛋疼。
來兩個例子先:
代碼1:
#define StrType1 char*//自定義類型1
typedef char* StrType2;//自定義類型2
這兩種類型定義可能是很多人跌過的坑。
代碼2:
enum version
{
v_Starter,
v_HomeBasic,
v_HomePremium,
v_Professional,
v_Enterprise,
v_Ultimate,
};
#define v_current v_Ultimate
#if v_current == v_Starter
char* szversion = "Starter";
#elif v_current == v_HomeBasic
char* szversion = "HomeBasic";
#elif v_current == v_HomePremium
char* szversion = "HomePremium";
#elif v_current == v_Professional
char* szversion = "Professional";
#elif v_current == v_Enterprise
char* szversion = "Enterprise";
#elif v_current == v_Ultimate
char* szversion = "Ultimate";
#endif
版本控制坑。
基本使用就不說了,主要是要注意如果要編寫適用性強的宏,參數最好是要加括號,保證在宏擴展中傳入表達式參數時,也會由括號產生的高優先級保證展開後的代碼意義不變。
下面說說我常用的並且極大簡化代碼書寫量的宏應用:
首先說說宏的遞歸調用。
其實宏只是一個簡單的擴展替換功能,沒有辦法在自身當中調用自身的。
所以要用宏來實現遞歸,需要考慮多層宏的封裝,並用參數指定實際遞歸的深度以達到模擬遞歸的效果。
應用1:
請看下面的代碼:
#define OP_INFO_1 OP_INFO_0 OP_INFO_PART(1)
#define OP_INFO_2 OP_INFO_1 OP_INFO_PART(2)
#define OP_INFO_3 OP_INFO_2 OP_INFO_PART(3)
#define OP_INFO_4 OP_INFO_3 OP_INFO_PART(4)
#define OP_INFO_5 OP_INFO_4 OP_INFO_PART(5)
#define OP_INFO_6 OP_INFO_5 OP_INFO_PART(6)
#define OP_INFO_7 OP_INFO_6 OP_INFO_PART(7)
#define OP_INFO_8 OP_INFO_7 OP_INFO_PART(8)
#define OP_INFO_9 OP_INFO_8 OP_INFO_PART(9)
#define OP_INFO(n) OP_INFO_##n
#if OP_COUNT == 1
#define OP_INFOS OP_INFO(1)
#elif OP_COUNT == 2
#define OP_INFOS OP_INFO(2)
#elif OP_COUNT == 3
#define OP_INFOS OP_INFO(3)
#elif OP_COUNT == 4
#define OP_INFOS OP_INFO(4)
#elif OP_COUNT == 5
#define OP_INFOS OP_INFO(5)
#elif OP_COUNT == 6
#define OP_INFOS OP_INFO(6)
#elif OP_COUNT == 7
#define OP_INFOS OP_INFO(7)
#elif OP_COUNT == 8
#define OP_INFOS OP_INFO(8)
#elif OP_COUNT == 9
#define OP_INFOS OP_INFO(9)
#endif
這是一個可以模擬最高遞歸層次爲9的宏,具體的遞歸層次數通過OP_COUNT來定義。最終我們只需要編寫OP_INFO_0宏(可以認爲是整個宏展開代碼執行的初始代碼)和OP_INFO_PART宏(即每層遞歸所要執行的代碼)。
帶參數版本:
//Linux版:
#define OP_INFO_1(param,args...) OP_INFO_0 OP_INFO_PART(1,param)
#define OP_INFO_2(param,args...) OP_INFO_1(args) OP_INFO_PART(2,param)
#define OP_INFO_3(param,args...) OP_INFO_2(args) OP_INFO_PART(3,param)
#define OP_INFO_4(param,args...) OP_INFO_3(args) OP_INFO_PART(4,param)
#define OP_INFO_5(param,args...) OP_INFO_4(args) OP_INFO_PART(5,param)
#define OP_INFO_6(param,args...) OP_INFO_5(args) OP_INFO_PART(6,param)
#define OP_INFO_7(param,args...) OP_INFO_6(args) OP_INFO_PART(7,param)
#define OP_INFO_8(param,args...) OP_INFO_7(args) OP_INFO_PART(8,param)
#define OP_INFO_9(param,args...) OP_INFO_8(args) OP_INFO_PART(9,param)
#define OP_INFO(n,param,args...) OP_INFO_##n(param,args)
#if OP_COUNT == 1
#define OP_INFOS(param,args...) OP_INFO(1,param,args)
#elif OP_COUNT == 2
#define OP_INFOS(param,args...) OP_INFO(2,param,args)
#elif OP_COUNT == 3
#define OP_INFOS(param,args...) OP_INFO(3,param,args)
#elif OP_COUNT == 4
#define OP_INFOS(param,args...) OP_INFO(4,param,args)
#elif OP_COUNT == 5
#define OP_INFOS(param,args...) OP_INFO(5,param,args)
#elif OP_COUNT == 6
#define OP_INFOS(param,args...) OP_INFO(6,param,args)
#elif OP_COUNT == 7
#define OP_INFOS(param,args...) OP_INFO(7,param,args)
#elif OP_COUNT == 8
#define OP_INFOS(param,args...) OP_INFO(8,param,args)
#elif OP_COUNT == 9
#define OP_INFOS(param,args...) OP_INFO(9,param,args)
#endif
//Windows版:
#define OP_INFO_1(param,...) OP_INFO_0 OP_INFO_PART(1,param)
#define OP_INFO_2(param,...) OP_INFO_1(##__VA_ARGS__) OP_INFO_PART(2,param)
#define OP_INFO_3(param,...) OP_INFO_2(##__VA_ARGS__) OP_INFO_PART(3,param)
#define OP_INFO_4(param,...) OP_INFO_3(##__VA_ARGS__) OP_INFO_PART(4,param)
#define OP_INFO_5(param,...) OP_INFO_4(##__VA_ARGS__) OP_INFO_PART(5,param)
#define OP_INFO_6(param,...) OP_INFO_5(##__VA_ARGS__) OP_INFO_PART(6,param)
#define OP_INFO_7(param,...) OP_INFO_6(##__VA_ARGS__) OP_INFO_PART(7,param)
#define OP_INFO_8(param,...) OP_INFO_7(##__VA_ARGS__) OP_INFO_PART(8,param)
#define OP_INFO_9(param,...) OP_INFO_8(##__VA_ARGS__) OP_INFO_PART(9,param)
#define OP_INFO(n,param,...) OP_INFO_##n(param,##__VA_ARGS__)
#if OP_COUNT == 1
#define OP_INFOS(param,...) OP_INFO(1,param,##__VA_ARGS__)
#elif OP_COUNT == 2
#define OP_INFOS(param,...) OP_INFO(2,param,##__VA_ARGS__)
#elif OP_COUNT == 3
#define OP_INFOS(param,...) OP_INFO(3,param,##__VA_ARGS__)
#elif OP_COUNT == 4
#define OP_INFOS(param,...) OP_INFO(4,param,##__VA_ARGS__)
#elif OP_COUNT == 5
#define OP_INFOS(param,...) OP_INFO(5,param,##__VA_ARGS__)
#elif OP_COUNT == 6
#define OP_INFOS(param,...) OP_INFO(6,param,##__VA_ARGS__)
#elif OP_COUNT == 7
#define OP_INFOS(param,...) OP_INFO(7,param,##__VA_ARGS__)
#elif OP_COUNT == 8
#define OP_INFOS(param,...) OP_INFO(8,param,##__VA_ARGS__)
#elif OP_COUNT == 9
#define OP_INFOS(param,...) OP_INFO(9,param,##__VA_ARGS__)
#endif
由於gcc和vs編譯器的實現原理不同,上面的這種windows寫法在vs中編譯運行並不能達到預想的效果。如果你是vs用戶,希望對比vs和gcc編譯器的差別,可以在下面的鏈接中使用gcc:http://cpp.sh/。
測試上面宏的執行結果的例子:
首先在這段宏的前面,加上:
#define OP_COUNT 9
在測試函數中使用下面的代碼測試:
#define OP_INFO_0
#define OP_INFO_PART(num,param) printf("param%d:%s。",num,#param);
OP_INFOS(abc,def,ghi,jkl,mno,pqr,ts,uvw,xzy);
應用2:
接下來一個應用,我自己稱之爲延遲定義。
請看下面一段代碼:
#define OP_VARS \
OP_VAR_I(height,20,"高度") \
OP_VAR_I(width, 10,"寬度") \
OP_VAR_I(length,10,"長度") \
/*Macro End*/
如果直接調用OP_VARS肯定會報錯,因爲它調用的OP_VAR_I沒有定義。
所以接下來就要看怎麼正確使用這個宏定義了。
接上面的代碼:
enum
{
#define OP_VAR_I(var, initvalue, comment) em_##var,
em_min = 0,
OP_VARS
em_max,
#undef OP_VAR_I
};
namespace cube
{
#define OP_VAR_I(var, initvalue, comment) int var = initvalue;
OP_VARS
#undef OP_VAR_I
void Set(int emtype, int value)
{
#define OP_VAR_I(var, initvalue, comment) if(emtype == em_##var){ var = value; }
OP_VARS
#undef OP_VAR_I
}
int Get(int emtype)
{
#define OP_VAR_I(var, initvalue, comment) if(emtype == em_##var){ return var; }
OP_VARS
#undef OP_VAR_I
}
}
可以看到,每次在使用OP_VARS前後,重新定義和取消定義OP_VAR_I宏,那麼我們只需要在最開始一次性定義OP_VARS需要操作的內容,接下來改改OP_VAR_I宏,就能完成對所有數據的操作,而避免了大量同質化代碼的重複拷貝粘貼。
應用3:
前面提到變參數的宏,其實兩種編譯器的處理都差不多(至少不涉及二次傳遞時是一樣的處理方式),都是連接下來的參數帶逗號一起原封不動地替換的被替換的額位置,所以,結合這個特性,在上面第二個例子的基礎上發散一下,會得到另一個有趣的應用。
請看下面的代碼:
//Linux版:
#define _MY_MACRO_(type,name,args...) type name args;
//Windows版:
#define _MY_MACRO_(type,name,...) type name __VA_ARGS__;
使用:
class A
{
public:
A(int _1,int _2,int _3,int _4,LPCTSTR _5)
: m_1(_1)
, m_2(_2)
, m_3(_3)
, m_4(_4)
, m_5(_5){}
private:
int m_1;
int m_2;
int m_3;
int m_4;
LPCTSTR m_5;
};
_MY_MACRO_(A,a,(1,2,3,4,_T("zxj")))
_MY_MACRO_(int,b,(1))
_MY_MACRO_(LPCTSTR,c,(_T("abc123")))
_MY_MACRO_(bool,d)
可以發現,我們可以使用同一個宏,可以實現任何數據類型的定義。那麼在結合上面的例子,你是不是有所發現呢?
對!我們只需要在一個類似OP_VARS的宏的位置定義上所有我們需要的成員變量,那麼我們可以用同一套宏(這裏只有一個,就是OP_VAR_I)完成對自定義的類或者其他結構的所有數據的定義、初始化、Set、Get、等等其他你能想到的操作。而你要做的,就是簡單的一組“#define”和“#undef”組合加上調用“OP_VARS”。是不是大大簡化了代碼的書寫呢?而且還減少了代碼的出錯機率哦!
但是不得不提的是,這樣寫也有弊端,可讀性會大大降低。但這也是仁者見仁智者見智的,對熟悉這種寫法的人(比如經常這麼幹的我)來說,反而覺得更加清晰,但是對於從來沒這麼寫過的人,看你的代碼就如同火星文一樣,實在是一種煎熬啊。
應用4:
我們都知道c/c++的編譯過程有幾個步驟:1)預編譯,2)彙編,3)編譯成二進制代碼,4)鏈接生成執行文件。講到宏的使用,那我們今天關注的階段就是預編譯的階段,就是在這個時期將代碼中的宏展開,還有生成模板函數等動作。
既然C編譯器已經給我們提供瞭如此強大的宏擴展功能,你有沒有想過再充分發掘一下它的使用方式呢,這就是接下來我們要講到的:
--利用宏擴展結合預生成事件(linux可以自己修改makefile完成相同動作)來自動生成我們需要的代碼。
聽起來可能有點不好理解,或者你沒有類似的應用場景,但在我的項目中經常會使用。結合應用3來說,在vs中要使用智能提示之類的插件自動完成對標識符的輸入的話,首先要在代碼中顯式的定義了這些標識符,但是在應用3中,變量(標識符)的定義用宏自動完成了,那麼在接下來寫代碼的過程中,在輸入這些變量名稱的時候是不會有任何提示的,對於習慣智能提示插件自動完成功能的小夥伴們來說這簡直是不能忍受的。有沒有辦法解決呢,當然有!
你可以寫出類似的代碼:
//Linux版:
#define _MY_MACRO_(type,name,args...) type var_name args;
//Windows版:
#define _MY_MACRO_(type,name,...) type var_name __VA_ARGS__;
使用:
class A
{
public:
A(int _1,int _2,int _3,int _4,LPCTSTR _5)
: m_1(_1)
, m_2(_2)
, m_3(_3)
, m_4(_4)
, m_5(_5){}
private:
int m_1;
int m_2;
int m_3;
int m_4;
LPCTSTR m_5;
};
_MY_MACRO_(A,a,(1,2,3,4,_T("zxj")))
_MY_MACRO_(int,b,(1))
_MY_MACRO_(LPCTSTR,c,(_T("abc123")))
_MY_MACRO_(bool,d)
#define a var_a
#define b var_b
#define d var_c
#define d var_d
那麼在接下來的代碼中我們再使用“a,b,c,d”變量的時候就有提示了。但是如果自己這樣在define一遍和直接定義“a,b,c,d”有什麼區別呢,工作量並沒有減少,反而會讓代碼看起來很搞笑。
所以,怎麼做呢?
額,臨時有點事,有空再寫,大家可以思考一下哦~
未完待續。。。。
鏈接:我項目中用到的應用http://blog.csdn.net/u010300403/article/details/51543327