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

这篇具有很好参考价值的文章主要介绍了cpp_10_多重继承_钻石继承_虚继承。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

cpp_10_多重继承_钻石继承_虚继承,Cpp,c++

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都不偏移

 cpp_10_多重继承_钻石继承_虚继承,Cpp,c++  cpp_10_多重继承_钻石继承_虚继承,Cpp,c++    cpp_10_多重继承_钻石继承_虚继承,Cpp,c++

// 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  钻石继承

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

        cpp_10_多重继承_钻石继承_虚继承,Cpp,c++

2.1  钻石继承的问题

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

                           cpp_10_多重继承_钻石继承_虚继承,Cpp,c++

// 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)公共虚基类子对象汇聚子类对象中仅存一份实例 

                cpp_10_多重继承_钻石继承_虚继承,Cpp,c++

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

3.2  虚继承实现原理

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

                   cpp_10_多重继承_钻石继承_虚继承,Cpp,c++

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

        虚基类子对象,不在中间子类子对象中,保存在最后。(通过上述开关量可知)文章来源地址https://www.toymoban.com/news/detail-823840.html

到了这里,关于cpp_10_多重继承_钻石继承_虚继承的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【OpenCV】features2d_converters.cpp:2:10: fatal error: common.h: 没有那个文件或目录

    Linux环境下使用opencv的dnn模块调用yolov4遇到的坑(纯CPU) Ubuntu安装opencv4.4,第一次编译完成安装成功,发现编译时少加了几个选项,于是重新编译,结果报如下错误 发现opencv_contrib-4.4.0/modules/xfeatures2d/test/下的features2d文件夹是在第一次编译中报错少了一个hpp文件才加进去的,

    2024年02月15日
    浏览(28)
  • c#调用cpp库,debug时不进入cpp函数

    选中c#的项目,右击属性,进入属性页,点击调试,点击打开 调试启动配置文件UI ,打开 启用本机代码调试 。

    2024年02月16日
    浏览(31)
  • C++(20):多重继承与虚继承

    多重继承 是指从多个直接基类中产生派生类的能力。多重继承的派生类继承了所有父类的属性。 在派生类的派生列表中可以包含多个基类: 每个基类包含一个可选的访问说明符。如果说明符被忽略掉了,则 class 对应的默认访问说明符是 private , struct 对应的是

    2024年02月10日
    浏览(24)
  • 【QML】QML与cpp交互(一)—— QML直接调用cpp函数

    目录 1、cpp 创建一个类 2、将类对象暴露给QML 3、QML通过对象直接调用cpp函数 类模板如下:  要求:  使用  Q_OBJECT 宏需要继承 QObject 类。Q_OBJECT能够启用信号和槽机制、使用动态属性系统。(使用 Q_OBJECT 宏的类需要通过Qt的元对象编译器(moc)进行处理。) 使用  Q_INVOKABLE 修

    2024年02月02日
    浏览(30)
  • Alpaca-cpp(羊驼-cpp): 可以本地运行的 Alpaca 大语言模型

    Stanford Alpaca (羊驼):ChatGPT 学术版开源实现 Alpaca-Lora (羊驼-Lora): 轻量级 ChatGPT 的开源实现(对标 Standford Alpaca) I know。这几天介绍了很多 Alpaca,各种羊驼,似乎有些随心所欲、杂乱无章。但实际上,正如《寒战》中梁家辉饰演的李文斌被廉政公署问话时所说:“要在(每一个

    2023年04月24日
    浏览(48)
  • 4.4——多重继承

    在前面学习了一个派生类只有一个基类,这种派生方法称为单继承或单基派生。当一个派生类具有两个或多个基类时,这种派生方法称为多重继承或多基派生。 在C++中,声明具有两个以上基类的派生类与声明单基派生类的形式相似,只需要将继承的多个基类用逗号分隔即可,

    2024年02月03日
    浏览(25)
  • python对象的多重继承

    一个从多个父类继承过来的子类,可以访问所有父类的功能。并不推荐使用。 多重继承最简单有用的形式是mixin。假设在之前Contact类增加一个功能,允许给self.email发送一封邮件。 EmailableContact这个类不做任何特别的事(实际上,它仅仅是起到一个独立的类的作用),但是通过

    2024年02月16日
    浏览(25)
  • visual studio c++单项目中存在多个cpp,怎么单独运行某个cpp

    首先一个项目里只能有一个main方法。如果有多个cpp,这些cpp总共包含多个mian方法,点击 “本地windows 调试器”时,会报\\\"main已经在helloworld.obj中定义\\\"。  如果要调试.cpp,那么须保证这个项目里面只有一个main方法。 所以参考以下解决方案 1. 单个项目中保证多个cpp中只有一个

    2024年02月16日
    浏览(34)
  • 使用开源的zip.cpp和unzip.cpp实现压缩包的创建与解压

    目录 1、使用场景 2、压缩包的创建 3、压缩包的解压 4、CloseZipZ和CloseZipU两接口的区别 

    2024年02月07日
    浏览(40)
  • Solidity 多重继承 C3算法

      执行D.bar的结果 执行顺序 最右C.bar C里有 super.bar,执行的是b里的 bar B里的 super.bar 执行的是a里的bar 具体原因是: Solidity Diamond Inheritance - Guides and Tutorials - OpenZeppelin Forum solidity和python一样采用C3 继承算法 去掉代码里的 super.bar,结果就变成

    2024年02月12日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包