宏是什么
宏定义是C语言提供的三种预处理功能的其中一种,这三种预处理包括:宏定义、文件包含、条件编译。宏定义和操作符的区别是:宏定义是替换,不做计算,也不做表达式求解。
宏定义的种类
1. 对象宏: (不带参数)
[0]#define [1]SIZE [2]10
[0]#define 定义宏 undef 取消宏定义
[1] 宏名称, 通常用大写字母
[2] 宏定义的内容,在编译期要替换宏名字的内容
2. 函数宏:(带参数)
#define ADD(x, y) x + y
Demo
#define ADD(x, y) x + y
# define SIZE 10
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
int size = SIZE + 10;
NSLog(@"size = %d", size);
int result = ADD(1, 2);
NSLog(@"result = %d", result);
}
通过xcrun -sdk iphonesimulator clang -rewrite-objc 后我们可以看一下代码转化后的结果
int size = 10 + 10;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cg_1qk1vr1577l3c1f6bbdbt5hm0000gn_T_ViewController_032276_mi_0, size);
int result = 1 + 2;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cg_1qk1vr1577l3c1f6bbdbt5hm0000gn_T_ViewController_032276_mi_1, result);
从上面的结果中可以看出。这里编译器只是进行了替换。
宏函数并非平常的函数操作,其本质还是代码块的替换。
看一个交换 a, b 两个值的宏
#define YM_Swap(_a_, _b_) { __typeof__(_a_) _t_ = _a_; (_a_) = (_b_); (_b_) = (_t_); }
int left_number = 10;
int right_number = 100;
YM_Swap(left_number, right_number);
NSLog(@"left_number = %d, right_number = %d",left_number, right_number);
转化后的结果:
int left_number = 10;
int right_number = 100;
{ int _t_ = left_number; (left_number) = (right_number); (right_number) = (_t_); };
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cg_1qk1vr1577l3c1f6bbdbt5hm0000gn_T_ViewController_646835_mi_0,left_number, right_number);
从上面的结果来看,只是代码的替换,而不是作为一个函数去执行。这就是为什么有人疑问为什么宏能把两个数正真交换,而普通的函数只是参数的交换。
int totalNumber = SIZE - ADD(2, 3);
NSLog(@"totalNumber = %d", totalNumber); // 5 还是11
rewrite 后的代码
int totalNumber = 10 - 2 + 3;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cg_1qk1vr1577l3c1f6bbdbt5hm0000gn_T_ViewController_90b4d5_mi_0, totalNumber);
结果是11,还是代码替换。那如果我们想要实现 10 - 5 怎么办?
最简单的方法就是给 2 + 3 放在一个小括号里面了
#define ADD(x, y) (x + y)
int totalNumber = SIZE - ADD(2, 3);
rewrite 后的代码
int totalNumber = 10 - (2 + 3);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cg_1qk1vr1577l3c1f6bbdbt5hm0000gn_T_ViewController_d954c3_mi_0, totalNumber);
当多个宏连接使用时,运算符的级别跟连接符的级别是一样的,并不会先计算出结果然后再连接。只会一一展开
对于需要多行的宏,可以使用 \ 来连接
#define MINA(A,B) __typeof__(A) __a = (A);\
__typeof__(B) __b = (B);\
__a < __b ? __a : __b;
宏的返回值
当需要把宏内容的最后一条语句的执行结果当作返回值时,我们就可以用到clang c 的扩展 ({})
#define YM_TEST(_a_, _b_) _a_ = 100; _b_=200;
int a = 10;
int b = 20;
int res = YM_TEST(a, b);
NSLog(@"res = %d", res);
转换后的代码
int a = 10;
int b = 20;
int res = a = 100; b=200;;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cg_1qk1vr1577l3c1f6bbdbt5hm0000gn_T_ViewController_602c07_mi_0, res);
输出结果 res = 100
如果想要结果为最后一个表达式的值,
#define YM_TEST(_a_, _b_) ({_a_ = 100; _b_=200;})
int a = 10;
int b = 20;
int res = YM_TEST(a, b);
NSLog(@"res = %d", res);
转换后的代码
int a = 10;
int b = 20;
int res = ({a = 100; b=200;});
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cg_1qk1vr1577l3c1f6bbdbt5hm0000gn_T_ViewController_f857bd_mi_0, res);
输出结果 res = 200
上面的就是为了举例而举例,实际应用可以参考一下前文提到的
#define YM_Swap(_a_, _b_) { __typeof__(_a_) _t_ = _a_; (_a_) = (_b_); (_b_) = (_t_); }
##
连接连个变量
oc中min宏的定义
#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__)
int c = MIN(10, 20);
NSLog(@"c= %d",c);
转换后的代码
int c = ({ int __a0 = (10); int __b0 = (20); (__a0 < __b0) ? __a0 : __b0; });
__COUNTER__ 是一个全局的变量, 这里为0
我们调用时,实际操作为 min (10, 20, 0)
__typeof__(A) __NSX_PASTE__(__a,L) = (A) 转成 int __a0 = 10
__a##0 的结果就是__a0,就是简单的连接。
全局的COUNTER 完全避免了名字的重复。
##__VA_ARGS__
处理可变参数
#define DeLog(format, ...) do { \
printf("<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
__LINE__, __func__); \
NSLog(format, ##__VA_ARGS__); \
} while (0)
在宏中使用 … 定义一个可变参数, 当需要用到这个可变参数时,就要使用 ##VA_ARGS 来解析所有的可变参数。
do {} while(0)是干什么用的, 为什么不直接写成 {}。 说实话其实没什么用,只是让转化后的代码更好看,如果没有 do…while 转换后的代码 { };。 如果有,转换后的代码 do{ } while(0); 只是花括号后面跟一个;不好看而已, 话说回来了,我们又看不到。所以就无所谓了,写成什么都行。而且编译器会把他优化掉,不会影响效率。