• C++中的继承(下)


    🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
    在这里插入图片描述

    一、如何定义一个不能被继承的类

    1.私有父类的构造函数

    因为父类构造函数的私有,继承过来的构造函数不可见
    C++98通过这种方式在实例化的时候间接做到

    class A
    {
    public:
    	A()
    	{}
    protected:
    	int _a;
    };
    
    class B:public A
    {
    };
    int main()
    {
    	B b;//报错
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.final关键字

    C++11就可以通过final关键字直接实现不可再继承(其他类在继承的时候就直接报错)

    class A final
    {
    //	...
    };
    
    • 1
    • 2
    • 3
    • 4

    final修饰的类也叫做最终类

    二、多继承

    1.单继承与多继承

    单继承:一个子类只有一个直接父类时称这个继承关系时单继承
    多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

    在这里插入图片描述

    class Derive:public Base1,public Base2
    
    • 1

    2.拷贝的顺序与继承顺序

    在这里插入图片描述
    从上图可以看出,拷贝的顺序和继承的顺序有关

    3.成员的地址映射关系

    在这里插入图片描述
    在这里插入图片描述
    因为栈的使用习惯是从高地址向低地址存储,但是操作系统访问内存的顺序是从低地址向高地址,所以存储的位置关系是这样的

    4.复杂的菱形继承

    C++是有多继承的,多继承就是由两个或者以上直接父类,就有可能导致菱形继承的问题,实际当中是很忌讳菱形继承的,所以后面的语言都不支持多继承。
    在这里插入图片描述
    在标准库中,也用到过菱形继承

    单继承和多继承会引发一个特别复杂的问题:菱形继承
    在这里插入图片描述
    在这里插入图片描述

    菱形继承的问题:
    1.数据冗余:Person的数据有两份,重复占用空间,构造的时候需要构造两次,拷贝的时候也要拷贝两次等等,都是浪费
    2.二义性:就是因为有相同的数据,所以访问的时候不知道去访问谁(会报字段不明确的错误),除非你这里直接去显示的访问(但是终究还是有构造两次,拷贝两次的浪费问题)

    5.菱形继承问题的解决–菱形虚拟继承

    问题提出

    菱形继承问题的解决需要用到虚继承
    虚继承的最上面的那个基类被称作 虚基类

    class Student: virtual public Person
    {	
    protected:
    	int _num;
    };
    class Teacher: virtual public Person
    {	
    protected:
    	int _id;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通过虚继承,能让继承下来的重复数据只有一份

    他是怎么做的呢?
    虚继承会在原来放虚基类成员的地方放一个指针,这个指针指向的表称为虚基表,存的是偏移量,因为菱形继承使用虚继承,需要被放在虚基表中的成员就是虚基类中的(只需要存一个指针,去找到虚基表,就可以找到多个虚基类的成员)
    所以同一把虚基类的成员放在最下面,用偏移量来找

    验证

    验证:
    在这里插入图片描述
    可以看出两个东西
    1.存储虚基类成员的位置换成了指向虚基表的指针
    2.虚基类的成员放在了最下面(为什么等会儿说),之所以没看到_a=1是因为被_a=2覆盖了,因为这时的两个_a是同一个。也就解决了菱形继承带来的数据冗余和二义性的问题

    然年再继续找到他的B的虚基表,看到存的是14,因为是六进制数,所以应该是20,正好对应_a的位置。所以能看出它存储的是偏移量
    在这里插入图片描述

    从这么一点数据量来看的话,确实没有节省空间,甚至还多了,但是如果这个公共的资源很大呢?原来需要很大的空间来存储两份,现在只需要两个指针和存储一份

    虚继承遇到赋值兼容转换(切割或切片)

    虚继承时,如果遇到切割/切片会发生什么
    在这里插入图片描述
    因为B是虚继承,所以会有一张虚表,他是通过d中从虚表拿到偏移量,找到_a(基类的资源),再进行切片,重新初始化b对象中的虚表。

    另外,也因为赋值兼容转换,所以要保证对象的存储模型的一致。

    用虚继承可以解决菱形继承的问题,但是还是付出了一些代价的,无论是访问的效率还是复杂程度,但是也是在可接受的范围之内,但是菱形继承,能不用就不用

    三、其他问题

    1.继承和组合

    继承一般都指公有继承
    继承是一种is-a的关系,也即是说每个派生类对象都是一个基类对象(人-学生,植物-花)
    组合是一种has-a的关系,假设B组合了A,每个B对象中都有一个A对象,组合也是复用,与继承的区别就在于组合受到权限的一些限制,比如继承能用基类的protected,组合就不行(比如轮胎-车 眼睛-头就是组合)

    class C
    {
    };
    class D
    {
    protected:
    	C _c;
    };
    //这就是一个组合 has-a
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.优先选用组合

    继承允许根据基类的实现来定义派生类的实现,这种通过生成派生类的复用称为白箱复用(white-box reuse),在继承方式中,基类的内部细节对子类可见,继承一定程度上破坏了基类的封装。基类的改变,对派生类有很大的影响,派生类和基类间的依赖关系很强,耦合度高。

    对象组合式类继承之外的一种复用方式,新的更复杂的功能可以通过组装或组合对象的方式来获得,对象组合要求被组合的对象有良好定义的接口,这种看不见底层细节的复用方式称为黑箱复用(black-box reuse).所以组合间没有很强的依赖关系,耦合度低。

    优先选用组合有助于保持每个类的封装

    但是有些关系就是要用继承(is-a)的,另外要实现多态,也必须要继承。类和类的关系可以用组合就用组合,关系越松散,执行效率越高,短板效应。

    比如说A有10个成员,1个公有,9个保护
    B继承A,A的任意改变都会改变B,有十种关联关系
    组合的话,语法上就限制了保护的成员我用不了,所以不改这个公有就和我没有瓜西,只有一种关联关系。

    在这里插入图片描述

  • 相关阅读:
    为什么同一表情‘‘.length==5但‘‘.length==4?本文带你深入理解 String Unicode UTF8 UTF16
    reticulate | R-python调用 | 安装及配置 | conda文件配置
    GMM算法
    正则表达式的学习心得
    隧道未来如何发展?路网全息感知,颠覆公路交通安全史
    自然语言生成技术现状调查:核心任务、应用和评估(1)
    自定义JPA函数扩展,在specification中实现位运算
    zabbix邮件报警配置
    网页按钮点击动画
    树莓派4B无屏幕连接Wi-Fi/启用ssh/创建用户
  • 原文地址:https://blog.csdn.net/zhu_pi_xx/article/details/128027129