C++ - 继承 一些 细节 - 组合 和 继承的区别

2023-11-19

 前言

 本篇博客基于     C++ - 继承_chihiro1122的博客-CSDN博客          之上列出一些例子,如果有需要请看以上博客。

 继承的例子

 例1

 上述例子应该选择 C。

首先不用说,p3肯定是指向 d 对象的开头的;p1 也是指向 d 对象的开头的;

不同的是,p3 指向开头,p3 看到的是整个 d 对象; 而 p1 指向开头,p1 的类型是 base1* 指针的类型决定了这个指针能看多大的空间,p1 只能看 base1 个对象大的空间,那么 p1  只能看到 构造d对象之前,最先构造的 Base1 父类对象。(p1 是经典的切片)

 对于 p2 ,它指向的是 在 Base1 父类对象之后创建的 Base2 父类对象的开头位置,因为 Base2 父类对象不在 &d 这个位置,但是 p2 指针的类型是 Base2* ,所以要发生偏移,指向 Base2 父类对象的开头。(p2 同样的是 切片)

具体指向如下所示:

 现在我们再对上述题目进行修改,上述的 子类继承是先继承 Base1 再继承 Base2;现在我们先继承 Base2,再继承 Base1:

class Derrive : public Base2, public Base1 { public : int _d };

这时候结果就不一样了,是 p2 和 p3 指向 d 对象的开头;而 p1 指向中间的 base1;

这是因为 子类在构造的时候,要先构造父类对象,而如果是多继承,就要继承的先后顺序去构造父类。

 例2

 上述题目,如果按照继承当中构造顺序的话(先构造父类对象,在构造子类对象),按道理 打印的是 abacad(a 代表的是 class A,以此类推)。如果这样想的话就步入误区了。

但是打印结果却是:

class A
class B 
class C
class D

我们主要注意, D的构造函数当中的初始化列表:

D(const char* s1, const char* s2 , const char* s3, const char* s4)
:B(s1,s2), C(s1 , s3) , A(sa)

这里的初始化列表不是先走 B 的构造,而是 A的构造;之前也说过,构造函数当中初始化列表,不是按照初始化列表的顺序初始化,而是按照声明的顺序来初始化成员。

在上述A是写在最前面的,A 就是最先声明的,就要最先被初始化(构造)。

然后再去走 B 的构造,在 B 的构造函数的初始化列表当中也有 A(s1),但是此时不会去走,因为 A(s1) 在第一步 D 的构造函数当中就已经构造了,所以就不会去走 A(s1) 这一步构造。这里是菱形继承衍生出来的编译器自己处理的结果

以此类推,菱形继承除了之前说过的坑之外,还有很多的坑,上述就是其一,所以我们在写代码的时候要尽量避免写出菱形继承的结构。 

 既然在 B 和 C的构造函数的初始化列表当中的 A(s1) 不会构造,那么能不能去掉呢?

 当然是不能的。因为,上述例子只是 基于 D 子类的构造的菱形继承当中没有用到,但是如果只是 B 和 C 单独的构造,就可能需要用到 A(s1)了。

为什么说是可能需要呢?因为 A 基类当中是实现了 构造函数的 ,而且这个构造函数不是默认构造函数,如果不调用编译器不会自动调用;当然如果A 的构造函数当中有默认构造函数,可以不调用。

 例3

 其实在官方库当中,官方就自己使用了 菱形继承这个坑,在 io 输入输出流 的库当中,就有使用了菱形继承:
 

 上述的 iostream 当中有些功能上想使用 istream 和 ostream 两个类当中的某些功能,就直接继承了 istream 和 ostream 这两个类。但是这个两个类 都继承了 ios 这个基类,此处就构成了 菱形继承。

虽然是菱形继承,我们在使用上还是没有问题的。

但是不是代表建议我们在需要的时候首先考虑菱形继承,菱形继承不到万不得已建议是不要使用的。

 组合 和 继承

 什么是组合

所谓组合就是把多个类组合在一起,当我们在一个类的成员当中,定义了一个或多个其他类的对象成员,那么这个类就和其他类构成了组合的关系。如下所示:

class A
{
    // 成员变量 成员函数
};

class B
{
   private:
    A _aa; // 有A类对象的成员
}

 向上述的 A 类和B类就构成了组合关系,只要构造了 B类对象,就会在其中构造出 A类对象成员出来。

那么组合和继承都可以实现相同的效果,那么两者有什么区别呢?

继承和组合的区别

 我们通常称 继承是 白箱复用;而组合是 黑箱复用

 这里的 “白” 和 “黑”,分别指的是 “看得见” 和 “看不见” 的意思。

这里就不得不衍生出两种测试了:

  • 黑盒测试:测试人员看不见目标的内部实现,测试人员根据功能对目标进行测试。
  • 白盒测试:测试人员可以看见目标的内部实现,测试人员根据目标的内部实现去写测试用例。

 上述两种测试我们显而易见的发现,白盒测试相对于黑盒测试更加严格,在测试上也更加困难。

那么上述的 白箱复用 和 黑箱复用 也是一样的,都是看得见和看不见的问题。

如上述例子,在 B 当中组合了 A,那么 A 当中 public 修饰的成员变量 和 成员函数 都可以在 B 当中 使用 和 修改,但是 例如 private 修饰的,保护起来的 成员 就不能再 B 当中使用和 修改了,除非使用有元函数等等 ,突破权限的操作。这就是 黑箱复用

 那么 组合 和 继承的 区别也就显而易见了。

 继承 和 组合 的好处和坏处

 继承 当中 基类的内部实现细节 子类是可见的,这在一定程度上破坏了 我们对基类的封装。而且因为是子类直接继承父类当中的成员的关系,如果父类当中某一成员变量 或 成员函数发生了改变,这一改变对其子类的影响是很大的。对于继承来说,基类和其派生类之间的依赖关系很紧,两者之间的耦合度高。

对象组合 是 类继承之外的一种复用选择,组合同样可以实现继承的功能,但是组合对 被组合 的对象(如上述的A类)要求是有良好定义的接口。而且组合的耦合性更低。

假设 A类 当中有 100个成员,20个公有成员,80 个 私有/保护成员。对于继承来说,修改 A类 当中 100个成员当中的任意一个都会对 其派生类 B类有影响;但是对于组合来说,只要不修改 20个公有成员,对 B类影响都不大。对于组合来说 公有的越少,耦合度就越低,但是对于继承来说不管公有多少个,两者都有很大的耦合度。

在现实编程当中,我们尽量能使用组合就使用组合,因为组合的耦合度低,而且也可以达到继承的功能;但是不能为了使用组合而使用组合,因为继承还是有很多适用场景的,适合用继承就使用继承,而且多态的使用是必须使用继承的,组合是不能实现多态的。

那么现实当中我们该如何更好的分辨我们该如何使用 组合 和 继承呢?
 

其实,public:继承,是一种 is-a 的关系,is-a 意思是:一个子类就是一个父类; 比如:一个老师是一个人,一个学生也是一个人·····

而 组合 是一种 has-a 的关系,has-a 意思是:假设B组合了A,那么每一个 B 对象当中都有一个 A 对象。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ - 继承 一些 细节 - 组合 和 继承的区别 的相关文章

随机推荐

  • CocosCreator之KUOKUO带你做小小赛车-摄像机跟随

    本次引擎2 0 5 编辑工具VSCode 目标 小小赛车 先亮素材 很简单 就两个 爱给网中的赛道 以及一个小车 好了 让我们新建工程然后把赛道放进去 调整方向与大小 然后把小车拖上去 这样 我是把赛道放大了2倍 旋转了90度 拖一拖位置
  • onclick传参使用function()

    对于有需要传参的按钮 需要按照以下的方式进行 直接上代码
  • 9、Linux(Ubuntu 18)安装Redis以及C操作Redis

    扩展知识 头文件搜索 Linux中库的头文件 首先include有两种写法 一种是 include 另一种是 include xxx 这两种写法的区别是 include xxx 会首先在当前目录下搜索头文件 不递归 如果找不到的话再去系统目
  • 3分钟玩转:ES6 模块化

    ES6 模块 ES6 使用 export 和 import 导出和导入模块 导出模块 一个模块就是一个独立的 JS 文件 该文件内的变量外部无法获取 若希望能让外部获取模块内的变量 则要用 export 关键字暴露变量 分别暴露 命名行内导
  • Windows11右键菜单太烦人,简单几步即可恢复旧版完整菜单

    Windows 11已经推出一段时间了 相比Windows 10 界面确实美观了不少 同时也有很多新的设计 但是并不是每个人都能很快适应这种新设计 被广泛吐槽的一点就是右键菜单的改变 增加了显示更多选项 原来的很多右键选项被隐藏起来了 原本
  • tkinter 的界面美化库:ttkbootstrap 使用教程

    嗨害大家好鸭 我是芝士 tkbootstrap 是一个基于 tkinter 的界面美化库 使用这个工具可以开发出类似前端 bootstrap 风格的 kinter 桌面程序 如果会 tkinter 学习起来就会非常简单 如果不会的话只要先花
  • opencv python contours结构

    opencv python contours结构 经常需要构造 如果没记住内部具体结构 需要到网上处找 且找不到 就要自己findcontours然后打印出来 比较麻烦 contours的结构 比如一个box有xmin ymin xmax
  • 今天发现一个好网站 http://www.phpv.net/

    该网站的空间速度快 资料丰富 容易搜索 更新快 爽
  • 运维之道

    方法一 rc local 1 由于在centos7中 etc rc d rc local的权限被降低了 所以需要赋予其可执行权 chmod x etc rc d rc local 2 赋予脚本可执行权限 假设 opt script auto
  • pytorch训练error

    问题一 在pytorch上训练分割模型时 出现cuda runtime error 59 device side assert triggered at xxx 解决办法 通过CUDA LAUNCH BLOCKING 1 python3 m
  • python----小数点精度控制round()

    python版本也会影响结果 python2把x四舍五入为远离0的最近倍数 如round 0 5 1 round 0 5 1 python3则会把x四舍五入为最近的偶数倍数 如round 0 5 0 round 1 5 2 0 round
  • 查看解决inode使用率100%的问题

    今天登录后端服务器查看 发现程序报错日志中存在磁盘空间不足的情况 df h后发现磁盘空间充足 df ih发现 app分区inode使用率100 开始查找原因 进到 app 下 然后 for i in do echo i find i wc
  • hashMap常见的问题解答

    1 HashMap的数据结构 hashmap采取数组 链表的数据结构 在遇到哈希冲突的时候采用链表结构来解决哈希冲突 jdk1 8后分成了两种情况 bucket中元素个数大于8的时候 自动转换为红黑树的结构 目的是因为链表的查询速度比较慢
  • vue+element table 合并列

    vue element table 合并列
  • 【TCP/IP详解 卷一:协议】TCP的小结

    前言 TCP学习的综述 在学习TCP IP协议的大头 TCP协议 的过程中 遇到了很多机制和知识点 详解中更是用了足足8章的内容介绍它 TCP协议作为 应用层 和 网络层 中间的 传输层协议 既要为下面的网络层协议保证连接的可靠性 IP协议
  • 通过Jib将Springboot应用通过Docker部署

    一 安装Docker 1 更新Yum包 yum update 2 卸载旧版本 如果安装过旧版本的话 1 删除软件包 yum remove y docker docker client docker client latest docker
  • 【Espruino】NO.14 温湿度传感器DHT11

    http blog csdn net qwert1213131 article details 35828873 本文属于个人理解 能力有限 纰漏在所难免 还望指正 小鱼有点电
  • 环境变量是如何生效的——以Linux操作系统为例

    什么是环境变量 从我们学习Java开始 就经常接触一个东西 PATH 也叫环境变量 环境变量是操作系统提供给应用程序访问的简单 key value字符串 windows linux mac都有同样的概念 环境变量的作用 当我们拥有一个可执行
  • Git版本回退并强制推送到远端

    Git版本回退并强制推送到远端 本文参考廖雪峰的Git教程 前言 本文章解决问题的前提是本人不小心修改了本地代码仓库的最外层目录权限 不知道原权限是什么 导致本地git提示几十个文件被修改过 实际内容并未修改 可能是目录权限改变被git识别
  • C++ - 继承 一些 细节 - 组合 和 继承的区别

    前言 本篇博客基于 C 继承 chihiro1122的博客 CSDN博客 之上列出一些例子 如果有需要请看以上博客 继承的例子 例1 上述例子应该选择 C 首先不用说 p3肯定是指向 d 对象的开头的 p1 也是指向 d 对象的开头的 不同