菱形继承和菱形虚拟继承
C++三大特性 继承 封装 多态
继承就是一种管理行为。为了使我们规范的去使用类里面的成员。把成员函数或者成员变量保护起来,必须通过对象去调用。
继承其实是类级别的复用。对父类没有任何影响。子类将父类的成员都继承下来,只是关系发生了变化。
菱形继承
单继承: 一个子类只有一个直接父类时,称这个继承关系为单继承
多继承:一个子类有两个或两个以上的直接父类时称这个继承关系为多继承
菱形继承: 菱形继承是多继承的一种特殊情况
菱形继承带来的问题: 从成员模型可以看出来,菱形继承有数据冗余和数据二义性的问题。在最下面的一层D类中,对象会有2份最上层对象A类里面的成员
再来看一个例子,来看菱形继承所带来的问题
class Person
{
public:
string name;
};
class Student : public Person
{
public:
string No;
};
class Teacher: public Person
{
public:
string id;
};
class Course : public Student, public Teacher
{
public:
string course;
};
int main ()
{
//这样就会有二义性无法明确知道访问的是哪一个
Course c;
c.name = "tom";
// 这样就可以解决二义性的问题, 但是数据冗余问题无法解决
c.Student::name = "wang";
c.Teacher::name = "lili";
}
可以看出:
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在B类和C类的继承A类时使用虚拟继承,就可以解决菱形继承的问题。
菱形虚拟继承
菱形虚拟继承的概念:
菱形虚拟继承就是在多个类同时继承一个类的时候加上virtual关键字,使得父类的变量在全局只有一份,多个继承父类的类可以同时找到它并修改它
作用就是: A类是父类, B,C类继承父类, D类继承B,C类。 那么A类的成员变量就在B和C类中,但是D类继承B,C类,A类的成员变量就在D类中有两份。菱形虚拟继承的作用是:使得A类的成员变量在对象中只有一份。
菱形虚拟继承的做法:
在多个类同时需要继承同一个父类的时候,在继承方式前加上virtual关键字。
例子:
class Person
{
public:
string name;
};
class Student : virtual public Person //学生类需要继承人这个类 加上virtual关键字,虚拟继承
{
public:
string No;
};
class Teacher: virtual public Person //老师类也需要继承人这个类 加上virtual关键字,虚拟继承
{
public:
string id;
};
class Course : public Student, public Teacher
{
public:
string course;
};
int main ()
{
Course c;
c.name = "tom"; //把name改为tom,所有类里面的name全都该为tom
}
总结:这样子,就不会产生数据二义性了,和数据冗余了,因为是继承,所有所有类里面都含有基类变量name。但是菱形虚拟继承做的是在所有类里面的name变量都是同一个。所有你Course类里面的name改变,所有类成员里面的name变量都是"tom"
那么如何实现的呢??
虚拟继承的原理(虚拟继承解决数据冗余和二义性的原理)
我们可以借助内存窗口观察对象的成员模型。
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;
d.B::_a = 1;
d.C::_a = 2;
cout << d.B::_a << endl; //输出2
cout << d.C::_a << endl; //输出2
d._b = 3;
d._c = 4;
d._d = 5;
}
程序结果是什么呢?
因为解决了菱形继承的问题,所以B对象中的_a和C对象中的_a都是同一个_a, 所以对_a最后一个赋值的是2, 所以都是2。
如果在B和C继承A的时候,都不加上virtual关键字,那么就不会菱形继承了。 所以会有数据冗余的问题,所以第一个d.B::_a输出的是1,第二个d.B_a输出的就是2.
菱形继承的内存对象模型(继承的时候不加上virtual关键字)
我们可以看到,在B类和C类中第一个位置的变量都是A类中成员变量_a, D类的对象中包含了B类和C类的成员变量,那么_a就被包含了两次,已经有冗余了。
菱形虚拟继承的内存对象模型:
A是虚基类,A是被继承的。所以B和C都会有虚基表指针。虚基表指针指向虚基表,虚基表中存的是偏移量,通过偏移量可以找到虚基类中的成员变量。
总结:
把虚基类A的成员a放在所有对象的最下面。 B类和C类的对象空间里面存放一个指针,叫做虚基表指针。指向一个虚基表。虚基表指针记录的是当前位置距离下面虚基类的偏移量。因为B类和C类都要找到它。就是通过偏移量找到它的,也就是通过偏移量找到a。
问题总结:
问题1: 什么是菱形继承?菱形继承带来的问题是什么?
问题2: 什么是菱形虚拟继承? 如何解决数据二义性的问题?
-
菱形继承是当有多个类(A,B)同时继承一个父类©时,同时也有一个类(D)同时继承这多个类的其中的(>=2)几个(A,B)。
这样的话,C中含有的成员变量,在A和B中都会含有一份,D同时继承A和B,那么C中的成员变量就会在D中存在两份。
所以菱形继承带来的问题是,含有重复的变量包含,会造成数据冗余和数据二义。 -
菱形虚拟继承就是在多个类同时继承一个类的时候加上virtual关键字,使得继承父类的变量在全局只有一份,多个类可以同时找到它并修改它。菱形虚拟继承是依靠虚基表来解决数据二义性问题的。通过virtual虚拟继承的类的栈中都会存一个虚基表指针,标识父类变量的偏移量,以找到父类变量。使得全局只有一个父类的变量