一、C++中虚拟继承
1、虚拟继承的概念
C++使用虚拟继承,解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一份拷贝,同一个函数名也就只有一个映射。
2、解决的问题
解决了数据成员的二义性问题,避免了数据指向不一致问题,同时也节省了内存空间。
例如下面这个简单的例子:
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A()" << endl;
}
public:
int _a;
};
class B1 :public A
{
public:
B1()
{
cout << "B1()" << endl;
}
public:
int _b1;
};
class B2 :public A
{
public:
B2(){}
int _b2;
};
class D :public B1, public B2
{
public:
D(){}
int _d;
};
int main()
{
D d;
d._a = 1;
d._a = 0;
d._b1 = 2;
d._b2 = 3;
d._d = 4;
return 0;
}
当直接访问_a时,就会出现编译出错的问题,编译器不知道要访问的是哪个,如果变成class B1::virtual public A和
class B2::virtual public A,运行结果就不存在指代不明的问题了,如下:
那么虚拟继承的内部到底是怎样实现或者运行的呢,我们一起看看派生类对象在内存中的存储形式,会发现是有一个偏移量表格的作用使得虚拟继承的执行解决二义性问题的,如下图所示
3、程序的执行顺序
1)首先执行虚基类的构造函数,多个虚基类的构造函数按照被继承的顺序构造:
2)执行基类的构造函数,多个基类的构造函数按照被继承的顺序构造;
3)执行成员对象的构造函数,多个成员对象的构造函数按照声明的顺序执行;
4)执行派生类自己的构造函数;
5)析构函数按照与构造函数的相反顺序执行;
4、虚拟继承与普通继承的区别
1)假设derived 继承自base类,那么derived与base是一种“is a”的关系,即derived类是base类,而反之错误;
假设derived 虚继承自base类,那么derivd与base是一种“has a”的关系,即derived类有一个指向base类的vptr。
2)书写形式上:虚拟继承多了一个关键字virtual;3)对象模型:虚拟继承的内存单元多了四个字节用于存储偏移量表格的地址,并且基类部分位于派生类之下
普通继承:基类部分内存在上,派生类在下;
4)对于继承的访问形式:普通继承直接访问基类,虚拟继承需要先访问偏移量表格的地址,找到相对于基类的偏移量,进而访问基类成员;
5)构造函数不同:虚拟继承会多压栈一个1,检测是否是虚拟继承;
5、虚拟继承的缺陷
因为虚拟继承中要通过偏移量表格间接访问基类成员,所以访问效率比较低;
虚拟继承的体系比较复杂;
二、继承体系中的作用域
1. 在继承体系中基类和派生类是两个不同作用域;
2. 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以 使用 基类::基类成员 访问)--隐藏 --重定义
3. 注意在实际中在继承体系里面最好不要定义同名的成员。
三、继承与转换--赋值兼容规则--public继承
1. 子类对象可以赋值给父类对象(切割/切片)
2. 父类对象不能赋值给子类对象
3. 父类的指针/引用可以指向子类对象
4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
四、友元函数与继承
1、友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
2、静态成员函数/变量可以被继承。