在阅读一个较大的解决方案中,对于其他文件夹下的.h和.cpp文件,有时候#include“XXX.h”文件,有时候是#include“XXX.cpp”文件,而且二者还不能更换。下面就好好分析一下他们二者的区别。
测试
测试:XXX.h和XXX.cpp有没有在解决方案里的差别
如果 .h 文件和 .cpp 文件都已经添加在解决方案里,只要在 main 的头文件中 include 对应的 .h 文件即可。
如果 .h 文件和 .cpp 文件不在解决方案里,可能在其他文件夹里,单独 include.h 文件就会报错“error LNK2019: 无法解析的外部符号”。
但是单独 include.cpp 就能运行了,而此时当前 main.cpp 里面有没有对应的 .h 头文件都可以正确运行(因为 .h 文件里有 #pragma once 防止多次定义),
这个很好解释,include 就是把对应的代码拷贝一份进去,拷贝之后,自定义函数的定义本来就在使用前,有没有头文件的声明都可以。为了验证 include 是不是就是将程序拷贝,做了实验验证。
在自定义函数使用前 #include“XXX.h” 用于函数的声明(意思是在 main.cpp 最前面 #include“XXX.h),在 main 函数结束之后#include“XXX.cpp”,运行果然正确。
初步结论:说明当.h文件和.cpp文件不在解决方案里的时候,#include就是纯粹的拷贝复制工作。但是当 .h 和 .cpp 文件在解决方案的情况需要进一步验证。
同时也说明,.cpp 文件有没有包含进工程文件的情况是不同的。
探究
按照所有查到的信息都说明一点内容:凡是#include的头文件,都是把对应的文件信息拷贝复制一份进来。显然这个笼统的概念是不对的。
源程序->预处理->编译和优化->生成目标文件->链接->可执行文件
参考之前我转载的3篇博文C++编译与链接的三部曲(强烈推荐看一看)。大致总结如下:
预处理(简单替换)
预处理做如下工作:
预处理器主要负责以下的几处:
1.宏的替换
2.删除注释
3.处理预处理指令,如#include,#ifdef
编译和优化(高级语言->汇编语言)
词法分析 – 识别单词,确认词类;比如int i;知道int是一个类型,是一个关键字以及判断i的名字是否合法。
语法分析 – 识别短语和句型的语法属性;
语义分析 – 确认单词、短语和句型的语义特征;
代码优化 – 修辞、文本编辑;
代码生成 – 生成译文。
内联函数的替换就发生在这一阶段。
编译的另一个重要方面就是编译单元
什么是编译单元呢?简单来说一个cpp文件就是一个编译单元。
在集成式的IDE中,我们往往点击一下运行便可以了,编译的所有工作都交给了IDE去处理,往往忽略了其中的内部流程。
事实上编译每个编译单元(.cpp)时是相互独立的,即每个cpp文件之间是不知道对方的存在的(不考虑#include “xxx.cpp" 这种奇葩的写法)。
编译器会分别将每个编译单元(.cpp)进行编译,生成相应的obj文件,然后链接器会将所有的obj文件进行链接,生成最终可执行文件内部链接与外部链接。
这里能作为单独编译单元的是添加进工程的cpp文件,外部文件夹的cpp文件并不单独成为编译单元。
生成目标文件(汇编语言->二进制机器语言)
汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。
在最终的目标文件中,编译器把一个cpp编译为目标文件的时候,除了要在目标文件里写入cpp里包含的数据和代码,还要至少提供3个表:
未解决符号表;
导出符号表;
地址重定向表
未解决符号表提供了所有在该编译单元里引用但是定义并不在本编译单元里的符号及其出现的地址;
导出符号表提供了本编译单元具有定义,并且愿意提供给其他编译单元使用的符号及其地址。
地址重定向表提供了本编译单元所有对自身地址的引用的记录。
这就是不同cpp之间的通讯方式。
从这个定义方式上看,主cpp文件和其他所有cpp文件都是平等的关系,只不过主cpp里面包含了main函数而已,而main函数和其他所有函数也是平等的,只不过只有main函数可以作为工程的入口函数而已。
链接(汇总成一个目标文件)
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
由此,我们可以理解了经常报错的一些情况,就是未解决符号表和导出符号表之间没有定义或者重复定义的情况的。
外部文件夹的h和cpp文件
外部文件夹的cpp文件并不能生成单独的编译文件,所以,不参与未解决符号表和导出符号表的生成,而#include外部的h头文件也只是复制进来头文件中的函数声明而已,没有定义。所以当需要使用外部文件夹的cpp文件的时候,直接在头部加上#include cpp文件,或者在头部#include h文件,尾部#include cpp文件,就相当于我们平时写的自定义函数一样。