您的位置:首页 > 编程语言 > C语言/C++

C++ Macro

2016-06-17 18:07 639 查看
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: