最近因为种种的机缘巧合,在阅读ReactiveCocoa
的源码,然后就被它里面对宏的神奇使用所折服了,我想从此将踏上不归之路。。
RAC
中有一个RACmetamacros.h
文件,这个文件里定义了大量的宏,里面对宏的使用到了出神入化的地步,可以在预处理时就得到可变参数个数,可以在书写RAC(obj,x)
的时候,就自动提示出obj
对象的属性x
。今天我们就简单的学习一些关于宏的基础知识,然后举几个例子来具体的分析一下。
首先是关于宏里面各种符号意义的介绍,下面所列出来的是相对比较常见的。
\的作用:换行,因为
#define
只在当前行起作用,为了看的更简单、更直观,可以使用\换行显示。#
的作用:它的功能是将其后面的宏参数字符串化,简单地说就是宏变量的左右加上双引号
。##
的作用:它表示将两个参数连接起来这种运算。注意函数宏必须是有意义的运算,因此你不能直接写AB来连接两个参数,而需要写成例子中的A##B
。_
的作用:用来声明变量(注意在宏里面 _1 也是一个变量,可以看做是变量x1。。)__VA_ARGS__
:总体来说就是将左边宏中 ... 的内容原样抄写在右边__VA_ARGS__
所在的位置。__FILE__
:宏在预编译时会替换成当前的源文件名。__LINE__
:宏在预编译时会替换成当前的行号。__FUNCTION__
:宏在预编译时会替换成当前的函数名称。
有了以上的这些基础知识,我们先来看一个比较简单的例子。(例子来源于孙源大神的一篇文章,可惜没有注释,可能大伙看了也不明白是怎么出来的。。)
[Macro]预处理时计算可变参数个数
#define COUNT_PARMS2(_a1, _a2, _a3, _a4, _a5, RESULT, ...) RESULT
#define COUNT_PARMS(...) COUNT_PARMS2(__VA_ARGS__, 5, 4, 3, 2, 1)
int count = COUNT_PARMS(1,2,3); // 预处理时count==3
下面就来一步步分析一下是怎么在预处理的时候通过宏得到count
的个数的。
-
COUNT_PARMS(1,2,3)
中的1,2,3
是可变参数,将1,2,3
当做一个整体参数传入到COUNT_PARMS2(__VA_ARGS__, 5, 4, 3, 2, 1)
中的__VA_ARGS__
中,对应上面所列出的第5点规则:【__VA_ARGS__
总体来说就是将左边宏中 ... 的内容原样抄写在右边__VA_ARGS__
所在的位置】
所以此时COUNT_PARMS(1,2,3)
就等价于COUNT_PARMS2(1,2,3, 5, 4, 3, 2, 1)
。 - 看一下
COUNT_PARMS2
的定义:COUNT_PARMS2(_a1, _a2, _a3, _a4, _a5, RESULT, ...) RESULT
,其中前面的_a1, _a2, _a3, _a4, _a5
, 相当于是变量,对应上面的第4条规则:_ 的作用:用来声明变量(注意在宏里面 _1 也是一个变量,可以看做是变量x1。。)
因为宏定义前5个是变量,所以会将COUNT_PARMS2(1,2,3, 5, 4, 3, 2, 1)
中的1,2,3,5,4
参数依次赋值给_a1, _a2, _a3, _a4, _a5
, 此时COUNT_PARMS2(_a1, _a2, _a3, _a4, _a5, RESULT, ...)
中RESULT
对应的就是第6个参数3
,后面的那个...
可变参数对应的就是最后的两个参数2和1
了,我们看到宏定义最后将RESULT
返回,而这个RESULT
恰好就是我们想要的结果了,这里思考一下如果我们求COUNT_PARMS(1,2,3,4,5,6,7)
的可变参数个数的话,RESULT会是多少呢
带着疑问,我们继续看个更复杂一些的----
ReactiveCocoa
中的metamacro_argcount
宏,这也是一个用来在预编译的时候求可变参数个数的宏。
先看一下它的定义
#define metamacro_argcount(...) metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
#define metamacro_at(N, ...) metamacro_concat(metamacro_at, N)(__VA_ARGS__)
#define metamacro_concat(A, B) metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B
#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)
#define metamacro_head(...) metamacro_head_(__VA_ARGS__, 0)
#define metamacro_head_(FIRST, ...) FIRST
我们假设int a = metamacro_argcount(1,2, 3);//a = 3
看这个是怎么一步一步的计算出来的。步骤跟上面的那个简单例子基本一致。
注意我们为了书写方便,在此用X
替代20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
把
1,2,3
当参数传入宏里面
metamacro_argcount(1,2,3)
就相当于metamacro_at(20,1,2,3,X)
看下
metamacro_at
宏的定义metamacro_at(N, ...)
,所以第一步中的20
相当于这里的N
,而1,2,3,X
则对应宏中的可变参数 ...继续往下走,看
metamacro_concat_(A, B) A ## B
这个宏,对应上述的规则3
,此处会将传入的A和B做一个拼接。而从第二步对应过来的话,那么metamacro_at
对应的是A
,而20
对应的是B
,将它们做一个拼接得到metamacro_at20
,所以此时第二步中metamacro_concat(metamacro_at, N)(__VA_ARGS__)
,就等价于metamacro_at20(1,2,3,X)
因为要将__VA_ARGS__
替换成前面的可变参数1,2,3,X
。此时我们将X展开,就会得到metamacro_at20(1,2,3,20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
查看
metamacro_at20
宏发现里面20个变量
,将上一步中的前20个变量
依次代入即可。此时metamacro_at20
宏中的...
可变参数就是剩下的3,2,1
了,这时候我们就得到得到metamacro_head(3,2,1)
继续往下查看
metamacro_head_(__VA_ARGS__, 0)
的实现,将3,2,1
代入后得到metamacro_head_(3,2,1, 0)
。这个宏返回的是第一个元素
,而这个3正好是可变参数的个数了。
需要注意的一点是,我们所输入的可变参数的个数是有最大值限制的,比如刚刚RAC
中的最大可变参数数目是20
,如果我们输入的超过了20
的话,那么就会出现错误。
如果看到这里,你没有蒙圈的话,那么恭喜你已经初步进入“宏”的奇妙漂流中了,之后会加入对它的数学分析。。