C++ 中到底是应该include .h文件还是应该include .cpp文件

在阅读一个较大的解决方案中,对于其他文件夹下的.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就能运行了,而此时.cpp里面有没有对应的.h头文件都可以正确运行(因为.h文件里有#pragma once防止多次定义),这个很好解释,include就是把对应的代码拷贝一份进去。拷贝之后,自定义函数的定义本来就在使用前,有没有头文件的声明都可以。为了验证include是不是就是将程序拷贝,做了实验验证。在自定义函数使用前#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文件,就相当于我们平时写的自定义函数一样。


终于搞明白了,so,Let’s enjoy it~

  • 29
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
C++】多⽂件程序结构 以前写⼀个C++多⽂件程序的时候经常为哪些东西应该放在.h⽂件⾥,哪些东西应该放在.cpp⽂件⾥⽽疑惑。稍有不慎就搞出⼀ 个"error:LNK2005 已经在*.obj定义"的重复定义错误,就算解决了这个问题⾃⼰实际上也还是⼀知半解。最近去了解了C++多⽂件程 序结构的知识,才搞清楚了这些问题的本质。在此总结⼀下,如有错误,欢迎指出。 声明与定义 ⾸先从声明和定义说起。 声明是数据对象的和函数的描述。声明的作⽤就是让编译器知道实体的名字,以及其数据类型或函数签名。如: external int x; //变量声明 void fun(); //函数声明 class A; //类声明 定义则是实体本⾝,代表着实体在⼀个作⽤域的唯⼀描述。 如: int x; //变量定义 void fun() {…} //函数定义 class A {…}; //类定义 因此,可以理解为,声明是定义的引⽤,⽽定义是实体本⾝。 外部链接性与内部链接性 定义具有链接性。链接性分为内部链接和外部链接。 外部链接:外部链接的定义可被定义所处的翻译单元(.cpp)内看见,也可以被其他翻译单元引⽤。 具有外部链接性的: · ⾮inline函数。包括命名空间⾮静态函数、类成员函数和类静态成员函数 · 类静态成员变量总有外部链接。 · 命名空间(不包括⽆名命名空间)⾮静态变量 内部链接:内部链接的定义只能在该定义所处的翻译单元内看见。 具有内部链接性的: · 所有的声明 · 命名空间(包括全局命名空间)的静态⾃由函数、静态友元函数、静态变量的定义、const常量定义 · enum定义 · inline函数定义(包括⾃由函数和⾮⾃由函数) · 类(class、struct、union)的定义 注:在类体定义的成员函数为内联(inline)函数,属于内部链接。 实质上声明没有链接性的概念,但可以理解成声明总是内部链接的,因为它只对它所在的翻译单元有效。如果我们把声明置于头⽂件,则由 于包含该头⽂件的每个翻译单元都独⽴复制了该声明(见下⽂预处理部分的说明),因此每个翻译单元都能"看到"这个声明。 预处理、编译和链接 C++,源程序要被翻译成可执⾏⽂件,都要经过三个步骤:预处理、编译和链接。 预处理:阅读源程序,执⾏预处理指令,嵌⼊指定源⽂件。预处理指令以"#"号开始。如#include指令实现⽂件包含。当⼀个.cpp⽂件编 译前,它⾸先递归地包含头⽂件,形成⼀个含有所有必要信息的单个源⽂件,也就是⼀个翻译单元。 编译: 编译器每次翻译⼀个.cpp⽂件(翻译单元),并输出对象⽂件(.o或.obj)。对象⽂件含有.cpp⽂件内定义的所有函数编译后的机器 码,也包含.cpp⽂件内定义的全局变量和静态变量。此外,对象⽂件也可能含有未定义引⽤,这些未定义的引⽤就是该翻译单元内有声明, 但是在这个.cpp⽂件没有定义的函数和全局变量。 那么,这些没有定义的东西在哪?答案是这些东西定义在其他.cpp⽂件。 要怎么找到呢?这就是链接器的任务了。 链接:有外部链接的定义可以在对象⽂件产⽣外部符号,这些外部符号可以被所有其他的翻译单元访问,⽤来解析他们未定义的引⽤。链 接器的⼯作就是读取所有对象⽂件,并尝试解决对象⽂件之间的交差引⽤。如果成功,则产⽣可执⾏程序。当⽆法解决外部引⽤的时候,根 据情况链接器有两种报错: 1、当找不到引⽤的⽬标时,就会产⽣"⽆法解决的外部符号"错误。 2、当找到两个或以上相同名字的实体(函数或变量时),就会产⽣"符号被多重定义"错误。 因此,要让程序正确地链接,⾸先不能声明⼀个实体,却没有相应的定义。⽐如在A.cpp⾥⾯声明⼀个void fun();但在这个⽂件和其他⽂件 都没有这个函数的定义(也就是函数的实现),这就会产⽣"⽆法解决的外部符号"错误。同时,也不能重复地定义具有外部链接性的实 体。⽐如,在A.cpp⽂件⾥⾯定义int x,同时在B.cpp⾥⾯⼜定义⼀个int x。这样就会出现"符号被多重定义"。 头⽂件 了解了上⾯⼏点知识,我们就可以理解和回答⼀些问题了。 (1)为什么不要把外部链接的定义放在头⽂件⾥⾯? 因为我们知道cpp在预编译的时候会递归包含头⽂件,因此,如果⼀个头⽂件包含了⼀个外部链接的定义,其他包含它的.cpp⽂件都会有⼀ 个相同的外部链接的定义。出现"符号被多重定义"也就不难理解了。要特别注意的是,类的静态成员变量和⼀般的静态变量不⼀样,它具 有外部链接性,因此假设你在头⽂件定义⼀个: class A { static int member; } 那么该静态成员变量的定义不能放在这个头⽂件⾥⾯。⽽是应该在某个.cpp⽂件⾥⾯写定义: int A::member; (2)有内部链接性的定义

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值