⌨️多继承的概念
多继承指的是一个派生类是由多个基类继承而来的;
而在生活当中也有类似的例子:番茄既可以是水果,也可以是蔬菜;
而在C++2.0的版本中,就提出了多继承的概念,多继承允许一个派生类是由多个基类继承而来;
语法 🖱️
class Teacher { protected: int _id;//工号 }; class Student{//使用virtual关键字 protected: int _num;//学号 }; class Grad : public Teacher ,public Student{ protected: string _major; };
以该类为例:
该类中出现了Teacher
老师类类与Student
学生类两个类;
同时举出了一个Grad
研究生类代表Teacher
类与Student
两个类的派生类;
上述语法即为多继承;
⌨️棱形继承
虽然棱形继承一定程度提高了继承的多样性;
但随之而来的也是一定的问题;
以多继承为基础,同时也出现了棱形继承;
棱形继承是在多继承基础上产生的,假设一个基类拥有多个派生类,并在多次继承之后又将其若干个派生类(或者其子类)多继承了一个派生类即为棱形继承;
【一个标准的棱形继承】
以图来看棱形继承并不存在过多问题;
但棱形继承的本质问题为数据冗余以及存在数据的二义性;
假设有一个类为Person
;
class Person{ public: string _name; };
以该类继承出了两个派生类分别为Teacher
与Student
类;
class Teacher : public Person{ protected: int _id;//工号 }; class Student : public Person{//使用virtual关键字 protected: int _num;//学号 };
最后以上面的两个类作为基类再继承了一个派生类为Grad
class Grad : public Teacher ,public Student{ protected: string _major; };
此时的Grad
类中被继承的Person
中的_name
成员该从Teacher
与Student
中哪个类访问;
这就是所谓的二义性;
以图中34
,35
行为例,可以这么解决数据的二义性问题(使用域作用限定符使得数据指向一个类域之中);
但是对于类中的数据冗余是不可避免的;
这里的类中以内置类型(不存在在堆中开辟空间)为例,若是存在需要开辟空间将会是一个较高的数据冗余问题;
如果从上个例子不能很好的观察到所谓的数据冗余和二义性的问题,接下来我将再举一个例子,并以GDB调试的形式观察其中的问题;
假设存在一段代码:
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; }; void test_2(){ D d; d.C::_a = 1; d.B::_a = 2; d._b = 3; d._c = 4; d._d = 5; }
编译并使用-g
选项生成一个可调试的可执行程序;
并在GDB调试中再test_2
函数的最后一行处打上断点并运行,在该断点处打印此时对象d
时的状态;
此时的d
对象中存在两个A
类中的属性,这就是数据冗余;
⌨️虚继承
当然在紧接着在该版本的后一个版本中更新了对于解决棱形继承的问题 —— 虚继承virtual
关键字virtual
即为虚继承的关键字;
语法:
class A { public: int _a; }; class B : virtual public A{//使用virtual关键字表示虚继承 public : int _b; }; class C : virtual public A{ public : int _c; }; class D : public B, public C{ public : int _d; };
虚继承是如何解决数据冗余和二义性的(不谈虚表概念)?🖱️
以上面的代码为例,此时一样采用GDB调试的方式进行观察;
class A { public: int _a; }; class B : virtual public A{//使用virtual关键字表示虚继承 public : int _b; }; class C : virtual public A{ public : int _c; }; class D : public B, public C{ public : int _d; }; void test_2(){ D d; d.C::_a = 1; d.B::_a = 2; d._b = 3; d._c = 4; d._d = 5; }
由于对于GDB调试来说并不是很好观察,所以采用可能比较麻烦的方式对其进行调试;
打印出对应的地址并对其进行观察:
从图中的可以看出内存的大致分布;
在d
对象的首地址处可以看到存储了一个指针,这个指针所指向的位置为一个数值(偏移量);
可以看到在_c
与_b
之间的内存中其中一处所存储的为一个指针,而该指针指向的位置也存储了一个指针,而这个指针正是存储偏移量0x00000010
的,而通过这个内存的地址0x7fffffffe430
+这个偏移量
即可访问到这个虚继承体系中公共的成员;
当需要对虚继承中共有的成员数据进行操作时将以特定的形式对这个共有的数据进行访问;
同时在一般情况下请不要使用虚继承,同时也尽量不要使用棱形继承; |