C中的预处理均以#
开头,末尾不加;
。它们可出现在程序中的任何位置,其作用域是自出现点到所有源程序的末尾。
编译预处理是在编译前对源程序进行的一些加工,由预处理程序来处理
宏定义
不带参数
定义:#define 宏名 字符串
如:#define PI 3.141592653589793
编译预处理程序遇到宏时,就用其代表的字符串(无双引号)替换,即所谓的宏替换
注意:
- 宏定义是用宏名代替字符串,宏扩展时仅作简单替换,不做语法检查。语法检查在编译时进行
- 宏定义不是c语句,后面不能有
;
,否则,会连同;
一起替换。如:#define PI 3.1416;
,则return (PI * r * r);
会被替换成return (3.1419; * r * r);
- 宏定义可以引用已定义好的宏名,如:
c
#define PI 3.14
#define R 2.0
#define AREA PI * R * R
带参数
定义:#define 宏名(参数列表) 字符串
如:#define max (x,y) ((x) > (y) ? (x) : (y))
注意:
- 宏名与(参数列表)之间不能有空格,若有,则会将(参数列表)视为字符串的一部分被替换,即效果等价于一个不带参数的宏替换。如:
#define max (x,y) ((x) > (y) ? (x) : (y))
printf(max(2, 3));
=> printf((x,y) ((x) > (y) ? (x) : (y))(2, 3));
- 整个宏扩展及各参数均要用
()
括起来。否则如下:
#define square(x) x*x
a = square(n+1);
=> a = n+1*n+1;
- 宏扩展最外面的括号也必需,否则:
#define square(x) (x)*(x)
a = 27 / square(3);
=> a = 27 / (3)*(3);
- 宏调用时是以实参替代形参,而非值传递
宏调用与一般函数调用的区别:
- 时空效率不同:宏替换时要用宏体去替换宏名,这往往会使程序体积膨胀,加大系统存储开销。但它不像函数调用要进行参数传递、保存现场、返回等操作,所有时间效率比函数高。故,通常对简洁的表达式,以及在调用频率高、要求快速响应的场合,才用宏调用比采用函数调用合适
- 宏虽可带有参数,但是宏替换只是进行字符串替换,不作任何其它操作,如参数值的计算、传递等
解除宏定义
定义:#undef 宏定义名
如:
int main() {
#define PI 3.14
a = PI * 2 * 2; // 正确
#undef PI
b = PI * 2 * 2; // 错误,PI宏定义已被解除,不存在
return 0;
}
文件包含
文件包含,即一个源文件可以将另一个源文件的全部内容包含到自己的文件中。
主要有两种包含方式
#include "文件标识"
:文件标识中包含文件路径。按照这种格式定义时,预处理程序首先在原来的源文件目录中检索该指定的文件;若未找到,则按照系统指定的标准方式检索其它文件目录,直至找到为止。#include <文件标识>
:按此方式指定文件时,预处理程序仅会按照系统指定的标准方式检索文件目录。
故,引用的是系统提供的头文件时,(1)和(2)均可,但推荐(2),因为可减少检索时间;但引用的是自定义文件时,则只能使用(1)
条件定义与条件编译
条件编译使得同一源程序在不同的编译条件下得到不同的目标代码。常是为了增强程序的可移植性或者便于程序的调试
#ifdef 宏名
程序段1 // 若宏名已被 #define 定义,即已经存在,则编译程序会选择编译程序段1,否则编译程序段2
#else
程序段2
#endif
#ifndef 宏名
程序段1 // 若宏名未被 #define 定义,即不存在,则编译程序会选择编译程序段1,否则编译程序段2
#else
程序段2
#endif
#if 表达式1
程序段1
#elif 表达式2
程序段2
#else
程序段3
#endif
其中第三类条件编译的例子如下:
#define A 1
#define B 2
#define C A
#if A == B
程序段1
#elif A == C
程序段2
#else
程序段3
#endif
需注意,由于条件编译是发生在程序执行之前,故表达式中引用的任何变量均无效,只能引用宏定义的符号常量