宏的“奇妙漂流”

作者: ccSundayChina | 来源:发表于2017-04-14 01:43 被阅读688次

    最近因为种种的机缘巧合,在阅读ReactiveCocoa的源码,然后就被它里面对宏的神奇使用所折服了,我想从此将踏上不归之路。。

    扬帆起航.jpg

    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的话,那么就会出现错误。

    如果看到这里,你没有蒙圈的话,那么恭喜你已经初步进入“宏”的奇妙漂流中了,之后会加入对它的数学分析。。

    要相信这只是一扇打开更广阔世界的门而已。。

    相关文章

      网友评论

        本文标题:宏的“奇妙漂流”

        本文链接:https://www.haomeiwen.com/subject/kfviattx.html