山东企业网站建设公司,营销策划公司名字大气好听,网站设,怎么看待网站开发本篇要分享的内容是C中多继承的缺陷#xff1a;菱形继承。
以下为本篇目录
目录
1.多继承的缺陷与解决方法
2.虚继承的底层原理
3.虚继承底层原理的设计原因 1.多继承的缺陷与解决方法
首先观察下面的图片判断它是否为多继承 这实际上是一个单继承#xff0c;单继承的特…本篇要分享的内容是C中多继承的缺陷菱形继承。
以下为本篇目录
目录
1.多继承的缺陷与解决方法
2.虚继承的底层原理
3.虚继承底层原理的设计原因 1.多继承的缺陷与解决方法
首先观察下面的图片判断它是否为多继承 这实际上是一个单继承单继承的特点是一个子类只有一个直接继承的父类即使又多层继承关系但是只有一个直接父类都称作单继承
多继承的图示如下 可以看到多继承中的子类扮演了两个角色就相当于桃花既能开出好看的桃花也能结果。
所以多继承的特点是一个子类有两个或以上的直接父类时称这个关系叫做多继承。
那在上图中使用多继承是没有错误的他可以在一个类中结合多个类的特点多继承的本身并没有错误但是出错的往往是在一些使用场景下会有缺陷如下图 有了多继承可能就会导致菱形继承如上图。
可以看到Student类和Teacher类都会继承Person中的属性
但是此时Assistant同时又继承了Student类和Teacher类的话Person中的属性在Assistant中就会出现两次会有二义性。
这也是为什么java语言中没多继承用法的原因。
观察如下代码
#includeiostream
using namespace std;
class Person
{
public:string _name; // 姓名
};class Student : public Person
{
protected:int _num; //学号
};class Teacher : public Person
{
protected:int _id; // 职工编号
};class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};
void Test()
{// 这样会有二义性无法明确知道访问的是哪一个Assistant a;a._name peter;// 需要显示指定访问哪个父类的成员可以解决二义性问题但是数据冗余问题无法解决a.Student::_name xxx;a.Teacher::_name yyy;
} 可以看到在报错中它有规定使用的访问限定符也就是说它会将同样的属性及信息再继承一份也就是同时具有两份数据信息从而导致数据冗余占用空间
如果Person类的空间很大那么浪费的空间会更大。
那如何解决这样的问题呢
首先我们可以使用访问限定符解决二义性
这样使用没有问题
其次是在出现菱形继承的玩儿法之后C祖师爷又更新了一个关键字virtual虚拟
我们只需要在被多继承的类的继承方法前加上virtual即可使用虚继承
#includeiostream
using namespace std;
class Person
{
public:string _name; // 姓名
};class Student : virtual public Person
{
protected:int _num; //学号
};class Teacher : virtual public Person
{
protected:int _id; // 职工编号
};class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};
void Test()
{// 这样会有二义性无法明确知道访问的是哪一个Assistant a;a._name peter;// 需要显示指定访问哪个父类的成员可以解决二义性问题但是数据冗余问题无法解决a.Student::_name xxx;a.Teacher::_name yyy;
} 使用了虚继承之后就可以调用冗余数据的属性了 可以看到使用了虚继承之后不管是Student类中的_name还是Person类中的_name都不管用了使用a直接可以调用_name并且所用的空间也是同一份地址空间。
这样就解决了在菱形继承中数据冗余的问题。
但是在一个庞大的项目中这样的问题语法依旧会坑害不少人所以尽量的能少用多继承就要少用多继承。
2.虚继承的底层原理
既然要了解菱形继承的底层原理我们不妨设计一个简单一点的代码便于观察代码如下
class A
{
public:int _a;
};
class B : public A
//class B : virtual public A
{
public:int _b;
};
class C : public A
//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;d._b 3;d._c 4;d._d 5;return 0;
}
那上面的菱形继承关系也很简单如下图 除了观察菱形继承外main函数中的内容对菱形继承的测试也同样重要
我们将代码调试并观察内存窗口。
使用D创建了对象d在内存中观察d的地址即可。 可以看到的是在B类存放了两个两个值1和3
C类中也存放了两个值2和4
D类中存放了一个值2他与对象d中修改_d的值相同
那这样存放数据是什么意思呢
上面的代码没有使用虚函数所以存放了两个值导致了数据的二义性
接下来我们使用虚函数虚函数可以解决数据冗余和二义性的问题我们继续观察内存的变化
class A
{
public:int _a;
};
//class B : public A
class B : virtual public A
{
public:int _b;
};
//class C : public A
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;d._b 3;d._c 4;d._d 5;return 0;
}
使用了虚函数继续观察内存模块 和上面对比我们发现在B类中存放了两行数据第一行为一个地址第二行为所修改的数据
在C类中存放的数据与B类相似
D和A中的数据被修改为最后所修改的数据
可以看到在B类和C类中将地址取代了第一次所存的数据从而达到解决数据二义性的目的
那这个存放的地址又是什么意思是呢 我们继续再调出一个监视内存的窗口来观察 再第二个观察内存的窗口中输入B类的地址这时你就会发现在第二个内存表中存在一个数这个数子就是距离最终修改a的偏移量上图为十六进制的14
当我们将B类中的第一行存放的地址加上十六进制的14就会得到_a最终的值_a0;
我们再来举出一组例子来证明不是巧合 可以看到_a只被赋值而B中不仅存放了_b的值同样也存放了一个指针指向了距离A的偏移量也同样是将B类中第一行的地址加上指针所指的偏移量8就是1所在的位置。
以上就是设计的原理虽然设计很多内存和地址的关系但是这就是虚函数底层的实现设计。
3.虚继承底层原理的设计原因
那为什么要这么设计呢
如以下情况 一个B类创建的指针会指向bb对象也有可能指向d对象
所以我们无法得知这个指针所指向的对象就只能靠指针来检查另一块内存上所存放的偏移量通过计算偏移量来计算虚继承中二义性的变量。
以上就是菱形继承的设计缺陷以及后序的设计的解决思路以及解决思路的底层设计。
其实多继承本身没有问题只是菱形继承的用法让多继承成为了大坑。
即使本人水平有限尽管不遗余力但本篇对虚继承的探索仍有不足还请读者指正感谢您的阅读。