cpp_10_多重继承_钻石继承_虚继承

1  多重继承

        一个类可以同时从多个基类继承实现代码。

1.1  多重继承的内存布局

        子类对象内部包含多个基类子对象。

        按照继承表的顺序依次被构造,析构的顺序与构造严格相反

        各个基类子对象按照从地址到高地址排列。

// miorder.cpp 多重继承:一个子类有多个基类
#include <iostream>
using namespace std;class A {
public:int m_a;A() { cout << "A()" << endl; }~A() { cout << "~A()" << endl; }
};class B {
public:int m_b;B() { cout << "B()" << endl; }~B() { cout << "~B()" << endl; }
};class C {
public:int m_c;C() { cout << "C()" << endl; }~C() { cout << "~C()" << endl; }
};class D : public A, public B, public C { // 汇聚子类
public:int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {D d; // |A基类子对象|B基类子对象|C基类子对象|m_d|-->|m_a|m_b|m_c|m_d|cout << "汇聚子类对象d的大小:" << sizeof(d) << endl; // 16D* pd = &d;cout << "整个汇聚子类对象的地址 D* pd: " << pd << endl;cout << "A基类子对象的首地址: " << &d.m_a << endl;cout << "B基类子对象的首地址: " << &d.m_b << endl;cout << "C基类子对象的首地址: " << &d.m_c << endl;cout << "D类自己的成员变量&m_d: " << &d.m_d << endl;return 0;
}

1.2  多重继承的类型转换

        (1)将子类对象的指针,隐式转换为它的某种基类类型指针,编译器会根据各个基类子对象在子类对象中的位置,进行适当的偏移计算,以保证指针的类型与其所指向目标对象的类型一致。

                反之,将任何一个基类类型的指针静态转换为子类类型,编译器,编译器同样会进行适当的偏移计算。

                不建议使用:无论在哪个方向上,重解释类型转换reinterpret_cast都不偏移

       

// convercmp.cpp 多重继承的类型转换
#include <iostream>
using namespace std;class A {
public:int m_a;
};
class B {
public:int m_b;
};
class C {
public:int m_c;
};
class D : public A, public B, public C { // 汇聚子类
public:int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {D d; // |A基类子对象|B基类子对象|C基类子对象|m_d|-->|m_a|m_b|m_c|m_d|cout << "汇聚子类对象d的大小:" << sizeof(d) << endl; // 16D* pd = &d;cout << "整个汇聚子类对象的地址 D* pd: " << pd << endl;cout << "A基类子对象的首地址: " << &d.m_a << endl;cout << "B基类子对象的首地址: " << &d.m_b << endl;cout << "C基类子对象的首地址: " << &d.m_c << endl;cout << "D类自己的成员变量&m_d: " << &d.m_d << endl;cout << "---------------隐式转换---------------" << endl;A* pa = pd;cout << "D* pd--->A* pa: " << pa << endl;B* pb = pd;cout << "D* pd--->B* pb: " << pb << endl;C* pc = pd;cout << "D* pd--->C* pc: " << pc << endl;cout << "---------------static_cast---------------" << endl;D* p1 = static_cast<D*>(pa);cout << "A* pa--->D* p1: " << p1 << endl;D* p2 = static_cast<D*>(pb);cout << "B* pb--->D* p2: " << p2 << endl;D* p3 = static_cast<D*>(pc);cout << "C* pc--->D* p3: " << p3 << endl;cout << "---------------reinterpret_cast---------------" << endl;pa = reinterpret_cast<A*>(pd);cout << "D* pd--->A* pa: " << pa << endl;pb = reinterpret_cast<B*>(pd);cout << "D* pd--->B* pb: " << pb << endl;pc = reinterpret_cast<C*>(pd);cout << "D* pd--->C* pc: " << pc << endl;return 0;
}

        (2)引用的情况与指针类似,因为引用的本质就是指针。

1.3  多重继承的名字冲突

        如果在子类的多个基类中,存在同名的标识符,那么任何试图通过子类对象,或在子类内部访问该名字的操作,都将引发歧义

        解决办法1:子类隐藏该标识符(因噎废食,不建议);

        解决办法2:通过作用域限定操作符::显示指明所属基类。

// scope.cpp 多重继承的名字冲突问题 -- 作用域限定符
#include <iostream>
using namespace std;class A { // 学生类
public:int m_a;int m_c; // 成绩
};class B { // 老师类
public:int m_b;int m_c; // 工资
};class D : public A, public B { // 助教类
public:int m_d;
//  int m_c; // 在业务上毫无意义,仅仅就是为了将基类的m_c隐藏void foo() {A::m_c = 100; }
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {D d; // |A基类子对象|B基类子对象|m_d| --> |m_a m_c|m_b m_c|m_d|cout << "汇聚子类对象d的大小:" << sizeof(d) << endl; // 20d.B::m_c = 8000; return 0;
}

2  钻石继承

        一个子类继承自多个基类,而这些基类又源自共同的祖先,这样的继承结构称为钻石继承(菱形继承):

        

2.1  钻石继承的问题

        公共基类子对象,在汇聚子类对象中,存在多个实例:

                           

// diamond.cpp 钻石继承
#include <iostream>
using namespace std;
class A { // 公共基类(人类)
public:int m_a; // 年龄
};
class X : public A { // 中间子类(学生类)
public:int m_x;
};
class Y : public A { // 中间子类(老师类)
public:int m_y;
};
class Z : public X, public Y { // 汇聚子类(助教类)
public:int m_z;void foo() {X::m_a = 20; // 歧义}
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {Z z; // |X中间子类子对象|Y中间子类子对象|m_z|-->// |A公共基类子对象 m_x|A公共基类子对象 m_y|m_z|-->|m_a m_x|m_a m_y|m_z|cout << "汇聚子类对象z的大小: " << sizeof(z) << endl; // 20z.Y::m_a = 20; // 歧义return 0;
}

         

3  虚继承

3.1  钻石继承的解决方法

        在继承表中使用virtual关键字。

        虚基类子对象,不在中间子类子对象中,保存在最后。

        虚继承可以保证:

                (1)公共虚基类子对象汇聚子类对象中仅存一份实例 

                

                (2)公共虚基类子对象被多个中间子类子对象共享 

3.2  虚继承实现原理

        汇聚子类对象中的每个中间子类子对象都持有一个指针,通过该指针可以获取 中间子类子对象的首地址 到 公共虚基类子对象 的首地址的 偏移量

                   

// virtualinherit.cpp 虚继承 -- 钻石继承问题的解决法门
// (1) 公共基类(A类)子对象 在 汇聚子类(Z类)对象中 只存在一份
// (2) 公共基类(A类)子对象 要被 多个中间子类(X/Y类)子对象 共享
#include <iostream>
using namespace std;
#pragma pack(1)
class A { // 公共基类(人类)
public:int m_a; // 年龄
};
class X : virtual public A { // 中间子类(学生类)
public:int m_x;void setAge( /* X* this */ int age ) {this->m_a = age; // (1) this (2) X中间子类子对象 (3) 指针1 (4) 偏移量 // (5)this+偏移量 (6)A公共基类子对象首地址 (7)m_a}
};
class Y : virtual public A { // 中间子类(老师类)
public:int m_y;int getAge( /* Y* this */ ) {return this->m_a; // (1) this (2) Y中间子类对象 (3) 指针2 (4) 偏移量 // (5)this+偏移量 (6)A公共基类子对象首地址 (7)m_a}
};
class Z : public X, public Y { // 汇聚子类(助教类)
public:int m_z;void foo() {m_a = 20; }
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {Z z; // |X中间子类子对象|Y中间子类子对象|m_z|A公共基类子对象|-->// |指针1 m_x|指针2 m_y|m_z|m_a|cout << "汇聚子类对象z的大小: " << sizeof(z) << endl; //  32z.setAge( 28 ); // setAge( &z, 28 ); cout << z.getAge() << endl; // getAge( &z );return 0;
}

3.3  虚继承的构造函数

// virtualCons.cpp 虚继承的构造函数
#include <iostream>
using namespace std;
#pragma pack(1)
class A { // 公共虚基类
public:int m_a;
};
// 开关量X=0;
class X : virtual public A { // 中间子类
public:X() {//【*】//【int m_x;】// if( 开关量X==0 ) { // 只有开关量X为0时,X类构造函数才会创建 A虚基类子对象//  【A();】// }}int m_x;
};
// 开关量Y=0;
class Y : virtual public A { // 中间子类
public:Y() {//【*】//【int m_y;】// if( 开关量Y==0 ) { // 只有开关量Y为0时,Y类构造函数才会创建 A虚基类子对象//  【A();】// }}int m_y;
};
class Z : public X, public Y { // 汇聚子类
public:Z( ) {// 开关量X=1,开关量Y=1  (将 开关量置为1 )//【X();】//【Y();】//【int m_z;】//【A();】// 开关量X=0,开关量Y=0  (复位开关量)}int m_z;
};
int main( void ) {X x; // |指针 m_x|A虚基类子对象| --> |指针 m_x|m_a|cout << "x对象的大小: " << sizeof(x) << endl; // 16Y y; // |指针 m_y|A虚基类子对象| --> |指针 m_y|m_a|cout << "y对象的大小: " << sizeof(y) << endl; // 16Z z; // |X中间子类子对象|Y中间子对象|m_z|A公共虚基类子对象| // --> |指针 m_x|指针 m_y|m_z|m_a|cout << "z对象的大小: " << sizeof(z) << endl;return 0;
}

3.4  虚继承特点

        虚基类子对象,不在中间子类子对象中,保存在最后。(通过上述开关量可知)

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

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

相关文章

Vs2019安装教程

1、下载链接&#xff1a;Visual Studio 较旧的下载 - 2019、2017、2015 和以前的版本 (microsoft.com)https://visualstudio.microsoft.com/zh-hans/vs/older-downloads/2、下载 而后跟进安装&#xff1a;&#xff08;虽然这是2022的&#xff0c;但是和2022和2019基本差不多&am…

基于ssm学费管理系统的设计与实现论文

摘 要 当下&#xff0c;如果还依然使用纸质文档来记录并且管理相关信息&#xff0c;可能会出现很多问题&#xff0c;比如原始文件的丢失&#xff0c;因为采用纸质文档&#xff0c;很容易受潮或者怕火&#xff0c;不容易备份&#xff0c;需要花费大量的人员和资金来管理用纸质文…

【DevOps-08-2】Harbor的基本操作

一、简要描述 Harbor作为镜像仓库,主要的交互方式就是将镜像上传到Harbor上,以及从Harbor上下载指定镜像 在传输镜像前,可以先使用Harbor提供的权限管理,将项目设置为私有项目,并对不同用户设置不同角色,从而更方便管理镜像。 二、Harbor添加用户和项目 1、添加Harbor用…

2024--Django平台开发-Django知识点(五)

day05 django知识点 今日概要&#xff1a; 中间件 【使用】【源码】cookie 【使用】【源码 - Django底层请求本质】session【使用】【源码 - 数据库请求周期中间件】 1.中间件 1.1 使用 编写类&#xff0c;在类型定义&#xff1a;process_request、process_view、process_…

C语言入门教程,C语言学习教程(第三部分:C语言变量和数据类型)一

第三部分&#xff1a;C语言变量和数据类型 本章也是C语言的基础知识&#xff0c;主要讲解变量、数据类型以及运算符&#xff0c;这其中涉及到了数据的存储格式以及不同进制。 一、大话C语言变量和数据类型 在《数据在内存中的存储&#xff08;二进制形式存储&#xff09;》一…

高级路由学习试题

文章目录 高级路由学习试题一.高级路由题目答案 二.OSPF 相关答案 三.基础知识答案 高级路由学习试题 一.高级路由题目 1.以下属于ITOIP特性的有&#xff08;&#xff09; A、智能 B、开放 C、融合 D、标准 2.层级化网络模型将网络划分为&#xff08;&#xff09; A、汇…

满足ITOM需求的网络监控工具

IT 运营管理&#xff08;ITOM&#xff09;可以定义为监督 IT 基础架构的各种物理和虚拟组件的过程;确保其性能、运行状况和可用性;并使它们能够与基础架构的其他组件无缝协作。IT 运营管理&#xff08;ITOM&#xff09;在大型 IT 管理模型中也发挥着积极作用&#xff0c;包括 I…

Spark---RDD依赖关系

文章目录 1.1 RDD依赖关系1.2 血缘关系1.3 依赖关系分类1.3.1 窄依赖1.3.2 宽依赖 1.4 RDD阶段划分和任务划分1.4.1 RDD阶段划分 1.1 RDD依赖关系 在Spark中&#xff0c;一个RDD的形成依赖于另一个RDD&#xff0c;则称这两个RDD具有依赖关系(一般指相邻的两个RDD之间的关系) ,R…

超维空间M1无人机使用说明书——53、ROS无人机二维码识别与降落——V2升级版本

引言&#xff1a;使用二维码引导无人机实现精准降落&#xff0c;首先需要实现对二维码的识别和定位&#xff0c;可以参考博客的二维码识别和定位内容。本小节主要是通过获取拿到的二维码位置&#xff0c;控制无人机全向的移动和降落&#xff0c;本小节再V1版本的基础上增加了动…

Zabbix6.4 监控系统 密码忘记怎么办

Zabbix6.4 监控系统 密码忘记怎么办&#xff1f; 如下图 本次主要介绍在Zabbix6.4中重置用户密码的步骤。 步骤 如果您忘记了Zabbix密码并且无法登录&#xff0c;请向Zabbix管理员求助。 超级管理员用户可以在用户配置表单中更改所有用户的密码。 如果超级用户忘记了密码&a…

【控制篇 / 策略】(7.4) ❀ 03. 地理地址对象在路由中的应用 ❀ FortiGate 防火墙

【简介】如何做到访问国内走Wan1&#xff0c;访问国际走Wan2 &#xff1f;当企业有多条宽带&#xff0c;特别是有国际专线的时候&#xff0c;这个需求就很普遍了。通过地理地址对象可以快速的解决这些问题。 策略路由 当我们有多条宽带的时候&#xff0c;我们有两种方法分流&am…

5、C语言:结构

结构 结构的基本知识结构与函数传递结构 结构数组、指向结构的指针自引用结构&#xff08;二叉树&#xff09;表查找类型定义&#xff08;typedef&#xff09;联合位字段 结构也是一种数据类型。类似于int、char、double、float等。 结构是一个或多个变量的集合&#xff0c;这些…