【C++历险记】面向对象|菱形继承及菱形虚拟继承

news2025/1/19 0:54:57

个人主页:兜里有颗棉花糖💪
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创
收录于专栏【C++之路】💌
本专栏旨在记录C++的学习路线,望对大家有所帮助🙇‍
希望我们一起努力、成长,共同进步。🍓
在这里插入图片描述

目录

  • 一、多继承以及菱形继承
  • 二、多继承引发的问题
    • 多继承二义性问题的解决方式
      • 方式一:作用域解析运算符
      • 方式二:虚拟继承
  • 三、虚拟继承解决数据冗余和二义性的原理

一、多继承以及菱形继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承。

比如:
在这里插入图片描述
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。

比如:在这里插入图片描述

菱形继承:菱形继承是多继承的一种特殊情况,指一个派生类直接或间接地从两个或者更多个基类继承成员,而这些基类又直接或间接地继承自同一个基类。

比如:
在这里插入图片描述

下面是两种简单的菱形继承的模型:
在这里插入图片描述

二、多继承引发的问题

C++继承体系中的多继承虽然给我们提供了代码的灵活性和重用性,但是也会引发一些问题:多继承会引发菱形继承问题,而菱形继承问题又会引发菱形虚拟继承问题。

下面来看菱形继承的问题:
在这里插入图片描述
上面的对象成员模型构造中,可以看出菱形继承有数据冗余二义性的问题。在Assistant的对象中Person成员会有两份(即_age有两份)。

  • 数据冗余问题(本质就是浪费空间):存在数据重复的问题,比如Person中的_age要存储两份
  • 二义性问题访问不明确,如下图。
    在这里插入图片描述

多继承二义性问题的解决方式

C++是如何解决多继承带来的二义性问题呢?

方式一:作用域解析运算符

我们可以通过作用域解析运算符,即::来解决多继承中的二义性问题。使用作用域解析运算符来明确指定调用哪个基类的成员函数或变量。
请看:
在这里插入图片描述
在这里插入图片描述

方式二:虚拟继承

虚拟继承:用于解决菱形继承或多继承中的二义性问题的一种机制。通过使用virtual关键字,在继承链中只创建一个共同基类的实例,从而避免了二义性。

请看:

class Person
{
public:
	string _name; // 姓名
	int _age;
};
class Student : virtual public Person
{
protected:
	int _num; //学号
};
class Teacher : virtual public Person
	
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
void Test1()
{
	Assistant as;
	as.Student::_age = 18;
	as.Teacher::_age = 21;
	as._age = 24;
}

解释:

首先,有一个基类 Person,它包含了姓名 _name 和年龄 _age 两个成员变量。

接下来有两个派生类 Student 和 Teacher,它们都以虚拟继承的方式继承自基类 Person。这样做是为了避免后续的Assistant 类在同时继承Student 和 Teacher 时,包含了两个相同的 Person 实例导致的二义性问题。

最后,有一个派生类 Assistant,它同时继承自 Student 和 Teacher 类。由于 Student 和 Teacher都是以虚拟继承的方式继承自 Person,在Assistant 类中就只会有一个共同的 Person 实例。

在这里插入图片描述

调试结果如下:
在这里插入图片描述

在这里插入图片描述

三、虚拟继承解决数据冗余和二义性的原理

我们已经知道:虚拟继承是C++中的一种继承方式,用于解决多重继承中的数据冗余和二义性问题。当一个类需要从多个基类中继承,而这些基类又有共同的基类时,就会产生二义性问题。

那虚拟继承又是如何解决这些问题的呢?

现在我们来研究虚拟继承原理,下面是一个简化的菱形继承继承体系,请看:

class A
{
public:
	int _a;
};
class B : public A
{
public:
	int _b;
};
class C : public A
{
public:
	int _c;
};
class D : public B, public C
{
public:
	int _d;
};
int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

运行调试结果如下:

监视窗口
在这里插入图片描述

内存窗口
在这里插入图片描述

可以看到上述代码中存在数据冗余的问题类D继承了类B和类C,与此同时类B和类C都继承了类A,所以可以看到在类D中有两个继承自类A的子对象,分别来自类B和类C。因此,在类D中存在数据冗余,同一个成员变量_a在类D的内存布局中会出现两次,一次来自类B的继承,一次来自类C的继承。这是因为默认情况下,多次继承同一个基类会导致该基类的成员在派生类中有多份副本

下面来看虚拟继承是如何解决上述问题的,请看:

class A
{
public:
 int _a;
};
class B : virtual public A//虚拟继承
{
public:
 int _b;
};
class C : virtual public A//虚拟继承
{
public:
 int _c;
};
class D : public B, public C
{
public:
 int _d;
};

实例一:

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	d._a = 0;
	return 0;
}

下面是上述代码虚拟继承的调试内存窗口:
在这里插入图片描述

示例二(仅仅添加了对象D d2;):

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	d._a = 0;
	D d2;
	return 0;
}

现在再来看一下内存窗口:
在这里插入图片描述
在这里插入图片描述

示例三(再来看一个对象模型):

int main()
{
	B b;
	b._a = 1;
	b._b = 2;
	return 0;
}

在这里插入图片描述
示例四:

int main()
{
	D d;
	d._a = 1;

	B b;
	b._a = 2;
	b._b = 3;

	B* ptr = &b;
	ptr->_a++;

	ptr = &d;
	ptr->_a++;
	return 0;
}

指针 ptr 指向对象 b 或对象 d 时,无论 ptr 指向的是哪个对象,当使用 ptr->_a 访问类A的成员时,编译器都会使用存储在类D对象中的偏移量来调整指针,以便正确地访问虚基类A的成员变量 _a

好了,本文就到这里啦,再见啦友友们!!!

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

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

相关文章

《多线程编程实战指南》总结

Java 并发和多线程编程推荐《Java 并发编程实战》和《多线程编程实战指南》,前者是外国非常受欢迎的书籍的翻译本,后者是国人写的书,符合国人的思维模式。 进程、线程与任务 在操作系统中会运行多个程序,一个运行中的程序就是一个…

msvcp120.dll丢失的解决方法?全面解决方法推荐

msvcp120.dll是Windows操作系统中的一个关键组件,如果丢失或损坏,可能会导致系统崩溃或无法正常运行。本文将介绍三种解决msvcp120.dll丢失问题的方法。 随着计算机应用的广泛普及,越来越多的人开始遇到各种电脑问题。其中,msvcp…

淘宝商品销量接口API更新(总销+精准月销API)

不少客户有获取淘宝商品销量的需求,淘宝商品销量接口主要用于以下业务场景。有不齐全的欢迎大家补充。 库存管理:商家可以通过接口获取到实时的销量信息,更好地进行库存管理。供应链计划:商家可以通过接口了解到商品的销售趋势&a…

一篇文章教会你什么是二叉搜索树

二叉搜索树 二叉搜索树概念二叉搜索树操作1.二叉搜索树的查找2.二叉搜索树的插入3.二叉搜索树的删除4.二叉搜索树的遍历 二叉搜索树的实现1.二叉搜索树节点结构2.二叉搜索树类3.二叉搜索树的构造及析构4.二叉搜索树的拷贝构造及赋值重载5.二叉搜索树插入6.二叉搜索树查找7.二叉…

Mybatis学习|日志工厂、分页

1.日志工厂 如果一个数据库操作,出现了异常,我们需要排错。日志就是最好的助手! 曾经: sout、debug 现在:日志工厂! 我们主要掌握STDOUT_LOGGING 和LOG4j 在Mybatis中具体使用哪个一日志实现,在设置中设定! 在mybatis核心配置文件中&#…

使用 ElasticSearch 作为知识库,存储向量及相似性搜索

一、ElasticSearch 向量存储及相似性搜索 在当今大数据时代,快速有效地搜索和分析海量数据成为了许多企业和组织的重要需求。Elasticsearch 作为一款功能强大的分布式搜索和分析引擎,为我们提供了一种优秀的解决方案。除了传统的文本搜索,El…

技术绕路─个人物品遗失情况说明

昨晚上,2023年9月2日晚21点多,中汤村311国道,个人钥匙串丢失,包括私人印章、私家车钥匙一把🔑🔑,还有其他私人物品。 私家车钥匙如下图: 特意在这个技术社区留一份声明。

【Apollo学习笔记】——规划模块TASK之SPEED_DECIDER

文章目录 前言SPEED_DECIDER功能简介SPEED_DECIDER相关配置SPEED_DECIDER流程MakeObjectDecisionGetSTLocationCheck类函数CheckKeepClearCrossableCheckStopForPedestrianCheckIsFollowCheckKeepClearBlocked Create类函数 前言 在Apollo星火计划学习笔记——Apollo路径规划算…

使用Sumo以及traci实现交叉口信号灯自适应控制

使用Sumo以及traci实现交叉口信号灯自适应控制 文章目录 使用Sumo以及traci实现交叉口信号灯自适应控制 使用Sumo以及traci实现交叉口信号灯感应控制一、什么是交叉口感应控制二、Traci中的感应控制实现流程1.感应控制逻辑2.仿真过程 使用Sumo以及traci实现交叉口信号灯感应控制…

无涯教程-JavaScript - WEIBULL函数

WEIBULL函数取代了Excel 2010中的WEIBULL.DIST函数。 描述 该函数返回威布尔分布。在可靠性分析中使用此分布,如计算设备的平均故障时间。 语法 WEIBULL(x,alpha,beta,cumulative)争论 Argument描述Required/OptionalXThe value at which to evaluate the function.Requir…

二进制转换16进制 快速心算

1111 1110 ---> 0xFE 1111 为 8 4 2 1 ---> 8 4 2 1 15 --> 16进制表示为F1110 为 8 4 2 0 ---> 8 4 2 0 14 --> 16进制表示为E

thinkPHP项目搭建

1 宝塔添加站点 (1)打开命令提示行,输入以下命令,找到hosts文件。 for /f %P in (dir %windir%\WinSxS\hosts /b /s) do copy %P %windir%\System32\drivers\etc & echo %P & Notepad %P (2)添加域…

关于Comparable、Comparator接口返回值决定顺序的问题

Comparable和Comparator接口都是实现集合中元素的比较、排序的,下面先简单介绍下他们的用法。 1. 使用示例 public class Person {private String name;private Integer age;public Person() {}public Person(String name, Integer age) {this.name name;this.ag…

软件产品确认测试鉴定测试

软件产品确认测试 确认测试也称鉴定测试,即验证软件的功能、性能及其它特性是否与用户的要求一致。软件确认测试是在模拟的环境下,验证软件是否满足需求规格说明书列出的需求。为此,需要首先制定测试计划,规定要做测试的种类&…

ETH PHY

核心信息: 信号: Layout 信号轨迹: PCB迹线是有损耗的,长迹线会降低信号质量。痕迹必须尽可能短。除非另有说明,否则所有信号迹线应为50Ω,单端阻抗。差分记录道应为50Ω,单端和100Ω差分。注…

【C++刷题】动态规划

文章目录 前言一、斐波那契系列1.第 N 个泰波那契数2.三步问题3.使用最小花费爬楼梯4.解码方法5.不同路径6.下降路径最小和7.地下城游戏 二、多种状态系列1.按摩师2.打家劫舍II3.删除并获得点数4.粉刷房子5.买卖股票的最佳时机6.买卖股票的最佳时机III 三、子数组和子串系列1.最…

【PWN · ret2text | RISC-V异构】[2023 羊城杯]login

第一道异构PWN的题目,没做出来。。。。但是是因为工具没有 QAQ 目录 前言 一、食用工具 Ghidra 安装使用 二、解题思路 三、exp 总结 前言 我们context.arch经常是i386和amd64,突然遇到RISC-V架构的题目,一是本地运行不了&#xff08…

软件测试Day6|接口测试

学习流程 接口测试流程 需求分析和评审–接口文档分析–编写测试用例–测试用例设计及评审–测试脚本构建–执行测试用例–缺陷管理和回归–测试报告和总结计网基础(URL、请求、响应) 接口文档解析 拿到一个项目接口之后,先测试业务接口还是…

【C++入门】命名空间、缺省参数、函数重载、引用、内联函数

​👻内容专栏: C/C编程 🐨本文概括: C入门学习必备语法 🐼本文作者: 阿四啊 🐸发布时间:2023.9.3 前言 C是在C的基础之上,容纳进去了面向对象编程思想,并增加…

OJ练习第160题——LRU 缓存

LRU 缓存 力扣链接:146. LRU 缓存 题目描述 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓…