第 7章 编译预处理命令
2004年 3月
河北建筑工程学院
编译预处理是 C++编译系统的一个重要组成部分, 它负
责分析处理几种特殊的指令, 这些指令被称为预处理命令 。
在 C++源程序文件中, 加入编译预处理命令, 可以改进程
序设计环境, 提高编程效率 。 但它们不是 C++语言的组成
部分, 不能直接对它们进行编译, 编译系统在对源程序进
行正式的编译之前, 必须先对这些命令进行预处理, 经过
预处理后的程序不再包括预处理命令, 然后由编译系统对
预处理后的源程序进行通常的编译处理, 得到可供执行的
目标代码 。
C++提供的预处理命令主要有以下三种,
宏定义
文件包含
条件编译
这些命令均以 #开头, 每行一条命令, 因为它们不是 C++
的语句, 所以 命令后无分号 。
7.1 宏定义
宏定义分为两类,不带参数的宏定义和带参数的宏定义。
7.1.1 不带参数的宏定义
#define称为宏定义指令, 它的一般格式为,
#define 标识符 字符串
作用:用一个指定的标识符来代表一个字符串, 字符串可以
是常量, 关键字, 语句, 表达式, 还可以是空字符 。
其中,标识符又称为宏名,字符串称为宏体。
编译预处理时,如果程序中出现宏定义命令,预处理程序就
把该命令以后的程序中所有同宏名一致的标识符全部替换为
所定义的宏体,这个过程称为“宏展开”。
例如,
#define PI 3.1415926
PI来代替字符串
,3.1415926”
※ 注意,在预编译处理时, 将程序中在该命令以后
出现的所有的 PI都用, 3.1415926”代替 。
【 例 7.1】 输入圆半径,求圆周长、圆面积、球体积。
#include <iostream.h>
#define PI 3.1415926
void main()
{
double l,s,r,v;
cout<<"inputradius:"<<endl;
cin>>r;
l=2*PI*r;
s=PI*r*r;
v=4.0/3.0*PI*r*r*r;
程序运行结果,
input radius,6
l=37.6991
s=113.097
v=904.779
说明,
(1) 宏名一般用大写字母表示, 以便与变量名相区别 。
(2) 使用宏名代替一个字符串,可以减少程序中重复书写
某些字符串的工作量,当需要改变某一个常量时,可以只
改变 #define命令行,做到一改全改,不容易出错
(3) 宏定义是用宏名代替一个字符串,在宏展开时只是作
简单的字符串替换,并不对语法是否正确进行检查。
例如在输入下列宏命令时,
#define PI 3.1415926
将小数点,,”错写成了,,”,在编译预处理时也不会报
错, 只在编译时才会发现错误并报告错误 。
(4) 宏定义不是 C++语句, 一定不要在行末加分号, 如果
加了分号, 会将分号当成字符串的一部分进行替换 。
(5) 通常把 #define命令放在一个文件的开头, 使其定义在
本文件内全部有效, 即作用范围从其定义位置起到文件结
束 。
(6) 可以使用 #undef命令来取消宏定义的作用域。
#undef 命令的一般格式为,
#undef 宏名
它的作用是通知编译预处理系统取消前面由 #define命令所
定义的宏名, 使其定义的宏名不再起作用 。
例如,
#define PI 3.1415926
void main()
{ …… }
#undef PI
f1()
{ …… }
由于 #undef的作用, 使 PI的作用范围在 #undef行处终止, 因
此, 在 f1函数中, PI不再代表 3.1415926。 这样可以灵活控制
宏定义的作用范围 。
(7) 宏定义允许嵌套定义, 即在进行宏定义时, 可以使用已定
义过的宏名 。
(8) 对程序中用双引号括起来的字符串内的字符,即使与宏
名相同,也不进行替换
【 例 7.2】 求半径为 3的圆周长, 圆面积和球体积 。
#include <iostream.h>
#define R 3
#define PI 3.1415926
#define L 2*PI*R
#define V 4.0/3.0*PI*R*R*T
void main()
{
cout<<"L="<<L<<"S="<<S
<<" V="<<V<<endl; }
程序运行结果,
L= 18.8496
S=28.2743
V=113.097
(9) 宏定义命令是专门用于预编译处理的, 它与定义
变量的含义不同, 只作字符替换, 不分配内存空间 。
7.1.2 带参数的宏
? 这种宏不只是进行宏体的替换,还要进行参
数的替换,
一般形式,
#define 宏名(形式参数表) 字符串
字符串中包含了在形式
参数表中所指定的参数
例如,
#define S(a,b) a*b
area = S(3,2) 得 3*2
【 例 7.3】 计算圆面积
#include <iostream.h>
#define PI 3.1415926
#define S(r) PI*r*r
void main()
{
float a,area;
a=3.6;
area=S(a);
//经过宏展开后为 area=3.1415926*a*a
cout<<"r="<<a<<" area="<<area<<endl;
}
程序的运行结果,
r=3.6 area=40.715
说明,
(1) 带参数的宏展开时, 不仅要进行宏体对宏名的替换,
还要将宏名后面括号中的实参代替 #define 命令行中形参 。
(2) 在宏展开时, 如果实参是表达式, 则在定义宏时, 应
将字符串中的形式参数外面加上括号 。
(2) 例如,
#define S(r) PI*r*r #define S(r) PI*(r)*(r)
area=S(a+b) area=S(a+b)
宏展开后形式 宏展开后形式
PI*a+b*a+b PI*(a+b)*(a+b)
(3) 在宏定义时,在宏名与带参数的括号之间不应加
空格,否则将空格以后的字符都作为代换字符串的一
部分。
例如, 有下列宏定义语句,
#define S (r)PI*r*r ( 表示空格 )
S代表宏名 由于空格的介入使得 S代表字符串,(r)PI*r*r”而出现错误
带参数的宏与函数 的区别,
(1) 函数调用时,要先求出实参表达式的值,然后代
入形参;而带参数的宏只是作简单的字符串替换。
(2) 函数调用是在程序运行时处理的,分配临时的内
存单元;而带参数的宏则是在预编译时进行的,在展开
时并不分配内存单元,不进行值的传递处理,也没有
“返回值”的概念。
(3) 函数中的形参和实参的类型要求一致;而宏名无类型,
它的参数也无类型,宏展开时只是把指定的字符串代替宏名
即可。
(4) 调用函数时只可得到一个返回值,而用宏可以设法得到
多个值。
(5) 使用宏次数多时,宏展开后源程序会变长;而函数调用
不会使程序变长。
(6) 宏替换不占运行时间,只占编译时间;而函数调用则占
用运行时间。
7.2 文件包含
? 文件包含是指一个源文件可以将另一个源文
件的内容全部包含进来,即将另外的文件包含到
本文件之中。
? C++用 #include命令来实现文件包含的功能。
一般形式为,
Ⅰ #include <文件名 >
Ⅱ #include,文件名”
作用,
预编译处理时, 把, 文件名, 指定的文件内容复制到本文
之中, 再对合并后的文件进行编译 。 图 7.1表示了文件包
的含义 。
file1.cpp file2.cpp file1.cpp
#include<file2.cpp>
B
B
A
包含
A
(a) (b) (c)
图 7.1 文件包含的含义
图中 (a)为文件 file1.cpp,该文件包含命令 #include <file2.cpp>,
文件还有其它部分以 A表示 。 图中 (b)为另一文件 file2.cpp,文
件内容以 B表示, 它被 file1.cpp包含 。
文件还有其它部分以 A表示 。 图中 (b)为另一文件 file2.cpp,
文件内容以 B表示, 它被 file1.cpp包含 。 在编译预处理时,
要对 #include命令进行文件包含处理, 将 file2.cpp的内容复
制到文件 file1.cpp的 #include <file2.cpp>命令处, 得到图中
(c)所示的结果 。 这样, 在其后面所进行的编译中, 将, 包
含, 以后的 file1.cpp( 即图中 (c)所示 ) 作为一个源文件单位
进行编译 。
【 例 7.4】 文件包含的例子 。
#include <iostream.h>
#include <file2.cpp>
void main()
{ float x1,x2,max;
cin>>x1>>x2;
max=max2(x1,x2);
cout<<"max=("<<x1<<",
"<<x2<<")"<<"="<<max<<
endl;
}
文件 file2.cpp的内容如下,
float max2(float x,float y)
{
if (x>y) return (x);
else return(y);
}
注意,
在预编译处理后, 已经将这两个文件连接成一个文件,
编译时作为一个源文件编译, 得到一个目标文件 。
从理论上说, #include命令可以包含任何类型的文件 。 一
般情况下, #include命令都放在源文件的头部, 所以, 将
#include命令中包含的文件称为头文件, 常用,h或,hpp作为
头文件的扩展名 。

说明,
(1) 一个 #include命令只能指定一个被包含文件, 如果要
包含 n个文件, 必须用 n个 #include命令 。
(2) 被包含文件与其所在文件, 在预编译处理后, 已成为
一个文件, 因此, 如果被包含文件定义有全局变量, 在
其它文件中不必用 extern关键字声明 。
(3) 在 #include命令中, 文件名可用双引号或尖括号括起
来 。 用尖括号时, 表示系统到 C++库函数头文件所在的
目录中寻找要包含的文件, 这称为标准方式 。 用双引号
时, 系统先到用户当前目录中寻找要包含的文件, 若找
不到, 再按标准方式查找 。 一般说, 如果用 #include命令
来包含库函数的相关文件时, 用尖括号;如果包含的是
用户自己编写的文件时 ( 这种文件一般放在当前目录
中 ), 用双引号 。
(4) 使用包含文件命令时, 一定要注意把被包含文件作
为一个独立文件来存放 。
7.3 条件编译
? 对部分内容指定编译的条件就是“条件编
译”
条件编译命令有以下几种形式,
形式一,
#ifdef 指令
#ifdef 标识符
程序段 1
#else
程序段 2
#endif
可以没有
标识符在 #define中
定义过则执行程序
段 1否则执行程序
段 2
形式二,#ifndef 指令
#ifndef 标识符
程序段 1
#else
程序段 2
#endif
可以没有
标识符在 #define
中没有定义过则
执行程序段 1否则
执行程序段 2
形式三,
#if 指令
#if 表达式
程序段 1
#else
程序段 2
#endif
当表达式的值为真时执
行程序段 1为假时执行
程序段 2
【 例 7.5】 输入一行字母字符, 根据需要设置条件编译,
使之能将字母全改为大写字母输出, 或全改为小写字母输
出 。
#include <iostream.h>
#define LETTER 1
void main()
{
char str[20]="C Language",c;
int i;
i=0;
while ((c=str[i])!= '\0')
{
i++;
#if LETTER
if (c>='a'&&c<='z')
c-=32;
#else
if (c>='A'&&c<='Z')
c+=32;
#endif
cout<<c;
}
cout<<endl; }
程序的运行结果为,
C LANGUAGE
由于在程序开头, 先定义了 LETTER为 1,在编译预
处理时, 条件为真, 则对第一个 if 语句进行编译, 运行
时使小写字母变成了大写字母 。 如果将 LETTER定义为 0,
则在编译预处理时, 对第二个 if语句进行编译, 运行时会
使大写字母变成小写字母, 输出结果如下,
c language。
在使用 #ifdef和 #ifndef宏命令时, 应特别注意, 其控制
条件是真是假与标识符是非零还是零无关, 而只是看标
识符是否被定义过 。
第七章习题
2,定义一个带参数的宏,使两个参数的值互换,并写出程序,
输入两个数作为使用宏时的实参,输出交换后的两个值