[精通Objective-C]预处理器

[精通Objective-C]预处理器

参考书籍:《精通Objective-C》【美】 Keith Lee

目录

预处理器概述

预处理根据一系列预定义规则,使用一些字符序列替换输入的字符序列。这些操作主要分为以下三步:

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;}

宏具有强大的功能,但是非常依赖复杂宏的代码会难以维护,因此,不要过度使用宏!

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值