玩转iOS“宏定义”

本文详细介绍了iOS中宏定义的使用,包括宏的本质、宏的类型、宏的展开规则,以及如何编写安全的宏。通过实例分析,探讨了宏在函数式宏、多语句宏、不定参数宏等方面的应用技巧,还展示了MIN与MAX、NSAssert等系统宏的巧妙实现。文章强调了编写宏时的注意事项,如使用小括号、避免变量冲突等,并提倡在实际开发中不断学习和优化宏的使用,以提升代码质量。
摘要由CSDN通过智能技术生成

宏定义在C类语言中非常重要,因为宏是一种预编译时的功能,因此其可以比运行时更高层面的对程序流程进行控制。在初学宏定义的时候,大家可能都会有这样一种感觉:就是完全替换么,太简单了。但如果你真这么想,那你就太天真了,不说自己编写宏,在Foundation框架中内置定义的许多宏要看明白也要费一番脑筋。本篇博客,总结了前辈的经验,同时收集了一些编写非常巧妙的宏进行分析,希望可以帮助大家对宏定义有更加深刻的理解,并且可以将心得应用于实际开发中。

一、准备

宏的本质是预编译时的替换,在开始正文之前,我们需要先介绍一种观察宏替换后结果的方法,这样帮助我们更方便的对宏最终的结果进行验证与测试。Xcode开发工具自带查看预编译结果的功能,首先需要对工程编译一遍,之后选择工具栏中的Assistant选项,打开助手窗口,如下图所示:

之后选择窗口的Preprocess选项,即可打开预编译结果窗口,可以看到,宏被替换后的最终结果,如下图所示:

后面,我们将使用这种方式来对编写的宏进行验证。

二、关于“宏定义”

宏使用#define来进行定义,宏定义分为两种,一种是对象式宏,一种是函数式宏。对象式宏通常对来定义量值,在预编译时,直接将宏名替换成对应的量值,函数式宏在定义时可以设置参数,其作用与函数很类似。

例如,我们可以将π的值定义成一个对象式宏,在使用的时候,用有意义的宏名要比直接使用π的字面值方便很多,例如:

#import <Foundation/Foundation.h>
#define PI 3.1415926
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        CGFloat res = PI * 3;
        NSLog(@"%f", res);
    }
    return 0;
}

函数式宏要更加灵活一些,例如对圆面积计算的方法,我们就可以将其定义成一个宏:

#define PI 3.1415926
#define CircleArea(r) PI * r * r
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        CGFloat res = CircleArea(1);
        NSLog(@"%f", res);
    }
    return 0;
}

现在,有了这个面积计算宏我们可以更加方便的计算圆的面积了,看上去很完美,后面我们就使用这个函数式宏为例,来深入理解宏的原理。

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的点击加入群聊iOS交流群:789143298 ,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术,大家一起交流学习成长!

三、从一个简单的函数式宏说起

再来看下上面我们编写的计算面积的宏,正常情况下好像没什么问题,但是需要注意,归根结底宏并不是函数,如果完全把其作为函数使用,我们就可能会陷入一系列的陷阱中,比如这样使用:

#define PI 3.1415926
#define CircleArea(r) PI * r * r
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        CGFloat res = CircleArea(1 + 1);
        NSLog(@"%f", res);
    }
    return 0;
}

运行代码,运算的结果并不是半径为2个圆的面积,哪里出了问题呢,我们还是先看下宏预编译后的结果:

CGFloat res = 3.1415926 * 1 + 1 * 1 + 1;

一目了然了,由于运算符的优先级问题导致了运算顺序错误,在编程中,所有运算符优先级产生的问题都可以使用一种方式解决:用小括号。对CircleArea宏进行一下改造,如下:

#define CircleArea(r) (PI * (r) * (r))

对执行顺序进行了强制的控制,代码执行又恢复了正常,看上去好像是没有问题了,现在就满意了还为时过早,例如下面这样使用这个宏:

#import <Foundation/Foundation.h>
#define PI 3.1415926
#define CircleArea(r) PI * (r) * (r)
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        int r = 1;
        CGFloat res = CircleArea(r++);
        NSLog(@"%f, %d", res, r);
    }
    return 0;
}

运行,发现结果又错了,不仅计算结果与我们的预期不符,变量自加的的结果也不对了,我们检查其展开的结果:

CGFloat res = 3.1415926 * (r++) * (r++);

原来问题出在这里,宏在展开的时候,将参数替换了两次,由于参数本身是一个自加表达式,所以被自加了两次,产生了问题,那么这个问题怎么解决呢,C语言中有一种很有用的语法,即使用大括号定义代码块,代码块会将最后一条语句的执行结果返回,修改上面宏定义如下:

#import <Foundation/Foundation.h>
#define PI 3.1415926
#define CircleArea(r)   \
({                      \
    typeof(r) _r = r;   \
    (PI * (_r) * (_r)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值