喜欢读一些开源项目源码的人,总是会发现,大神的代码中总是有那么一些简短而高效的宏定义,点击进去一看,发现晦涩难懂,别说学习了,有时候理解都是一种困难,但是宏定义本身并没有那么难,但是写出一个好的宏当然还是需要丰富的经验和技术,接下来就说一说宏定义,看懂大神的宏是第一步,偶尔写一个也是装逼的好办法~
定义:
宏定义分为两种:一种是对象宏(object-like macro)另一种就是函数宏(function-like macro)
根据名字也可以理解到,对象宏就是用来定义一个量,通过这个宏可以拿到这个变量,比如我们定义一个π值: #define PI 3.1415926在这里如果用到π值时,就不需要再写出一个浮点数了,而直接使用PI就相当写入了这个常量浮点数,其本质的意义在于把代码中的PI在编译阶段替换为真正的常量,一般用来定义一些常用的常量,比如屏幕的宽高、系统版本号等。但是需要注意的是,但你定义一个表达式为宏的时候,需要透过宏的表面,看到器编译的本质,例如
#define MARGIN 10 + 20
但你用它来计算一个宽度时,如果用到了MARGIN * 2,结果将会非你所愿,你得到的会是一个50而并非60,展开html" target="_blank">表达式就可以看到
MARGIN * 2 // 展开可以得到
// 10 + 20 * 2 = 50
我们需要考虑到它的运算优先级,解决的方式很简单,再它的外层加上一个小括号
#define MARGIN (10 + 20) // MARGIN * 2 // (10 + 20) * 2 = 60
函数宏的作用就类似于一个函数一样,它可以传递参数,通过参数进行一系列的操作,比如我们常用的计算两个数的最大值,我们可以这样来定义
#define MAX(A,B) A > B ? A : B
这样写看起来是没有问题的,进行简单的比较MAX(1,2)发现也是没有什么问题,但是当有人使用你的宏进行更加复杂的计算时就回出现新的问题,比如进行三个数值的计较时,可能会这样写
int a = 3; int b = 2; int c = 1; MAX(a, b > c ? b : c) // = 2
结果肯定也不是你想要的,最大值很明显是3,但是计算的结果确实2,这其中发生了什么导致计算出错,我们可以展开宏来一探究竟,下面是宏的展开
MAX(a,b > c ? b : c); //a > b > c ? b : c ? a : b > c ? b : c //(a > (b > c ? b : c) ? a : b) > c ? b : c // 这是运算的优先级 // 带入值可以看出 //( 3 > (2 > 1 ? 2 : 1 ) ? 3 : 2) > 1 ? 2 : 1 // (3 > 2 ? 3 : 2) > 1 ? 2 : 1 // 3 > 1 ? 2 : 1
想必大家都看出来了问题所在,还是由于优先级的问题,所以在此谨记,反正多写两个括号也不会累着,不管会不会出现问题, 写上小括号终究是保险一些~
可是总有写奇葩的写法会出现,而且看开起来还很有道理的样子~
c = MAX(a++,b); // **我直接展开给你看就得了** // c = a++ > b ? a++ : b // c = 3++ > 2 ? 3++ : 2 // c = 4 // a = 5
不管这样写的那个人是有多欠揍,但是毕竟看起来是没有任何问题的,所有我们要处理这样的情况,但是使用我们普通的小括号已经无法解决,我们需要使用赋值扩展({...})相信有朋友已经认出来了这种用法了,我们可以使用这样的方法来计算出一个对象,而不用浪费变量名,可以形成小范围的作用域来计算特殊的值
int a = ({ int b = 10; int c = 20; b + c; }) // a = 30; int b; // 继续使用b和c当变量名也没有问题 int c;
再回到现在这个问题上,我们该如何改装这个宏来让其适应这个坑爹的写法呢
#define MAX(A,B) ({__typeof(A) __a = (A);__typeof(B) __b = (B); __a > __b ? __a : __b; })
__typeof()就是转换为相同类型的变量值,就完美的解决了这个问题,但是还有一个不怎么会发生的意外,通过上面也可以知道,我们生成了新的变量__a, __b,如何有人使用了__a,__b,就会应为变量名重复而编译错误,如果有人这样用了,你可以拿起你的键盘砸他一脸,原因当然不是__a使你的宏错误了,而是__a到底是什么意思,变量名的重要性不言而喻,除非你和看代码的人有仇,否则请使用有意义的变量名,接下来让我们看一看官方的MAX是如何实现的
#define __NSX_PASTE__(A,B) A##B #if !defined(MAX) #define __NSMAX_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__(__b,L) : __NSX_PASTE__(__a,L); }) #define MAX(A,B) __NSMAX_IMPL__(A,B,__COUNTER__) #endif
这是Function框架中的MAX定义,我么来一步一步的解析它,首先看见的是
#define __NSX_PASTE__(A,B) A##B
// 将A和B连接到一块
它的作用是将A和B连接到一块,用来生成一个的字符串,比如A##12就成了A12
接下来我们看到了一个有三个参数的宏定义__NSMAX_IMPL__(A,B,__COUNTER__)
#if !defined(MAX) #define __NSMAX_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__(__b,L) : __NSX_PASTE__(__a,L); }) #define MAX(A,B) __NSMAX_IMPL__(A,B,__COUNTER__) #endif
我们先来解释__COUNTER__是什么,__COUNTER__是一个预编译宏,它将会在每次编译时加1,这样的话可以保证__NSX_PASTE__(__b,__CONNTER__)生成的变量名不易重复,但是这样还是有那么点危险,就是你要是起变量名叫__a20,那就真的真的没有办法了~
可变参数宏
说起可变参数,我们用的最多的一个方法NSLog(...)就是可变参数了,可变参数意味着参数的个数是不定的,而NSLog作为我们调试时一个重要的工具实在时太废物了,只能打印对应的时间和参数信息,而文件名,行数,方法名等重要的信息都没有给出,今天我们就借此来实现一个超级版NSLog宏~~~
#define NSLog(format, ...) do { fprintf(stderr, "<%s : %d> %s\n", \ [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__); \ (NSLog)((format), ##__VA_ARGS__); \ fprintf(stderr, "-------\n"); \ } while (0)
首先看这个宏的定义NSLog(format,...)发现它有...,这就是可变参数,而__VA__ARGS__就是除了format外剩下的所有参数,接下来我们发现使用了一个do{}while(0)循环,说明这个循环只执行一便就回停止,感觉废话啊,我们的目的就是只执行一遍啊,但这样写又是为了进行防御式编程,如果有人这样写的话
if (100 > 99)
NSLog(@"%@",@"Fuck");
就会出现无论如何都会执行后两个打印,出现的问题想必大家也都知道,那我们直接使用{}给扩起来不就行了,实际操作后确实是解决了这个问题,但是再扩展一下,当我们使用了if{} else if{}时又会出现新的问题
if (100 > 99) NSLog(@"%@",@"Fuck"); else { } // 展开后可得 if (100 > 99) { fprintf(stderr, "<%s : %d> %s\n", [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__); (NSLog)((format), ##__VA_ARGS__); fprintf(stderr, "-------\n");}; else { }
编译错误,大家也发现了NSLog后面会跟上;,如果我么直接使用了{}后,会在编译时在外面加上;,导致编译错误,而使用了do{} while(0)循环后就不会出现这个问题了
if (100 > 99) do { fprintf(stderr, "<%s : %d> %s\n", [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__); (NSLog)((format), ##__VA_ARGS__); fprintf(stderr, "-------\n");} while(0); else { }
到此位置问题解决的差不多了,看一下内部的结构,__FILE__是编译的文件路径,__LINE__是行数,__func__是编译的方法名,下面我们又看见了
(NSLog)((format), ##__VA_ARGS__);
##上面已经看见过了,在这里的作用差不多,也是连接的意思,__VA_ARGS__是剩下的所有参数,使用##连接起来后就时NSLog(format,__VA_ARGS__)了,这就是NSLog的方法了,但是不知道有没有人发现一个细节,如果__VA_ARGS__为空的话,那岂不是成了NSLog(format,)这样肯定会编译报错的,但是苹果的大神们早就想到了解决的方法,如果__VA_ARGS__为空的话,在这里##将会吞掉前面的,,这样一来就不会出问题了。然后我们就可以使用这个强大的NSLog()了。
接下说一下多参数函数的使用
- (void)say:(NSString *)code,... { va_list args; va_start(args, code); NSLog(@"%@",code); while (YES) { NSString *string = va_arg(args, NSString *); if (!string) { break; } NSLog(@"%@",string); } va_end(args); }
我们可以要先定义一个va_list args来定义多参数变量args,然后通过va_start(args, code)来开始取值,code是第一个值,va_arg(args, NSString *)来定义取出的值类型,取值方式有点像生成器,取完之后调用va_end(args)来关闭。这就是整个过程,平时很少使用这样的方法,如果你有什么好的实用方法请评论指教~~~
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
#define 叫做 宏定义命令,它也是C语言预处理命令的一种。所谓 宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。 我们先通过一个例子来看一下 #define 的用法: 运行结果: 120 注意第 6 行代码 , 被 代替了。 就是宏定义, 为宏名, 是宏的内容(宏所表示的字符串)。在预处理阶段,对程序中所有出现的“宏名”,预处理器都会
Objective-C是通用语言,它是在C语言编程语言之上开发的,它增加了Small Talk编程语言的功能,使Objective-C成为面向对象的语言。 它主要用于开发iOS和Mac OS X操作系统及其应用程序。 最初,Objective-C是由NeXT为其NeXTSTEP操作系统开发的,之后苹果公司使用它来开发iOS和Mac OS X,并接管了Objective-C。 1. 面向对象的编程
主要内容:typedef 与 #define 区别Objective-C编程语言提供了一个名称为的关键字,可以使用此关键字为类型指定新名称。 以下是为单字节数字定义术语的示例 - 在此类型定义之后,标识符可以用作类型的缩写(或别名),例如: 按照惯例,大写字母用于这些定义,以提醒用户类型名称实际上是符号缩写,但可以使用小写,如下所示 - 也可以使用为用户定义的数据类型指定名称。 例如,使用带结构的来定义新的数据类型,然后使用该数据类型直接定义结构
本文向大家介绍微信小程序 自定义创建详细介绍,包括了微信小程序 自定义创建详细介绍的使用技巧和注意事项,需要的朋友参考一下 微信小程序 自定义创建,最近自己捣鼓微信小程序的东西,这里对自定义创建做一个简单的资料整理,也许可以帮助大家。 微信小程序 自定义创建 自定义创建与默认创建完全相同, 只是不要勾选quick start即可 淡定(不要看到报错就紧张, 一定要淡定) 看看它说了什么,
本文向大家介绍C 语言指针变量详细介绍,包括了C 语言指针变量详细介绍的使用技巧和注意事项,需要的朋友参考一下 数据在内存中的地址也称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量。 在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。指针变量的值就是某份数据的地址,这样的一份数据可以是数组、字符串、函数,也可以是另外的一个普通变量或指针变量。 现在假设有一个 char 类
本文向大家介绍Mysql Explain 详细介绍,包括了Mysql Explain 详细介绍的使用技巧和注意事项,需要的朋友参考一下 Mysql Explain 这里做一个资料的全面整理。 一.语法 explain < table_name > 例如: explain select * from t3 where id=3952602; 二.explain输出解释 +----+---------