C++程序编译的处理过程一般分为预处理、编译( 和连接,1所示。预 及汇编) 如图5. 处理(preproces)是在程序编译之前,由预处理器(preprocesor)对源程序中各种预处理 命令进行先行处理,处理完毕自动进入对源程序的编译。编译时先分析、后综合,分析是 指编译器对源程序进行词法分析和语法分析;综合是指代码优化、存储分配和代码生成。 为了完成这些分析综合任务,编译器采用对源程序进行多次扫描的办法,每次扫描集中完 成一项或几项任务,也有一项任务分散到几次扫描中完成的。大多数的编译器直接产生 机器语言的目标代码,但有的编译器则先产生汇编语言的符号代码,然后调用汇编器 (asembler)进行翻译加工处理,产生目标代码。连接器将在不同文件中的目标代码和库 函数代码经过重定位处理连接成可执行文件。 图5.连接处理过程 1 编译、 预处理命令不是C++本身的组成部分,更不是C++语句,它是C++标准规定的可以 出现在C++源程序文件中的命令。这些命令必须以“#”开头,结尾不加分号,可以放置 在源程序中的任何位置,其有效范围是从出现位置开始到源程序文件末尾。 预处理命令的操作对象是编译器和连接器,用来设置程序编译和连接时的各种参数, 当编译工作完成后,预处理命令的作用即告完成,因而它不会出现在目标代码和可执行文 件中。预处理命令是C++的一个重要特点,合理地使用预处理功能,可以优化程序设计 环境,提高程序的通用性、可读性和可移植性。 C++标准提供了多种预处理命令,如宏定义、文件包含和条件编译等,本章介绍常用 的预处理命令。 5.宏定义 1 在C++源程序中允许用一个标识符来代表一个字符文本,称为宏,标识符为宏名。 宏是由宏定义命令事先定义的。预处理时,对程序中所有后续的宏名实例(称为宏引用), 预处理器都用字符文本去替换,称为宏替换或宏展开。 宏定义通常用于定义程序中的符号常量、类型别名、运算式代换和语句代换等,其命 令为#define,分为不带参数的宏定义和带参数的宏定义。 5.1.1 不带参数的宏定义 不带参数的宏定义的命令形式为 #define 宏名 字符文本 其中: (1)宏名按标识符语法取名,习惯上用大写字母,以便与变量等其他名称有所区别。 (2)字符文本可以为C++允许的标识符、关键字、常量数值、表达式及各种符号等。 (3)宏名两侧至少用一个空白符间隔,且这个空白符不属于字符文本。 预处理时,程序中所有的宏名将被字符文本完全替换,然后再进行编译。 例如有宏定义 #define PI 3.1415926 那么程序代码 L=2*PI*r; //PI 宏引用 在预处理时宏替换为 L=2*3.1415926*r; 又如有宏定义 #define M y*y+ 5*y 那么程序代码 S=3*M+4*M+5*M; //M 宏引用 在预处理时宏替换为 S=3*y*y+5*y+4*y*y+5*y+5*y*y+5*y; 使用宏定义,可以减少程序中重复书写某些字符文本的工作量,不容易出错;而且程 序员习惯性将常量值定义为符号常量,无论是编写程序或是阅读程序都有记忆简单、见名 知义的优点。 【例5.1】 计算半径为r 的圆周长、圆面积、圆球表面积和圆球体积。 程序代码如下: 1 #include 2 using namespace std; 3 #define PI 3.1415926 //不带参数的宏定义 4 int main() 180 5 { 6 double r,L,S,SQ,V; 7 cin >>r; //输入半径 8 L=2*PI*r; //计算圆周长 9 S=PI*r*r; //计算圆面积 10 SQ=4.0*PI*r*r; //计算圆球表面积 11 V=4.0*PI*r*r*r/3.0; //计算圆球体积 12 cout<<"L="< 2 using namespace std; 3 int M1(int y) 4 { 5 return((y)*(y)); 6 } 7 #define M2(y) ((y)*(y)) 8 int main() 9 { 185 10 int i,j; 11 for (i=1,j=1;i<=5;i++) cout< 或 #include "头文件名" 说明: (1)一个#include命令只能包含一个头文件,包含多个头文件要用多个#include命 令,且每个文件包含命令占一行。通常,头文件的扩展名为.h或.hpp。 (2)第一种形式与第二种形式的区别是编译器查找头文件的搜索路径不一样。第一 种形式仅在编译器INCLUDE系统路径中查找头文件,第二种形式先在源文件所处的文 件夹(用户路径)中查找头文件,如果找不到,再在系统路径中查找。 一般地,如果调用标准库函数或者专业库函数包含头文件时,使用第一种形式;包含 程序员自己编写的头文件时,将头文件放在源文件所处的文件夹中且使用第二种形式。 (3)头文件的内容通常是函数声明、全局性常量、数据类型声明和宏定义等信息,一 般不包括定义,如函数定义和变量定义等。所起的作用就是为其他程序模块提供声明性 信息,而定义、函数实现代码等应放在源文件中。 在实际编程中,如果程序是由多个源文件组成的,一般采用工程方式来集合,而不是 使用文件包含命令。 1.文件包含的路径问题 文件包含命令中的头文件名可以写成绝对路径的形式。例如: 187 #include "C:\DEV\GSL\include\gsl_linalg.h" #include 这时直接按该路径打开头文件,此时第一种形式或第二种形式的命令没有区别。请注意, 由于文件包含命令不属于C++语法,因此"C:\DEV\GSL\include\gsl_linalg.h"不能理解 为字符串,其中的“\”不要写成“\\”。 头文件名也可以写成相对路径的形式。例如: #include #include #include "user.h" #include "share\a.h" 这时的文件包含命令是相对系统INCLUDE路径或用户路径来查找头文件的。 假设编译器系统INCLUDE路径为“C:\DEV\MinGW\include”,则 #include //cmath 在C:\DEV\MinGW\include #include //zlib.h 在C:\DEV\MinGW\include\zlib 假设用户路径为“D:\Devshop”,则 #include "user.h" //user.h 在D:\Devshop 或C:\DEV\MinGW\include #include "share\a.h" //a.h 在D:\Devshop\share 或C:\DEV\MinGW\include\share 如果在上述路径中找不到头文件,会出现编译错误。 2.文件包含的重复包含问题 头文件有时需要避免重复包含(即多次包含),例如一些特定声明不能多次声明,而且 重复包含增加了编译时间。这时可以采用以下两个办法之一。 (1)使用条件编译。例如: #if !defined(_FILE1_H_C6793AB5_ _INCLUDED_) #define _FILE1_H_C6793AB5_ _INCLUDED_ … //头文件内容 #endif 即将头文件内容放在一个条件编译块中,第1次编译时编译条件成立,故继续往下编译, 第2行使编译条件为假,这样再次编译头文件时,头文件内容就不会编译了。条件中的宏 定义“_FILE1_H_C6793AB5__INCLUDED_”,为了与其他编译条件相区别,故意写得很 长、很怪。 (2)使用特殊预处理命令#pragma。例如: #pragma once … //头文件内容 即在头文件第1行增加这个预处理命令,它的意思是:在编译一个源文件时,只对该文件 188