宏定义 - Adaicon/iOS-notes GitHub Wiki
宏的本质就是代码生成器,在预处理器的支持下实现代码的动态生成。
1、定义
宏定义是C语言提供的三种预处理功能的其中一种,这三种预处理包括:宏定义、文件包含、条件编译。
- 宏定义:#define 指令定义一个宏,#undef指令删除一个宏定义。
- 文件包含:#include指令指定一个文件的内容被包含到程序中。
- 条件编译:#if,#ifdef,#ifndef,#elif,#else和#endif指令可以根据编译器可以测试的条件来将一段文本包含到程序中或排除在程序之外。
一个C程序在编译的时候,从源文件开始到最后生成二进制可执行文件,一共经历4个阶段:
- 预处理阶段:讲头文件编译进来,以及宏替换,只是单词变换,所以宏定义不会进行类型检查
- 编译阶段:做词法分析、语法分析、语义分析,将代码翻译成汇编语言
- 汇编阶段:将汇编语言处理成机器语言
- 链接阶段:合并各个目标文件为一个可执行文件
分类:可以将宏按类型分为对象宏和函数宏,也可以按传入参数分为带参数的宏和不带参数的宏。
2、用法:
#define 宏名 字符串
- 宏定义写在函数外,作用域为宏定义命令到源程序结束
- 宏定义可以嵌套
- 字符串中不能包含宏,即使存在宏,也会将宏名单做字符串处理;
1、字符化
**#define STRING @#s // 's**'
2、#符号的用法 在使用#define定义宏时,可使用操作符#在字符串中输出实参:
#define AREA(x,y) printf(“长为“#x”,宽为“#y”的长方形的面积:%d\n”,(x)*(y));
用#字符串化:
#define NSSTRING #str // "str"
3、连接
如果宏体所在标示符中有##,该操作符将宏中的两个部分连接成一个内容。
#define COMMAND(PREFIX, NAME) PREFIX##NAME
4、换行 遇到需要换行的可以用\号连接;
#define WEEKEND @[ \
@"Saturday", \
@"Sunday", \
]
5、嵌套
#define SCREEN_WIDTH CGRectGetWidth([[UIScreen mainScreen] bounds]) //!< 屏幕宽度
#define WIDTH_320 (SCREEN_WIDTH==320)
if (WIDTH_320) {
//屏宽为320时,to do something
}else
{
//TODO
}
6、宏方法
因为看起来像是一种函数调用,但不同于函数。比如一个比较大小的操作: (1)如果用宏来实现
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
int main()
{
printf("max: %d \n", MAX(1, 2));
}
- 不需要检查参数,更灵活地传参
(2)如果用函数来实现
int max(int a, int b)
{
if (a > b)
return a;
return b;
}
int main()
{
printf("max: %d \n", max(1, 2));
}
- 形参的类型需要确定,调用时对参数进行检查;
- 调用函数时需要额外的开销:操作函数栈中的形参、返回值等;
宏定义这种不需要管类型的好处正是动态生成代码的关键,与C++ 中的模板参数有点类似
#define VEC(T) \
struct vector_##T { \
T *data; \
size_t size; \
};
int main()
{
VEC(int) vec_1 = { .data = NULL, .size = 0 };
VEC(float) vec_2 = { .data = NULL, .size = 0 };
}
7、可变参数的处理
宏定义的参数可以是不确定的,可以定义的时候用三个点... 来接收可变参数,使用的时候用VA_ARGS表示可变参数:
#define debug1(...) printf(__VA_ARGS__)
debug1("this is debug1: %d \n", 1);
如果在三个点(...)的前面加上了一个参数名,那么在使用时就一定要使用这个参数名,而不能使用 VA_ARGS 来表示可变参数,如下:
#define debug2(args...) printf(args)
debug1("this is debug2: %d \n", 2);
#define debug3(format, ...) printf(format, __VA_ARGS__)
debug3("this is debug4: %d \n", 4); //正确
debug3("hello \n"); //报错,因为扩展开是 printf("hello \n",); 多了个逗号
通过 ## 符号把这个多余的逗号给自动删掉:
#define debug3(format, ...) printf(format, ##__VA_ARGS__)
#define debug4(format, args...) printf(format, ##args)
有了## 表示可以是0个参数
8、特殊功能宏
8.1、NS_ASSUME_NONNULL_BEGIN && NS_ASSUME_NONNULL_END Swift 中我们必须指定一个对象是否为空,(即?和!)OC中也增加了 __nullable 和 ___nonnull 用于指定对象是否为空。 每个属性、方法都指定 ___nonnull 和 __nullable 是一件非常繁琐的事。为了减轻开发工作量,苹果提供了两个宏:NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END 。这两个宏之间的代码里的所有简单指针对象都被默认为 ___nonnull,我们只需要去指定 __nullable 的指针。
NS_ASSUME_NONNULL_BEGIN
@interface TestNullabilityClass ()
@property (nonatomic, copy) NSArray * items;
- (id)itemWithName:(nullable NSString *)name;
@end
NS_ASSUME_NONNULL_END
8.2 LINE
在Foundation中定义了一个宏叫 LINE 这个宏是告诉你代码在第几行 你可以在任何一行代码打印__LINE__(NSLog(@”%d”,LINE))看看 是不是NSLog所在代码的行数? 这个大家可以想,如果做后台异常判断的时候把这个代码行数打出来,那不是很容易定位到问题所在地方?
3、c语言的宏
3.1预定义宏
除了基本的宏操作还有预定义宏,预定义宏是为了方便处理一些有用的信息,里面定义了一些预处理标识符,也就是预定义宏。预定义宏的名称都是以“__”(两条下划线)开头和结尾的,如果宏名是由两个单词组成,那么中间以“_”(一条下划线)进行连接。并且,宏名称一般都由大写字符组成。 下面是常见的预定义宏:
FUNTION 获取当前函数名
DATE 丐前源文件的编泽口期,用 “Mmm dd yyy”形式的字符串常量表示
FILE 当前源文件的名称,用字符串常量表示
LINE 当前源义件中的行号,用十进制整数常量表示,它可以随#line指令改变
TIME 当前源文件的最新编译吋间,用“hh:mm:ss”形式的宁符串常量表示
STDC 如果今前编泽器符合ISO标准,那么该宏的值为1,否则未定义
COUNTER 无重复的计数器,从程序启动开始每次调用都会++,常用语宏中定义无重复的参数名称
func 所在scope的函数名称,常见于log中
3.2系统相关宏
__has_include 用来检查是否引入了某个文件
NS_ASSUME_NONNULL_BEGIN & NS_ASSUME_NONNULL_END 在这两个宏之间的代码,所有简单指针对象都被假定为nonnull
__cplusplus 识别是c代码还是c++代码
__has_feature(objc_arc) 判断是否是ARC,否则为MRC
@available(iOS 11, *) 当前iOS11是否满足需求
TARGET_IPHONE_SIMULATOR 满足条件时,执行模拟器代码;否则执行非模拟器代码
__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0 设备系统大于8.0 以上的代码
NS_REQUIRES_SUPER 申明子类如果重写该方法,必须调用该父类方法
FOUNDATION_EXPORT 用于定义常量,在检测值是否相等时直接比较指针,效率比较快
NS_AVAILABLE_IOS(8_0) 这个方法可以在iOS3.0及以后的版本中使用,如果在比5.0更老的版本中调用这个方法,就会引起崩溃
NS_DEPRECATED_IOS(2_0, 6_0) 这个方法在iOS2.0引入,6.0被删除
NS_AVAILABLE(10_8, 6_0) 这个宏告诉我们这方法分别随Mac OS 10.8和iOS 6.0被引入
NS_DEPRECATED(10_0, 10_6, 2_0, 4_0) 这个方法随Mac OS 10.0和iOS 2.0被引入,在Mac OS 10.6和iOS 4.0后被废弃
NS_CLASS_AVAILABLE(10_11, 9_0) 这个类分别随Mac OS 10.11和iOS9.0被引入
NS_ENUM_AVAILABLE(10_11, 9_0) 这个枚举分别随Mac OS 10.11和iOS9.0被引入
__IPHONE_OS_VERSION_MAX_ALLOWED 允许最大的iOS版本
__IPHONE_OS_VERSION_MIN_ALLOWED 最低的iOS版本
4、条件编译
条件编译的本质是选择性的编译,其意义在于:
⑴ 增加代码的兼容性,一套代码兼容多个硬件平台或者软件平台;
⑵ 区分产品的调试版本和正式发布版本;
⑶ 不同的产品线共用代码,使用条件编译来产生适用不同产品的目标文件;
⑷ 同时也为程序员提供了一种屏蔽代码块的方式 #if 0….#endif。
使用:
1、#if
若常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译
#if 常量表达式
程序段
#else
程序段
#endif
#if 常量表达式1
程序段1
#elif 常量表达式 2
程序段2
#else
程序段m
#endif
2、使用#ifdef和#ifndef
若只需要判断是否定义了该符号常量。这时,可不使用#if命令,而使用另外一个预编译命令———#ifdef.其意义是,如果#ifdef后面的标识符已被定义过,则对“程序段1”进行编译;如果没有定义标识符,则编译“程序段2”。一般不使用#else及后面的“程序2”。
#ifdef 标识符
程序段 1
#else
程序段 2
#endif
而#ifndef的意义与#ifdef相反,其格式如下:
#ifndef 标识符
程序段 1
#else
程序段 2
#endif
3、使用#defined和#undef
在#ifdef和#ifndef命令后面的标识符是使用#define进行定义的。在程序中,还可以使用#undef取消对标识符的定义,其形式为:
#if defined 标识符
程序段 1
#endif
#undef 标识符
#define MAX 100
……
#undef MAX
在以上代码中,首先使用#define定义标识符MAX,经过一段程序代码后,又可以使用#undef取消已定义的标识符。使用#undef命令后,再使用#ifdef max,将不会编译后的源代码,因为此时标识符MAX已经被取消定义了。
5、Objective-C 中的宏定义
5.1 宏定义Class
#define CreateClass(className)\
@interface className : NSObject\
-(void)sayHello;\
@end\
@implementation className\
-(void)sayHello \
{\
NSLog(@”Hello,%d”,__LINE__);\
}\
@end\
CreateClass(ABC)
5.2 Block变量弱引用宏
在使用Block的时候 为了防止循环引用 需要变量弱引用 代码会比较麻烦
#define WEAK_REF(obj) \
__weak typeof(obj) weak_##obj = obj; \
使用时:
WEAK_REF(abc);
Block中用:weak_abc即可
6、开发中常用
参考: