技术教程 为何C++需要头文件源文件分离,以及链接器的多重定义报错
我在刚开始学C++的时候,就一直有个疑问。
不管是教学还是看的其他教程文章,采用的都是定义扔头文件实现放源文件,而且include的永远是头文件。
那我就寻思着,不要定义直接实现源文件并且include源文件怎么你了?
啥都不知道嘛,不信邪就去试试,你还真别说,还真就不行。
直接上示例代码:
// a.cpp #include <iostream> #include "include.cpp" using namespace std; int main(){ cout << "Hello World!" << endl; IncludeFunc(); return 0; }
// include.cpp #include <iostream> using namespace std; void IncludeFunc(){ cout << "IncludeFunc" << endl;}
// include.hpp void IncludeFunc();

而把a.cpp里的#include "include.cpp"
改成#include "include.hpp"
之后,立马就没事了。

这是怎么回事呢?
首先明确一下,#include干了什么。
#include只会对文件内容进行复制粘贴。
就比如a.cpp里的#include <iostream>
,预处理阶段预处理器会打开iostream这么个文件,把里面所有的内容复制出来替换到#include <iostream>
所在位置。
由于是复制粘贴,搞的和宏定义一样,因此你甚至可以在变量名称之类的地方使用#include
,这点我这里不做演示。
如果想看演示的话可以看这个:
C++分文件为什么要include .h文件,include .cpp会怎么样?_哔哩哔哩_bilibili 空降07:15
解释清楚这个之后,我们就可以继续向后看了。
注意我上面MSVC的命令,我把a.cpp和include.cpp都编译了。
而a.cpp里面有一个include "include.cpp"
,那么include.cpp里的全局函数void IncludeFunc()
就会被复制到a.cpp里面再编译一次。
发现问题没有?此时被编译的a.cpp和include.cpp里面都有一个签名是void IncludeFunc()
的函数,并且位于同一作用域。
而他俩返回值类型和参数列表都是一样的,函数名称也一致,这就导致了无法发生重载,事实上就相当于是下面这样:
#include <iostream> using namespace std; void IncludeFunc(){ cout << "IncludeFunc" << endl;} void IncludeFunc(){ cout << "IncludeFunc" << endl;} int main(){ cout << "Hello World!" << endl; IncludeFunc(); return 0; }
这个就很容易看懂了嘛,一眼重定义。
那为什么包含头文件就没事呢?
因为头文件里放的东西都不一样啊。
include.cpp里面是函数签名+实现,而include.hpp里面只有一个签名,没了。
这种只有签名没有实现的结构叫函数声明。
如果函数经过声明并且实现不在本文件内,那么链接器就会去其他文件里面查找实现。
所以,只需要include头文件+编译实现cpp文件即可解决问题。