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

news2024/11/24 18:40:35

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; // 16

    D* 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; // 16

    D* 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; // 20
    d.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; // 20

    z.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; //  32

    z.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; // 16
    Y y; // |指针 m_y|A虚基类子对象| --> |指针 m_y|m_a|
    cout << "y对象的大小: " << sizeof(y) << endl; // 16

    Z 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.coloradmin.cn/o/1372779.html

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

相关文章

电脑找不到d3dcompiler43.dll怎么修复,教你5个可靠的方法

d3dcompiler43.dll是Windows操作系统中的一个重要动态链接库文件&#xff0c;主要负责Direct3D编译器的相关功能。如果“d3dcompiler43.dll丢失”通常会导致游戏无法正常运行或者程序崩溃。为了解决这个问题&#xff0c;我整理了以下五个解决方法&#xff0c;希望能帮助到遇到相…

深度学习算法应用实战 | 利用 CLIP 模型进行“零样本图像分类”

文章目录 1. 零样本图像分类简介1.1 什么是零样本图像分类?1.2 通俗一点的解释 2. 模型原理图3. 环境配置4. 代码实战5. Gradio前端页面5.1 什么是 Gradio ? 6 进阶操作7. 总结 1. 零样本图像分类简介 1.1 什么是零样本图像分类? “零样本图像分类”&#xff08;Zero-shot …

NLP|LSTM+Attention文本分类

目录 一、Attention原理简介 二、LSTMAttention文本分类实战 1、数据读取及预处理 2、文本序列编码 3、LSTM文本分类 三、划重点 少走10年弯路 LSTM是一种特殊的循环神经网络&#xff08;RNN&#xff09;&#xff0c;用于处理序列数据和时间序列数据的建模和预测。而在N…

66、python - 代码仓库介绍

上一节,我们可以用自己手写的算法以及手动搭建的神经网络完成预测了,不知各位同学有没有自己尝试来预测一只猫或者一只狗,看看准确度如何? 本节应一位同学的建议,来介绍下 python 代码仓库的目录结构,以及每一部分是做什么? 我们这个小课的代码实战仓库链接为:cv_lea…

springboot医院信管系统源码和论文

随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&#xf…

【Linux 内核源码分析笔记】系统调用

在Linux内核中&#xff0c;系统调用是用户空间程序与内核之间的接口&#xff0c;它允许用户空间程序请求内核执行特权操作或访问受保护的内核资源。系统调用提供了一种安全可控的方式&#xff0c;使用户程序能够利用内核功能而不直接访问底层硬件。 系统调用&#xff1a; 通过…

代理IP连接不上/网速过慢?如何应对?

当您使用代理时&#xff0c;您可能会遇到不同的代理错误代码显示代理IP连不通、访问失败、网速过慢等种种问题。 在本文中中&#xff0c;我们将讨论您在使用代理IP时可能遇到的常见错误、发生这些错误的原因以及解决方法。 一、常见代理服务器错误 当您尝试访问网站时&#…

关于Geek软件的下载

直接百度搜geek出来的前几条似乎都是广告&#xff1a; 点进去之后是这个界面&#xff1a; 然后安装到最后一步提示要付费才能安装成功&#xff1a; 然后如果是用谷歌搜索&#xff1a; 有free版和pro版&#xff1a; free版下载之后压缩包解压就是exe不需要安装 综上&#xff0c…

金蝶EAS pdfviewlocal 任意文件读取漏洞

产品简介 金蝶EAS 为集团型企业提供功能全面、性能稳定、扩展性强的数字化平台&#xff0c;帮助企业链接外部产业链上下游&#xff0c;实现信息共享、风险共担&#xff0c;优化生态圈资源配置&#xff0c;构筑产业生态的护城河&#xff0c;同时打通企业内部价值链的数据链条&a…

【leetcode】力扣热门算法之K个一组翻转链表【困难】

题目描述 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节…

JS-基础语法(一)

JavaScript简单介绍 变量 常量 数据类型 类型转换 案例 1.JavaScript简单介绍 JavaScript 是什么&#xff1f; 是一种运行在客户端&#xff08;浏览器&#xff09;的编程语言&#xff0c;可以实现人机交互效果。 JS的作用 JavaScript的组成 JSECMAScript( 基础语法 )…

JavaSE 反射、枚举及Lambda的使用

目录 1 反射1.1 定义1.2 用途1.3 反射基本信息1.4 反射相关的类1.4.1 Class类(反射机制的起源 )1.4.1.1 Class类中的相关方法 1.4.2 反射示例1.4.2.1 获得Class对象的三种方式1.4.2.2 反射的使用 1.5 优缺点 2 枚举2.1 背景及定义2.2 使用2.3 优缺点2.4 枚举和反射2.5 总结2.6 …

调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配

作者推荐 【动态规划】C算法312 戳气球 关键字&#xff1a; 函数调用约定 混合编程 __stdcall c WINAPI APIENTRY _cdecl 调用方出错提示如下&#xff1a; 调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参…

C++qt-信号-信号槽

1、概念 信号和槽是两种函数&#xff0c;这是Qt在C基础上新增的特性&#xff0c;类似于其他技术中的回调的概念。 信号和槽通过程序员提前设定的“约定”&#xff0c;可以实现对象之间的通信&#xff0c;有两个先决的条件&#xff1a; 通信的对象必须都是从QObject类中派生出来…

threejs 光带扩散动画

目录 一、创建光带 (1) 设置光带顶点 (2) 设置光带顶点透明度属性 二、光带动画 完整代码 html文件代码 js文件代码 最后展示一下项目里的效果&#xff1a; 最近项目中要求做一段光带效果动画&#xff0c;尝试着写了一下&#xff0c;下面是本次分享光带扩散动画的效果预…

地铁判官(外包)

到处都是说外包不好不好的&#xff0c;从没有想过自身问题。 例如&#xff1a; 技术人员动不动就是说&#xff0c;进了外包三天&#xff0c;一年&#xff0c;三年之后技术退步很多。就算你这样的人进了甲方&#xff0c;也是个渣渣。(声明一下&#xff0c;我也是外包&#xff0…

CMU15-445-Spring-2023-Project #2 - 前置知识(lec07-010)

Lecture #07_ Hash Tables Data Structures Hash Table 哈希表将键映射到值。它提供平均 O (1) 的操作复杂度&#xff08;最坏情况下为 O (n)&#xff09;和 O (n) 的存储复杂度。 由两部分组成&#xff1a; Hash Function和Hashing Scheme&#xff08;发生冲突后的处理&…

阿里云99元一年2核2G3M云服务器值得买吗?

阿里云作为国内领先的云服务提供商&#xff0c;一直致力于为用户提供优质、高效的服务。目前&#xff0c;阿里云推出的99元一年2核2G3M云服务器&#xff0c;更是引发了广大用户的关注。本文将详细解析这款云服务器的特点、优势以及适用场景&#xff0c;为大家上云提供参考。 一…

Android逆向学习(六)绕过app签名校验,通过frida,io重定向(上)

Android逆向学习&#xff08;六&#xff09;绕过app签名校验&#xff0c;通过frida&#xff0c;io重定向&#xff08;上&#xff09; 一、写在前面 这是吾爱破解正己大大教程的第五个作业&#xff0c;然后我的系统还是ubuntu&#xff0c;建议先看一下上一个博客&#xff0c;关…

阿赵UE学习笔记——8、贴图导入设置

阿赵UE学习笔记目录 大家好&#xff0c;我是阿赵。   继续学习虚幻引擎的用法&#xff0c;这次来说一下贴图的导入设置。   在内容浏览器里面可以看到纹理类型的资源&#xff0c;就是贴图了&#xff0c;鼠标悬浮在上面可以看到这个纹理贴图的信息&#xff1a; 双击纹理贴图…