C++对多继承的理解

news2025/1/15 17:38:13

学到C++时我们知道了继承但是一般都是使用单继承为主,单继承就是一个子类只能继承一个父类而多继承是指一个子类可以同时继承多个父类。

菱形继承

 菱形继承是多继承中的一个特殊情况。当一个子类同时继承两个具有共同父类的类时,就会出现菱形继承问题。但是这种情况下,子类会继承同一个父类的特性和方法两次,导致特性和方法的冗余。

代码视角的菱形继承

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};

此时如果Assistant创建a对象时,如果此时访问a对象的_name成员时(也就是People类里的成员)就产生了歧义,因为此时存在两种情况:访问的是Student类里的_name成员,也可能是Teacher类里的_name成员。所以就产生了二义性的问题。

但是也是有解决方法的:指定类成员

    a.Student::_name = "陈同学";
	a.Teacher::_name = "陈老师";

但是这样就显得代码十分冗余看起来就十分别扭。这种菱形继承就相当于是继承同一父类两次,这是完全没必要的,就像你一个人身份证上不可能有两个名字吧

解决策略 

 虚继承(virtual inheritance):在父类之间的继承关系中,使用关键字virtual来声明继承关系。这样在派生类中只会有一份共同祖先的数据,从而避免了冗余数据的问题。

//在存在数据冗余的基类下的派生类加上virtual关键字
class Person
{
public:
	string _name; // 姓名
};
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; // 主修课程
};

所以此时就只有一份Person数据了。

验证:(即使指定类初始化也是进行统一初始化)

 

虚继承中的内存关系

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;
};

先看看非虚拟继承数据的存储空间:


 虚继承下的数据空间地址:(32位机器测试)

   但是为什么存在二义性的数据公有一份后类B和类C里还存了一个一个类似地址的东西,那么在内存窗口查看一下:

经过分析可以了解到非虚拟继承时的共有数据存放的位置在虚拟继承时存放的是一个地址,而该地址下存放的数据恰恰就是与共有的祖先数据的偏移量。就相当于为D类创建了一个偏移量表(不占D对象内存),以便为D创建多个对象都可以用该表查看偏移量从而找到共同祖先数据。

为什么要存一个地址:

其实这样做的目的就是以便父子类对象的赋值。因为在继承关系中,子类继承了父类的特性和方法。子类和父类之间是一种"is-a"的关系。"is-a"关系表示子类是父类的一种类型。所以子类对象赋值给父类是天然的,不会产生临时对象。这也叫作父子类赋值兼容规则(切割/切片)。相当于将子类多余父类的数据切割再赋值给父类。

而在虚拟继承中同样是满足父子类复制兼容的,所以就拿上代码来说当我们B b=d;的过程时B类中并没有A类的成员数据,所以说就将类似B类这种类再存一个指针,而指针中存的就是改地址处与A类成员地址的偏移量,所以在赋值的过程中编译器就会以这种方式来寻找A类的成员数据。


 其实虚继承以后不仅仅是D类的对象数据是这样的存储方式,其实B类C类创建的对象也是这种存储方式。

 

继承和组合

继承是指一个类(称为派生类或子类)可以从另一个类(称为基类或父类)中继承属性。

组合是指一个类可以包含其他类的对象作为自己的成员。


在继承关系中,子类继承了父类的特性和方法,但父类并不是子类的成员对象。public继承,子类和父类之间是一种"is-a"的关系,而不是"has-a"的关系。"is-a"关系表示子类是父类的一种类型,而"has-a"关系表示一个类具有另一个类的成员对象。而组合的两个类就是"has-a"的关系。


通过继承,派生类可以重用基类的代码和功能,并可以扩展或修改这些功能。继承可以建立类之间的"是一个"关系,其中派生类是基类的一种类型。继承可以实现代码的重用和面向对象编程的多态性。

通过组合,一个类可以将其他类的对象组合起来实现更复杂的功能。组合可以建立类之间的"具有"关系,其中包含对象是类的一部分。通过组合,可以灵活地构建类之间的关系,实现更灵活和模块化的设计。

选择使用

选择继承还是组合取决于具体的场景和需求。一般来说,当两个类之间存在"是一个"关系,并且派生类需要重用基类的代码和功能时,使用继承更合适。而当两个类之间存在"具有"关系,并且一个类需要使用另一个类的功能,但并不满足"是一个"关系时,使用组合更合适。

但是一般能使用组合尽量使用组合。组合的耦合度低,代码维护性更好。

 

有关继承的题目

test_1

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 //先继承的先创建
{
	public: int _d; 
};
int main() {
	Derive d;
	Base1* p1 = &d;
	Base2* p2 = &d;
	Derive* p3 = &d;
	return 0;
}//判断p1 p2 p3的关系???

 分析:先创建Derive类的对象,而Derive类同时也继承了Base1和Base2这两个类,所以地址空间模拟图应该是:

因为Base1继承在Base2的前面,所以Base1先被创建出来,所以p1和p3都是指向起始处,所以地址值是相同的,但是两个指针指向的内容范围是不同。而Base2就显然不同于Base1。


test_2(虚拟继承)

class A {
public:
	A(const char* s)
	{ cout << s << endl; }
	~A() {}
};
class B :virtual public A
{
public:
	B(const char* s1, const char* s2)
		:A(s1) 
	{ cout << s2 << endl; }
};
class C :virtual public A
{
public:
	C(const char* s1, const char* s2) 
		:A(s1) 
	{ cout << s2 << endl; }
};
class D :public B, public C
{
public:
	D(const char* s1, const char* s2, const char* s3, const char* s4) 
		:B(s1, s2), C(s1, s3), A(s1)
	{
		cout << s4 << endl; 
	}
};
int main() 
{
	D* p = new D("class A", "class B", "class C", "class D");
	delete p;
	return 0;
}

首先是new并且初始化一个D类的对象,此时不难看出ABCD四个类呈现菱形继承的关系,而且还是虚拟继承。所以可以明确类A只会创建一份D类的初始化列表中显示调用了其父类的构造函数,但是初始化列表的顺序并不是真的调用顺序,这依赖于继承顺序。所以再看D类的继承顺序是B类在前C类在后,所以先创建B类,但是此时别急构造,B类虚拟继承了A类所以想要创建B类之前是先构造A类,所以最先调用构造函数的一定是A类其次就是B类,再C类,虽然初始化列表最后还显示调用了A类的构造函数,但是这是虚拟继承所以实际上并不会再多调用一次。


成员变量初始化:成员变量走初始化列表进行初始化的顺序是依据于成员变量在类里被声明的顺序

继承类的初始化:先继承的先初始化即先被调用


 

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

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

相关文章

2 spring 识别自定义实现BeanFactoryPostProcessor 的接口

如果自定义实现了BeanFactoryPostProcessor接口&#xff0c;那么想让spring识别到的话&#xff0c;有两种方式&#xff1a; 1 定义在spring的配置文件中&#xff0c;让spring自动识别 2 调用具体的addBeanFactoryPostProcessor方法 方法1 的代码实现 定义实现BeanFactoryPo…

淘宝拍立淘接口,按图搜索商品接口,图片识别接口,图片上传搜索接口,图片搜索API接口,以图搜货接口

淘宝拍立淘图片搜索接口可以通过上传或输入图片链接的方式&#xff0c;调用淘宝的图片搜索引擎&#xff0c;返回与该图片相关的所有淘宝商品。 使用该接口需要先申请淘宝开放平台的App Key和App Secret&#xff0c;获取相应的API访问权限。在调用接口时&#xff0c;需要传入商…

YOLOv5算法改进(12)— 如何去更换主干网络(1)(包括代码+添加步骤+网络结构图)

前言:Hello大家好,我是小哥谈。YOLOv5采用的主干网络是CSPDarknet53,它是Darknet53的改进版本,采用了Cross Stage Partial连接(CSP)结构,可以提高模型的效率和准确率。在学术上,为了提升YOLOv5算法模型的准确率或者鲁棒性等,已经有很多改进方案问世。更换主干网络作为…

[C++]:2初识C++(auto) + 类和对象上:

[TOC](初识C(auto) 类和对象上) 一.初始C 1.auto关键字&#xff1a;(C11) 1.作为一个变量的类型给这个类型初始化&#xff0c;auto自动识别初始化这个变量值的类型&#xff0c;为auto类型的这个变量开辟一个合适的空间。 补充&#xff1a; 1.typeid(变量名).name—>可以打…

快速入门:Spring Cache

目录 一:Spring Cache简介 二:Spring Cache常用注解 2.1:EnableCaching 2.2: Cacheable 2.3:CachePut 2.4:CacheEvict 三:Spring Cache案例 3.1:先在pom.xml中引入两个依赖 3.2:案例 3.2.1:构建数据库表 3.2.2:构建User类 3.2.3:构建Controller mapper层代码 3.…

ModuleNotFoundError: No module named ‘torch‘

目录 情况1,真的没有安装pytorch情况2(安装了与CUDA不对应的pytorch版本导致无法识别出torch) 情况1,真的没有安装pytorch 虚拟环境里面真的是没有torch,这种情况就easy job了,点击此链接直接安装与CUDA对应的pytorch版本,CTRLF直接搜索对应CUDA版本即可查找到对应的命令.按图…

【复盘】主从延迟以及 Waiting for tablemetadata lock 线上问题

背景 今晚DBA给一个大表添加索引&#xff0c;1000多W&#xff0c;正好风控系统这个时间段有查询这个表的请求&#xff0c;于是就出现了复制延迟。 这是正常下的延迟 可以看出基本都是是100毫秒以下。 Waiting for tablemetadata lock&#xff0c;并且业务跑的SQL出现锁等待…

实现Traefik工具Dashboard远程访问:搭建便捷的远程管理平台

文章目录 前言1. Docker 部署 Trfɪk2. 本地访问traefik测试3. Linux 安装cpolar4. 配置Traefik公网访问地址5. 公网远程访问Traefik6. 固定Traefik公网地址 前言 Trfɪk 是一个云原生的新型的 HTTP 反向代理、负载均衡软件&#xff0c;能轻易的部署微服务。它支持多种后端 (D…

24-数据结构-内部排序-基数排序

基数排序 基数排序&#xff0c;给关键字分成d位&#xff08;组&#xff09;&#xff0c;&#xff0c;对每一位的情况&#xff0c;可能会出现的值位r&#xff08;基数&#xff09;个&#xff0c;然后分成r个队列&#xff0c;对每个对林进行分配耗时O(n)&#xff0c;最后按照改位…

join、inner join、left join、right join、outer join的区别

内连接 inner join(等值连接)&#xff1a;只显示两表联结字段相等的行&#xff0c;(很少用到&#xff0c;最好别用)&#xff1b; 外连接 left join&#xff1a;以左表为基础,显示左表中的所有记录,不管是否与关联条件相匹配,而右表中的数据只显示与关联条件相匹配的记录,不匹配…

【C/PTA】顺序结构专项练习

本文结合PTA专项练习带领读者掌握顺序结构&#xff0c;刷题为主注释为辅&#xff0c;在代码中理解思路&#xff0c;其它不做过多叙述。 7-1 是不是太胖了 据说一个人的标准体重应该是其身高&#xff08;单位&#xff1a;厘米&#xff09;减去100、再乘以0.9所得到的公斤数。已…

山海鲸可视化B/S架构应用

一、什么是B/S架构 BS架构&#xff08;Browser-Server架构&#xff09;是一种常见的软件架构模式&#xff0c;其中系统的核心业务逻辑和数据处理都发生在服务器端&#xff08;Server&#xff09;&#xff0c;而客户端&#xff08;Browser&#xff09;主要负责显示和用户交互。…

【AIGC核心技术剖析】用于 3D 生成的多视图扩散模型

MVDream是一种多视图扩散模型,能够从给定的文本提示生成一致的多视图图像。多视图扩散模型从二维和三维数据中学习,可以实现二维扩散模型的泛化和三维渲染的一致性。我们证明了这样的多视图先验可以作为可推广的 2D 先验,与 3D 表示无关。它可以通过分数蒸馏取样应用于 2D 生…

vue视频直接播放rtsp流;vue视频延迟问题解决;webRTC占cpu太大卡死问题解决;解决webRTC播放卡花屏问题:

播放多个视频 <div class"video-box"><div class"video"><iframe style"width:100%;height:100%;" name"ddddd" id"iframes" scrolling"auto" :src"videoLeftUrl"></iframe>&l…

Python---练习:求世界杯小组赛的总成绩(涉及:布尔类型转换为整型)

案例 世界杯案例 需求&#xff1a; 世界杯案例&#xff0c;世界杯小组赛的比赛规则是我们的球队与其他三支球队进行比赛&#xff0c;然后根据总成绩(积分)确定出线资格。小组赛球队实力已知(提示用户输入各球队实力&#xff09;&#xff0c;我们通过一个数字表示。如果我们赢…

C#冒泡排序算法

冒泡排序实现原理 冒泡排序是一种简单的排序算法&#xff0c;其原理如下&#xff1a; 从待排序的数组的第一个元素开始&#xff0c;依次比较相邻的两个元素。 如果前面的元素大于后面的元素&#xff08;升序排序&#xff09;&#xff0c;则交换这两个元素的位置&#xff0c;使…

yolov5多个框重叠问题

NMS&#xff08;Non-Maximum Suppression&#xff0c;非极大值抑制&#xff09;是一种在计算机视觉和目标检测领域常用的技术。它通常用于在图像或视频中找出物体或目标的位置&#xff0c;并剔除重叠的边界框&#xff0c;以确保最终的检测结果准确且不重叠。 会出现多个框重叠…

2020年12月 Python(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python编程(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 要对二维列表所有的数据进行格式化输出,打印成表格形状,程序段如下: ls = [[金京,89],[ 吴树海,80]<

fastadmin框架token验证

在FastAdmin框架中&#xff0c;Token验证是一种常见的身份验证方法&#xff0c;用于确保用户请求的安全性和合法性。本文将介绍如何在FastAdmin框架中实现Token验证。 什么是Token验证&#xff1f; Token验证是一种基于令牌(Token)的身份验证方式。在这种方式下&#xff0c;用…

Docker部署Jumpserver堡垒机

Jumpserver 是全球首款完全开源的堡垒机&#xff0c;使用 GNU GPL v2.0 开源协议&#xff0c;是符合 4A 的专业运维审计系统。 Jumpserver 使用 Python / Django 进行开发&#xff0c;遵循 Web 2.0 规范&#xff0c;配备了业界领先的 Web Terminal 解决方案&#xff0c;交互界面…