[精通Objective-C]预处理器
参考书籍:《精通Objective-C》【美】 Keith Lee
目录
- 精通Objective-C预处理器
- 目录
- 预处理器概述
- 预处理器语言
- 预处理器指令
- 宏
预处理器概述
预处理根据一系列预定义规则,使用一些字符序列替换输入的字符序列。这些操作主要分为以下三步:
Created with Rapha?l 2.1.0输入源文件执行文本翻译将输入的源文件拆分成多个记号将输入代码转换为预处理器语言
1.文本翻译:预处理会将输入的源文件拆分成代码行、使用单个字符替换三字母组合、将被断开的连续行合并为较长的代码行和使用单个空格替换注释。三字母组合是指C语言中用来代表单个字符的三字符序列。
2.记号转换:预处理器将上一步骤处理过的代码转换为记号序列。
3.基于预处理器语言的转换:如果记号序列中含有预处理语言元素,就根据这些记号进行转换。
前两个操作是自动执行的,而最后一个操作是由添加到源文件中的预处理器语言函数执行的。
预处理器语言
预处理器语言是一门完全独立的编程语言。预处理语言对源文件进行的转换主要包括源文件的内容,条件编译和宏展开。预处理器语言元素会在程序编译前处理源文件,但预处理器不能识别Objective-C代码。
预处理器指令
预处理器指令格式:
#指令名 指令参数
预处理器指令以#开头,后面紧跟指令名,之后是相应的参数:
#import "Atom.h"
预处理器指令会将换行符号用作结束符号。要使预处理器指令扩展为多行,可使用反斜杠\连接两行代码:
#define DegreesToRadians(x) \
((x)*3,14159/180)
预处理器指令主要包括了以下4类:头文件包含,条件编译,诊断,#pragma指令
头文件包含类指令有#include和#import
#include <Foundation/Foundation.h>
#include "Atom.h"
#import <Foundation/Foundation.h>
#import "Atom.h"
双引号和尖括号的区别在于,使用双引号时,编译器会先从存储源文件的目录中搜索被包含的头文件。如果没有找到,编译器会在默认目录中搜索头文件,默认目录是预先配置的用于搜索系统标准头文件的目录;而使用尖括号时,编译器会直接在默认目录中搜索被包含的头文件。按惯例,应使用尖括号封装标准头文件,而其他文件用双引号封装。
而#import与#include的区别在于,#import可确保头文件仅在源文件中被包含一次,因而能够防止递归包含。例如之前章节([精通Objective-C]对象和消息传递)中,源文件main.m包含了头文件Hydrogen.h和Atom+Nuclear.h,这两个文件又都包含了头文件Atom.h,通过#import指令包含头文件Hydrogen.h和Atom+Nuclear.h就可以让main.m只包含头文件Atom.h一次,如果使用#include的话,就需要在头文件Atom.h中添加包含警卫:
#ifndef ATOM_H
#define ATOM_H
@interface Atom : NSObject
......
@end
#endif
条件编译指令有#if、#elif、#else、#endif、#ifdef和#ifndef可以根据条件是否成立,确定包含或不包含部分或者全部源文本。
条件编译指令#if、#elif和#else的用法与Objective-C中的if、else if和else类似,只是结束整个条件语句时要加上#endif。同样地#if和#endif也可以嵌套使用。
//预处理器会展开INPUT_ARGS标识符,如果该标识符是一条宏命令,它就会被相应的值替换,如果不是或者这个宏没有值,则被替换为0
#if INPUT_ARGS <= 0
#warning "No input arguments defined"
#elif INPUT_ARGS > 100
#error "Input arguments are too many"
#else
#define Sum INPUT_ARGS
#endif
之前的包含警卫就是使用的#ifdef和#ifndef
#ifndef ATOM_H
#define ATOM_H
@interface Atom : NSObject
......
@end
#endif
等价于
#if !defined ATOM_H
#define ATOM_H
@interface Atom : NSObject
......
@end
#endif
诊断类预处理器指令有#warning、#error和#line。
以下是#warning和#error的用法:
#warning "No input arguments defined"
#error "Input arguments are too many"
而#line的语法为
#line 行号 "文件名"
该行号将被赋予下一行代码,而后续的代码行都会拥有每行加1的行号,当编译出错时,编译器会显示含有出错文件名称和相应行号的错误信心。由于大多数编译工具都能够显示源文件的行号,以及错误和警告信息,因此很少需要用到#line。
添加#pragma指令可以在弹出窗口设置标签
//在弹出窗口中设置创建分割线
#pragma mark -
//在弹出窗口中设置标签名称
#pragma mark Main
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
#pragma mark -
效果如下所示,可以在标注位置通过选择标签跳转到指定位置,这在大型工程和类中尤为有用:
宏
宏是具有名称的代码段。当在源代码中使用某个名称时,其代表的代码段就会替换它。只用预处理器宏指令可以定义常量值,还可以配合输入参数值提供类似函数的功能。使用#define定义宏,#undef移除宏。
宏只能执行简单的替换,所以在使用宏时要一定要小心。以下面的一个计算平方的宏为例
#define SQUARE(x) x * x
int result = SQUARE(4 + 2);
得到的结果不是36,而是是14,因为int result = SQUARE(4 + 2);
等价于int result = 4 + 2 * 4 + 2;
。
如果要想得到我们想要的结果应该写成:
#define SQUARE(x) ((x) * (x))
实际上,函数型宏和对象型宏的定义中都应该使用括号,另外,应使用花括号封装用于执行而非返回值的多行函数型宏:
#define SWAP(a,b) {a^=b; b^=a; a^=b;}
宏具有强大的功能,但是非常依赖复杂宏的代码会难以维护,因此,不要过度使用宏!