2.9 编写自己的头文件
一般类定义都会放入头文件(header file)。
为了允许把程序分成独立的逻辑块,C++支持所谓的分别编译(separate compilation)。
2.9.1 设计自己的头文件
头文件为相关声明提供了一个集中存放的位置。头文件一般包含类的定义、extern变量的声明和函数的声明。
使用或定义这些实体的文件要包含适当的头文件。
保证所有文件使用给定实体的同一声明;当声明需要修改时,只有头文件需要更新。
为了减少处理头文件的编译时间,有些C++的实现支持预编译头文件。
1. 头文件用于声明而不是用于定义
当设计头文件时,记住定义和声明的区别是很重要的。定义值可以出现一次,而声明则可以出现多次。
因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。
对于头文件不应该含有定义这一规则,有三个例外。头文件可以定义类、值在编译时就已知道的const对象和inline函数。这些实体可在多个源文件中定义,只要每个源文件中的定义是相同的。
2. 一些const对象定义在头文件中。
const变量默认时是定义该变量的文件的局部变量。
常量表达式是编译器在编译时就能够计算出结果的表达式。
要使初始化式可见,一般都把const变量定义在头文件中。
C++中的任何变量都只能定义一次。定义会分配存储空间,而所有对该变量的使用都关联到同一存储空间。
当我们在头文件中定义了const变量后,每个包含该头文件的源文件都有了自己的const变量,其名称和值都一样。
MyClass.h:
const int i = 10;
main.cpp:
#include "stdafx.h"
#include <iostream>
#include "MyClass.h"
int main()
{
int j = i;
std::cout << j <<std::endl;
}
如果const变量不是用常量表达式初始化,那么它就不应该在头文件中定义。相反,和其他的变量一样,该const变量应该在一个源文件中定义并初始化。应在头文件中为它添加extern声明,以使其能被多个文件共享。
2.9.2 预处理器的简单介绍
#include指示只接受一个参数:头文件名。预处理器用指定的头文件的内容替代每个#include。我们自己的头文件存储在文件中。系统的头文件可能用特定于编译器的更高效的格式保存。无论头文件以何种格式保存,一般都含有支持分别编译所需的类定义及变量和函数的声明。
1. 头文件经常需要其他头文件
包含其他头文件是如此司空见惯,甚至一个头文件被多次包含进同一源文件也不稀奇。
使得头文件安全的通用做法,是使用预处理器定义头文件保护符(header guard)。头文件保护符用于避免在已经看到头文件的情况下重新处理该头文件的内容。
2. 避免多重包含
为了避免名字冲突,预处理器变量经常用全大写字母表示。
#define指示接受一个名字并定义该名字为预处理器变量。 #ifndef指示检测指定的预处理器变量是否未定义。如果预处理器变量未定义,那么跟在其后的所有指示都被处理,直到出现#endif。
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass{
public:
int i;
};
#endif
为了保证头文件在给定的源文件中只处理一次,我们首先检测#ifndef。第一次处理头文件时,测试会成功,因为MYCLASS_H还未定义。下一条语句定义了MYCLASS_H。这样的话,如果我们编译的文件恰好包含了该头文件。#ifndef指示会发现MYCLASS_H已经定义,并且忽略该头文件的剩余部分。
头文件应该还有保护符,即使这些头文件不会被其他头文件包含。编写头文件保护符并不困难,而且如果头文件被包含多次,它可以避免难以理解的编译错误。
如果头文件包括在尖括号(<>)里,那么认为该头文件是标准头文件。编译器将会在预订义的位置查找该头文件,这些预定义的位置可以通过设置查找路径环境变量或者通过命令行选项来修改。如果头文件名括在一对引号里,那么认为它是非系统头文件,非系统头文件的查找通常开始于源文件所在的路径。