——基于VS2017编译器环境下
一,单继承
二,多继承
三,菱形继承
四,虚拟继承
五,菱形虚拟继承
一,单继承
Derived类是Base类的子类,它从Base类中继承了某些成员,成员函数。
下面,我们看看子类继承后,在内存中是如何存放的?
还有就是子类从父类中继承,那些东西是不可以继承的,那些是可以继承的?
代码如下:
class Base
{
public:
Base()
{}
int _b;
//int _c;
};
class Derived:public Base
{
public:
Derived()
{}
int _d;
};
int main()
{
Derived d;
d._b = 1;
d._d = 2;
return 0;
}
从图看出,当派生类对象生成时,对象的大小=基类的大小+派生类的大小,系统给对象开辟了派生类大小的字节,先存放从Base类中继承下来的成员变量,再存放自己的成员变量。
class Base1
{
public:
Base1()
{}
int _b1;
//int _c;
};
class Base2
{
public:
Base2()
{}
int _b2;
};
class Derived :public Base1,public Base2
{
public:
Derived()
{}
int _d;
//int _e;
};
int main()
{
Derived d;
d._b1 = 1;
d._b2 = 2;
d._d = 3;
//d._c = 4;
//d._e = 5;
return 0;
}
多继承中。派生类对象的空间按照继承的顺序,从对象首地址开始,依次存放来自基类的成员变量。
三,菱形继承
菱形继承:D类多继承B1,B2类,而B1,B2类又单继承于C类
class C
{
public:
C()
{
cout << "C::C" << endl;
}
int _c;
};
class B1 :public C
{
public:
B1()
{
cout << "B1::B1" << endl;
}
int _b1;
};
class B2 :public C
{
public:
B2()
{
cout << "B2::B2" << endl;
}
int _b2;
};
class D : public B1, public B2
{
public:
D()
{
cout << "D::D" << endl;
}
int _d;
};
int main()
{
D d;
cout << sizeof(d) << endl;
//d._c = 1; //如果这样访问_c,会出现二义性
d.B1::_c = 0;//带作用域,访问不会出错
d.B2::_c = 1;
d._b1 = 2;
d._b2 = 3;
d._d = 4;
return 0;
}
通过对对象的成员赋值,验证是否如我们猜想的对象模型一样:
由于B1,B2类都是继承C类,所以它们两个都继承 了C类中的_c成员,当D类去多继承B1,B2类时,就会把他们中的成员都继承下来,所以在D类中出现了两个_c成员。当我们通过对象去访问_c成员时,编译器不懂我们访问的是哪个_c成员,就会出现——二义性。
第一种解决办法:加作用域访问符 d.B1::_c = 0; d.B2::_c = 1;
第二种解决办法:虚拟继承
四,虚拟继承
虚拟继承是C++继承体系中特殊的继承方式,如果离开了多重继承下,虚拟继承就完全失去了存在的必要性。(因为这样只会使效率降低和占用更多的空间)为了解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。从而解决了——二义性的问题。
虚拟继承格式,只要在继承权限前加关键字:virtual
class Base
{
public:
Base()
{}
int _b;
};
class Derived :virtual public Base
{
public:
Derived()
{}
int _d;
};
int main()
{
Derived d;
cout << "Derived的大小为:" << sizeof(d) << endl;
d._b = 1;
d._d = 2;
return 0;
}
如果我们没有显式的定义构造函数,则编译器会自动的合成构造函数,并进行调用,
且构造函数的参数有两个:this指针和1
对比以前的构造函数,类的构造函数只有一个默认的this指针作为参数,而虚拟继承中,多传了一个1作为参数。
是了区分普通继承和虚拟继承。
我们以前面的单继承模型来想看看:派生类对象d的大小为基类大小加上派生类的成员变量大小,等于8字节。
而虚拟继承下,我们看看大小为多少?
大小为12字节,与我们单继承模型的大小不一样,多了4个字节,为什么呢?我们看看对象d的内存分配:
如果是普通继承的话,对象的内存应该是先放从父类继承下来的成员变量,再放自己的成员变量,而虚拟继承中,把父类继承下来的成员变量放在了最后,通过对象前4个字节的指针,找到偏移表,才能访问到继承下来的成员变量。
如果对象前4个字节存放的是一个指针,那么它指向哪里?存放这个指针有什么用?
存放这个指针有什么用呢,我们看看反汇编,编译器是如何给_b赋值得:
class C
{
public:
int _c;
};
class B1 : virtual public C
{
public:
int _b1;
};
class B2 : virtual public C
{
public:
int _b2;
};
class D :public B1, public B2
{
public:
int _d;
};
int main()
{
D d;
cout << "D类的大小为:" << sizeof(D) << endl;
d._c = 1;
d._b1 = 2;
d._b2 = 3;
d._d = 4;
return 0;
}
从上面的虚拟继承中,可能猜想到,派生类D的大小。如果只在对象前4个字节中存放偏移表的地址,那么对象大小可能为20个字节。如果对象中存放着两个指针分别指向两个偏移表,那么对象大小就有24个字节。
大小为24,即对象中存放着两个指针,指向两个偏移表,我们看看对象的内存:
菱形虚拟继承的模型为:
如有错误,望得到指正。谢谢。