在编写C++程序文件的时候,我们会定义头文件在.h / .hpp / .hxx, 源文件.cpp / .cxx / .cc,那他们的区别是什么呢?这也是我长期以来的一个疑问。在查阅资料之后有了一定的了解。
首先得回顾一下一个C程序或者C++程序从代码到能运行需要进行的几个步骤。如下是一个简单的hello world命令,而gcc在将其编译成可执行代码时,需要进行四个步骤, 预处理 -> 编译 -> 汇编 -> 链接。
1
2
3
4
5
6
7
8
// hello.c
#include <iostream>
int main(){
std::cout << "Hello, World!" << std::endl;
return 0;
}
1
2
3
$ g++ -o test.out main.cpp # 编译
$ ./test.out # 执行
Hello world!
- 其中预处理用于将所有的#include头文件以及宏定义替换成其真正的内容,预处理之后得到的仍然是文本文件,但文件体积会大很多。
- 这里的编译不是指程序从源文件到二进制程序的全部过程,而是指将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程。
- 汇编过程将上一步的汇编代码转换成机器码(machine code),这一步产生的文件叫做目标文件,是二进制格式
- 链接过程将多个目标文以及所需的库文件(.so等)链接成最终的可执行文件(executable file)。
在一个C++项目中,如果所有的代码都写在一个源文件中,任何一次细小甚微的代码改动都需要重新编译所有的代码。这样的效率是及其低下的。所以我们会发现成熟的project中,会将代码匀给多个源文件cpp中,这样也有利于提高代码的可读性。
那么为什么还有头文件的存在呢?有时候某个定义的函数func可能被多个源文件使用,如果没有头文件,这个函数func只能手动在各个源文件中被定义和声明多次。而有了头文件func.h,则只需将该函数声明在头文件中声明和定义。
但是在预处理过程中,在包含该头文件的源文件中,该头文件的代码都要被添加进来。这样还是相当于这个函数在各个源文件中被定义和声明以及编译多次,所以目前使用的一般方法是头文件只负责声明函数,包括参数以及返回类型,但具体内容在同名的源文件func.cpp中补充,这样一来,其他源文件使用该函数的时候只需要链接func.cpp生成的目标文件func.o。
Reference
https://www.toptal.com/c-plus-plus/c-plus-plus-understanding-compilation#:~:text=The%20first%20step%20that%20the,are%20included%20from%20source%20files.
https://www.cplusplus.com/articles/Gw6AC542/#:~:text=So%20what’s%20the%20difference%20between,%2C%20but%20you%20shouldn’t.
https://www.cnblogs.com/CarpenterLee/p/5994681.html