ps网站首页直线教程,西安做网站程序,网站建设方面书籍,店铺设计分析一.什么是菱形继承
菱形继承是多继承的一种特殊情况#xff0c;一个类有多个父类#xff0c;这些父类又有相同的父类或者祖先类#xff0c;那么该类就会有多份重复的成员#xff0c;从而造成调用二义性和数据冗余。
class Person
{public:Person(){cout P…一.什么是菱形继承
菱形继承是多继承的一种特殊情况一个类有多个父类这些父类又有相同的父类或者祖先类那么该类就会有多份重复的成员从而造成调用二义性和数据冗余。
class Person
{public:Person(){cout Person构造 endl;}
public:int _name 0;int _age 0;
};class Student : public Person
{public:Student(){cout Student构造 endl;}int _stuid 0;
};class Teacher : public Person
{
public:Teacher(){cout Teacher构造 endl;}int _jobid 0;
};class Assistant : public Student, public Teacher
{
public:Assistant(){cout Assistant构造 endl;}int _task 0;
};
int main()
{Assistant a;//a._name;//二义性:访问Student的_name还是Teacher的_name呢?//需要指定类域访问a.Student::_name 1;a.Student::_age 2;a._stuid 3;a.Teacher::_name 4;a.Teacher::_age 5;a._jobid 6;a._task 7;return 0;
} 从a的内存布局可以看到a中有两份_name和_age它们是从Student和Teacher类继承下来的。二义性的问题可以通过指定类域访问解决,但数据冗余的问题是无法规避的,必须引入新的技术——虚继承
二.虚继承的用法
只需在继承那个祖先类时加上关键字virtual即可 class Person
{public:Person(){cout Person构造 endl;}
public:int _name 0;int _age 0;
};class Student : virtual public Person
{public:Student(){cout Student构造 endl;}int _stuid 0;
};class Teacher : virtual public Person
{
public:Teacher(){cout Teacher构造 endl;}int _jobid 0;
};class Assistant : public Student, public Teacher
{
public:Assistant(){cout Assistant构造 endl;}int _task 0;
};
int main()
{Assistant a;a.Student::_name 1;a.Student::_age 2;a._stuid 3;a.Teacher::_name 4;a.Teacher::_age 5;a._jobid 6;a._task 7;return 0;
}虚继承前 虚继承后 可以看到Person构造函数只调用了一次。 再来看看虚继承后a的内存分布 虚继承后重复的那部分成员被单独拎了出来只有一份此时就不存在二义性的问题了。a.Student::_namea.Student::_namea._name访问的是同一份数据。同时也解决了数据冗余的问题。
三.虚继承的原理 Student和Teacher中多出的这两个东西是什么呢这似乎是一个地址那我们在内存中看一看注意是小端存储低字节存低位数据高字节存高位数据故地址应该为007e9b4c和007e9b54 注意这是16进制故第一个数 是20第二个数是12。 在看看上面的内存分布会发现006ff8d0这个地址加上20006ff8d8加上12刚好是006ff8e4也就是重复的Person那部分变量的起始地址。 Assistant对象中将Person放到的了对象组成的最下面这个Person同时属于Student和Teacher给Student和Teacher都添加一个指针指向的一张表。这两个指针叫虚基表指针这两个表叫虚基表。虚基表中存了偏移量通过偏移量可以找到下面的Person。事实上虚基表中存放了两个数据第二个数是偏移量第一个数与多态中的虚表有关这里不作展开后面的多态会讲到。 四.例题 1.多继承中指针偏移问题下面说法正确的是( ) class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main(){Derive d;Base1* p1 d;Base2* p2 d;Derive* p3 d;return 0;
}Ap1 p2 p3 Bp1 p2 p3 Cp1 p3 ! p2 Dp1 ! p2 ! p3 d的内存分布如图p1和p3相等不同之处在于p3解引用能访问整个对象p1解引用只能访问Base1哪部分 2.下面程序输出结果是什么? using namespace std;
class A{
public:A(const char *s) { coutsendl; }~A(){}
};
class B:virtual public A
{
public:B(const char *s1,const char*s2):A(s1) { couts2endl; }
};
class C:virtual public A
{
public:C(const char *s1,const char*s2):A(s1) { couts2endl; }
};
class D:public B,public C
{
public:D(const char *s1,char *s2,const char *s3,const char *s4):B(s1,s2),C(s1,s3),A(s1){ couts4endl;}
};
int main() {D *pnew D(class A,class B,class C,class D);delete p;return 0;
} Aclass A class B class C class D Bclass D class B class C class A Cclass D class C class B class A Dclass A class C class B class D 首先明确一点A的构造函数会调一次还是三次因为虚继承解决数据冗余的问题A的成员在D中只有一份故只会调用一次构造函数。那么调用时机在哪呢应该是在调用BC的构造函数之前A构造完后构造BC时就不会再去调用A的构造函数了。而B,C的构造函数谁先调用答案是按继承顺序来B先继承故B先调用。故答案为A
假如把两个virtual去掉A的构造函数会调用几次答案是编译错误去掉virtual后这就是一个普通的多继承B,C中都有AD不能单独去初始化A。去掉初始化列表中的A(s1)就正确了A会被构造两次。