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.mzph.cn/news/613338.shtml

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

相关文章

Java高级工程师20道面试题、答案及案例

文章目录 Java高级工程师面试题、答案及案例&#xff1a; 问题&#xff1a; 在Java中&#xff0c;如何实现线程安全的单例模式&#xff1f;请写出双重检查锁定&#xff08;Double-Checked Locking&#xff09;的实现方式。 答案与案例&#xff1a; public class Singleton {pri…

AWS简介(Amazon Web Services )想使用怎么办?

Amazon Web Services&#xff08;AWS&#xff09;是由亚马逊公司提供的云计算平台和服务。AWS提供了一系列基础设施服务&#xff0c;包括计算能力、存储选项、数据库、机器学习、分析、物联网、安全性等&#xff0c;帮助组织和开发者建立和管理他们的应用。 以下是AWS的一些关…

查看navicat链接密码

导出链接,带密码导出 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/239bcf0ad22d4de98524fa4d7db4a30d.png 查看密码 这个是navicat加密后的密码&#xff0c;需要解密 使用一段代码解密 这个是php代码&#xff0c;没有本地php环境&#xff0c;可以看在线…

解析工会排队:动静奖励结合的魅力

每天五分钟讲解一个商业模式知识&#xff0c;大家好我是模式策划啊浩Zeropan_HH。 数字时代数字思想&#xff0c;当你还在苦恼如何让自己的商业城堡扩大时&#xff0c;不如放空思想来看看啊浩的文章&#xff0c;或许可以给你一些启发。今天的给大家分享的模式来源于《微三云赢…

如何在群辉NAS使用Docker搭建容器魔方并实现无公网ip远程访问

文章目录 1. 拉取容器魔方镜像2. 运行容器魔方3. 本地访问容器魔方4. 群辉安装Cpolar5. 配置容器魔方远程地址6. 远程访问测试7. 固定公网地址 本文主要介绍如何在群辉7.2版本中使用Docker安装容器魔方&#xff0c;并结合Cpolar内网穿透工具实现远程访问本地网心云容器魔方界面…

mysql8.0 1055报错问题 [安装包]安装修改

mysql1055报错问题 this is incompatible with sql_modeonly_full_group_by 刚从5.7 升级到8.0 再运行之前的代码就给我搞事情 例如 select a , b from table group by b 之前没问题 8.0默认有个only_full_group_by 这个是强效验证的 网上看了看资料 发现我的sql_Mode里面 没…

智慧校园实验室安全综合管理平台如何保障实验室安全?

一、建设思路 实验室安全综合管理平台是基于以实验室安全&#xff0c;用现代化管理思想与人工智能、大数据、互联网技术、物联网技术、云计算技术、人体感应技术、语音技术、生物识别技术、手机APP、自动化仪器分析技术有机结合&#xff0c;通过建立以实验室为中心的管理体系&…

2019年认证杯SPSSPRO杯数学建模D题(第一阶段)5G时代引发的道路规划革命全过程文档及程序

2019年认证杯SPSSPRO杯数学建模 5G下十字路口车辆通行效率的讨论和建模 D题 5G时代引发的道路规划革命 原题再现&#xff1a; 忙着回家或上班的司机们都知道交通堵塞既浪费时间又浪费燃料&#xff0c;甚至有的时候会带来情绪上的巨大影响&#xff0c;引发一系列的交通问题。…

C++day3作业

完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&#xf…

数据分析讲课笔记01:数据分析概述

文章目录 零、学习目标一、本次课程概述二、数据分析的背景&#xff08;一&#xff09;进入大数据时代&#xff08;二&#xff09;数据分析的作用 三、什么是数据分析&#xff08;一&#xff09;数据分析的概念&#xff08;二&#xff09;数据分析的分类1、描述性数据分析2、探…

三菱PLC如何运用工业智能网关进行数据采集

在工业自动化领域&#xff0c;PLC&#xff08;可编程逻辑控制器&#xff09;作为一种关键的控制系统&#xff0c;发挥着越来越重要的作用。数据采集作为PLC应用的重要环节&#xff0c;关乎整个控制系统的稳定性和可靠性。而三菱PLC作为知名PLC的品牌&#xff0c;其数据采集技术…

《剑指 Offer》专项突破版 - 面试题 8 : 和大于或等于 k 的最短子数组(C++ 实现)- 详解同向双指针(滑动窗口算法)

目录 前言 一、暴力求解 二、同向双指针&#xff08;滑动窗口算法&#xff09; 前言 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 输入一个正整数组成的数组和一个正整数 k&#xff0c;请问数组中和大于或等于 k 的连续子数组的最短…

C++多线程打工人

为啥写这个&#xff0c;今天面试问到了~当时基于信号量写了一个单线程无锁队列的实现&#xff0c;但是面试官实际想要的是多线程条件变量实现的方式。 基本概念 生产者消费者模型是一种常见的并发设计模式&#xff0c;用于处理生产者&#xff08;生成数据&#xff09;和消费者&…

Rough.js:创建手绘、草图外观的图形

Rough.js 是一个小型的(<9kB gzipped)图形库&#xff0c;它可以让你以草图、手绘风格进行绘制。 该库定义了绘制直线、曲线、圆弧、多边形、圆和椭圆的基元。它还支持绘制 SVG 路径。 Rough.js 可以同时处理 Canvas 和 SVG。 安装 从npm安装&#xff1a; npm install --s…

【PDF密码】PDF文件为什么无法修改?为什么PDF文档不支持编辑?

pdf文件大家应该都经常接触&#xff0c;但是不知道大家会遇到这种情况&#xff1a;有些PDF文件打开之后无法编辑&#xff1f;是什么原因呢&#xff1f;今天我们来分析一下都是那些原因导致的。 首先我们可以考虑一下&#xff0c;PDF文件中的内容是否是图片&#xff0c;如果确认…

中国社科院大学与美国杜兰大学金融管理硕士项目——熬过寒冬,春日暖阳已不远

在金融领域&#xff0c;寒冬似乎成了无法避免的阶段。然而&#xff0c;对于那些坚守岗位的金融从业者来说&#xff0c;熬过寒冬并非无望。正如冬去春来&#xff0c;只要我们采取明智的策略&#xff0c;迈出坚定的步伐&#xff0c;春日的暖阳已在不远方照耀。社科院与美国杜兰大…

Vue2x的自定义指令和render函数使用自定义指令

在某些情况下&#xff0c;我们需要对底层DOM进行操作&#xff0c;而内置的指令不能满足需求&#xff0c;就需要自定义指令。一个自定义指令由一个包含类似组件的生命周期的钩子的对象来定义&#xff0c;钩子函数会接收到指令所绑定的元素作为参数。 定义指令 常用两种方式进行…

只有金蝶用户,才懂金蝶BI方案的含金量

资深金蝶系统用户都看得明明白白&#xff0c;金蝶系统侧重于企业资源的计划和管理&#xff0c;而当前企业数字化运营决策不仅需要高效合理的流程管理&#xff0c;更需要一套完善的数据分析方案&#xff08;金蝶BI方案&#xff09;&#xff0c;无缝对接金蝶系统&#xff0c;智能…

Spring MVC 的RequestMapping注解

RequestMapping注解 使用说明 作用&#xff1a;用于建立请求URL和处理请求方法之间的对应关系。 出现位置&#xff1a; 类上&#xff1a; 请求 URL的第一级访问目录。此处不写的话&#xff0c;就相当于应用的根目录。写的话需要以/开头。它出现的目的是为了使我们的 URL 可以…

Python类型转换,数据类型转换函数大全 与 strip()函数介绍

Python类型转换&#xff0c;数据类型转换函数大全 虽然 Python 是弱类型编程语言&#xff0c;不需要像 Java 或 C 语言那样还要在使用变量前声明变量的类型&#xff0c;但在一些特定场景中&#xff0c;仍然需要用到类型转换。 比如说&#xff0c;我们想通过使用 print() 函数…