将实现同一模块的代码放到同一个源文件,而将相应的声明放到对应的头文件中。
源文件
源文件以“.cpp”等为文件后缀名,它主要用于实现程序的各种功能单元。比如,可以将程序划分为多个子模块,而一个源文件负责实现一个子模块;或者是一个程序由多个类构成,而一个源文件负责实现一个类。
头文件
头文件以“.h”等为文件后缀名,主要包含一些需要在多个源文件之间共用的函数、数据、类等的声明。因为头文件可以被多个源文件使用#include 预编译指令引用,所以可以在多个源文件内共享这些声明,以达到共用的目的。比如,前面的例子中多次引用“iostream”这个头文件,就是为了共享声明在其中的 cin/cout 对象,让自己的源文件可以直接使用这些对象而不用自己声明。
大多数时候,源文件和头文件是成对出现的。我们把某个子模块中需要共享的声明放在头文件中,而将其具体的实现放在对应的源文件中。比如,常常把一个类的声明放在头文件中,而把它的具体实现放在对应的源文件中。源文件和头文件不仅在内容上有所差别,在使用上也大有不同:
- 从引用的角度讲
源文件可以通过#include 引用一个或多个头文件,达到共用其中各种声明的目的。当然,如果某个头文件需要用到另外一个头文件中声明的内容,也可以引用另外一个头文件。但是头文件不可以引用源文件。虽然这在语法上是允许的,但是在实际使用中是不规范的用法,没人会这样做。 - 从预处理的角度讲
编译器在对源文件进行编译之前,会对源文件进行一定的预处理,其中一个步骤就是用#include
指令后头文件的内容替换掉源文件中的这个指令本身,形成最终的源文件参与编译。换句话说,头文件可以看成是多个源文件的共有部分。这些共有部分被抽取出来成为一个头文件,而这个头文件反过来又被多个源文件引用,以此来达到代码复用的目的。 - 从编译的角度讲
在编译一个程序的时候,程序的源文件会被直接编译成相应的 obj 文件;而对于头文件,如果这个头文件没有被任何源文件直接或者间接引用,那么这个头文件就相当于一个多余的无用文件,不会参与整个程序的编译过程,即使其中有语法错误也不会在编译时被检测出来。
头文件宏
为了防止头文件被多次引用,导致其中的内容被重复多次声明,所以需要一些预编译指令来防止这一错误的发生。
定义一个头文件宏,然后采用条件编译的方式来防止头文件被重复多次引用。
所谓条件编译, 指的是一种特殊的编译方式,它会根据不同的条件(比如,某个宏是否已经定义)而决定参与编译的代码,进而改变最终结果的行为。
// Human.h Human 类的头文件
// 用#ifndef 预编译指令判断 HUMAN_H 头文件宏是否已经定义
// 如果没有( if not define),则表示这个头文件尚未被引用,可以继续处理,
// 反之,表示这个头文件已经被引用,则跳过后面的代码。
#ifndef HUMAN_H
// 定义 HUMAN_H 头文件宏,表示这个头文件已经被引用
#define HUMAN_H
class Human // Human 类的声明
{
// …
};
#endif // 与#ifndef 指令匹配,表示条件编译的代码段结束