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; // 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  虚继承特点

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Rust类型之字符串

字符串 Rust 中的字符串类型是String。虽然字符串只是比字符多了一个“串”字&#xff0c;但是在Rust中这两者的存储方式完全不一样&#xff0c;字符串不是字符的数组&#xff0c;String内部存储的是Unicode字符串的UTF8编码&#xff0c;而char直接存的是Unicode Scalar Value…

大模型学习之书生·浦语大模型4——基于Xtuner大模型微调实战

基于Xtuner大模型微调实战 Fintune简介 海量数据训练的base model指令微调Instructed LLM 增量预训练微调 增量数据不需要问题&#xff0c;只需要答案&#xff0c;只需要陈述类的数据 指令跟随微调 指定角色指定问题给对应的user指定答案给assistant LIaMa2InternLM 不同的模…

什么是Modbus协议?

Modbus协议是一种在工业自动化领域广泛应用的通信协议&#xff0c;它允许不同设备之间进行可靠的数据交换和控制。该协议最初由Modicon公司于1979年创建&#xff0c;旨在提供一种简单而有效的方法&#xff0c;使PLC&#xff08;可编程逻辑控制器&#xff09;和其他自动化设备能…

前端绕过无限Debug

1.准备 burp : https://pan.baidu.com/s/1aqCywnF_S-HzIWVGLjiW-A 提取码: mpen BurpLoaderKeygen:链接: https://pan.baidu.com/s/1Vck_hFMT2YXP1cbmYfFqsA 提取码: qggp 点击Next后把Request粘贴到LoaderKeygen中&#xff0c;然后把Response粘贴到Burp Suite中 注&#xff1…

2024年【熔化焊接与热切割】考试内容及熔化焊接与热切割免费试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 熔化焊接与热切割考试内容是安全生产模拟考试一点通总题库中生成的一套熔化焊接与热切割免费试题&#xff0c;安全生产模拟考试一点通上熔化焊接与热切割作业手机同步练习。2024年【熔化焊接与热切割】考试内容及熔化…

in <module> input = brower.find_element(‘kw‘)

1. 概念名称&#xff1a; in <module> input brower.find_element(kw) 2. 概念定义&#xff1a; 这行代码使用了Selenium WebDriver的find_element方法来定位页面上的一个元素 3. 我对概念的理解&#xff1a; find_element方法用于查找页面上的元素&#xff0c;但这里的…

Mysql是怎样运行的--下

文章目录 Mysql是怎样运行的--下查询优化explainoptimizer_trace InnoDB的Buffer Pool&#xff08;缓冲池&#xff09;Buffer Pool的存储结构空闲页存储--free链表脏页&#xff08;修改后的数据&#xff09;存储--flush链表 使用Buffer PoolLRU链表的管理 事务ACID事务的状态事…

Triumphcore FPGA调测试记录

FPGA采用Xilinx pynq Z2开发板。基于V2.5版本开发 OverView uart端口映射 BUG调试记录 2024.1.7 复位状态导致取指时序错误 错误波形&#xff1a; 正确波形 问题代码&#xff1a; 2024.1.9 clock_wizard设置输入时钟是输出时钟的2^n倍&#xff0c;输出时钟的占空比才…

电能质量Python实现全家桶——全网最低价

往期精彩内容&#xff1a; 电能质量扰动信号数据介绍与分类-Python实现-CSDN博客 Python电能质量扰动信号分类(一)基于LSTM模型的一维信号分类-CSDN博客 Python电能质量扰动信号分类(二)基于CNN模型的一维信号分类-CSDN博客 Python电能质量扰动信号分类(三)基于Transformer…

数据结构之单调栈、单调队列

今天学习了单调栈还有单调队列的概念和使用&#xff0c;接下来我将对其定义并配合几道习题进行讲解&#xff1a; 首先先来复习一下栈与队列&#xff1a; 然后我们来看一下单调栈的定义&#xff1a; 单调栈中的元素从栈底到栈顶的元素的大小是按照单调递增或者单调递减的关系进…

Spring之整合Mybatis底层源码

文章目录 一、整体核心思路1 . 简介2. 整合思路 二、源码分析1. 环境准备2. 源码分析 一、整体核心思路 1 . 简介 有很多框架需要与Spring进行整合&#xff0c;而整合的核心思路就是把其他框架所产生的对象放到Spring容器中&#xff0c;让其成为一个bean。比如Mybatis&#x…

使用requests库测试post请求 操作流程

第一步 谷歌f12或其他抓包工具抓包&#xff0c;这里随机抓一个post请求 url&#xff1a;https://eva2.csdn.net/v3/06981375190026432f77c01bfca33e32/lts/groups/dadde766-b087-42da-8e67-d2499a520ee7/streams/a0119567-bf91-4314-ab75-f683ba6c0c0a/logs 第二步 导包 impo…

uniapp在web端怎么使用svg图标呢

在图标库中添加好项目用到的图标&#xff0c;点击symbol点击生成在线链接 点击生成的在线链接&#xff0c;此时会跳转到一个新窗口&#xff0c;是一个js文件 复制这个js文件的内容 然后在uniapp中新建svg.js文件&#xff0c;把从上面复制的代码粘贴到这个svg.js中 在main.js中引…

在本地测试nginx中localhost不行,需要写成127.0.0.1

在Windows 10系统的命令提示符cmd中&#xff0c;执行命令ping localhost&#xff0c;并没有出现我与其的ip地址“127.0.0.1”&#xff0c;而是“[::1]”。 问题原因 在cmd中ping localhost解析出来的是ipv6的::1的原因是windows有个优先解析列表&#xff0c;当ipv6的优先级高于…

C++学习笔记——对象的指针

目录 一、对象的指针 二、减少对象的复制开销 三、应用案例 游戏引擎 图像处理库 数据库管理系统 航空航天软件 金融交易系统 四、代码的案例应用 一、对象的指针 是一种常用的技术&#xff0c;用于处理对象的动态分配和管理。使用对象的指针可以实现以下几个方面的功…

pythroch abaconda 安装 cuda、版本确定、pytorch 安装

一、简述 公司有一个深度学习的项目&#xff0c;身上也没有其他项目&#xff0c;恰好乘着个机会学一下pytorch 和YOLOv8. 1、下载abaconda https://repo.anaconda.com/archive/ 2、安装 环境变量要✔ 其他一直下一步 3、测试 (base) C:\Users\alber>conda -V cond…

Tensorflow Lite从入门到精通

TensorFlow Lite 是 TensorFlow 在移动和 IoT 等边缘设备端的解决方案&#xff0c;提供了 Java、Python 和 C API 库&#xff0c;可以运行在 Android、iOS 和 Raspberry Pi 等设备上。目前 TFLite 只提供了推理功能&#xff0c;在服务器端进行训练后&#xff0c;经过如下简单处…

C++11_lambda表达式

文章目录 一、lambda表达式1.lambda的组成2.[capture-list] 的其他使用方法2.1混合捕捉 二、lambda表达式的使用场景1.替代仿函数 总结 一、lambda表达式 lambda表达式是C11新引入的功能&#xff0c;它的用法与我们之前学过的C语法有些不同。 1.lambda的组成 [capture-list] …

消息开始事件message start event

一&#xff1a;bpmn 二&#xff1a;java repositoryService.createDeployment().name("消息事件流程").addClasspathResource("bpmn/msg_event_process.bpmn").deploy(); identityService.setAuthenticatedUserId("huihui"); ProcessInstance p…

vue3中ref和reactive联系与区别以及如何选择

vue3中ref和reactive区别与联系 区别 1、ref既可定义基本数据类型&#xff0c;也可以定义引用数据类型&#xff0c;reactive只能定义应用数据类型 2、ref在js中取响应值需要使用 .value&#xff0c;而reactive则直接取用既可 3、ref定义的对象通过.value重新分配新对象时依旧…