在学习c++,被头文件搞得一头包,时常出现重定义的错误,今天就理一下c++的头文件,彻底把它搞懂。C++与C一样,所有变量都需要先声明才能使用,这种”啥都别说,先报名字”的做法,不仅应用在了变量身上,一个函数也需要先声明后使用,并且这个函数只能定义一次。

起步

比如:

1
2
3
4
5
6
7
8
9
void A()
{
B(); // 未声明调用
}
void B()
{
// your code
}

上面的例子中,由于B出现的顺序在A之后,但是A先调用未声明B,因此会报错。解决办法如下:

1
2
3
4
5
6
7
8
9
10
11
void B(); //此处声明
void A()
{
B();
}
void B() //此处定义(实现)
{
// your code
}

上面的知识很简单,学过c基础的同学都知道,上面的例子是说明,声明与定义不一样,调用函数前,这个函数一定是要 先声明 (可以声明无数次) ,其函数的定义可以延后,并且 只能定义一次 。明白了要点之后回到正题。

C++和其他语言一样可以分别编译,用于将代码分成若干个cpp,每个cpp负责不同的功能。如果但是几个cpp文件相互依赖的话,我们直接include cpp源文件 容易出现重复定义的错误。至于为什么会重复定义呢?这是因为include的时候,相当于把目标文件的内容包含进来,比如A包含了B,因为B里面的某个函数有具体的定义(实现),所以A里面就相当定义了一次B里面的函数,但是等到编译B的时候,B也定义了一次函数,导致重复定义的错误。

不犯重复定义的函数也很简单,不需要include源文件,直接在依赖者中(a.cpp)先声明然后使用即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
//a.cpp
void test();
int main()
{
test();
}
//b.cpp
void test()
{
// your code
}

现在弄出个头文件的目的就是,比如A要使用B的函数f(),A肯定不能包含具体定义的文件(重复定义),A就包含具有B文件里所有函数声明的头文件B.h,要知道多次定义会报错但是多次声明不会报错。因此,C++推荐的写法是定义与声明分开,头文件写声明,源文件写定义,源文件之间依赖通过包含源文件的头文件来实现。同时,头文件时一个存着一堆声明文件,因此就不用像上面的代码那样,在依赖者中写入被依赖者的声明(解耦)。

实例

现在存在的一个场景是,源文件A依赖源文件B的f(),源文件A叫做A.cpp,源文件B叫做B.cpp,源文件B的头文件叫做B.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* A.cpp */
# include "B.h"
int main()
{
f();
}
/* B.h */
#ifndef project_B_H //头文件保护措施
#define project_B_H
void f(); //声明
#endif
/* B.cpp */
# include "B.h"
void f() // 实现
{
// your code
}

在上述例子中,B的头文件里面由一些宏定义,这些是为了防止重复定义,具体的场景是,两个源文件include了同一个头文件。而编译时,这两个源文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突,因此需要加上#ifndef和#endif,当中ifndef和define后面跟是一个唯一标识,通常规范的格式为项目名_类名_H(H代表头)

如果换成类的写法就是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* A.cpp */
# include "B.h"
int main()
{
f();
}
/* B.h */
#ifndef project_B_H //头文件保护措施
#define project_B_H
class B {
public:
void f();
};
#endif
/* B.cpp */
# include "B.h"
B::f()
{
// your code
}

当然具体的实现也可直接写在头文件里,完全没有问题。

include “” 与 include <>

#include < file > 编译程序会先到 标准函数库 中找文件

#include “file” 编译程序会先从 当前目录 中找文件

头文件中可能会出现的extern关键字

在C语言中,修饰符extern用在变量或者函数的声明前,用来说明此变量/函数是在别处定义的,要在此处引用。

在C++中extern还有另外一种作用,用于指示C或者C++函数的调用规范。比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同,用此来解决名字匹配的问题。

总结

老实说,这种定义与声明分开的写法还真有点蛋疼,如果一改就要改两处,所以感觉如果定义与声明都写在头文件里,然后一个include就完事了,这种做法比较方便点。当中应该考虑到兼容c等各种历史原因吧~