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

news2025/1/2 2:45:54

 前言

 本篇博客基于     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 对象。

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

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

相关文章

网站监控系统最佳实践之静态资源采样上报

作者 观测云 产品服务部门 深圳团队 朱端畅 背景说明 通过 RUM 采集前端数据时,若采集的数据过多,可能会导致占用过多的网络带宽以及其他资源。特别是刚进入首页加载数据时,可能会调用几十次甚至更多次 v1/write/rum?precisionms数据采集接…

spacy安装旧版本en_core_web_sm的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

Navicat Premium 16.2.7 数据库管理教程

Navicat Premium 16.2.7是一款功能强大的数据库管理工具,支持多种数据库类型,包括MySQL、Oracle、SQL Server等。以下是使用Navicat Premium的基本步骤: 安装Navicat Premium软件。打开Navicat Premium,在主界面上选择“新建连接…

实现高效数据存储:OpenStack Swift与本地文件系统的完美对接

文章目录 Swift对接本地文件系统前言控制节点新增20G磁盘针对磁盘做分区(2个)针对磁盘格式化卸载原有的 Swift 虚拟设备创建两个目录并挂载更改权限创建builder创建ring再平衡测试配合glance对接swiftglance对接swift测试 Swift对接本地文件系统 前言 实…

OpenCV实战(31)——基于级联Haar特征的目标检测

OpenCV实战(31)——基于级联Haar特征的目标检测 0. 前言1. Haar 特征图像表示2. 基于级联 Haar 特征的二分类分类器3. 级联分类器算法流程4. 使用 Haar 级联检测器进行人脸检测5. 完整代码小结系列链接 0. 前言 在机器学习基础一节中,我们介…

【Linux】文件缓冲区

目录 一、缓冲区图解二、自定义实现文件操作函数三、强制刷新内核缓冲区(fsync) 提到文件缓冲区这个概念我们好像并不陌生,但是我们对于这个概念好像又是模糊的存在脑海中,之间我们在介绍c语言文件操作已经简单的提过这个概念&…

NoSQL MongoDB Redis E-R图 UML类图概述

NoSQL NoSQL(Not only SQL)是对不同于传统的关系数据库的数据库管理系统的统称,即广义地来说可以把所有不是关系型数据库的数据库统称为NoSQL。 NoSQL 数据库专门构建用于特定的数据模型,并且具有灵活的架构来构建现代应用程序。NoSQL 数据库使用各种数…

CIM和websockt-实现实时消息通信:双人聊天和消息列表展示

欢迎大佬的来访,给大佬奉茶 一、文章背景 有一个业务需求是:实现一个聊天室,我和对方可以聊天;以及有一个消息列表展示我和对方(多个人)的聊天信息和及时接收到对方发来的消息并展示在列表上。 项目框架概…

SNP 分享:SAP S/4HANA Cloud 私有云版本及其独特优势

近几年来,SAP一直强调其愿景是帮助客户达成智慧型企业(Intelligent Enterprise),为此其相关产品也在不断进行快速迭代,其核心就是S4HANA。同时SAP一直强调其要成为一家云计算公司,近些年也一直在推行云优先战略(Cloud First)。因此…

指针(通过指针间接访问内存)

#include <iostream> #include <algorithm> using namespace std; int main() { int a 2;//定义指针 &#xff1a; 数据类型 *指针变量名;int *p &a;cout << &a << " " << p << endl;//使用指针 &#xff1a; 可以通过…

怎么把表情包做成动态?分享一个简单的方法

表情包在我们的日常交流中已经成为了一种非常流行的表达方式&#xff0c;而将表情包做成动态则可以让它更加生动有趣。本文将介绍如何将表情包制作成动态图&#xff0c;以及一些简单的方法和制作注意事项。 制作动态表情包的方法有很多种&#xff0c;以下是其中两种简单易行的方…

SpringCloud面试题大全(Netflix+Alibaba)

SpringCloud面试题大全 ​ Spring cloud 是一个基于 Spring Boot 实现的服务治理工具包&#xff0c;用于微服务架构中管理和协调服务的。Spring Cloud 是一系列框架的有序集合。它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发&#xff0c;如服务发现注…

华为云云服务器评测 | 3分钟搞懂如何在华为云服务器安装Nginx并配置静态访问页面

文章目录 一、什么是Nginx&#xff1f;二、申请华为云服务器三、使用XShell连接华为云服务器并安装Nginx四、FileZilla连接服务器五、Linux下安装Nginx❇️配置80端口并关闭Linux防火墙✳️测试 六、配置静态html至华为云服务器并访问⚠️在华为服务器新建路径⏰使用Filezilla上…

java+ssm+mysql电费管理系统

项目介绍&#xff1a; 使用javassmmysql开发的用户电费管理系统&#xff0c;系统包含超级管理员&#xff0c;系统管理员、用户角色&#xff0c;功能如下&#xff1a; 超级管理员&#xff1a;管理员管理、用户管理、用电管理&#xff08;用电记录、缴费提醒&#xff09;、电费…

C++多态案例-设计计算器类

1.前置知识点 多态是面向对象的三大特性之一 多态分为两类 静态多态&#xff1a;函数重载和运算符重载都属于静态多态&#xff0c;复用函数名动态多态&#xff1a;派生类和虚函数实现运行时多态 静态多态和动态多态的区别 静态多态的函数地址早绑定-----编译阶段确定函数地…

Navicat Premium 16.2.7 for Mac

Navicat Premium 16是一款功能强大的跨平台数据库管理工具&#xff0c;支持多种数据库类型&#xff0c;如MySQL、MariaDB、Oracle、SQLite、PostgreSQL等等。它提供了丰富的数据库管理功能和工具&#xff0c;可以帮助开发人员和数据库管理员快速地创建、管理和维护数据库。 Nav…

采用第11代Intel®Core处理器的多网口嵌入式边缘计算平台

Intel Core™ 11th i7/i5/i3/Celeron 处理器 及 8GB DDR4 3200Mb/s 内存4 x GbE, 3 x USB 3.2 Gen2, 1 x USB2.0, 1 x HDMI 1.4, 1 x DP 1.4a, 4 x RS232/422/485可选的第二堆栈支持多达2 x iDoor扩展&#xff0c;用于扩展无线连接、工业现场总线或更多I/O紧凑型无风扇设计零电…

1.15 自实现GetProcAddress

在正常情况下&#xff0c;要想使用GetProcAddress函数&#xff0c;需要首先调用LoadLibraryA函数获取到kernel32.dll动态链接库的内存地址&#xff0c;接着在调用GetProcAddress函数时传入模块基址以及模块中函数名即可动态获取到特定函数的内存地址&#xff0c;但在有时这个函…

做答题小程序前期需要准备哪些工作

做一个答题小程序前期需要做哪些准备工作呢&#xff1f; 第一、要有明确的答题活动规则需求&#xff0c;比如是想用个人答题形式、还是pk答题形式&#xff0c;每个模式具体的出题规则和得分规则&#xff0c;这些要计划清楚&#xff0c;让开发答题小程序的公司能够充分理解你的需…

vue3哪个数组方法在vue2上做了升级处理

在 Vue 3 中&#xff0c;v-for 指令的数组更新行为进行了升级处理。在 Vue 2 中&#xff0c;当使用 v-for 渲染数组时&#xff0c;如果对数组进行了以下操作&#xff0c;Vue 无法检测到变化&#xff1a; 直接通过索引修改数组元素&#xff0c;例如 arr[0] newValue修改数组的…