C语言 宏定义

简单点,宏定义就是原封不动的替换,要检查自己编写的宏定义是否正确,只需要用宏替换相应的代码(块)即可。宏大致分为两种,一种是对象宏,另一种是函数宏(带参数的宏)。


对象宏:


#define SFNavigationBarHeight 44.0

函数宏:


我们着手定义一个两个数中求最小值的宏。

初始版:
#define SF_MIN(A, B) A < B ? A : B
如果写成这样:
    int a = 3 * SF_MIN(1, 2);
    // a -> 2
跟我们预期结果不一样,我们把宏展开看看:
    // 3 * 1 < 2 ? 1 : 2
    // 3 < 2 ? 1 : 2
    // 2
这里面主要涉及到运算符优先级的问题,小于号和比较符号优先级低于乘法运算符,所以先计算了乘法,那就要加上括号。

修正版1:
#define SF_MIN(A, B) (A < B ? A : B)
如果写成这样呢:
    int a = SF_MIN(3, 4 < 5 ? 4 : 5);
    // a -> 4
跟我们预想的结果还是不一样,我们期望的值是3,结果是4。同样我们展开宏看看:
    // (3 < 4 < 5 ? 4 : 5 ? 3 : 4 < 5 ? 4 ; 5)
    // ((3 < (4 < 5 ? 4 : 5) ? 3 : 4) < 5 ? 4 ; 5)
    // ((3 < 4 ? 3 : 4) < 5 ? 4 : 5)
    // (3 < 5 ? 4 : 5)
    // 4

修正版2:
#define SF_MIN(A, B) ((A) < (B) ? (A) : (B))
如果写成这样的:
    int a = 1;
    int b = SF_MIN(a++, 4);
    NSLog(@"a = %d, b = %d", a, b);

    //预期结果:a = 2, b = 1.
    //实际结果:a = 3, b = 3.
展开宏再看看:
    //-> int b = ((a++) < (4) ? (a++) : (4))
    //-> int b = 1 < 4 ? 1 : 4 , a = 2
    //-> int b = 2 < 4 ? 2 : 4 , a = 3
    //-> int b = 3 < 4 ? 3 : 4
在比较a++和4的时候,先取a = 1,此时b = 1,a自增1,a = 2。接下来条件比较得到(2 < 4)为真,又触发了一次a++,此时a已经为2,b = 2,最后a再次自增为3, b = 3。我们可以看到,我们期望的是a++只执行一次,但由于宏的展开导致a++被执行了多次。我们要用到GNU C的赋值扩展,即({...})这样的形式,这种形式的语句在顺序执行之后,会将最后一次的表达式的赋值作为返回。先来个简单的例子:
    int a = ({
        int b = 1;
        int c = 2;
        b + c;
    });
    printf("%d", a);
    // a -> 3

修正版3:
#define SF_MIN(A, B) ({  \
    __typeof__(A) __a = (A); \
    __typeof__(B) __b = (B); \
    __a < __b ? __a : __b;   \
})

说明:typeof是gcc中对C/C++语法的一个扩展,用来静态获取参数类型,比如:

    int a = 10;
    typeof(a) b = 4; // 相当于int b = 4;
    __typeof__("12345") c = "Axe"; //相当于const char c[4] = "Axe";

这里定义了三个语句,分别以输入的类型申明了__a__b,并使用输入为其赋值,接下来做一个简单的条件比较,得到__a__b中的较小值,并使用赋值扩展将结果作为返回。这样的实现保证了不改变原来的逻辑,先进行一次赋值,也避免了括号优先级的问题,可以说是一个比较好的解决方案了。如果编译环境支持GNU C的这个扩展,那么毫无疑问我们应该采用这种方式来书写我们的MIN宏,如果不支持这个环境扩展,那我们只有人为地规定参数不带运算或者函数调用,以避免出错。

Apple 在Clang中彻底解决了这个问题,我们可以看下苹果是怎么定义这个宏的:

#define __NSX_PASTE__(A,B) A##B

#define __NSMIN_IMPL__(A,B,L) ({ __typeof__(A) __NSX_PASTE__(__a,L) = (A); __typeof__(B) __NSX_PASTE__(__b,L) = (B); (__NSX_PASTE__(__a,L) < __NSX_PASTE__(__b,L)) ? __NSX_PASTE__(__a,L) : __NSX_PASTE__(__b,L); })

#define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)

可能这样看起来比较吃力,我们先美化一下__NSMIN_IMPL__(A,B,L)这个宏。我们知道宏是可以插入换行符而不影响其含义的,改写如下:

#define __NSX_PASTE__(A,B) A##B

#define __NSMIN_IMPL__(A,B,L) ({ \
__typeof__(A) __NSX_PASTE__(__a,L) = (A); \
__typeof__(B) __NSX_PASTE__(__b,L) = (B); \
(__NSX_PASTE__(__a,L) < __NSX_PASTE__(__b,L)) ? __NSX_PASTE__(__a,L) : __NSX_PASTE__(__b,L); \
})

#define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)
__NSMIN_IMPL__(A,B,L)__其实可转化为下列的伪代码:
__typeof__(A) __a__COUNTER__ = (A);
__typeof__(B) __b__COUNTER__ = (B);
(__a__COUNTER__ < __b__COUNTER__) ? __a__COUNTER__ : __b__COUNTER__;
这样看起来就清晰多了,可以看到MIN宏共由三个宏组合而成的。

第一个__NSX_PASTE__(A,B)中出现的##,它是一个特殊的符号,表示将两个参数连接起来,函数宏必须是有意义的计算,你不能直接写AB来连接两个参数。

MIN(A,B)其实是调用了__NSMIN_IMPL(A,B,L)__这个宏,前两个就是我们输入的A、B,最后一个参数__COUNTER__,是一个预定义的宏,这个值在编译过程中将从0开始计数,每次被调用时加1。因为它的唯一性,所以很多时候被用来构造独立的变量名称。有了这些基础,再来看最后实现的宏就很简单了。整体思路和前面GUN C MIN是一样的,却别在于为变量名__a和__b添加了一个计数后缀,这样可避免变量名相同而导致问题的可能性。






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值