预处理
写在文章之前:程序中的源代码计算机是无法识别的,需要将写好的代码转成0、1二进制代码,计算机才能识别。将源代码转成二进制代码需要两步:编译和链接。编译是通过编译器将每个文件的代码都转为二进制代码,在这个过程中,如果有语法错误,会有编译失败的提示,如果成功,那么会生成对应多个目标文件。在一个文件中可能会到其他文件,因此,还需要将编译生成的目标文件和系统提供的文件组合到一起,这个过程就是链接。经过链接,最后生成可执行文件。通常人们所理解的程序运行就是编译和链接两个阶段,但实际上在编译之前预处理器要进行预处理操作,处理完成之后才进入到编译阶段。因为预处理指令是在编译之前就进行了,所以它比程序运行时进行操作的效率高。
预处理:分析程序前先处理的语句,它可以识别散布在程序中的特定语句。所有的预处理语句都适用“#”开头,这个符号必须是一行中的第一个非空字符。
预处理可以大概分为三类:文件包含、宏定义和条件编译。见下:
1.文件包含
文件包含:在当前文件中用到其它文件中的函数或方法或其它信息时,可以将其它文件的头文件包含进来,然后再当前文件中使用,文件包含一般放到文件的开头。
如果使用C语言编程,文件包含是#include<> 或者 #include ""。如果使用Objective-C语言,文件包含为#import<>或者#import ""。#include 和 #import最大的区别是** #import 在导入文件的时候进行了去重复检查,此外,""和<>两也是有区别的,""一般是用来引用自定义的文件,<>一般是用来引用系统的文件。程序在执行的时候,会根据你写的样式,优先去寻找对应类型的文件。比如<>**会先去找系统文件,如果找不到,再去找自定义文件。所以正确的选择样式,能够提高程序的执行效率。
循环引用:在使用文件包含的时候,会遇到A文件中用到B文件,B文件中用到A文件,这种互相使用包含的关系就有点类似死循环了,运行的时候就是报错。解决这个问题最好的办法就是用@class代替文件包含,@class就是表明有这个类,等在源文件中真正用到的时候才会去包含文件。
代码:
#import <Foundation/Foundation.h>
@class B;
@interface A : NSObject
@property (nonatomic, strong) B *obj;
@end
#import <Foundation/Foundation.h>
@class A;
@interface B : NSObject
@property (nonatomic, strong) A *obj;
@end
2.宏定义
在程序中,有一些常量或者简短的函数是会被多次重复调用的,对于这些常用的数据,我们可以使用宏定义。使用宏定义可以快速的完成程序中的多处配置,最大的好处就是只要修改宏定义的值,所有使用宏定义的值都会发生改变。此外,宏定义是在程序编译之前进行替换和设置,比定义成全局变量或者函数的效率要高。
宏定义是通过#define来实现的,一般写在程序文件包含的下面。宏名通常用全部的大写字母表示,下面,就通过代码来看一下宏定义的使用。
代码:
#import <Foundation/Foundation.h>
#define JR_PI 3.14
#define JR_MAX(a,b) ((a>b)?(a):(b)) //得到两个数中较大值
#define JR_SQUARE_1(n) n*n //求数字的平方
#define JR_SQUARE_2(n) (n)*(n) //求数字的平方
#define JR_HELLO @"hello world";
int main(int argc, const char * argv[]) {
@autoreleasepool {
int num1 = JR_MAX(1, 2);
NSLog(@"max = %i",num1);
//结果: max = 2
int num2 = JR_SQUARE_1(2);
NSLog(@"2的平方 = %i",num2);
//结果:2的平方 = 4
int num3 = JR_SQUARE_1(2+1);
NSLog(@"(2+1)的平方 = %i",num3);
//结果:(2+1)的平方 = 5
int num4 = JR_SQUARE_2(2+1);
NSLog(@"(2+1)的平方 = %i",num4);
//结果:(2+1)的平方 = 9
}
return 0;
}
在代码中可以看出,同样都是求一个数的平方,但是两个宏定义得到的结果却是不一样的,第一个计算2+1的平方的时候是2+1*2+1,所以结果为5,答案错误。因此在写宏定义的时候,带参数需要设置小括号,确保正确性。
条件编译
条件编译:在编译之前由预处理器来根据处理语句进行判断,如果满足条件,就编译满足条件下的代码。反之就不进入编译环节。
条件编译主要分为两种:一种是判断是否定义过某个宏,根据是否定义过这个宏,来决定是否编译某段代码。另外,还有一组语句和条件结构中的阶梯if结构非常类似,但是写法上有区别,是#if、#elif、#else、#endif组成。需要注意的是,无论哪种,都要有#endif 结束标志。此外,最重要的一点是:条件编译中的条件不能使用普通变量,一般会选择使用宏定义。
代码:
#import <Foundation/Foundation.h>
#define JR_COUNT 10
int main(int argc, const char * argv[]) {
@autoreleasepool {
#if defined(JR_COUNT)
NSLog(@"定义了 COUNT 这个宏");
#endif
#if defined(JR_MAX)
NSLog(@"没有定义了 JR_MAX 这个宏");
#endif
#if JR_COUNT==1
NSLog(@"JR_COUNT=1");
#elif JR_COUNT==2
NSLog(@"JR_COUNT=2");
#elif JR_COUNT==3
NSLog(@"JR_COUNT=3");
#else
NSLog(@"JR_COUNT=%i",JR_COUNT);
#endif
}
return 0;
}
最后补充一下预处理常用指令:
# 空指令,没有任何效果
#include 包含一个源代码文件
#define 定义宏
#undef 取消定义宏
#if 如果条件为真,则编译下面的代码
#elif 如果前面的#if不为真,则编译下面的代码
#endif 结束一个#if...#elif条件编译块
#ifdef 如果已经定义了某个宏,则编译下面的代码
#ifndef 如果没有定义某个宏,则编译下面的代码
#error 停止编译并显示错误信息
网友评论