本文转载于http://orzz.org/cxx-macro-play/

一、普通玩法

1. 基本功能

定义一个宏来替换字符串:

#define MACRO_PRINTF printf

这样我们使用MACRO_PRINTF就如同printf一样。

我们还可以像这样定义一个宏名称,但不写任何东西:

#define MACRO_NAME

然后像这样通过预定义的开关来调整代码实现:

#ifdef  MACRO_NAME
#define MACRO_FUNC(x, y)
#endif

我们可以像这样定义一个类似函数的宏:

#define MACRO_SELECT(exp, a, b) ((exp) ? (a) : (b))

但是像这样定义的宏定义时需要小心。
比如假如我们定义了一个比较大小的宏如下:

#define MACRO_MAX(a, b) ((a) > (b) ? (a) : (b))

像这样使用是不会有问题的:

int n = MACRO_MAX(2, 1);

但是若是这样使用,就可能会有性能问题:

int func(int a)
{int x = 0;for(int i = 0; i < a; ++i)x += a;return x;
}
// ...
int n = MACRO_MAX(func(10), 99);

因为我们的func(10)在宏展开中写了两次,因此func内部的循环也执行了两次。

遇到这种情况,函数式的宏就不如真正的函数好用了,比如写一个泛型的大小比较函数:

template <typename T>
inline const T& max(const T& a, const T& b)
{return ((a > b) ? a : b);
}

2. 参数的字符串化和拼接

我们可以像这样把一个宏变成一个字符串:

#define MACRO_STRING(x)     #x

然后宏在碰到#的时候,后面的x就不会被展开。因此这样写可以让任何输入的x都变成字符串。

为了让x展开,我们需要把宏嵌套一层:

#define MACRO_EXPAND(x)     MACRO_STRING(x)

宏在解析的时候,碰到第二层宏,会先展开x,再传递下去。
我们通过MACRO_EXPAND就可以得到x展开之后的样子,并把它变成字符串。

通过这个技巧,可以让我们在程序的运行时观察一个宏展开后的样子:

printf(MACRO_EXPAND(MACRO_FUNC(x)));

我们还可以把宏输入的两个参数直接连接起来:

#define MACRO_CAT(x, y)     x##y

同样,想要让参数展开后再连接,我们需要这样:

#define MACRO_GLUE(x, y)    MACRO_CAT(x, y)

3. 变参宏

C99编译器标准里描述了可变参数的宏,目前主流的编译器也都支持它(gcc还另有一套可变参数宏写法,不过实际用起来大同小异)。
它的语法差不多是这个样子:

#define MACRO_ARGS(...)     __VA_ARGS__

一般使用起来大概像这样个样子:

#ifdef _DEBUG
#define MACRO_PRINTF(fmt, ...)  printf(fmt, __VA_ARGS__)
#else
#define MACRO_PRINTF(fmt, ...)
#endif // _DEBUG

若__VA_ARGS__为空,printf会被展开成这样:printf(fmt, )。为了解决这个问题,gcc里的写法是在__VA_ARGS__前面写上##:

#define MACRO_PRINTF(fmt, ...)  printf(fmt, ##__VA_ARGS__)

这样若变参为空时,附带着逗号也会被自动干掉。
vc里对这种写法也支持,不过就按一般的写法(不加##)也不会有问题。vc的编译器在处理变参宏时,若变参为空,会自动删除前面的逗号。

4. FILELINEFUNCTION、…

这些玩意是编译器内置的宏定义。他们分别是:

FILE:当前源文件名
LINE:当前源代码行号
FUNCTION:当前的函数名
DATE:当前的编译日期
TIME:当前编译时间
STDC:当要求程序严格遵循ANSI C标准时该标识被赋值为1

等等。
gcc编译器还支持其他的一些,比如:

PRETTY_FUNCTION:当前的函数完整的声明(包含返回值参数表之类)

这样的话,我们可以利用它们写一个调试输出用的宏:

#ifndef __GNUC__
#define __PRETTY_FUNCTION__ __FUNCTION__
#endif
#ifdef _DEBUG
#define MACRO_TRACE(fmt, ...) printf("%s %s (%d) -> ", __FILE__, __PRETTY_FUNCTION__, __LINE__); printf(fmt, ##__VA_ARGS__)
#else
#define MACRO_TRACE(fmt, ...)
#endif // _DEBUG

二、文艺玩法

1. 检查函数执行结果

#define MACRO_CHECK(ensure) if (!(ensure)) { MACRO_TRACE("Check ERROR: %sn", #ensure); } else

可以这样用:

int func(int a)
{return 0;
}// ...

MACRO_CHECK(func(10) > 0);
若判断错误,则输出函数名加参数以供调试。

还可以在执行成功后让它做后续动作:

MACRO_CHECK(func(10) > 0)
{// Do Something
}

2. switch case

用下面的写法可以让switch语句自动输出调试信息,而且还可以省略break:

#define MACRO_CASE(x) break; case x: MACRO_TRACE("%s", #x);// ...int a = 1;
switch (a)
{
default:{// Do something}
MACRO_CASE(0){// Do something}
MACRO_CASE(1){// Do something}
MACRO_CASE(2){// Do something}
}

3. 语法糖

比如实现一个简单的for_each:

template <typename T, typename U>
bool inside(const T& i, const U& set)
{return (i <= set);
}#define FOR_EACH(type, set) for(type i = 0; inside(i, set); ++i)// ...int x = 0;
FOR_EACH(int, 10)
{x += i;
}
printf("%dn", x);

在这里面我们可以根据类型的不同重载出特定的限制函数来扩充FOR_EACH的功能,也可以使用模板来实现“type i = 0”的自动推导。

4. 简化重复的代码

比如,我们可以通过下面的宏简单方便的为类增加一个属性:

#define PROPERTY_VAR(name)  MACRO_CAT(_, name)
#define PROPERTY_SET(name)  MACRO_GLUE(set, name)
#define PROPERTY_GET(name)  MACRO_GLUE(get, name)()
#define PROPERTY(type, name) type PROPERTY_VAR(name); const type& PROPERTY_SET(name)(const type& param) { if (PROPERTY_VAR(name) != param) PROPERTY_VAR(name) = param; return PROPERTY_VAR(name); } const type& PROPERTY_GET(name) { return PROPERTY_VAR(name); }

使用方法:

class Demo
{
public:PROPERTY(int, A)
};// ...Demo d;
d.PROPERTY_SET(A)(10);
printf("%dn", d.PROPERTY_GET(A));

三、奇葩玩法

1. 获得宏参数的个数

想要玩出这种效果,是需要动一下脑筋的。。
宏是“死”的,真正意义上的死代码。它没办法像模板那样推导,没办法像函数那样重载,没办法玩递归(编译器发现宏自身的嵌套,会停止展开下一层宏),甚至可变参数对宏来说,都只是一整块无法区分的符号。

那么让我们抛开任意多个参数自动判断的变态想法,来专注实现个数限定下的参数判断吧。
假设我们的参数个数最多不会超过10个,然后有下面的宏MACRO_ARGS:

#define MACRO_ARGS(...)     __VA_ARGS__

想要计算出__VA_ARGS__里面有多少个参数,最好能够利用宏自身区分参数的机制。
比如我们可以再定义一个宏,然后把它们俩套起来:

#define MACRO_ARGS_FILTER(_1,_2,_3,_4,_5,_6,_7,_8,_9)
#define MACRO_ARGS_CONTER(...)  MACRO_ARGS_FILTER(__VA_ARGS__)

但是目前FILTER里什么都没有,当CONTER把参数分散放入FILTER时,我们必须要让FILTER能够计算出传入参数的个数才行。
这一步的思维跳跃是比较难思考到的:

#define MACRO_ARGS_FILTER(_1,_2,_3,_4,_5,_6,_7,_8,_9,_N, ...) _N
#define MACRO_ARGS_CONTER(...)  MACRO_ARGS_FILTER(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)

通过CONTER给FILTER默认10个参数,这时__VA_ARGS__若有1个以上的参数,那么FILTER里的_N就会自动被挤到(9, 8, 7, 6, 5, 4, 3, 2, 1, 0)中合适的位置上。

这样的写法有一个小缺点,就是当参数为空时,CONTER仍然返回1。
想想就知道为什么了:__VA_ARGS__为空时FILTER里的逗号并不会被消掉。
为了让它消掉逗号,在gcc里需要这样写:

#define MACRO_ARGS_FILTER(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_N, ...) _N
#define MACRO_ARGS_CONTER(...)  MACRO_ARGS_FILTER(0, ##__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)

可以测试一下:

printf("%dn", MACRO_ARGS_CONTER(a, b, c, d));

看看它的返回结果是不是4。

当需要更多参数个数的判断时,我们只需要拓展FILTER和CONTER里的数字队列就行了。

上面的写法是gcc下的,在vc里稍有不同:vc编译器在处理__VA_ARGS__的时候,会直接把__VA_ARGS__里的所有内容(包括逗号)作为一整个参数传入下面一个宏里。也就是说,在vc编译器里__VA_ARGS__是不会被展开成参数列表传入第二层的,而是变成了一个大“参数”。

为了让vc能够把__VA_ARGS__打开,我们可以在FILTER的外部包一层什么都不做的宏:

#define MACRO_ARGS_(exp)        exp
#define MACRO_ARGS_CONTER(...)  MACRO_ARGS_(MACRO_ARGS_FILTER(0, ##__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))

这样的写法就可以同时支持gcc和vc了。

但是其实还有一个小问题。在vc中实际测试的时候可以发现,当CONTER什么都不写的时候,它仍然返回的是1。
这是因为在vc中,虽然在__VA_ARGS__不存在时会自动去掉前面的逗号,但是若是将它作为一个参数传递给下面一层宏的时候,逗号却会被保留下来。
vc不比gcc,可以通过##强制让编译器去掉这个逗号,因此这个小问题在vc里基本无解了。
但是不要紧,一般我们也不会像“CONTER()”这样去调用。至少,现在已经能够成功的自动计算10个以下的参数个数了。

2. 自动生成重复的代码

我们需要实现一个这样的功能:输入一个数字n和一个标识符x,然后得到一个把x连续重复了n次的字符串。
宏定义可以看起来像这个样子:

#define MACRO_N(n, x)

我们可以先从最简单的做起:当n为0或1时应该是个什么样子。
这个很简单,我们很快可以写出下面的代码:

#define MACRO_0(x)
#define MACRO_1(x)          x

那么接着,当n为2、3、4的时候呢?
我们可以套用前面的宏,把它们一个个连起来:

#define MACRO_2(x)          MACRO_GLUE(x, MACRO_1(x))
#define MACRO_3(x)          MACRO_GLUE(x, MACRO_2(x))
#define MACRO_4(x)          MACRO_GLUE(x, MACRO_3(x))
// ...

现在我们有了一堆宏了,下面就需要想办法让MACRO_N能够自动的调用它们:

#define MACRO_N(n, x)       MACRO_GLUE(MACRO_, n)(x)

下面我们可以用printf(“%sn”, MACRO_EXPAND(MACRO_N(3, str)));来试一试,屏幕上会输出“strstrstr”。

单看这种宏似乎没有什么大用处,但是在一些特殊场景下,这种玩法可以帮我们节省大量的代码。
比如说,在实现仿函数模板的时候,我们会需要“变参模板”的功能,因为一个仿函数模板根本不知道需要支持的函数会有多少个参数。由于目前的主流编译器暂时还不支持C++11标准里的变参模板,因此我们可能需要像下面这样写:

template <typename R>
class Functor0;
template <typename R, typename T1>
class Functor1;
template <typename R, typename T1, typename T2>
class Functor2;
// ...

这代码。。实在是太蛋疼了。

有了前面那样的技术手段,我们可以先把参数自动拓展的功能玩出来。这需要先把前面那个宏变得更加通用化一些。
考虑到“typename T1, typename T2, typename T3”这样的符号串,必定在开头或结尾,会有一个符号和其他的不同(因为没有分隔用的逗号),通用化一些的宏应该需要再增加一个参数,来代表开头或结尾的特殊串。
这里实现一个以开头作为特殊串的自动化“Repeat”宏(当然,也可以尝试以结尾作为特殊串):

/*Automate repetitive types of content
*/
#define REPEAT_0(head, body)
#define REPEAT_1(head, body)     head(1)
#define REPEAT_2(head, body)     REPEAT_1(head, body)body(2)
#define REPEAT_3(head, body)     REPEAT_2(head, body)body(3)
#define REPEAT_4(head, body)     REPEAT_3(head, body)body(4)
#define REPEAT_5(head, body)     REPEAT_4(head, body)body(5)
#define REPEAT_6(head, body)     REPEAT_5(head, body)body(6)
#define REPEAT_7(head, body)     REPEAT_6(head, body)body(7)
#define REPEAT_8(head, body)     REPEAT_7(head, body)body(8)
#define REPEAT_9(head, body)     REPEAT_8(head, body)body(9)
#define REPEAT(n,head, body)     MACRO_GLUE(REPEAT_, n)(head, body)

为了实现我们最初的目标,需要预先定义两个待重复的标记并定义一个用来做特殊处理的宏:

#define TYPENAME_H(n) typename T##n
#define TYPENAME_B(n) , typename T##n
#define TYPENAME_N(n) REPEAT(n, TYPENAME_H, TYPENAME_B)

当调用TYPENAME_N(3)的时候,就可以得到“typename T1, typename T2, typename T3”。

现在,可以写一个“自动写代码的宏”了:

#define FUNCTOR_N(n) template <typename R, TYPENAME_N(n)> class Functor##n;

3. 伪变参模板

通过上面的一些手段,我们实现了“任意拓展参数个数”之类的功能,但当参数不同的时候,我们还是需要写不同的模板名来调用功能。
那么如何实现一个像FUNCTOR(…)这样的宏,可以自动根据参数个数选择不同的模板呢?

其实有了前面的第一个计算参数个数的技巧,这样的宏很容易写出来:

#define FUNCTOR_1(R)            Functor0<R>
#define FUNCTOR_2(R, T1)        Functor1<R, T1>
#define FUNCTOR_3(R, T1, T2)    Functor2<R, T1, T2>
// ...
#define FUNCTOR(...)            MACRO_ARGS_(MACRO_GLUE(FUNCTOR_,MACRO_ARGS_CONTER(__VA_ARGS__)(__VA_ARGS__))

注意这里不能直接写成这个样子:

  #define FUNCTOR(R, ...)    MACRO_GLUE(Functor, MACRO_ARGS_CONTER(__VA_ARGS__))<R,##__VA_ARGS__>

因为MACRO_ARGS_CONTER在vc下,没有参数的时候始终会返回1,而这里需要0。

但是这个代码还是太累赘了,写到FUNCTOR_9的时候不得不写9个参数。让我们把每个FUNCTOR_的定义都变成一样:

#define FUNCTOR_1(...)      Functor0<__VA_ARGS__>
#define FUNCTOR_N(R, ...)   MACRO_GLUE(Functor, MACRO_ARGS_CONTER(__VA_ARGS__))<R, __VA_ARGS__>
#define FUNCTOR_2(R, ...)   FUNCTOR_N(R, __VA_ARGS__)
#define FUNCTOR_3(R, ...)   FUNCTOR_N(R, __VA_ARGS__)
// ...

这样我们就可以轻松的拓展支持参数的个数,现在使用Functor可以非常愉快:

FUNCTOR(int);               // Functor0<int>
FUNCTOR(void, char*, int);  // Functor2<void, char*, int>
FUNCTOR();                  // Functor0<>, 为Functor0写一个默认的void参数,即可支持无参数的FUNCTOR

C++宏(Macro)的各种玩法相关推荐

  1. 自定义键盘码_?光效DIY+自定义宏:玩法多样的杜伽k320RGB机械键盘

    随着科技的不断发展和人们生活水平的不断提高,机械键盘凭借出色的手感,开始逐渐替代传统的薄膜键盘,而成为电脑用户的主流生产力工具,更有越来越多的游戏玩家,开始沉迷于机械键盘的便利与手感.下面评测的这款D ...

  2. AI视觉组仙人一步之高级玩法——从Python回归C语言

    开心的程序猿@NXP 2021-02-04 Thursday   读过之前两篇的童鞋们,想来已经开始着手开发属于自己的AI视觉应用了,当然,手中还没有OpenART套件的朋友们,也不用着急,可以先参照 ...

  3. Vim的几个高级玩法

    文章目录 vim的几种模式 扩展命令模式命令: 命令模式高频命令: 命令模式进阶命令: vim高级玩法 小结:  在Linux中编辑文件的场景非常之多,掌握一些关键命令和技巧.能够大大提高效率,使用体 ...

  4. 安卓关于健身的代码_亲子运动健身新玩法,娱乐享瘦两不误,让孩子不再沉迷电子产品...

    每次和姐妹逛街,看到那些漂亮小姐姐们在电玩城跳舞机上跳舞,心里都痒痒的想要自己上去试试,可就是因为不熟练,大庭广众之下害羞怕丢脸,最后放弃了.可每次路过还是会羡慕那些敢跳的姐姐们! 平常工作忙,压力大 ...

  5. 单片机C语言流水灯花样编程,51单片机学习之陆 —— 1.3 流水灯的花样玩法

    原标题:51单片机学习之陆 -- 1.3 流水灯的花样玩法 上一次我们点亮了一个1,其实仅仅点亮一个流水灯还是有些单调的. 这一次让我们试着尝试些别的花样 1 多个流水灯的点亮 a 打开将原来的c文件 ...

  6. 快手616第一轮宠粉主播诞生,从头部主播首战看今年616玩法

    一年一度的616大战已经拉开序幕. 回首去年616期间,电商直播总观看时长达4.8亿小时,辛巴近10小时带货金额12.5亿+.格力老总董明珠直播战绩破百亿-不断更替的微博热搜.持续被刷新的带货记录,无 ...

  7. Word2003的另类超级BT玩法(转)

    Word2003的另类超级BT玩法(转) 你是不是整天面对着电脑而感到头昏脑涨,但无穷无尽的工作却让你得不到外出休息的机会,哪怕只有十几分钟的时间.其实,我们在工作中也可以自我放松一下的. 一.别孤独 ...

  8. 一个LED的N种玩法--多线程

    一个LED的N种玩法--多线程 Author: chenzhufly Email: chenzhufly@126.com 2010-05-04 这篇我将来玩玩多线程操作,体会一下其中的乐趣.实际上这也 ...

  9. Linux /dev/mem的新玩法

    来自<解决Linux内核问题实用技巧之-dev/mem的新玩法> 接着上一篇文章<解决Linux内核问题实用技巧之 - Crash工具结合/dev/mem任意修改内存>继续,本 ...

最新文章

  1. Virtual DOM和diff算法 概念理解
  2. discuz! 7.2 manyou插件暴路径Get Webshell 0day
  3. 两个简单的前台显示构架01
  4. java:ToStringBuilder.reflectionToString重写toString
  5. DIY-希捷硬盘固件问题的解决方法
  6. php 新浪微博模拟登陆,python模拟新浪微博登陆功能(新浪微博爬虫)
  7. 电脑下载路径与安装路径设置 以及浏览器推荐
  8. 3.1 Vendor Model浅析
  9. YouTube和Twitch上的流媒体之间有何区别?
  10. 手机拍的照片计算机内存不足怎么办,手机内存不够用,照片应该怎么处理才能够少占用内存?...
  11. java对象list_java 8 从一个list对象转list对象的属性
  12. 澳元兑美元预测:美元可能因美国经济衰退担忧而进一步下跌(MogaFX)
  13. 信鸽推送-10005错误
  14. Could not resolve placeholder 占位符不能被解析
  15. Linux之父:我们都老了,但Linux维护后继无人
  16. unity 多个物体围绕一个点生成圆形状
  17. lazada发货_lazada怎么发货?
  18. linux如何查看磁盘占用
  19. STM32几种让LED灯闪烁的方法
  20. 【一些逻辑综合的思考题】

热门文章

  1. 《笨办法学Python》——习题5
  2. html怎么打英文字母,小英文字母怎么打
  3. 网易云的招股书,递交了几分情怀?
  4. 中文字符和英文字符判断
  5. Systemd工作原理及使用
  6. java火焰_java火焰图配置
  7. 去做海外,无数深坑-第一次使用java调用javascript,第一次调用curl
  8. win10无法完全关机的解决方法
  9. 在VMware上如何创建虚拟机以及Linux操作系统
  10. 影响债市行情的主要因素_影响债券转让价格的主要因素有哪些