精华内容
下载资源
问答
  • 菱形继承是多重继承中跑不掉的,Java拿掉了多重继承,辅之以接口。C++中虽然没有明确说明接口这种东西,但是只有纯虚函数的类可以看作Java中的接口。在多重继承中建议使用“接口”,来避免多重继承中可能出现的各种...
  • 继承是面向对象编程的一个重要的方式,通过继承,子类就可以扩展父类的功能。这篇文章主要介绍了Python多重继承之菱形继承,需要的朋友可以参考下
  • 主要介绍了C++中的菱形继承深入分析的相关资料,需要的朋友可以参考下
  • 菱形继承

    2021-02-02 11:06:30
    菱形继承 概念: 两个派生类继承同一个基类,又有某个类同时继承这两个派生类。这种继承被称为菱形继承,或者钻石继承 菱形继承的问题: 1.羊继承了动物的数据,骆驼也继承了动物的数据,当羊驼使用数据时,就会产生...

    菱形继承

    概念:
    两个派生类继承同一个基类,又有某个类同时继承这两个派生类。这种继承被称为菱形继承,或者钻石继承
    在这里插入图片描述
    菱形继承的问题:
    1.羊继承了动物的数据,骆驼也继承了动物的数据,当羊驼使用数据时,就会产生二异性
    2.羊驼继承动物的数据继承了两份,但是这份数据我们只需要一份
    虚继承前:

    #include<iostream>
    using namespace std;
    class Animal {
    public:
    	int age;
    };
    class Sheep : public Animal {};
    class Camel : public Animal {};
    class SheepTuo : public Sheep, public Camel {};
    int main()
    {
    	SheepTuo st;
    	st.Sheep::age = 18;
    	st.Camel::age = 19;
    	//当我们出现菱形继承的时候,有两个父类拥有相同数据,需要作用域加以区分
    	cout << "绵羊的年龄为:" << st.Sheep::age << endl; //18
    	cout << "骆驼的年龄为:" << st.Camel::age << endl; //19
         cout << "羊驼的年龄为:" << st.age << endl;  //会因为数据不明确报错
      //这份数据我们知道只需要有一份就可以了,菱形继承导致数据有两份,造成资源浪费
    	system("pause");
    	return 0;
    }
    

    虚继承后:

    #include<iostream>
    using namespace std;
    class Animal {
    public:
    	int age;
    };
    //利用虚继承,解决菱形继承的问题
    //继承之前加上关键字virtual变为虚继承
    //Aniamal类称为虚基类
    
    class Sheep :virtual public Animal {};
    class Camel :virtual public Animal {};
    class SheepTuo :virtual public Sheep, public Camel {};
    int main()
    {
    	SheepTuo st;
    	st.Sheep::age = 18;
    	st.Camel::age = 19;
    	//当我们出现菱形继承的时候,有两个父类拥有相同数据,需要作用域加以区分
    	cout << "绵羊的年龄为:" << st.Sheep::age << endl; //19
    	cout << "骆驼的年龄为:" << st.Camel::age << endl; //19
    	cout << "羊驼的年龄为:" << st.age << endl; //虚继承后,数据只有一份,现在打印年龄,三个都为19
    
      //这份数据我们知道只需要有一份就可以了,菱形继承导致数据有两份,造成资源浪费
    	system("pause");
    	return 0;
    }
    

    虚继承前,羊驼类中的内部结构:

    Sheep类里面存放的是自己m_age;
    Tuo类里面存放的是自己的m_age;
    两个age并不相同;
    SheepTuo会在继承时,继承两个名字同为age,但所存放数据内容不同,因此编译器不知道如何处理,会产生二异性;

    虚继承后,羊驼类中的内部结构:
    在这里插入图片描述
    sheep类里面所存放的变为了一个vbptr的指针
    vbptr指针解释:v----virtual b----base ptr-------pointer,虚基类指针
    vbptr虚基类指针指向一个虚基类表
    在这里插入图片描述

    虚函数表相当于一个数组里面存储的是虚函数表指针,偏移量相当于访问数组中第几个元素得到带元素的值,这里数组中的值代表着sheep和tuo类里面的指针需要移动几个字节才能访问到animal里面的age

    因此在发生虚继承后,羊和驼中原先继承下来的age变成了vbptr指针都指向各自的虚函数表,通过各自虚函数表中指针的偏移量找到animal基类中age,避免了菱形继承的二义性

    在这里插入图片描述
    可以利用羊驼类的内部结构分布,通过指针的来进行操作

    注意:指针无论类型,所占空间都为4字节

    展开全文
  • c++菱形继承

    2021-09-04 10:12:53
    菱形继承 菱形继承概念: 两个派生类继承同一个基类 又有某个类同时继承两个派生类 这种继承被称为菱形继承 典型的菱形继承案例: 菱形继承问题: 1.羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用...

    菱形继承

    菱形继承概念:

    两个派生类继承同一个基类
    又有某个类同时继承两个派生类
    这种继承被称为菱形继承

    典型的菱形继承案例:

    菱形继承问题:

    1.羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
    2.草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以了。

    代码示例:

    #include <iostream>
    using namespace std;
    //动物类
    class Anima1
    {
    public:
           int m_Age;
    };
    //利用虚继承 解决菱形继承的问题
    //继承之前 加上关键字 virtual 变为虚继承
    //Anima1类称为虚基类
    //羊类
    class Sheep :virtual public Anima1
    {
    };
    //驼类
    class Tuo :virtual public Anima1
    {
    };
    //羊驼类
    class SheepTuo :public Sheep, public Tuo
    {
    };
    void test01()
    {
           SheepTuo st;
           st.Sheep::m_Age =18;
           st.Tuo::m_Age = 28;
           //当菱形继承,两个父类拥有相同数据,需要加以作用域区分
           cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
           cout << "st.Tuo::m_Age =  " << st.Tuo::m_Age << endl;
           cout << "st.m_Age=" << st.m_Age << endl;
           //这份数据我们知道,只要有一份就可以,菱形继承导致数据有两份,造成资源浪费
    }
    int main()
    {
           test01();
           return 0;
    }
    
    

    总结:

    菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
    利用虚继承可以解决菱形继承问题

    可以加QQ群互相交流
    在这里插入图片描述

    也可以关注一下微信公众号黑马金牌编程
    在这里插入图片描述

    展开全文
  • 什么是继承 继承是什么: 继承是面向对象程序设计、使得代码可以复用的重要手段,它允许程序员在保持原有特性的基础上进行扩展,增加功能,这样产生的新类,称之为派生类 继承的目的: 让子类继承和复用父类定义的...

    1. 什么是继承

    继承是什么:
    继承是面向对象程序设计、使得代码可以复用的重要手段,它允许程序员在保持原有特性的基础上进行扩展,增加功能,这样产生的新类,称之为派生类。

    继承的目的:

    让子类继承和复用父类定义的成员和方法、继承下来的变量是独立的、各自拥有各自的内存空间。
    在这里插入图片描述

    2. 继承方式和访问限定符

    在这里插入图片描述

    1. 可见在派生类中,子类的访问方式是,MIN(继承方式,基类访问方式)

    2. 实际基类的private成员在派生类不可见,只是语法上无法访问,假如你调用基类的方法还是可以用的。
      在这里插入图片描述
      但是调用继承的方法可以用
      在这里插入图片描述

    3. 使用关键字class时默认的继承方式为private,使用struct时默认的继承方式是public,建议显示的写出继承方式

    4. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。

    5. 实际运用中,一般都是使用public继承,很少并且不提倡用protected和private继承,因为这两种继承下来的成员都只能在子类的类里面使用,实际中扩展延伸性不强

    2.1 基类与派生类赋值

    1. 派生类对象可以赋值给基类的的对象/指针/引用,这种方式叫做切片或者切割。即将子类中父类拿部分切下来,赋值过去
      在这里插入图片描述
      而且当发生切片的时候,_name是string类型,还会深拷贝。
      在这里插入图片描述

    应用场景:
    在这里插入图片描述
    在这里插入图片描述

    形参并没有发生隐式类型转换,假如是隐式类型转换的话,形参是肯定要加const的,因为引用的是临时对象

    1. 基类对象不能赋值给派生类对象
      类似于:参数是单向迭代器时,双向的可以传入,是双向迭代器的时候,单向的不可传入
    2. 基类的指针可以通过强转可以赋值给派生类的指针,前提是这个基类的指针必须指向一个派生类对象

    在这里插入图片描述

    1. 所有的前提都是在public继承下才会存在

    2.2 继承中的作用域

    1.在继承体系中基类和派生类都有独立的作用域(即可以定义同名变量)

    2.子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的 直接访问,这种叫做隐藏,也叫做重定义
    类似于全局变量和局部变量同名,就近使用局部变量。
    在子类成员函数中,可以使用基类::基类成员 显示访问

    3.需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏

    4.注意在实际中在继承体系里面最好不要定义同名的成员

    2.3 派生类的成员函数

    2.3.1 构造函数

    这种方法是错误的。
    在这里插入图片描述
    这里将它看做一个对象。调用他的构造函数,不写的话会自己调用默认构造函数(Tom)
    在这里插入图片描述

    假如没有默认构造函数的话必须在参数列表写(jerry)

    2.3.2 拷贝构造

    在这里插入图片描述

    2.3.3 赋值重载

    在这里插入图片描述
    上述四个,都是先调用父类的成员函数,然后再自己处理自己的成员变量

    2.3.4 析构函数

    析构函数十分特别。
    在这里插入图片描述
    第一个问题,竟然报错了,其实这里是构成了隐藏,但是函数名明明不一样为什么就构成隐藏了呢?

    由于多态的需要,所有的析构函数都被处理成了 destructor,所以他们的名字在编译器看来都是一样的。所以需要显示调用。
    第二个问题,这竟然调用了两次person的析构。
    在这里插入图片描述
    梳理一下过程,定义子类对象,由于参数列表中,初始化的顺序是声明的顺序,所以无论怎么写,都是父类构造函数先构造,然后在构造子类的。但是先析构的是子类对象。假如我们显示的调用就会使父类先析构。
    所以在汇编层面他会自己在子类成员析构后,自动调用父类成员。

    3. 继承与友元

    友元关系不能继承,也就是说基类的友元不能访问子类私有和保护成员。要使用的话必须重新声明

    4. 继承与静态

    基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。

    5. 菱形继承

    菱形继承是C++继承设计的一个缺陷,由于需要不断向前兼容,结果越陷越深,解决问题又要引入新的问题。
    这是一个单继承,不存在问题
    在这里插入图片描述
    在这里插入图片描述
    多继承用“,”分割,但是它引发出一个问题。

    在这里插入图片描述

    那就是菱形继承,一定会引起两个问题,数据冗余和二义性

    5.1 菱形虚拟继承

    在这里插入图片描述
    Person中有_name,Student,Teacher也一定会继承下来。由此助手类继承了Student和Tcacher,这样继承了两个_name。所以
    在这里插入图片描述
    所以我们使用的时候就必须加上访问限定符
    在这里插入图片描述
    所以二义性就可以这样解决。但是数据冗余怎么解决呢?
    使用virtual,虚继承来解决。
    在这里插入图片描述

    在这里插入图片描述
    注意,是在Student和Teacher类,继承时加virtual。
    Assisant不变。
    在这里插入图片描述

    在这里插入图片描述
    所以显示调用解决了二义性问题,但没有解决数据冗余,而virtual既解决了数据冗余而且也解决了二义性。

    5.2 虚基表

    在这里插入图片描述

    D d;
    sizeof(d);
    

    显然是20
    是因为d中继承了B和C里的成员。显然没有虚继承之前有着数据冗余和二义性。
    在这里插入图片描述

    将B,C两个类加上虚拟继承,那么加入虚拟继承后的类是多大呢?
    在这里插入图片描述

    前面提到,虚拟继承解决了数据冗余和二义性,只有4个成员变量。
    此时是否为16呢?
    在这里插入图片描述
    研究一下底层。
    在这里插入图片描述
    原本不加虚继承之前,继承了B中的a和b,继承了C中的a和c。对d对象取地址,对应的地址存放着有效值。可以看到加了虚继承后,原本那块地址对应的是一个冗余有效数据,现在变成了地址,也就是说那块地址现在存了一个指针。我们把它叫做虚基指针。它又指向一个虚基表。原本的两个a现在变成一个a,这个a,现在是一个虚基类对象,存储在最下面。
    在这里插入图片描述
    那么这个虚基指针指向的虚基表有什么用呢?

    在这里插入图片描述
    虚基表存着当前位置距离虚基类对象的偏移量,而存储着虚基指针的那个地址地址通过虚基指针拿到偏移量,相加得到之后,就指向了最下面的公共虚基类对象。这样就完成了处理数据冗余和二义性的问题。
    来看一个例子:

    B& rb=d;
    rb.a=1;
    

    d对象继承了B对象的a,rb先找到继承B对象的a的地址(引用的是C类型的话,就找c对象对应的a),不过此时由于虚继承那块地址不在存储有效值,而是存储这一个虚基指针,指向一个虚基表,存储着偏移量,然后在指向公共的虚基类对象,从而修改。

    有了多继承就会有菱形继承,有了菱形继承就会引进数据冗余和二义性,而C++用虚拟继承解决了这一问题,虚拟继承底层实现了,让那块地址对应的区域不在存储冗余数据的值,而是存储一个虚基指针,指向一个虚基表,虚基表存储着偏移量,对应类型来找自己类型的a时(B类型找b的a,C类型找c的a),通过+偏移量,找到公共的虚基a对象。

    6. 继承的总结与反思

    1. 有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。

    2. 多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。

    3. public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象
      在这里插入图片描述
      例如动物和狗,更符合is-a,那么就用继承。头和眼睛,更符合has-a,用组合。假如两个都比较符合就用组合

    4. 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适
      合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,但能用组合,就尽量用组合。符合高内聚低耦合。

    展开全文
  • C++菱形继承分析

    2018-04-04 22:25:43
    C++菱形继承对象构造部分分析和虚表内存布局,附件包括了源码和二进制文件
  • 首先, 什么是继承? 继承的概念 继承是指面向对象程序设计使代码可以复用的重要手段, 它允许程序员在保持原有类特性的基础上进行扩展, 增加功能, 这样产生的类, 称为派生类(也称作子类). 这样讲了一大串, 其实核心的...

    首先, 什么是继承?
    继承的概念
    继承是指面向对象程序设计使代码可以复用的重要手段, 它允许程序员在保持原有类特性的基础上进行扩展, 增加功能, 这样产生的类, 称为派生类(也称作子类).

    这样讲了一大串, 其实核心的点就是:
    继承是类设计层次的复用(也就是针对类的代码复用, 让我们可以不用再去写一些重复度较高的类代码)

    举例来说明:
    举例之前, 我们先来看一下继承的定义格式

    class A
    {};
    //B继承了A
    class B : public A
    {};//其中A为基类(父类), B为派生类(子类)
    

    首先不考虑继承

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Person
    {
    public:
    	void Print()
    	{
    		cout << _name << endl;
    		cout << _age << endl;
    	}
    protected:
    	string _name = "Person";
    	int _age = 18;
    };
    
    class Teacher
    {
    protected:
    	string _name = "Person";
    	int _age = 18;
    	int _workid = 66666;
    };
    
    class Student
    {
    protected:
    	string _name = "Person";
    	int _age = 18;
    	int _id = 123456;
    };
    
    int main()
    {
    	Person p;
    	Student s;
    	Teacher t;
    	
    	system("pause");
    	return 0;
    }
    

    我们实现的Teacher类比起Person类, 多了一个工作id
    Teacher类比起Person类, 多了一个学号, 其余均重复, 这样的实现就会产生代码的冗余, 因此有了继承来实现代码复用

    class Person
    {
    public:
    	void Print()
    	{
    		cout << _name << endl;
    		cout << _age << endl;
    	}
    protected:
    	string _name = "Person";
    	int _age = 18;
    };
    
    class Teacher : public Person
    {
    protected:
    	int _workid = 66666;
    };
    
    class Student : public Person
    {
    protected:
    	int _id = 123456;
    };
    int main()
    {
    	Person p;
    	Student s;
    	Teacher t;
    
    	p.Print();
    	t.Print();
    	s.Print();
    
    	system("pause");
    	return 0;
    }
    

    Teacher类与Student均继承了Person, 就拥有了Person的成员变量和成员函数
    运行结果如图

    在这里插入图片描述
    这样我们就不必在派生类中再去定义重复的成员变量(_name, _age)和成员函数(Print).
    注意:这里的例子只是为了说明继承帮助我们完成代码复用的便捷之处, 接下来我们进一步对继承进行了解

    通过上面的例子, 我们看到成员变量并没有使用private访问限定符去限定成员变量, 这是有原因的.
    原因就在于访问限定符和继承关系
    首先, 继承方式可分为3种: public继承, protected继承, private继承
    对应类中3种访问限定符: public protected private
    在类中访问限定符protected和private都是为了保护类的成员对外不可见, 一般的在类中保护成员时用protected或者private没有什么太大区别, 那么我们就会问, 那private和protected有一个不就好了吗, 其实他们的区别是在继承中体现出来的. 接下来我们详细讨论访问限定符和继承关系
    这里,我们将访问限定符和继承关系归纳总结到表格当中,如下图所示
    访问限定符和继承关系
    乍看上去可能让人头大, 那么我们根据这个表格和下面的总结将其理解,

    1. 如果基类的成员是private的(基类成员被访问限定符private限定),那么无论我们使用哪种继承方式, 派生类都无法访问到继承下来的基类成员.
      也就是说, 一般的我们要继承某一个类, 就不会将这个类的成员设置为private的, 因为这样的继承毫无意义
    2. 基于第一点, 如果不能使用private限定类中的成员, 这样不就破坏了封装性了吗, 为了继承而破坏封装性, 这显然不是我们想要的, 因此, 访问限定符protected站了出来, 这也就是private与protected的区别.
      意思就是, 我们想要继承一个基类, 但是又不想破坏封装性, 就要用到protected.
    3. 当我们想知道派生类中继承下来的成员是否可见, 其实很简单, 首先看基类成员的访问限定符, 其次看继承方式, 这两个哪个权限小, 继承下来的就是什么样的成员.(权限大小关系 public > protected > private)
      也就是 Min(基类成员的访问限定符, 继承方式).
      这一条可对应上面的表格来看.

    关于基类和派生类对象的赋值转换问题
    这里着重了解两点:

    1. 派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用
      举例来看:

       //基类
       class A
       {
       protected:
       	int _a = 1;
       	int _b = 2;
       	int _c = 3;
       };
       //派生类
       class B : public A
       {
       protected:
       	int _d = 4;
       };
       void test()
       {
       	B b;//派生类对象
       
       	A a = b;//派生类对象赋值给基类对象
       	A* pa = &b;//派生类对象赋值给基类指针
       	A& ra = b;//派生类对象赋值给基类引用
       }
      

    2.基类的对象不能赋值给派生类对象

    b=a;//基类对象不能赋值给派生类对象
    

    在这里插入图片描述
    画图来理解:

    在这里插入图片描述
    这也就是说,当我们将派生类对象赋值给基类对象时,有一种形象的说法叫做切片, 意思就是将派生类中继承自父类的部分再赋值回去.(派生类B中的成员_a,_b,_c均继承自父类A, 将这部分切出来赋值给过去).
    反过来看, 如果将基类赋值给派生类, 那么_d的值要从哪里来, 赋值为NULL吗? 那我访问_d成员不就出错了吗?

    其实基类的指针可以通过类型强转赋给派生类指针, 这里只简单提一下, 有兴趣可以继续了解.

    关于继承当中的隐藏(重定义)
    先来看下面一个场景

    class A
    {
    public:
    	void func()
    	{
    		cout << "A::func()" << endl;
    	}
    };
    
    class B : public A
    {
    public:
    	void func()
    	{
    		cout << "B::func()" << endl;
    	}
    };
    

    有一个基类A, 派生类B继承A, 要注意这个时候类A与类B中均有一个成员函数 func(), 这个时候我们接着来看

    A a;//定义一个基类对象
    a.func();//这里应该没什么疑问, 基类调用自己的func
    

    在这里插入图片描述
    那么子类去调用func呢?

    B b;
    b.func();
    

    我们知道, B类继承了基类A的func, 但是子类B又有一个自己的func, 那么它调用的到底是那一个呢?
    先看结果:
    在这里插入图片描述
    那继承下来的A类中的func去哪里了呢?
    这就涉及到了继承当中比较重要的一个知识点了!
    隐藏
    看到同名函数, 我们肯定会联想到函数重载, 切记, 这里不是函数重载!!!

    函数重载涉及到的同名函数是在同一作用域下, 这里的两个func明显处于不同的作用域, 一个是A::func(), 一个是B::func().
    因此, 这里是隐藏, 也叫作重定义.

    总结一下关键的点:
    1.在继承当中, 基类和派生类都有自己独立的作用域
    2.一旦出现子类和父类的同名成员, 子类就会隐藏父类的同名成员(也就是屏蔽掉对父类同名成员的访问)
    ps:只要函数名相同就构成隐藏
    3.当然,如果我们想要 在派生类中访问 被隐藏的基类同名成员, 需要使用 基类::基类成员 的方式
    比如下面这样:

    class A
    {
    public:
    	void func()
    	{
    		cout << "A::func()" << endl;
    	}
    };
    
    class B : public A
    {
    public:
    	void func()
    	{
    		cout << "B::func()" << endl;
    	}
    	void test()
    	{
    		func();//调用B类自己的func
    		A::func();//调用A类的func
    	}
    };
    

    在这里插入图片描述
    继承与友元
    注意友元关系是不能继承的, 也就说基类的友元并不能访问派生类的私有和保护成员.
    比如下面的示例:

    //定义一个Person类
    class Person
    {
    private:
    	char _name;
    };
    //类外访问Person的私有成员_name
    void _friendFunc(Person& p)
    {
    	cout << p._name << endl;//无法访问, 下图所示
    }	
    

    在这里插入图片描述
    将_friendFunc变为Person类的友元函数

    class Person
    {
    	friend void _friendFunc(Person& p);//友元函数
    private:
    	char _name;
    };
    

    此时_friendFunc函数可以访问到Person类的私有成员_name.

    接下来, Student类继承Person

    class Student;
    class Person
    {
    public:
    	friend void _friendFunc(const Person& p, const Student& s);//友元函数
    private:
    	char _name;
    };
    
    class Student : public Person
    {
    protected:
    	int _id;
    };
    
    void _friendFunc(const Person& p, const Student& s)
    {
    	cout << p._name << endl;
    	cout << s._id << endl;//无法访问
    }
    

    在这里插入图片描述

    继承与静态成员
    基类定义了static成员之后, 整个继承体当中只有一个这样的成员.(与多少个派生类无关, static成员只有一个)

    多继承,菱形继承以及菱形虚拟继承
    当一个子类只有一个直接父类时我们称这个继承关系为单继承.
    比如:

    class A
    {};
    class B : public A
    {};
    class C : public B
    {};
    

    当一个子类有两个或两个以上的父类时称这个继承关系为多继承
    比如:

    class A
    {};
    class B 
    {};
    class C : public A, public B
    {};
    

    在这里插入图片描述
    也正是由于多继承的存在, 菱形继承出现了, 菱形继承是多继承的一种特殊情况
    比如:

    class alpha
    {};
    class A : public alpha
    {};
    class B  : public alpha
    {};
    class C : public A, public B
    {};
    

    在这里插入图片描述
    菱形继承的出现也就导致了数据冗余和二义性的问题, 同样举例来看:
    看下面这段代码:

    class A
    {
    public:
    	int _a = 1;
    };
    class B : public A //B继承了A
    {
    public:
    	int _b;
    };
    class C : public A //C也继承A
    {
    public:
    	int _c;
    };
    class D : public B, public C //D同时继承B和C
    {
    public:
    	int _d;
    };
    int main()
    {
    	D d;
    	cout << sizeof(D) << endl;
    	return 0;
    }
    

    先想一想 sizeof(D)是多大呢?
    来计算一下, B继承了A,成员有 _a 和 _b 为8字节, C同理 8字节
    D同时继承了 B 和 C, 那么就是 8 + 8 + 4 = 20字节.
    在这里插入图片描述
    既然这样的计算符合预期, 这就说明在D中有两个_a, 一个继承自B, 一个继承自C
    显然造成了数据冗余和数据访问时的二义性.
    这时候我们去访问以下D中的_a试一下

    D d;
    cout << d._a << endl;
    

    在这里插入图片描述
    由于数据二义, 不知道我们访问是哪个_a, 因此编译直接报错
    所以我们应该用下面的方式去访问_a

    cout << d.B::_a << endl;
    cout << d.C::_a << endl;
    

    在这里插入图片描述
    明确了要访问哪一个_a, 这样就可以访问到继承下来的成员_a.
    为了更直观的感受, 我们使用监视窗口来看
    代码如下:

    class A
    {
    public:
    	int _a;
    };
    class B : public A
    {
    public:
    	int _b;
    };
    class C : public A
    {
    public:
    	int _c;
    };
    class D : public B, public C
    {
    public:
    	int _d;
    };
    
    
    int main()
    {
    	D d;
    
    	d.B::_a = 1;
    	d.C::_a = 2;
    
    	d._b = 3;
    	d._c = 4;
    	d._d = 5;
    
    	system("pause");
    	return 0;
    }
    

    开始调试
    在这里插入图片描述
    一开始d为空, 占20字节
    逐步往下走
    在这里插入图片描述
    对应关系如下
    在这里插入图片描述
    可以很直观的看到数据冗余的问题, _a有两个
    在访问_a的时候在前面加上域作用符的限定, 这其实也就解决了二义性的问题, 但是数据冗余的问题我们还没有解决.

    这也就引入了虚继承, 可以说虚继承就是为了解决菱形继承所出现的问题而出现的.
    虚继承也就是在继承的时候使用 virtual关键字
    利用虚继承修改上述代码:

    class A
    {
    public:
    	int _a;
    };
    class B : virtual  public A
    {
    public:
    	int _b;
    };
    class C : virtual  public A
    {
    public:
    	int _c;
    };
    class D : public B, public C
    {
    public:
    	int _d;
    };
    
    
    int main()
    {
    	D d;
    	cout << sizeof(D) << endl;
    
    	//cout << d._a << endl;
    	//cout << d.B::_a << endl;
    	//cout << d.C::_a << endl;
    	d.B::_a = 1;
    	d.C::_a = 2;
    
    	d._b = 3;
    	d._c = 4;
    	d._d = 5;
    
    
    	system("pause");
    	return 0;
    }
    

    注意,此时 sizeof(D)并不是20了, 因为虚继承解决了数据冗余和二义性问题, 那么我们会猜现在的sizeof(D)是不是16字节, 其实并不是, 我们来看结果
    在这里插入图片描述
    24字节, 解决了数据冗余的问题所占字节空间却变大了, 这是怎么回事呢? 同样的我们再来看监视窗口

    在这里插入图片描述
    注意看, 再走一步

    在这里插入图片描述
    显然, 此时_a只有一份接着往下走
    在这里插入图片描述
    对应关系如下:
    在这里插入图片描述
    看到这幅图, 我们会有一个疑问,
    88 cc 94 00

    94 cc 94 00
    这两个东西是什么呢, 看起来像是一个指针之类的东西, 我们再拿出一个内存窗口来看
    在这里插入图片描述
    这个时候就看的很清楚了, 其实这两个指针指向的是存偏移量的空间, 方便找到公共的A(因为B和C都继承了A).
    也就是说, 这里通过上面我们提到的88 cc 94 00和94 cc 94 00这两个指针, 指向一张表, 这个表称为虚基表(B和C虚继承自A, 也就是说A是虚基类), 而这两个指针叫做虚基表指针, 同过这个指针可以找到虚基表, 而虚基表中存了偏移量, 通过这个偏移量就能够找到B和C所共有的A.

    继承的总结
    很多人觉得C++语法较复杂, 其实多继承就是其中一个体现, 有了多继承, 就有了菱形继承, 而有了菱形继承就又有了菱形虚拟继承. 因此一般情况下, 不建议设计出菱形继承, 否则很有可能出现问题.

    以上就是我个人对继承这一块的一些相关理解, 有不恰当的地方希望大家指正, 十分感谢.

    展开全文
  • 继承是C++三大特性之一,而继承又分为单继承和多继承,将单继承和多继承组合起来就成了复杂的菱形继承,本文重点讲菱形继承带来的问题和如何解决该问题。 ●回顾知识: 单继承:一个子类只有一个直接父类时称这个...
  • 继承 继承机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到...
  • C++菱形继承问题和虚继承分析

    万次阅读 多人点赞 2019-01-10 00:03:58
    菱形继承 二义性 虚基类 虚基类的作用是 在间接继承共同基类时只保留一份基类成员。 class A//A 基类 { ... }; //类B是类A的功用派生类, 类A是类B的虚基类 class B : virtual public A { ... }; //类C是类A的功用...
  • 羊驼类继承的实际是两个虚基类指针 vbptr
  • C++继承之菱形继承

    千次阅读 2020-02-20 15:46:42
    在学习菱形继承之前,我们先了解两个概念:单继承、多继承。 单继承 一个子类只有一个直接父类时称这个继承关系为单继承。 多继承 一个子类有两个或以上直接父类时称这个继承关系为多继承。 菱形继承 菱形继承是多...
  • C++三大特性 继承 封装 多态 继承就是一种管理行为。为了使我们规范的去使用类里面的成员。把成员函数或者成员变量保护起来,必须通过对象去调用。 继承: 其实是类级别的复用。对父类没有任何影响。子类将父类的...
  • C++菱形继承原理分析

    2021-01-20 03:34:41
    菱形继承在C++继承中是一种复杂的继承关系,存在着二义性和数据冗余的问题,而菱形虚拟继承则是为了解决菱形继承所存在的问题。为了探讨菱形虚拟继承如何解决该问题,我先建立个一个较为简单的菱形继承模型。下面...
  • 继承体系概念继承方式赋值兼容规则同名隐藏子类对象的构造过程菱形继承 概念 是面向对象程序设计是代码可以复用的最重要的手段,它允许程序员在保持原有的类的特性的基础下进行拓展,增加功能。 这样产生的类被称为...
  • Java中的菱形继承

    2021-07-27 15:47:02
    1、什么叫菱形继承 菱形继承用通俗的话来说就是A继承B和C,B和C又继承于D。 直接看图片会清晰很多 但是由于java里是不允许抽象类多继承的,所以用接口代替 public interface D{ void D(); } public interface C{ ...
  • C++中的继承以及菱形继承

    千次阅读 2018-04-12 14:58:54
    继承:面向对象程序设计使代码可以复用的最重要手段,它允许程序员在保持原有类特性的基础上进行拓展,增加功能。   继承格式:class 派生类:(public,protected,private)基类   继承方式  ...
  • 继承的概念和定义<1> 什么是继承? 1. 继承的概念和定义 首先回顾一下C++面向对象的三大特性:封装、继承和多态。先解释前两个 封装:本质上一种更好的管理。相较于C语言面向过程,C++将数据和方法都要放在类...
  • C++继承(二):菱形继承,常见面试题 本章内容C++继承(二):菱形继承,常见面试题1.菱形继承的概念2.详解2.1菱形继承的问题2.1.1数据二义性:2.2.2数据冗余2.2虚拟继承解决数据冗余和二义性2.2.1用法2.2.2虚拟...
  • 一,什么是继承继承是类和类之间的关系,是代码复用的重要手段,允许在保持原有类结构的基础上进行扩展,创建的新类与原有的类类似,只是多了几个成员变量和成员函数。 二,继承的方式 class 派生类名 :继承...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 21,115
精华内容 8,446
关键字:

菱形继承