C++ 在.h文件中包含头文件和在.cpp文件中包含头文件有什么区别?前置声明

现有两个文件Test.h 和Test.cpp
#include 在Test.h中包含 和在Test.cpp中包含有什么区别?

1、在cpp文件中包含.h文件,要么你要用到这个头文件中的函数或者类,要么就是实现这个头文件;

2、.h —就是为了放一堆声明所产生的东西。
如果是定义放在.h中。 如果.h被重复包含多次,那么则会被报重定义。所以在.h 中都要—如果函数就要是inline ,如果是变量就要 selectany (windows)才不会被报错。

3、#include尽量写到cpp文件里。两个文件在.h文件里相互include,就会产生编译错误,而两个文件在.c文件互相include,就不会有该问题,因此在.h文件include就要避免互相包含的问题,而.cpp文件就不需要考虑

4、
1)在 .h 里面 include 的好处是:
如果很多.c,.cpp文件,都包含一批头文件,
如果复制很容易遗漏
如果输入,很容易出错

如果全部在一个.h, include 那么每个.c,.cpp文件只需要一个#include 语句
这样不仅输入量减少,
而且代码也美观多了
代码也主次分明了
毕竟,.c.cpp, 里面
要实现的函数,才是主要代码

2)主要缺陷,
可能会包含完全不需要的头文件,
增加编译工作量

5、如果你在a.h头文件中include了“stdio.h”,“iostream”,……一大堆
那么你的a.cpp源文件只要include你的a.h,就相当于include了“stdio.h”,“iostream”,……一大堆
但是当其他文件include你的a.h的同时也就包含了“stdio.h”,“iostream”,……一大堆

这个要看你个人需要,如果你需要让其他文件也include一大堆,那么写在a.h中就可以,其他文件包含a.cpp简单整洁无脑
如果只有a.cpp需要include一大堆,那么还是建议在a.cpp中include一大堆

6、如果a.c包含了头文件a.h,a.h包含了头文件b.h,b.c也包含了b.h,那么当b.h发生改变时,a.c和b.c都会重新编译

也就是所有包含了b.h的都会重新编译,无论是直接包含,还是间接包含

7、2点原则:

第一个原则:如果可以不包含头文件,那就不要包含了,这时候前置声明可以解决问题。如果使用的仅仅是一个类的指针,没有使用这个类的具体对象(非指针),也没有访问到类的具体成员,那么前置声明就可以了。因为指针这一数据类型的大小是特定的,编译器可以获知(C++编译器自上而下编译源文件的时候,对每一个数据的定义,总是需要知道定义的数据的类型的大小)

第二个原则:尽量在CPP文件中包含头文件,而非在头文件中。假设类A的一个成员是是一个指向类B的指针,在类A的头文件中使用了类 B的前置声明并编译成功,那么在A的实现中我们需要访问B的具体成员,因此需要包含头文件,那么我们应该在类A的实现部分(CPP文件)包含类B的头文件而非声明部分(H文件)

C++的类声明、前置声明、定义及各自优势、使用场景

当一个class的成员变量为另外一个类的指针类型的时候,此时,我们可以不在头文件中来包含这个类的头文件,即不使用include“.h”,而是采用前置声明的形式,对这个类进行一个类型的前置声明。

类的前置声明形式上:只有class关键字+类名,后面不带任何符号,即{}
白话解释:哎,b是个类,我要用它,你别管,它在某个地方是个真实的存在(也可能不存在,我就tm忽悠你的), 总之你不要管,我要用它”。这样跟编译器打过招呼之后,就能保证代码编译通过。
如下代码段

//文件a.hclass B;class A{public:B *b;    };//文件b.hclass B{public:void func1();};

a.hb.h这两个文件中定义了两个类分别是A和B。其中A类有一个成员b是Class B的指针,这个时候可使用类前置声明

前置声明省去了#include的处理、降低文件之间的编译依赖,从而避免不必要的编译时间浪费。

利用前置声明的利弊

  • 优点:避免#include, 避免修改一个被包含的头文件,导致整个文件重新编译。

  • 缺点:(摘自Google)如果前置声明关系到模板,typedefs, 默认参数和 using 声明,就很难决定它的具体样子了。

前置声明作用:
告诉编译器,这个类在其它地方定义,真正使用类的定义的时候才进行编译。

Note:类A在编译的时候不需要拿到类B的定义是因为这里面定义的是指针,而对于指针是不需要定义就可以进行内存布局的,在编译A的类的声明的时候,在进行内存布局的时候是不需要拿到B的定义的

但是在A的成员函数中如果使用这个指针b,则就需要在A对应的.cpp文件中引入B的定义。也就是在.cpp中需要include b.h

这里面对于前置声明,还需要注意一个事情,就是如果另外有一个类,这个类使用了类A,并且通过类A访问使用了A的成员变量b,比如下边这种情况

//文件c.h#include "a.h"class C{public:void print(){a.b->func1();}A a;};

编译时,讲会报错:不允许使用不完全(incomplete)类型A。
Because:C中访问了a成员b的成员函数。所以,此时也需要类B的完整定义,即include

比较正规的做法就是在C的头文件中也引入B的定义,这样编译器就能找到B的定义,从而编译通过

如法1

1
//文件c.h#include "a.h"#include "b.h"class C{...}2//文件a.h#include "b.h"//class B;class A{public:B *b;    };

法2
在类A的头文件中引入b.h头文件
这种写法,就是放弃了前置声明,直接在A中引入B的头文件,一次性将B的全部声明引入到A的头文件中。

这样也可以编译通过,但是会带来一个问题。就是以后所有引用A的头文件的其他文件,在编译的时候都需要编译一遍B,而在这些文件中可能压根就没有访问成员变量b。也就是说这些文件其实根本就不需要B的定义。这样会导致编译这些文件的时候,还需要编译一遍不相关的类B。这会导致编译缓慢

所以,这里面正确的做法是采用前一种方法。也就是说,头文件中能尽量少的引入头文件就应该尽量少的引用收文件,能用前置声明的就应该尽量使用前置声明

几种可以使用前置声明的 地方

1.以指针或引用的形式来引用类型

class A;
class B
{A *pa;//okA &ra;//okA * f(const A * pa);//okA & f(const A & pa);//ok
};

2.友元

class A;
class B
{friend A; //okfriend class A;//ok
};

3.作为返回值或者参数

class A;
class B
{void f(A a);//okA g();//ok
};

不可以使用前置声明的类型
1.使用完整的类引用

class A;
class B
{//A a; //error};

2.被当作父类

class E;
class F: public E  //error VC6.0--'E': base class undefined  Qt5.6-invalid use of incomplete of 'class C'
{};

使用前置声明和没有使用前置声明的编译过程及其差异

假设有两个类 A 和 B,A 类包含了 B 类的一个指针成员,并且在 A.cpp 文件中使用了 B 类的成员函数。我们将比较两种情况下的编译过程和差异。

情况1:使用前置声明
A.h

#ifndef A_H
#define A_Hclass B; // 前置声明class A {
public:A();void doSomethingWithB();
private:B* ptrB; // 使用B类的指针成员
};#endif // A_H

A.cpp

#include "A.h"
#include "B.h" // 包含B类的头文件A::A() {ptrB = new B(); // 使用B类的指针成员
}void A::doSomethingWithB() {ptrB->someFunction(); // 调用B类的成员函数
}

情况2:没有使用前置声明

A.h

#ifndef A_H
#define A_H#include "B.h" // 直接包含B类的头文件class A {
public:A();void doSomethingWithB();
private:B* ptrB; // 使用B类的指针成员
};#endif // A_H

A.cpp

#include "A.h"A::A() {ptrB = new B(); // 使用B类的指针成员
}void A::doSomethingWithB() {ptrB->someFunction(); // 调用B类的成员函数
}

编译过程及差异:

  • 使用前置声明(情况1):

    • A.h 中只有 B 类的前置声明,A.cpp 中包含了 A.h B.h
    • 编译器在编译 A.h 时会知道 B 类的存在,但不知道 B 类的具体内容。编译 A.h 时不需要 B 类的完整定义,只需要知道它是一个类的声明即可。
    • 如果 B 类的成员函数在 A.cpp 中被调用,则编译器需要知道这些函数的声明,因此需要在 A.cpp 中包含 B.h
    • 编译器将编译 A.cpp,生成目标文件 A.o
  • 没有使用前置声明(情况2):

    -A.h直接包含了 B.h,因此 A.h A.cpp 都会有 B 类的完整定义。

    • 在编译 A.cpp 时,编译器会将 B 类的完整定义和 A 类的定义都放入编译过程中。
    • 如果 B 类的头文件发生变化,会导致包含了 B.h 的文件都需要重新编译。
    • 编译器将编译 A.cpp,生成目标文件 A.o。

差异:

  • 使用前置声明:减少了对 B 类的完整定义的依赖,只需要类的声明即可,从而减少了编译时间。但需要在使用 B 类成员函数时,在 A.cpp 中包含 B.h。
  • 没有使用前置声明:导致 A.h 中包含了 B.h 的完整定义,增加了对 B 类的依赖,编译时间可能更长,并且如果 B.h 发生变化,可能会导致更多文件重新编译。

原文链接:https://blog.csdn.net/qq_42681507/article/details/130921276
原文地址:https://blog.csdn.net/xueruifan/article/details/50569639

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/232772.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VMware Ubuntu虚拟机忘记密码

​​原文 https://blog.csdn.net/ezconn/article/details/89328024​​​​​​​ 前言: 在VMware运行Ubuntu虚拟机时,开机之后忘记密码怎么办? 环境:Ubuntu版本:ubuntu-16.04.6-server-amd64;VMware版本…

乐理基础-弱起小节、弱起

弱起小节的定义: 1.音乐不是从强拍开始的,是从弱拍或次强拍开始的。 2.弱起小节会省去前面没有音乐的部分,它是不完整的小节,它的拍数是不够的。如图1 弱起小节的作用: 强拍经常要作为 和弦出现 和 变化的地方&#xf…

德人合科技 | 防止公司电脑文件数据资料外泄,自动智能透明加密保护系统

【透明加密软件】——防止公司电脑文件数据资料防止外泄,自动智能透明加密保护内部核心文件、文档、图纸、源代码、音视频等资料! PC端访问地址: www.drhchina.com 🌟 核心功能: 透明加密:采用高级加密算…

EasyExcel合并相同内容单元格及动态标题功能的实现

一、最初版本 导出的结果: 对应实体类代码: import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentLoopMerge; import com.al…

全链路压力测试:解析其主要特点

随着信息技术的飞速发展和云计算的普及,全链路压力测试作为一种关键的质量保障手段,在软件开发和系统部署中扮演着至关重要的角色。全链路压力测试以模拟真实生产环境的压力和负载,对整个业务流程进行全面测试,具有以下主要特点&a…

Nginx网站服务详解(Nginx服务的主配置文件 ——nginx.conf)

目录 一、全局配置的六个模块简介 二、Nginx配置文件的详解 1)全局配置模块 2)I/O 事件配置 3)HTTP 配置 4)web服务监听设置 5)其他设置 location常见配置指令:“root、alias、proxy_pass 对比&a…

【数据分享】2019-2023年我国地级市逐年新房房价数据(免费获取/Excel/Shp格式)

房价是一个城市发展程度的重要体现,一个城市的房价越高通常代表这个城市越发达,对于人口的吸引力越大!因此,房价数据是我们在各项城市研究中都非常常用的数据!之前我们分享了2019—2023年我国地级市逐月的新房房价数据…

揭秘`v-if`和`v-show`的区别:选择正确指令的技巧(下)

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

EasyExcel模板导出(行和列自动合并)

1.需求背景: ①需要从第三方获取数据,第三方接口有两个参数,开始时间和结束时间 ②获取回来的数据并没有入库,所以不能通过数据库将数据归类统计,excel合并大概的流程是判断上一行或者左右相邻列是否相同,然后进行合并,所以不能是零散的数据且客户要求每一个自治区和每一个航站…

系统分析师(软考)知识点整理(一)

第一章 信息 信息是不确定性的减少 xi: n个状态中的第i个状态p(xi):出现第i个状态的概率b: b一般取值为2 特征 #mermaid-svg-pvPkY9RE5GZIIIxl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-pvPkY9RE5GZIIIxl…

基本数据类型与 字符串相加的 知识点

在Java中,基本数据类型和字符串的相加操作会触发字符串连接(concatenation)操作。这是因为Java中的字符串是不可变的,即一旦创建,就不能修改。因此,当你将一个基本数据类型的值与字符串相加时,J…

系统学习Python——装饰器:基础知识-[函数装饰器:使用方法]

分类目录:《系统学习Python》总目录 函数装饰器已经从Python2.4开始可用。正如我们在前文所见到的,它们大体上是一种语法糖:在def语句结束时通过另一个函数来运行这个函数,把最初的函数名重新绑定到返回的结果。 函数装饰器是一种…

一篇文章带你了解各个程序员接单平台,让你选择不再迷茫!!!

相信现在很多程序员都已经走上了或者准备走上网上接单这条路,但是目前市面上的接单平台可谓五花八门,对于各个平台的优缺点,不同的程序员该如何选择适合自己的接单平台,你又是否了解呢? 接下来就让小编用一篇文章来为…

js对象转换为excel,excel转换为js对象

文章目录 前言js转excelexcel转js对象总结 前言 使用module.exports导出,require导入 修改后缀为mjs,可使用importd导入 ,export default \export导出(自用过一次,后面忘记怎么改的了) js转excel 需要安装nodejs node …

Gemini自曝中文用百度文心一言训练,网友看呆:大公司互薅羊毛??

谷歌Gemini中文语料疑似来自文心一言??? 先是有读者向我们爆料: 在谷歌Vertex AI平台使用该模型进行中文对话时,Gemini-Pro直接表示自己是百度语言大模型。 很快,有微博大V阑夕夜也发博称: 在…

超实用的Web兼容性测试经验总结,建议Mark

在日常工作中,我们经常碰到网页不兼容的问题。我们之所以要做兼容性测试,目的在于保证待测试项目在不同的操作系统平台上正常运行。 主要包括待测试项目能在同一操作系统平台的不同版本上正常运行;待测试项目能与相关的其他软件或系统的“和…

pip install xxx时候遇到Read timed out的问题

我们在安装一些包的时候,比如TensorFlow等等,如果直接使用: pip install tensorflow 这句命令来进行安装的时候,我们会发现此时安装的速度不仅非常慢,而且还有可能出现网络超时的情况,比如如下问题: Trac…

手撕HashMap源码2

目录 引言 putTreeVal红黑树添加结点方法讲解 treeifyBin进行树化的方法(虚假的树化) treeify真正的树化操作 从扩容的部分来分析红黑树的代码 split红黑树扩容迁移的方法 untreeify链化(退树成链) 红黑树代码分析 rota…

JS实现简单的gpt打字机效果

功能要求 在后台传输得到的数据在展示时候进行打字机效果一个字一个字进行展示 实现想法 1、在一个滚动容器中展示一个个文字,也就是将全部数据截取出来添加到一个新数组中 2、采用循环或者定时器进行编写 注意:前提条件是需要后端传值提供 换行符号…

Pytorch读写张量文件

目录 一、加载和保存张量 1、直接读写张量 2、读写张量列表 3、读写张量字典 二、加载和保存模型参数 一、加载和保存张量 1、直接读写张量 对于单个张量,我们可以直接调用load和save函数分别读写它们。这两个函数都要求我们提供一个名称,save要求将…