轻松学C++之预处理与宏_第1页
轻松学C++之预处理与宏_第2页
轻松学C++之预处理与宏_第3页
轻松学C++之预处理与宏_第4页
轻松学C++之预处理与宏_第5页
已阅读5页,还剩52页未读 继续免费阅读

付费下载

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第15章 预处理和宏 程序在编译之前先要经过预编译阶段。预编译的任务是根据程序中的宏指令补充和完善源代码。有了宏指令,可以使得某些源代码编辑任务变得轻松,同时也可以控制哪些源代码需要编译,哪些不需要编译。15.1

预处理概述

预处理器是专门用来处理宏指令的程序。在编译器运行之前,会先运行预处理器,查找所有的预处理指令。预处理指令以“#”开头,而且不以“;”结束,以区别于一般的语句。预处理器根据预处理指令生成新的源代码文件(临时文件,可以通过编译器的选项输出到指定目录中)。

编译器的作用是把源代码转换成汇编语言或机器指令。但是,编译器并不是直接编译程序员写成的源文件,而是编译经预处理器处理后所产生的新的源文件。这些新的源文件经过编译器生成目标文件,再经过链接器生成最终的可执行程序。

预处理器的任务就是执行源代码中的预处理指令,并对源代码进行相应的处理。因此,从预处理指令的类型来讲,预处理器的任务有这几部分:将其他文件包含到当前文件中、定义宏、定义类似函数的宏、实施条件编译。接下来,将详细介绍预处理器的各项任务。15.2

所谓宏,是程序中定义的用于替换复杂文本的简短文本。宏定义的一般格式如图15-1所示:图15-1宏的定义

宏名,即宏的名称,在预编译时被可替换文本替换。可替换文本,即宏名所指代的文本内容。在#define、宏名和可替换文本之间用空格(制表符)分隔。预编译程序将#define之后的第一个和第二个空格之间的文本作为宏名,其后所有文本作为“可替换文本”,而不管中间有多少个空格。15.2.1

宏展开

在程序的预编译期,预编译程序会解析源代码文本,执行替换源程序的动作,把宏引用的地方替换成定义处的文本。这个动作叫做宏的展开。图15-2这段程序是宏的一个简单例子。图15-2宏的使用

上图代码中cout后面的NUMBER在预编译时会被替换成100。

注意:在源代码文本中,并不是所有的宏名出现的地方都会进行展开。如果这个宏名出现在一个双引号中,则该宏名就成了字符串的一部分,也就不会进行展开了。15.2.2替代常量

用宏替代字面常量,其好处是直观、简洁、修改方

便。譬如对于圆周率,其值是一个无理常量

3.1415926···。如果在程序中每一处要使用圆周率地方都直接书写这个常量,那么源代码修改起来就不大方便。一旦要求改变数字的精度,则所有使用的地方都要修改。无疑修改量就比较大,而且也不能保证每一处都修改对。

如果使用宏替换字面常量,而程序中使用圆周率的地方都使用宏而非圆周率本身,那么当修改圆周率数值时,只要修改这个宏定义即可,大大减少了编程人员的工作量。例如图15-3所示代码,用宏替换常量3.1415926。图15-3宏替换字面常量

上图中使用宏PI替换了常量3.1415926,若程序中的常量需要变化,则只需要更改宏定义即可。

相对于常量,使用宏也有缺点。字面常量在编译时处理,有类型信息。而宏则是在预编译时展开,只是进行单纯的文本替换,没有类型信息。因此对于一些类型要求比较严格的地方,使用宏有一定的风险。

定义宏时可以使用前面已经定义的宏。例如:假设已经存在一个宏NUMBER,其替换的文本是100,当定义新的宏时,可以在宏的可替换文本中使用NUMBER,如图15-4所示。图15-4宏展开15.2.3替代运算符 除了可以用宏替代字面常量,还可以用宏替代某些运算符,包括加减乘除、逻辑与和逻辑非等,甚至函数和语句块的花括号。利用这些宏定义,可以编写出貌似违反C++语法、但实际上合法的源代码。例如:如图15-5所示的代码。图15-5宏替代运算符15.3

带参数的宏

简单的宏定义,如上节例子所示,只能进行简单的文字替换,扩展能力有限。如果宏能够像函数那样带有参数,并根据参数的不同自动展开成不同的文本,则宏的扩展能力将大大提高。实际上,在C++标准中,可以利用预处理命令#define定义带参数的宏。15.3.1

定义带参数的宏

有参数宏的宏名后需要带参数,其语法格式如图15-6所示:图15-6带参数宏的定义1

若可替换文本一行写不完,可以分成多行,并在每一行的末尾加上分行的符号“\”(除了最后一行)。其语法格式如图15-7所示:图15-7带参数宏的定义2

注意:要定义带参数的宏,则在宏名和左括号之间不能有空格(或制表符),否则就成了普通的宏定义,括号及其里面的内容也将会成为可替换文本的一部分。但是,在括号中的各个参数之间可以任意添加空格。

在可替换文本中可以引用括号中的参数,从而根据参数的不同,展开成不同的源代码文本。例如,下例定义了一个宏,接受两个参数,然后比较两个参数的大小,并输出其中的较小值,示例代码如图15-8所示。图15-8带参数的宏

注意:在上述例子中使用了带参数的宏

LESS,但是在语句的结尾处并没有分号。乍一看好像不符合C++的语法规范,但是实际上并没有错。这是因为宏是在预编译时处理的,而不是编译期。而且,宏展开后的结果符合C++语法规范,所以这些语句的结尾处没有分号并不会出错。15.3.2

注意宏展开的结果

宏的行为有时候和所认知的行为会有些不同,主要原因是通常认为宏作为语句块,会优先执行,但这是不对的,宏并没有那么“聪明”,或者可以说编译器并没有那么“聪明”,预编译器所做的只是忠实地将宏展开。我们来看图15-9所示的程序,预编译器不会自作聪明为“x+x”加括号,因此最后输出的结果是30。图15-9带参数宏的展开

如果凭借第一印象,很可能会做这样的计算:5*(5+5)=50。但是,预编译器并不这么认为,而会做这样的解释:5*5+5=30。忠实的将原来的宏展开。所以读者写宏的时候一定要小心,预防宏的行为不符合预期。

若想让结果输出为50,只要给宏加上括号即可,如图15-10所示。图15-10展开宏时注意括号

上图中的“cout<<5*INT(5)<<endl;”经预处理后,转换为“cout<<5*(5+5)<<endl;”。15.3.3带参数的宏与函数的比较带参数的宏在定义和使用方面同函数非常相似,都可以接受参数,并且可以根据不同的参数产生不同的结果。但是这两者毕竟是不同的东西,存在很大差异,例如:间,程序运行时并不存在。次,则其语句也将重复多次。换。息。(1)函数在运行时依然存在,但宏只存在于预编译期(2)函数所占内存空间只有一份,但如果宏被调用多(3)函数调用有时间和空间方面的开销,但宏没有。(4)函数接受参数的传递,但宏只是对参数的简单替(5)函数的参数有类型信息,但宏的参数没有类型信(6)函数可以调试,宏不可以。15.4

条件编译

条件编译指令可以对程序源代码的各部分有选择的进行编译,该过程就称为条件编译。常见的条件编译指令有#if、#elif、#else、#ifndef、#ifdef、#endif等。它们主用来在编译时进行选择,屏蔽掉一些代码。

以下内容将对条件编译内容进行相关介绍。15.4.1宏指令

C++中的宏指令都是在ANSI标准中的,表

15-1列出了常见的宏指令。表15-1常见的宏指令

#define在前面已经介绍过了,这里就不再讨论。#error可以强迫编译程序停止编译,用来在编译期间检查环境是否符合要求或

者与约束的条件发生了冲突。其使用格式

如图15-11所示:#define#error#include#if#else#elif#endif#ifdef#ifndef#undef#line#pragma图15-11强迫停止编译指令#error

当程序遇到这个关键字,就会停止编译,产生一个错误信息,并且输出后面的token-string。例如:#if

!defined(

cplusplus)#error

C++

compiler

required.#endif

上面这段代码的意思是在预编译期间检查当前的程序是否是C++编译环境,如果不是,就定义#error,让编译器停止编译。#if和#endif会在后面介绍,这里只需要将#if视为普通的if判断

语句,将#endif看成if判断的结束。

#include使编译程序将#include所指向的源文件导入进当前的源文件,被包含的文件必须被尖括号或者引号包围起来。#if,#else,#elif,#endif,#ifdef和#ifndef属于条件编命令,可以对程序的各个部分有选择地进行编译。#undef命令用来取消前面定义过的宏名。来看一个宏使用的例子,如图15-12所示。图15-12宏指令的使用

在程序的第一行定义了NUMBER宏,在第六行输出10。在第八行取消了NUMBER宏的定义,所以后面会输出“找不到

NUMBER的定义”。15.4.2

条件编译

#if,#else,#elif,#endif,#ifdef和#ifn是条件编译命令。所谓条件编译,就是可以将源文件中的代码分成几个部分,有选择的编译各个部分。对于前三个宏,可以理解为if,else和else

if,#endif表示这个条件编译选择的结束。看图15-13中的代码:图15-13用#if、#else和#elif进行条件编译

程序的第一行定义NUMBER宏,令其等于5。后面的代码判断是否等于相应的值,选择性地编译后面的语句,由于定义NUMBER等于5,编译器输出“Number

is

equal

to

5”

另一种条件编译的方式就是使用#ifdef和#ifndef。同样,这两个命令也用#endif作为作用域的结束。#ifdef判断后面的标识符是否被定义,通常都是指预定义的宏,#ifndef就是#ifdef的取反。看图15-14中的程序。图15-14用#ifdef和#ifndef进行条件编译

第四行判断是否定义DEBUG标识符(在第一行定义),然后编译后面的语句。再来看一个例子,示例代码如图15-15所示。图15-15条件编译1

代码第四行的disp函数中采用了条件编译指令编写,因为程序中定义了WINDOWS宏,所以输出“本程序当前运行在Windows环境下”,假若将“define

WINDOWS”语句注释掉,程序将输出“本程序运行在非

WINDOWS环境下”,如图15-16所示。图15-16条件编译2

代码第六行“#if

defined(WINDOWS)”语句中的“define(宏名)”的含义是若宏被定义则返回1,否则返回0,defined之间可以进行逻辑运算。15.5

文件包含和头文件卫士15.5.1

包含文件指令

包含文件的预处理指令是“#include”,其作用就是将别的文件包含到当前文件中。

在实际使用中,一般被包含的文件是头文

件。头文件中一般是函数、类等的声明,

包含到当前文件中后,就可以在当前文件

中引用头文件中的函数、类等。例如对于

下图中的源代码文件进行预编译后产生的

临时源代码文件如图15-17所示。图15-17包含头文件按照C/C++的语法要求,要使用某个标识符(变量、函数、类等),必须在使用之前

先声明。虽然在Some.cpp文件中没有函数

SomeFunction的声明,但是由于包含了头文件header.h,所以仍然可以使用该函数。15.5.2

搜索头文件

使用“#include”指令包含头文件时,其后的头文件有两种方式,一种是使用双引号,一种是使用尖括号。如图15-18所示。图15-18头文件的两种包含方式如果文件名用尖括号括起来,表明这个文件是一个工程或C++标准库头文件。预编译器会首先搜索在工程中预定义的目录,然后搜索C++编译器的安装目录。可以通过设置工程搜索路径环境变量或命令行选项来修改。如果文件名用一对引号括起来,则表明该文件是用户提供的头文件。预编译器首先从当前文件目录开始搜索,如果找不到,就从工程中定义的目录和编辑器的安装目录查找。15.5.3

头文件卫士

当工程中文件众多是,很有可能出现一个头文件被多次包含的情况。但是,C++中同一个类型被声明两次是非法的,来看一个例子。,如图15-19所示,文件Animal.h中定义了类CAnimal,文件pig.h中定义了类

CPig,文件horse.h中定义了类CHorse,主函数中用类CPig和CHorse分别定义了变量

pig和horse。图15-19重复定义1

如上图所示,在main函数中使用了CHorse和CPig两个类,所以需要包含这两个类的头文件,但是,这两个类都包含CAnimal的定义,编译器不知道该选择哪个,会报告重复定义的错误。如下图15-20所示,对上述代码进行编译,编译器给出了错误信息。图15-20 重复定义2为了避免因为重复包含头文件而重复定义类型,导致编译失败,这个时候就需要头文件卫士的帮助。所谓头文件卫士就是用一组宏命令将头文件包起来,使其不会被重复包含,看Animal.h的例子:#ifndef

ANIMAL_H#define

ANIMAL_H••#endif#ifndef是定义在头文件所有内容之前的,#endif是定义在所有内容之后的,用预编译命令#ifndef和#endif将整个Animal.h头文件中的内容包含起来。这样便不会有编译错误了,下面来分析一下。

当main函数所在文件第一次包含pig.h文件时,同时会导入Animal.h中的内容,这时预编译器分析当前文件没有定义ANIMAL_H,就会在当前文件中定义。当再次包含horse.h文件时,导入Animal.h,发现文件中已经定义了ANIMAL_H,就会查找#else或者

#endif,这时会直接跳转到#endif,不会包含当前文件的任何内容。

同理,如果有人想继续从Horse上继承,例如Whitehorse

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论