定义:
设有一个父类A类,B类和C类分别继承它,然后再有一个D类继承B类和C类。
这样就叫做菱形继承也叫钻石继承(Diamond inheritance)
上代码块:
运行结果:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<iostream> using namespace std; class A { public: A(int x = 1) :a(x) { cout << "A类构造函数----A()" << endl << endl; } ~A() { cout << "A类析造函数----~A()" << endl << endl; } int a;//把a放在公有位置,便于类外访问 }; class B:public A { public: B(int y = 2) :b(y) { cout << "B类构造函数----B()" << endl << endl; } ~B() { cout << "B类析构函数----~B()" << endl << endl; } private: int b; }; class C :public A { public: C(int z = 3) :c(z) { cout << "C类构造函数----C()" << endl << endl; } ~C() { cout << "C类析构函数----~C()" << endl << endl; } private: int c; }; class D :public B,public C//先继承的先构造 { public: D(int z1 = 4) :d(z1) { cout << "D类构造函数------D()" << endl << endl; } ~D() { cout << "D类析构函数------~D()" << endl << endl; } private: int d; }; void Test() { D obj; //obj.a = 10; cout << "sizeof obj=" << sizeof(obj) << endl << endl; } int main() { Test(); system("pause"); return 0; }
运行结果:
分析:
在创建D类对象时,先去调用了先继承的B类构造函数,在B类的构造函数中又去调用了A类的构造函数,返回D类的构造函数中后接着调用C类的构造函数,在C类的构造函数中又去调用A的构造函数。
PS:(调用基类的构造函数是在子类的初始化列表中执行的)
析构函数的调用与构造函数的调用顺序相反(先创建后销毁)
其中我们查看了D类对象的大小,结果为20。
因为:obj里面是包含了两个a,一个b,一个c,和一个d,因为都是int型,所以size=20.
如图所示:
但是上面的 代码有一个隐患,就是在类外使用a的时候,就会报错。
原因是,obj不知道是调用B类的a,还是调用C类的a,所以 产生了二义性。
如图:
但是..我们也可以通过 使用成员名限定来消除这个二义性
代码块:
void Test() { D obj; //obj.a = 10; obj.B::a = 10; obj.C::a = 20; cout << "sizeof obj=" << sizeof(obj) << endl << endl; cout << "obj.B::a = " << obj.B::a << endl; cout << "obj.C::a = " << obj.C::a << endl; }
运行结果:
但这里 只解决了二义性,并没有解决数据冗余(存储了两次a)问题。
所以, 菱形继承容易带来的数据冗余和二义性的问题。
由此C++就引入了
虚继承这个概念
虚继承了解一下...
概念:
解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将 共同基类设置为虚基类。这时从 不同 的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。
说白了就是 原先存两份的a,现在只存一份,这个a就是虚基类。
形式:
class 派生类名:virtual 继承方式 基类名
解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将 共同基类设置为虚基类。这时从 不同 的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。
说白了就是 原先存两份的a,现在只存一份,这个a就是虚基类。
形式:
class 派生类名:virtual 继承方式 基类名
如何实现.?-----
菱形虚继承
步骤:
1.首先让B类和C类分别虚继承A类
2.D类再公有继承B,C
2.D类再公有继承B,C
上代码块:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<iostream> using namespace std; class A { public: A(int x = 1) :a(x) { cout << "A类构造函数----A()" << endl << endl; } ~A() { cout << "A类析造函数----~A()" << endl << endl; } void dis() { cout << "a=" << a << endl; } int a;//把a放在公有位置,便于类外访问 }; class B:virtual public A//加上了关键字virtual,变成了虚继承 { public: B(int y = 2) :b(y) { cout << "B类构造函数----B()" << endl << endl; } ~B() { cout << "B类析构函数----~B()" << endl << endl; } void dis() { cout << "b=" << b << endl; } private: int b; }; class C :virtual public A//加上了关键字virtual,变成了虚继承 { public: C(int z = 3) :c(z) { cout << "C类构造函数----C()" << endl << endl; } ~C() { cout << "C类析构函数----~C()" << endl << endl; } void dis() { cout << "c=" << c << endl; } private: int c; }; class D :public B,public C//分别公有继承B,C { public: D(int z1 = 4) :d(z1) { cout << "D类构造函数------D()" << endl << endl; } ~D() { cout << "D类析构函数------~D()" << endl << endl; } void dis() { cout << "d=" << d << endl; } private: int d; }; void Test() { D obj; obj.B::a = 10; obj.C::a = 20; cout << "obj.B::a = " << obj.B::a << endl;//注意观察赋值前后结果 cout << "obj.C::a = " << obj.C::a << endl << endl; obj.a = 30;//注意这里没有报错,即二义性问题得到了解决 cout << "obj.B::a = " << obj.B::a << endl; cout << "obj.C::a = " << obj.C::a << endl<<endl; cout << "sizeof obj=" << sizeof(obj) << endl << endl;//记住这个size } int main() { Test(); system("pause"); return 0; }
运行结果:
分析:
在上个例子中我们分别给obj.B::a = 10; obj.C::a = 20;赋值,然后输出得到了两个不同的结果。
但是在这里,我使用了 obj.a=30,发现编译器并没有像之前一样报错,说明解决了二义性问题,然后再分别输出B类和C类里的a,发现都被改成了30,由此猜测,现在 B类和C类里的a放在同一块内存地址,至于为什么size=24后面自然就解答了。
但是在这里,我使用了 obj.a=30,发现编译器并没有像之前一样报错,说明解决了二义性问题,然后再分别输出B类和C类里的a,发现都被改成了30,由此猜测,现在 B类和C类里的a放在同一块内存地址,至于为什么size=24后面自然就解答了。
为了方便看现象我把Test函数修改成
代码块:
void Test() { D obj; cout << "--------------------------sizeof(obj)=" << sizeof(obj) << "--------------------------"<< endl << endl; }
让我们通过查看内存来验证一下:
截图1:
左图是虚继承,右图是去掉virtual的一般继承。
大家可能对左边的B,C类前四个字节有所疑问,为什么原先的a变成了一串地址,这串地址表示什么。?还有a却跑到D类的最下面去了。。
分析:
分析:
在菱形虚继承中,子类类对象中
前四个字节存放一个虚基类表指针,该
指针所指向的虚基类表,表中存放了两个偏移量,相对于自己的虚函数的偏移量和相对于基类的偏移量,可以通过偏移量来找到共享的基类成员。每个
虚基类都排布在派生类成员之后,虚基类的排列顺序与它们的声明顺序相同。
So..数据冗余和二义性的问题也就得到了解决。
至于上面为什么size=24,在这里也得到了解释,B类有一个指针和一个int,C类也有一个指针和int,再加上int d,和一个虚基类a,一共六个四字节,所以sizeof(obj)= 24
ok,上面的内容就是我在学习菱形继承和虚继承的一些收获,如有不对马上指正。。