头文件是c/c++中特有的概念。
首先解释声明和定义的区别。extern int x;此为变量x声明,void fun();此为函数fun()声明,class a;此为类a声明。int x;为变量x定义,void fun(){};则为fun()函数的定义,class a{};则为类a的定义。
头文件中存放预处理命令(#开头的命令),函数声明,类、结构体、联合定义(不能实例化对象),模板定义等。而不应该包含变量定义,函数定义等。头文件中的内容不会去生成目标代码,不会去为变量声明或类定义分配内存空间,都只是一些声明性的东西。#include在编译的预编译阶段展开用相应的头文件内容替换。
编译单元是指一个cpp文件,可以生成一个目标文件。同一个编译单元中可以有重复的声明,但是不允许重复的变量定义、函数定义、类定义。不同的编译单元可以有重复的声明和类定义(这里比较特殊,因为类定义只是声明性的东西,没有实际产生目标代码,故可以放在不同的编译单元中,而在同一编译单元中不能重复定义),不允许重复的变量定义和函数定义。
为了防止在同一个编译单元中头文件重复引用导致出现类重复定义等问题,在每个头文件定义时加入#ifndef #define #endif的定义。并且该条件编译指令也可以避免头文件的循引用,在程序预编译阶段,碰到#include命令即展开对应的头文件,在展开那个头文件时同理处理。比如有三个头文件a.h b.h c.h,a.h里面有#include "b.h",b.h里面有#include "c.h", c.h里面有#include "a.h",这就会造成文件的循环依赖,此时若有个文件a.c,其中#include "a.h",那在a.c文件编译之前,预处理程序就会不断的把这三个头文件的内容复制过来,超过了一定的数量,就会导致“头文件数太多”的编译错误。当使用#ifndef...#define...#endif条件编译命令后,第一次展开a.h b.h c.h的时候就已经定义了宏,到了c.h中的#include "a.h"时候,遇到了#ifndef,由于这个宏在上一次展开时已经定义了,所以这部分就跳过去了。也就是每个头文件最多只在每个源文件里面包含一次。
下面解决一个实际问题即两个类A,B中都有另一个类的指针,即彼此互相引用。这里可以用前置声明来解决。
头文件a.h
#ifndef A_H
#define A_H
class B;
class A
{
public:
B* b;
};
#endif
头文件b.h
#ifndef B_H
#define B_H
class A;
class B
{
public:
A* a;
};
#endif
使用头文件时的两个原则:
1、如果可以不包含头文件,那就不要包含了。这时候前置声明可以解决问题。如果使用的仅仅是一个类的指针,没有使用这个类的具体对象(非指针),也没有访问到类的具体成员,那么前置声明就可以了。因为指针这一数据类型的大小是特定的,编译器可以获知。
2、尽量在CPP文件中包含头文件,而非在头文件中。假设类A的一个成员是是一个指向类B的指针,在类A的头文件中使用了类B的前置声明并 便宜成功,那么在A的实现中我们需要访问B的具体成员,因此需要包含头文件,那么我们应该在类A的实现部分(CPP文件)包含类B的头文件而非声明部