一、分离式编译
在复杂的程序开发过程中,我们通常希望把程序的不同部分放到不同的文件中,实现程序按照逻辑拆分。分离式编译允许我们把程序分割到几个文件中去,每个文件独立编译。这样如果我们修改了其中一个源文件,那么只需要重新编译这个文件即可,然后由链接器重新链接生成可执行文件。
二、声明与定义
为了支持分离式编译,c++语言将声明和定义区分开来。声明是的名字为程序所知,定义是分配具体的与名字相关联的实体,因此,只允许定义一次,但是可以声明多次。一个文件如果想要使用别处定义的名字必须包含对那个名字的声明。变量的声明为在变量前加extern关键字且不赋值,函数的声明为函数原型。
三、头文件的作用
当源文件使用#include包含了某个头文件时,可以理解成编译器将这个头文件的内容直接复制到当前源文件中(通常会使用头文件预编译的方式来避免多次编译同一个头文件的内容),因此要非常注意变量的多重定义问题。如在某个头文件head中有如下变量定义:
//test.h
int x = 1;
现在在多个源文件中包含改头文件:
//test1.cc
#include"test.h"
//test2.cc
#include"test.h"
执行编译后会发现x被重复定义了,这是因为x相当于是一个全局变量,这样当同时编译test1.cc和test2.cc时会由于发现相同的全局变量名而报错。
如果修改在x的定义前加上const关键字的话就没有问题:
//test.h
const int x = 1;
因为const关键字会将变量x限定到单个源文件中去,这就相当于在test1.cc和test2.cc中的x是不一样的,在头文件中定义const变量的意义仅仅是统一了定义的方式。
通常,头文件包含那些只能被定义一次的实体,如类、const和constexpr变量,这样可以确保类或常量在各个文件中定义一致。但是需要注意头文件的多次包含问题,(注意:一个头文件能被多次包含到不同的源文件中,但是不能通过间接的方式被多次包含到同一个源文件)。为了解决多次包含问题可以使用头文件保护符,具体的细节可参考《c++ primer》第五版P68。
除此之外,头文件还能起到联系多个源文件的作用。比如多个源文件需要相互用到彼此的变量或函数,为了使得这部分会被相互访问的资源能被共享,可以将这些资源都声明在某个头文件中,这样,只要这些源文件都包含了这个头文件,就可以访问到这些变量或函数。
例如有如下头文件
//test.h
extern int x;
int add(int a, int b);
int fun(int a, int b);
在头文件中只声明了两个函数和一个变量。
这两个函数分别在如下两个源文件中定义
//test1.cc
#include <iostream>
#include"test.h"
using namespace std;
int add(int a, int b) {
return a + b;
}
int main()
{
cout << fun(1, 2) << endl;
cout << x <<endl;
return 0;
}
//test2.cc
#include "test.h"
int x = 1;
int fun(int a, int b) {
return a * add(a, b);
}
因此,只要他们都include了同一个包含了这些资源声明的头文件,在源文件中就可以访问到其他源文件中定义的函数和变量。
总结
一般会在头文件中存放如下内容:
- 变量和函数的声明。目的在于方便包含了这个头文件的源文件之间进行资源共享。
- 类的定义。目的在于确保所有包含了这个头文件的源文件中类的定义一致。