C/C++宏的奇技淫巧

http://misakamm.com/blog/209

宏的主要作用就是简化代码编写,简化一些需要重复编码的地方,以得到看起来更优雅的代码。但宏要用得好并不容易,用的不好很容易引发灾难性的后果。本文会介绍宏比较偏门但又很实用的技巧。

首先就是最常用的技巧(http://blog.misakamm.org/p/209):

define MACROCAT( x, y ) MACROCAT1 ( x, y )

define MACROCAT1( x, y ) x##y

define TOSTRING( s ) #s

MACROCAT把x和y展开后连結,而TOSTRING把s转化为字符串,比如可以printf(TOSTRING(%s), TOSTRING(abcdefg));

然后,因为宏不能递归,但可以做递归模拟,我们可以这样玩。比如要生成n位的二进制数并且从小到大构成的字符串(用到前面的宏):

define BIN_0(arg) TOSTRING ( arg )

define BIN_1(arg) BIN_0(MACROCAT(arg, 0)) “,” BIN_0(MACROCAT(arg, 1))

define BIN_2(arg) BIN_1(MACROCAT(arg, 0)) “,” BIN_1(MACROCAT(arg, 1))

define BIN_3(arg) BIN_2(MACROCAT(arg, 0)) “,” BIN_2(MACROCAT(arg, 1))

define BIN_4(arg) BIN_3(MACROCAT(arg, 0)) “,” BIN_3(MACROCAT(arg, 1))

int main()

{

puts(BIN_4());
return 0;

}

这里要注意的是,比如BIN_2(),实际上展开的结果是

“0” “0” “,” “0” “1” “,” “1” “0” “,” “1” “1”

不过c/c++规定这样连写的字符串,编译时就会合并成一个,于是就能用puts直接完整输出结果了

如果你想得到更多的位,很简单,只要你不介意,上面的宏复制并改改数字就可以了

不过,这样一改要改若干个数字,比较麻烦,能不能让它工作得更好?比如只要改宏名?

这个时候,就要用更富有技巧性的一招了:让每个宏多一个参数n,然后前面的BIN_x使用MACROCAT把它与数字连结起来,不就可以了么?

想法不错,不过问题是宏本身没有做减法的能力,能做的仅仅是替换。减1应该怎么实现呢?

其实不难,见以下定义:

define DECVAL_1 0

define DECVAL_2 1

define DECVAL_3 2

define DECVAL_4 3

define DECVAL_5 4

define DECVAL_6 5

define DECVAL_7 6

define DECVAL_8 7

define DECVAL_9 8

define DECVAL( n ) DECVAL_##n

好了,有了这个利器,我们就可以对原宏改造了,先拿0号和1号宏开刀:

define BIN_0(n, arg) TOSTRING ( arg )

define BIN_1(n, arg) MACROCAT(BIN_, DECVAL(n)) (DECVAL(n), MACROCAT(arg, 0)) \

    "," MACROCAT(BIN_, DECVAL(n)) (DECVAL(n), MACROCAT(arg, 1))

看得懂替换了一些什么吗?这样,后面的2,3,4,5号,只要复制一下1号的定义,改一改宏名就解决问题了

思考题:

这里生成的二进制结果是带前导0的,如何改写能使生成的结果不带前导0?

source: http://blog.misakamm.org/p/209

使用此法可以“递归”式生成很多类似代码,同时这个技巧也非常的实用,但递归构造并不容易,需要编写的人仔细想清楚,否则很容易出错,特别要注意宏展开的时机,一般不直接使用MACROCAT1宏,因为那个很可能不是你想要的结果

之后,到C99标准出台后(也就是说,下文内容与bc3/tc/vc6不兼容),宏里面多了一个狠角色:可变参数个数宏

比如可以 #define PRINTF(…) fprintf(stdout, VA_ARGS)

其中VA_ARGS代表了‘…’部分的全部参数,这样可以轻松的重定义库函数里不定参数的函数的输出行为,比如printf重定向到文件(虽然也可以用freopen实现,但只想说明宏也可以这样搞)

好了,下文将区分编译器来介绍,一共分为两派,vc派和gcc派(包括clang/objc),因为两者对以下代码的处理并不一致,需要使用略为不同的宏来实现,目前我也只遇到这两派。

现在的目的是这样,因为VA_ARGS包含了若干参数,我怎么才能知道里面参数有多少个呢?

比如写一个宏NUM_PARAMS(),里面写NUM_PARAMS(abc,a,d,e)的话,替换后得到的结果要是4,能办到吗?

广告时间:

http://blog.misakamm.org/p/209

广告过后,回来精彩的节目

首先先介绍gcc派的解决方案:

define PP_NARG(…) PP_NARG(VA_ARGS_, PP_RSEQ_N())

define PP_NARG(…) PP_ARG_N(VA_ARGS_)

define PP_ARG_N( \

    _1, _2, _3, _4, _5, _6, _7, _8, _9,_10,     _11,_12,_13,_14,_15,_16, N, ...) N

define PP_RSEQ_N() \

    16,15,14,13,12,11,10,     9,8,7,6,5,4,3,2,1,0

非常漂亮巧妙又简洁的方案,我想不用我多解释了吧?

不过,请注意,这是gcc的方案,以上代码放在vc8/vc9/vc2010等都会得不到正确的结果的,这个和vc的宏处理方式有关

接下来就是给出vc的解决方案(以下均以vc2008和vc2010为准)

define BRACKET_L() (

define BRACKET_R() )

define PP_NARG(…) \

PP_NARG_ ( __VA_ARGS__, PP_RSEQ_N() )

define PP_NARG_(…) \

PP_ARG_N BRACKET_L() __VA_ARGS__ BRACKET_R()

define PP_ARG_N( \

_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16, N,...) N

define PP_RSEQ_N() \

16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0

这里很特别的一点是对部分小括号做了替换。

问题在于PP_NARG_到PP_ARG_N做参数传递的时候,如果已有显式的括号,那么不对里面的宏做展开计算参数个数,仅直接按显式的逗号个数判断出参数个数,从而导致VA_ARGS被当成一个参数传入。而把括号用宏替换掉后,则不出现直接的括号,就先对宏做展开,而展开后,再展开新构造出来的宏,这样才能让参数匹配上。

不过gcc里面不能这么干,gcc会把宏名展开出来后,如果发现后面的符号并不是显式的括号,则把前面的宏符号化,不再展开。这两种不同的特性让我现在还不知道怎么编写宏能让两派都能兼容,正确展开出我想要的东西。

解释了两个编译器的不同点以后,后面不再解释相同的问题,而会同时给出两份代码。

另一个类似的问题,就是既然有不定个数的参数,如果我希望对每个参数都做一些处理,那如何做呢?

举例,实现一个宏#define SPREAD(…),要把参数里的东西连结成一个字符串

之前的例子里,已经实现了把不定参数展开的手段,现在我们来尝试递归下降式展开(gcc版本):

define SPREAD0( arg ) #arg

define SPREAD1(arg, …) SPREAD0(arg)

define SPREAD2(arg, …) SPREAD0(arg) SPREAD1(VA_ARGS,)

define SPREAD3(arg, …) SPREAD0(arg) SPREAD2(VA_ARGS,)

define SPREAD4(arg, …) SPREAD0(arg) SPREAD3(VA_ARGS,)

define SPREAD5(arg, …) SPREAD0(arg) SPREAD4(VA_ARGS,)

define SPREAD6(arg, …) SPREAD0(arg) SPREAD5(VA_ARGS,)

define SPREAD7(arg, …) SPREAD0(arg) SPREAD6(VA_ARGS,)

define SPREAD8(arg, …) SPREAD0(arg) SPREAD7(VA_ARGS,)

define SPREAD9(arg, …) SPREAD0(arg) SPREAD8(VA_ARGS,)

define SPREAD(…) SPREAD9(VA_ARGS)

在这里,每进入一层,就从VA_ARGS拆解一个最前面的参数出来,把剩下的参数给下一层

这里有一个细节是VA_ARGS后面有一个逗号,意思就是补一个空参数,避免后面参数不足

然后就可以用puts(SPREAD(1, 2, 3, 4));来测试了

当然,还要使用前文的方式处理一下(gcc版):

define SPREAD0( arg ) #arg

define SPREAD1(n, arg, …) SPREAD0(arg)

define SPREAD2(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) ( DECVAL(n), VA_ARGS, )

define SPREAD3(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) ( DECVAL(n), VA_ARGS, )

define SPREAD4(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) ( DECVAL(n), VA_ARGS, )

define SPREAD5(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) ( DECVAL(n), VA_ARGS, )

define SPREAD6(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) ( DECVAL(n), VA_ARGS, )

define SPREAD7(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) ( DECVAL(n), VA_ARGS, )

define SPREAD8(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) ( DECVAL(n), VA_ARGS, )

define SPREAD9(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) ( DECVAL(n), VA_ARGS, )

define SPREAD(…) SPREAD9 ( 9, VA_ARGS )

vc版:

pragma warning(disable:4003) // 去除警告

define SPREAD0( arg ) #arg

define SPREAD1(n, arg, …) SPREAD0(arg)

define SPREAD2(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) BRACKET_L() DECVAL(n), VA_ARGS, BRACKET_R()

define SPREAD3(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) BRACKET_L() DECVAL(n), VA_ARGS, BRACKET_R()

define SPREAD4(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) BRACKET_L() DECVAL(n), VA_ARGS, BRACKET_R()

define SPREAD5(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) BRACKET_L() DECVAL(n), VA_ARGS, BRACKET_R()

define SPREAD6(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) BRACKET_L() DECVAL(n), VA_ARGS, BRACKET_R()

define SPREAD7(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) BRACKET_L() DECVAL(n), VA_ARGS, BRACKET_R()

define SPREAD8(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) BRACKET_L() DECVAL(n), VA_ARGS, BRACKET_R()

define SPREAD9(n, arg, …) SPREAD0(arg) MACROCAT(SPREAD, DECVAL(n)) BRACKET_L() DECVAL(n), VA_ARGS, BRACKET_R()

define SPREAD(…) SPREAD9 BRACKET_L() 9, VA_ARGS, BRACKET_R()

以上只是模糊方式展开,因为参数个数不知道,后面会遇到宏参数为空的情况,于是vc编译器给出了警告

如果把之前说的过技巧,就是分析出不定参数个数的宏,与这个结合,将产生更大的威力,我们可以实现精确展开,就是在SPREAD宏的定义里,有9的地方使用宏PP_NARG(VA_ARGS)替换一下,于是VA_ARGS后面的逗号可以去掉,也可以简化一些代码了,也能避免展开后有你所不希望的多余字符出现。

测试考题1:

定义一宏#define printf,让它能把printf(str, a, b, c);替换成std::cout?a?b?c?std::endl;

参数个数不确定,不用考虑str的内容,但假设不多于10个参数

http://blog.misakamm.org/p/209

宏的威力还不止至此,当宏与C++模板编程结合的时候,真正的可怕就来临了。。。

测试考题2:

在C++0x之前,模板还没有不定参数,于是需要多个参数的时候,不得不手工解决,或者聪明的人,使用模板来生成多参模板代码。尝试一下这么做,看看和之前的问题难度加大在哪里。比如生成一个名为sum的模板函数,能接受1 – 10个参数,返回这些参数的相加的结果

文章附带:

第一考题参考答案:

define BINARY_E0(n, arg) TOSTRING ( arg )

define BINARY_E1(n, arg) MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 0) )\

"," MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 1) )

define BINARY_E2(n, arg) MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 0) )\

"," MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 1) )

define BINARY_E3(n, arg) MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 0) )\

"," MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 1) )

define BINARY_E4(n, arg) MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 0) )\

"," MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 1) )

define BINARY_E5(n, arg) MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 0) )\

"," MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 1) )

define BINARY_E6(n, arg) MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 0) )\

"," MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 1) )

define BINARY_E7(n, arg) MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 0) )\

"," MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 1) )

define BINARY_E8(n, arg) MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 0) )\

"," MACROCAT(BINARY_E, DECVAL(n)) ( DECVAL(n), MACROCAT(arg, 1) )

define BINARY_ENUM(n) MACROCAT(BINARY_E, n) ( n, )

define BIN_0(n, arg) TOSTRING ( arg )

define BIN_1(n, arg) MACROCAT(BIN_, DECVAL(n)) (DECVAL(n), arg) \

    "," MACROCAT(BINARY_E, DECVAL(n)) (DECVAL(n), MACROCAT(arg, 1))

define BIN_2(n, arg) MACROCAT(BIN_, DECVAL(n)) (DECVAL(n), arg) \

    "," MACROCAT(BINARY_E, DECVAL(n)) (DECVAL(n), MACROCAT(arg, 1))

define BIN_3(n, arg) MACROCAT(BIN_, DECVAL(n)) (DECVAL(n), arg) \

    "," MACROCAT(BINARY_E, DECVAL(n)) (DECVAL(n), MACROCAT(arg, 1))

define BIN_4(n, arg) MACROCAT(BIN_, DECVAL(n)) (DECVAL(n), arg) \

    "," MACROCAT(BINARY_E, DECVAL(n)) (DECVAL(n), MACROCAT(arg, 1))

define BIN_5(n, arg) MACROCAT(BIN_, DECVAL(n)) (DECVAL(n), arg) \

    "," MACROCAT(BINARY_E, DECVAL(n)) (DECVAL(n), MACROCAT(arg, 1))

define BIN_6(n, arg) MACROCAT(BIN_, DECVAL(n)) (DECVAL(n), arg) \

    "," MACROCAT(BINARY_E, DECVAL(n)) (DECVAL(n), MACROCAT(arg, 1))

define BIN_7(n, arg) MACROCAT(BIN_, DECVAL(n)) (DECVAL(n), arg) \

    "," MACROCAT(BINARY_E, DECVAL(n)) (DECVAL(n), MACROCAT(arg, 1))

define BIN_8(n, arg) MACROCAT(BIN_, DECVAL(n)) (DECVAL(n), arg) \

    "," MACROCAT(BINARY_E, DECVAL(n)) (DECVAL(n), MACROCAT(arg, 1))

define BIN_ENUM(n) “0” MACROCAT(BIN_, n) ( n, )

测试代码:puts(BIN_ENUM(8));

测试考题不提供答案。

相关文章

  1. 语法高亮测试
  2. 再一次尝试MMX优化AlphaBlend
  3. 闲的X痛就写了个C/C++源文件删除注释代码的程序
  4. C语言循环的小艺术
  5. 委托模式的C++初步实现的烂代码一份。。。摊手
  6. 另一种用位运算枚举所有组合的算法
  7. [原创] 88行代码实现俄罗斯方块游戏(含讲解)
  8. 另类的谢宾斯基(Sierpinski)分形三角形输出程序
时间: 2024-10-11 06:42:32

C/C++宏的奇技淫巧的相关文章

Objective-C 奇技淫巧--用来在category里加属性的宏

奇技淫巧 指过于奇巧而无益的技艺与制品. 转载请注明出处 http://blog.csdn.net/uxyheaven/article/details/46789065 众所周知,一般的情况下我们是没办法在category里加属性的. 如果想加,需要用到Associated. @interface NSObject (XYFlyweightTransmit) @property (nonatomic, strong) id uxy_flyweightData; @end @implementat

奇技淫巧:NOIP的读入优化

最近看到洛谷上面有一个读入优化的代码: inline char get_char(){//劲者快读 static char buf[1000001],*p1=buf,*p2=buf; return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++; } inline short read(){ short num=0; char c; while(isspace(c=get_char())); while(n

宏定义中的#,##,...,do{}while(0),__VA_ARGS__

宏定义中的#,## 1.在一个预处理器宏中的参数前面使用一个#,预处理器会把这个参数转换为一个字符数组 #define syslog(a) fprintf(stderr,"Warning: " #a"\n"); 2.简单的说,"## "是一种分隔连接方式,它的作用是先分隔,然后进行强制连接 举列 -- 试比较下述几个宏定义的区别 #define A1(name, type)  type name_##type##_type 或 #define A

Word中 简单宏的使用

 (注意:打开文档时按住 Shift 键可以阻止 AutoOpen 宏运行) 1:Word中能够自动运行的默认宏代码名称及触发条件如下 -------------------------------------------------------- 1.名称:AutoExec 条件:启动Word或加载全局模板 2.名称:AutoNew 条件:每次生成新文档时 3.名称:AutoOpen 条件:每次打开一个已有文档时 4.名称:AutoClose 条件:每次关闭文档时 5.名称:AutoExit

常用的预定义的宏

常用的预定义的宏 常用的预定义的宏有:__LINE__ 当前源程序行的行号,用十进制整数常量表示 __FILE__ 当前源文件的名称,用字符串常量表示 __DATE__ 编译时的日期,用"MM dd yyyy"形式的字符串常量表示 __TIME__ 编译时的时间,用"hh:mm:ss"形式的字符串常量表示 __STDC__ 当且只当编译器遵循ISO标准时,它的值是十进制常量1 __STDC__VERSION__ 如果编译器遵循C99,则这个宏的值是199901L,其

笔记3:预处理器-(2)宏定义

#define指令称为宏定义指令,通常用#define指令来定义一个宏用来代表其他东西的一个名字(如常量表达式等).通常来说预处理器会通过将宏的名字和它的定义存储在一起来响应#define指令.当这个宏在后面的程序中使用到时,预处理器会"扩展"宏,将宏替换为其定义值. 简单的宏 简单的宏的定义格式: #define 标识符 替换列表 如: #define DTE_LEN 80 #define TRUE 1 #define FALSE 0 #define PI 3.1415926 #de

BOOST_AUTO宏

在boost中,有个非常不错的宏BOOST_AUTO(),它的作用是自动给var定义类型,适合function()函数返回的值的类型. 1 int function() 2 { 3 return 10; 4 } 5 main() 6 { 7 BOOST_AUTO(var, function()); 8 } 上面的作用类适于: int function() { return 10; } main() { int var = function(); }

宏------进阶

宏定义的黑魔法 - 宏菜鸟起飞手册 宏定义在C系开发中可以说占有举足轻重的作用.底层框架自不必说,为了编译优化和方便,以及跨平台能力,宏被大量使用,可以说底层开发离开define将寸步难行.而在更高层级进行开发时,我们会将更多的重心放在业务逻辑上,似乎对宏的使用和依赖并不多.但是使用宏定义的好处是不言自明的,在节省工作量的同时,代码可读性大大增加.如果想成为一个能写出漂亮优雅代码的开发者,宏定义绝对是必不可少的技能(虽然宏本身可能并不漂亮优雅XD).但是因为宏定义对于很多人来说,并不像业务逻辑那

宏定义中使用do{}while(0)的好处 (转载)

宏定义中使用do{}while(0)的好处   #define MACRO_NAME(para) do{macro content}while(0) 的格式,总结了以下几个原因: 1,空的宏定义避免warning: #define foo() do{}while(0) 2,存在一个独立的block,可以用来进行变量定义,进行比较复杂的实现. 3,如果出现在判断语句过后的宏,这样可以保证作为一个整体来是实现: #define foo(x) /action1(); /action2(); 在以下情况