多文件结构和编译预处理命令
预处理指令
声明预处理指令的一般形式为:
# directive tokens
符号“#”必须是该行第一个非空白字符,但前面有空白符或退格符都可以,#与directive之间也可以有多个空白符,如下代码具有完全相同的效果:
#define size 100
#define size 100
# define size 100
一般一行声明一条预处理指令,但不排除用多行声明一条指令,字符“\”用在一行的末尾表示下行仍然接着该行,比如下面两个声明是完全对等的:
#define CheckError \
if (error) \
exit(1)
#define CheckError if (error) exit(1)
预处理指令声明中出现的注释以及一行单独一个#符号的情况在预编译处理过程中都会被忽略掉。
表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在此也同样被包含了两次,只不过它的形式不是那么明显而已。
多重包含在绝大多数情况下出现在大型程序中,它往往需要使用很多头文件,因此要发现重复包含并不容易。要解决这个问题,我们可以使用条件编译。如果所有的头文件都像下面这样编写:
#ifndef _HEADERNAME_H
#define _HEADERNAME_H
...
#endif
那么多重包含的危险就被消除了。当头文件第一次被包含时,它被正常处理,符号_HEADERNAME_H被定义为1。如果头文件被再次包含,通过条件编译,它的内容被忽略。符号_HEADERNAME_H按照被包含头文件的文件名进行取名,以避免由于其他头文件使用相同的符号而引起的冲突。
但是,你必须记住预处理器仍将整个头文件读入,即使这个头文件所有内容将被忽略。由于这种处理将托慢编译速度,所以如果可能,应该避免出现多重包含。
举例:
cpp1.h
//#ifndef Cpp1_//H两种都可以
//#define Cpp1_H
#if !define(Cpp1_H)
#define (Cpp1_H)
#include<iostream>
using namespace std;
class point {
public:
point (int x=0,int y=0):x(x),y(y) {}
point (const point &p);
~point() {count--;}
int getx() const {return x;}
int gety() const {return y;}
static void showcount();
private:
int x,y;
static int count;
};
#endif
main.cpp
view plain
#include<iostream>
#include "cpp1.h"
using namespace std;
int point::count=0;
point::point(const point&p):x(p.x),y(p.y){count++;
 
补充:软件开发 , 其他 ,