ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C++多继承

C++多继承 文章目录C多继承复杂菱形继承及菱形虚拟继承单继承、多继承、菱形继承菱形虚拟继承虚拟继承原理继承的总结和反思继承 vs 组合C多继承复杂菱形继承及菱形虚拟继承单继承、多继承、菱形继承单继承一个子类只有一个直接父类时称这个继承关系为单继承多继承一个子类有两个或以上直接父类时称这个继承关系为多继承classStudent{protected:int_num;};classTeacher{protected:int_id;};classAssistant:publicStudent,publicTeacher{protected:string _majorCourse;};菱形继承菱形继承是多继承的一种特殊情况。classPerson{public:string _name;};classStudent:publicPerson{protected:int_num;};classTeacher:publicPerson{protected:int_id;};classAssistant:publicStudent,publicTeacher{protected:string _majorCourse;};单继承在上一章节我们已经了解过了本章节我们详细了解一下多继承和多继承中的菱形继承。多继承我们可以认为是你父母生下了你你继承了你父母的基因菱形继承就好比你爷爷奶奶的两个孩子结婚生下了你我们知道正常非近亲结婚生子孩子一般不会出现问题的而近亲结婚生子这个孩子大概率是有问题的。而多继承中的菱形继承就相当于我们的近亲结婚生子为什么呢因为菱形继承我们的源头都是同一个那么我们在继承和静态的时候了解过static只会实例出一份来但是正常来说菱形继承的大部分数据不会是static也就是说它的所有派生类都可以去访问并修改数据那么在多继承中这份数据应该是每人独一份呢还是每人共享一份呢我们看一下下面这个代码就知道了classPerson{public:string _name;};classStudent:publicPerson{protected:int_num;};classTeacher:publicPerson{protected:int_id;};classAssistant:publicStudent,publicTeacher{protected:string _majorCourse;};intmain(){Assistant a1;// a1._name 张三; // 具有二义性不知道访问谁a1.Student::_name学生张三;//指定访问解决了二义性但是没有解决数据冗余的问题a1.Teacher::_name教师张三;couta1.Student::_name a1.Student::_nameendl;couta1.Teacher::_name a1.Teacher::_nameendl;couta1.Student::_name a1.Student::_nameendl;couta1.Teacher::_name a1.Teacher::_nameendl;}答案是独占那么这会有什么问题呢独占就像同一句话在不同场景里面表达的意思会有所不同那么这在我们生活中确实没什么太大影响但是在计算机中影响就比较大了因为它只能按语法和内存模型严格判断。独占会带来两个严重问题二义性Assistant同时继承了Student和Teacher而这两个基类又都继承自Person。因此Assistant对象里有两份Person子对象_name也就有两份。如果你直接写a1._name 张三;编译器在编译阶段就不知道你要访问的是Student路径上的_name还是Teacher路径上的_name于是直接报错。C 提供的解决办法是用作用域限定符显式调用比如a1.Student::_name 学生张三;。但这只是绕开了二义性并没有解决数据重复的问题。数据冗余通常某些冗余设计是为了提高程序的容错性或鲁棒性但是菱形继承的冗余并非有意设计而是继承关系导致的重复数据。在多继承中如果多个派生类都继承同一个基类且没有使用虚拟继承那么每个派生类都会包含一份基类子对象。继承层次越深、派生类越多最底层的派生类就会包含多份基类数据以及自身的成员造成内存膨胀管理起来也极易出错。那么该怎么解决上述问题呢我们接着来看。菱形虚拟继承虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系在Student和Teacher的继承Person时使用虚拟继承即可解决问题。需要注意的是虚拟继承不要在其他地方去使用。虚拟继承格式class派生类:virtual继承方式 基类{};classPerson{public:string _name;};classStudent:virtualpublicPerson{protected:int_num;};classTeacher:virtualpublicPerson{protected:int_id;};classAssistant:publicStudent,publicTeacher{protected:string _majorCourse;};voidTest(){Assistant a;a._namepeter;}虚拟继承原理那么虚拟继承是怎么解决二义性和数据冗余的呢为了便于大家的理解我们这里使用下面这个例子来演示并通过查看内存的方式来向大家展示一下虚拟继承是如何解决二义性和数据冗余的classA{public:int_a;};// class B : public AclassB:virtualpublicA{public:int_b;};// class C : public AclassC:virtualpublicA{public:int_c;};classD:publicB,publicC{public:int_d;};intmain(){D d;d.B::_a1;d.C::_a2;d._b3;d._c4;d._d5;return0;}下图是菱形继承的内存对象成员模型这里可以看到数据冗余下图是菱形虚拟继承的内存对象成员模型这里可以分析出D对象中将A放到的了对象组成的最下面这个A同时属于B和C那么B和C如何去找到公共的A呢这里是通过了B和C的两个指针指向的一张表。这两个指针叫虚基表指针这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。虚拟继承解决了二义性和数据冗余根据上面的图片我们知道都是在D的内存中包含的B/C那么既然都是在D的内存里为什么B/C还要去找属于自己的A那么我们看下面这个伪代码是否能为你解惑D d;B bd;C cd;当D给B/C赋值时是不是要找出B/C成员中的A才能赋值过去是的用 D 对象给 B/C 对象赋值时需要找出 B/C 成员中的 A 才能正确赋值。为什么呢我们可以把D想象是一个大箱子里面有三个小格子B 格子、C 格子、A 格子。但 A 格子只有一个放在箱子最底下。B 格子里有一张纸条写着“A 在我下面 20 厘米”。C 格子里也有一张纸条写着“A 在我下面 10 厘米”。现在你要把 D 箱子里的 B 格子单独拿出来复制成一个独立的 B 小箱子。这个 B 小箱子必须包含一个 A 小格子因为 B 是 A 的儿子。所以你打开 B 格子看到纸条按照“下面 20 厘米”找到了大箱子底部的 A 格子把它也复制到 B 小箱子里。同理复制C格子也是一样的总结虚拟继承是为了解决菱形继承的二义性和数据冗余的它通过虚基表的方式让所有派生类能够找到源头的基类。下面是上面的Person关系菱形虚拟继承的原理解释继承的总结和反思很多人说C语法复杂其实多继承就是一个体现。有了多继承就存在菱形继承有了菱形继承就有菱形虚拟继承底层实现就很复杂。所以一般不建议设计出多继承一定不要设计出菱形继承。否则在复杂度及性能上都有问题。多继承可以认为是C的缺陷之一很多后来的OO语言都没有多继承如Java。继承 vs 组合public继承是一种is-a关系派生类对象是一个基类对象。组合是一种has-a关系B 组合了 AB 对象中有一个 A 对象。继承是白箱复用基类内部细节对子类可见破坏封装基类改变会影响派生类依赖强、耦合高。组合是黑箱复用通过组装对象获得新功能要求对象有良好接口内部细节不可见类间依赖弱、耦合低有助于保持封装。实际应尽量多用组合耦合低、易维护但适合继承的关系用继承实现多态也必须继承。能用组合就用组合。// Car和BMW Car和Benz构成is-a的关系classCar{protected:string _colour白色;// 颜色string _num陕ABIT00;// 车牌号};classBMW:publicCar{public:voidDrive(){cout好开-操控endl;}};classBenz:publicCar{public:voidDrive(){cout好坐-舒适endl;}};// Tire和car构成has-a的关系classTire{protected:string _brandMichelin;// 品牌size_t _size17;// 尺寸};classCar{protected:string _colour白色;// 颜色string _num陕ABIT00;// 车牌号Tire _t;// 轮胎};完
返回列表