预处理程序提供了一些工具,使用这些工具更易于开发、阅读、修改程序,也易于将程序移植到不同的系统中。又称为宏。
#define
#define语句的基本用途之一就是给富豪名称指定程序常量。比如:
#define TRUE 1 //没有分号结尾
此处定义了名称TRUE,并使它等于值1。之后,名称TRUE可用于程序中任何需要常量1的地方,只要出现这个名称,预处理程序自动将这个名称替换为预定义的值1。
预定义名称不是变量,因此,不能给它赋值,除非替换指定值的结果实际上是个变量。#define语句中预定义名称右边的所有字符都会被预处理程序自动替换到程序中,这类似于文本搜索和替换。
#define通常放在程序的开始,#import或#include之后,这并不是必须的,它可以出现在程序的任何地方。预定义名称没有局部定义之类得说法,一旦定义一个名称,就可以在程序的任何地方使用它。
预定义程序假设定义包含在程序的一行中,如果需要第二行,那么上一行的最后一个字符必须是反斜线符号()。
预定义不仅适用于单个的值,也可以是更高级的表达式,比如带有参数的名称即函数宏,在函数宏时,函数名称和参数列表的左括号之间不允许有空格,如下:
(下边的例子摘自网络博文,原文非常优秀)
#define MIN(A,B) A < B ? A : B int a = MIN(1,2); // => int a = 1 < 2 ? 1 : 2; printf("%d",a); // => 1
不过这样很容易出问题,比如:
int a = 2 * MIN(3, 4); printf("%d",a); // => 4
这是因为预定义只是简单的文本替换,所以表达式变成了 int a=2*3<4?3:4;这里忽略了运算的优先级顺序,因此出错了。改进为
#define MIN(A,B) (A < B ? A : B) int a = MIN(3, 4 < 5 ? 4 : 5); printf("%d",a); // => 4
但是仍然存在风险,比如:
int a = MIN(3, MIN(4, 5)) //int a = MIN(3, 4 < 5 ? 4 : 5); // => int a = (3 < 4 < 5 ? 4 : 5 ? 3 : 4 < 5 ? 4 : 5); //希望你还记得运算符优先级 // => int a = ((3 < (4 < 5 ? 4 : 5) ? 3 : 4) < 5 ? 4 : 5); //给这个式子加上了括号 // => int a = ((3 < 4 ? 3 : 4) < 5 ? 4 : 5) // => int a = (3 < 5 ? 4 : 5) // => int a = 4
改进为#define MIN(A,B) ((A) < (B) ? (A) : (B)),这里依然是存在风险的,比如:
float a = 1.0f; float b = MIN(a++, 1.5f); printf("a=%f, b=%f",a,b); // => a=3.000000, b=2.000000
这是因为展开式为:float b = ((a++) < (1.5f) ? (a++) : (1.5f))。解决这个问题并不是一件很简单的事情,使用的方式也很巧妙。我们需要用到一个GNU C的赋值扩展,即使用({...})
的形式。这种形式的语句可以类似很多脚本语言,在顺次执行之后,会将最后一次的表达式的赋值作为返回。举个简单的例子,下面的代码执行完毕后a的值为3,而且b和c只存在于大括号限定的代码域中,如:
int a = ({ int b = 1; int c = 2; b + c; }); // => a is 3
因此,宏最终改进为:
#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
这里定义了三个语句,分别以输入的类型申明了__a
和__b
,并使用输入为其赋值,接下来做一个简单的条件比较,得到__a
和__b
中的较小值,并使用赋值扩展将结果作为返回。这样的实现保证了不改变原来的逻辑,先进行一次赋值,也避免了括号优先级的问题,可以说是一个比较好的解决方案了。可见,编写复杂的宏需要考虑很多细节。
#import
#import是引入文件用的,双引号表示引入本地文件,尖括号引入系统文件。#import是#include的升级版,它能够确保该文件只被引入一次,避免重复引用。
条件编译
条件编译通常用于创建可以在不同计算机系统上编译运行的程序,它也经常用来开关程序中的各种语句,例如,用来输出变量值的调试语句。
#ifdef #endif #else #elif #ifndef #undef
比如:
#ifdef IPAD #define imageF @"ihd.png" #else #define imageF @"i.png" #endif
只要之前定义过IPAD了,就可以了,并不需要它一定有值,比如 #define IPAD就可以了。
也可以通过命令行在程序编译时为预编译器定义名称,比如:
gcc -framework Foundation -D IPAD program.m 就为预处理程序定义了 IPAD,它使program.m中所有#ifdef IPAD都判断为TRUE。这种技术使得不必编辑源程序就可以定义名称了。
在Xcode的Build Settings中,可以在Preprocessor Macros选项下添加新的预定义名称并制定它们的值,比如常用的有DEBUG来标识是否是调试版本还是正式版本。