多文件结构和编译预处理命令
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
多文件结构和编译预处理命令
C++完整的源程序一般由三部分构成:类的定义,类成员的实现,主函数
在较大的项目中,常需要多个源文件(即多个编译单元),c++要求一个类的定义必须在使用该类的编译单元中。因此,把类的定义写在头文件中。
(重点)一个项目至少分三个文件:类定义文件(.h)、类实现文件(.cpp)、类的使用文件(.cpp)
(重点)对于复杂的程序:每个类都有单独的类定义和类实现。这样做的好处:可以对不同的文件进行单独编写、编译,最后链接,同时利用类的封
装性,在程序的调试和修改时只对其中某一个类的定义和实现修改,其余保持不动。
预处理指令声明中出现的注释以及一行单独一个#符号的情况在预编译处理过程中都会被
忽略掉。
宏定义在c++中依然使用,但最好的方式是在类型说明语句中用const修饰来取代宏定义。(重点)大型程序中,往往需要使用很多头文件,因此要发现重复包含并不容易。要解决这个问题,我们可以在头文件使用条件编译。(三种方式)
表1是所有预处理指令和意义
指令意义
#define 定义宏
#undef 取消定义宏
#include 包含文件
#ifdef 其后的宏已定义时激活条件编译块
#ifndef 其后的宏未定义时激活条件编译块
#endif 中止条件编译块
#if 其后表达式非零时激活条件编译块
#else 对应#ifdef, #ifndef, 或#if 指令
#elif #else 和#if的结合
#line 改变当前行号或者文件名
#error 输出一条错误信息
#pragma 为编译程序提供非常规的控制流信息
宏定义
#define指令定义宏,宏定义可分为两类:简单宏定义,带参数宏定义。
简单宏定义有如下一般形式:
#define 名字替换文本
它指示预处理器将源文件中所有出现名字记号的地方都替换为替换文本,替换文本可以是任
何字符序列,甚至可以为空(此时相当于删除掉文件中所有对应的名字)。
简单宏定义常用于定义常量符号,如:
#define size 512
#define word long
#define bytes sizeof(word)
因为宏定义对预编译指令行也有效,所以一个前面已经被定义的宏能被后来的宏嵌套定义(如上面的bytes定义用到了word)。对于下面这句代码:
word n = size * bytes;
它的宏扩展就是:
long n = 512 * sizeof(long);
使用简单宏定义定义常量符号起源于C语言,但在C++中,定义常量可以用const关键字,并且还附加类型检查的功能,因此C++中已经尽量避免使用宏定义来定义常量了。
带参数宏定义的一般形式为:
#define 名字(参数) 替换文本
其中参数是一个或多个用逗号分割的标识符;在“名字”和“(”之间不允许有空格,否则整个宏定义将退化为一个置换文本为“(参数) 替换文本”的简单宏定义。下例表示定义一个求两数中较大者的带参数宏Max。
#define Max(x,y) ((x) > (y) ? (x) : (y))
带参数宏的调用有点类似于函数调用,实参数目必须匹配形参。首先,宏的替换文本部分置换掉调用的代码,接着,替换文本部分的形参又被置换为相应的实参,这个过程叫做宏扩展。见下例:
n = Max (n - 2, k +6);
的宏扩展为:
n = (n - 2) > (k + 6) ? (n - 2) : (k + 6);
注意,宏扩展时有可能发生不预期的运算符优先级的变化,这时如果定义宏时将替换文本里出现的每个形参都用括号括起来就不会出现问题(如上述宏MAX所示)。
仔细考察带参数宏与函数调用的异同可以发现,由于宏工作在文本一层,相同功能的宏和函数调用产生的语义有时是不完全相同的,比如:
Max(++i, j)
扩展为
((++i) > (j) ? (++i) : (j))
可见i最后自增了两次,但相同功能的函数能够保证只自增一次。
带参数宏定义在C++中的使用同样也在减少,因为:1,C++的内联函数提供了和带参数宏同样高的代码执行效率,同时没有后者那样的语义歧义;2,C++模板提供了和带参数宏同样高的灵活性,还能够执行语法分析和类型检查。
最后讨论一点内容是宏能够被重定义,在重定义前,必须使用#undef指令取消原来的宏定义,#undef如果取消的是一个原本不存在的宏定义则视为无效。如:
#undef size
#define size 128
#undef Max
引用操作符和拼接操作符
预处理提供了两个特殊操作符操作宏内的参数。引用操作符(#)是一元的,后跟一个形参作为运算对象,它的作用是将该运算对象替换为带引号的字符串。
如有一个调试打印宏检查指针是否为空,为空时输出警告信息:
#define CheckPtr(ptr) \
if ((ptr) == 0) cout << #ptr << " is zero!\n"
此时#操作符将表达式中的变量ptr当成字符串输出为警告信息的一部分。因此,如下的调用:
CheckPtr(tree->left);
扩展为:
if ((tree->left) == 0) cout << "tree->left" << " is zero!\n";
注意:如果按照下面这样定义宏
#define CheckPtr(ptr) \
if ((ptr) == 0) cout << "ptr is zero!\n"
是不会得到期望结果的,因为宏不能在字符串内部进行置换。
拼接操作符(##)是二元的,被用来连接宏中两个实际参数,比如,如下宏定义
#define internal(var) internal##var
如果执行
long internal(str);
则被扩展为:
long internalstr;
在一般编程时很少用到拼接操作符,但在编写编译器程序或源代码生成器时特别有用,因为它能轻易的构造出一组标识符。
#include文件的一个不利之处在于一个头文件可能会被多次包含,为了说明这种错误,考虑下面的代码:
#include "x.h"
#include "x.h"
显然,这里文件x.h被包含了两次,没有人会故意编写这样的代码。但是下面的代码:
#include "a.h"
#include "b.h"
看上去没什么问题。如果a.h和b.h都包含了一个头文件x.h。那么x.h 在此也同样被包含了两次,只不过它的形式不是那么明显而已。
多重包含在绝大多数情况下出现在大型程序中,它往往需要使用很多头