前面我们分析了 继承多个类的情况。上一次分析的这样的情况:
今天看虚基类。先复习一下虚基类:类似下面这样的图
复习虚基类可以解决的问题:
在这之前先要复习一下多继承同一个爷爷类时带来的问题
空间问题
效率问题
二义性问题
//虚基类问题分析
class Teacher14Grand {
public:int grandage;
};class Teacher14Father1 :public Teacher14Grand{};class Teacher14Father2 :public Teacher14Grand {};class Teacher14 :public Teacher14Father1, public Teacher14Father2 {};void main() {cout << sizeof(Teacher14Grand) << endl; //4cout << sizeof(Teacher14Father1) << endl; //4cout << sizeof(Teacher14Father2) << endl; //4cout << sizeof(Teacher14) << endl; //8//从上述结果可以看到,Teacher14的大小是8。这个大小是8,应该是从Teacher14Fathrer1继承了4个字节,从Teacher14Fathrer2继承了4个字节//问题:二义性问题,因为Teacher14有的内存布局中是 继承了Teacher14Father1的4个字节,继承了Teacher14Father2的4个字节//且Teacher14Father1的4个字节是从Teacher14Grand继承的 int grandage//且Teacher14Father2的4个字节是从Teacher14Grand继承的 int grandage//因此当我们使用Teacher14的对象 tea给grandage赋值的时候,编译器不知道是给Teacher14Father1中的grandage赋值,还是给Teacher14Father2中的grandage赋值,这个就是二义性问题的原因Teacher14 tea;//tea.grandage = 80;//像这样写就是有问题的,编译器不知道给那个grandage赋值。//fix方案,指定给那个具体的赋值tea.Teacher14Father1::grandage = 90;//当然,访问的时候,也要指定的访问那一个grandagecout << tea.Teacher14Father1::grandage << endl;//最开始猜想:由于tea.Teacher14Father2::grandage没有赋过值,因此这个值是乱码//但是实际上写这一行的时候,会有build error。提示“使用了未初始化的局部变量tea”//cout << tea.Teacher14Father2::grandage << endl;
二义性问题引申:
//写到这里的时候,进而会想到一个问题。如果子类有和父类同名的变量,在这种case下,会不会也覆盖呢?
class Teacher15Grand {
public:int grandage = 80;
};class Teacher15Father1 :public Teacher15Grand {
public:int grandage;
};class Teacher15Father2 :public Teacher15Grand {};class Teacher15 :public Teacher15Father1, public Teacher15Father2 {};//写到这里的时候,进而会想到一个问题。如果子类有和父类同名的变量,在这种case下,会不会也覆盖呢?//我们在Teacher15中验证一下。注意:这里不同的是在 Teacher15Grand 中有给grandage初始值为88cout << sizeof(Teacher15Grand) << endl; //4cout << sizeof(Teacher15Father1) << endl;//8,说明,还是会继承Teacher15Grand中的grandage,然后还有自己的grandage,一共占用8个字节cout << sizeof(Teacher15Father2) << endl;//4cout << sizeof(Teacher15) << endl;//12//那么又有一个问题了,怎么去访问这3个有效的成员呢?Teacher15 tea15;tea15.Teacher15Father1::grandage = 99; //对于 Teacher15Father1的grandage进行赋值tea15.Teacher15Father1::Teacher15Grand::grandage = 199;//对于tea15.Teacher15Father2::grandage = 299;cout << tea15.Teacher15Father1::grandage << endl;cout << tea15.Teacher15Father1::Teacher15Grand::grandage << endl;cout << tea15.Teacher15Father2::grandage << endl;
debug代码得到内存图
因此我们可以画出来Teacher14的 内存图
复习虚基类可以解决的问题:
继承爷爷的类使用 virtual 继承即可
class Teacher16Grand {
public:int grandage;
};//让中间类虚继承爷爷
class Teacher16Father1 :public virtual Teacher16Grand {};//让中间类虚继承爷爷
class Teacher16Father2 :public virtual Teacher16Grand {};class Teacher16 :public Teacher16Father1, public Teacher16Father2 {};void main() {cout << sizeof(Teacher16Grand) << endl; //4cout << sizeof(Teacher16Father1) << endl; //8cout << sizeof(Teacher16Father2) << endl; //8cout << sizeof(Teacher16) << endl; //12//从上面的看到,Teacher16Fa;ther1 和 Teacher16Father2都多了4个字节。//这个四个字节是什么呢? 是虚基类表指针//引入两个概念 虚基类表,虚基类表指针// 只要有虚基类,就会有虚基类表 vbtable-- - virtual base table// 如果有子类的对象生成,就会有虚基类表指针 vbptr virtual base table pointerTeacher16 tea;tea.grandage = 8;cout << "断点在这里" << endl;}
使用2017开发人员命令试一下:
引出虚基类表,虚基类表指针 概念
只要有虚基类,就会有虚基类表 vbtable --- virtual base table
如果有子类的对象生成,就会有虚基类表指针 vbptr virtual base table pointer
注意和 虚函数表 和 虚函数指针区分 vtable (virtual table) , vptr 也叫做vfptr (virtual table pointer)
有虚基类和虚函数的同时存在时候 的内存图
内存图中先存储虚基类指针,然后再存储虚函数指针
class Teacher17Grand {
public:int grandage;public:void virtual grandfunc() {}
};//让中间类虚继承爷爷
class Teacher17Father1 :public virtual Teacher17Grand {};//让中间类虚继承爷爷
class Teacher17Father2 :public virtual Teacher17Grand {};class Teacher17 :public Teacher17Father1, public Teacher17Father2 {};void main() {//那么当既有虚基类表,也有虚函数表的时候,虚基类指针 和虚函数表指针那个放在前面呢?cout << sizeof(Teacher17Grand) << endl; //8cout << sizeof(Teacher17Father1) << endl; //12cout << sizeof(Teacher17Father2) << endl; //12cout << sizeof(Teacher17) << endl; //16Teacher17 tea;tea.grandage = 8;cout << "断点在这里" << endl;}
当虚基类,虚函数,爷爷类,父亲类,子类都有成员变量的时候
class Teacher17Grand {
public:int grandage;public:void virtual grandfunc() {}
};//让中间类虚继承爷爷
class Teacher17Father1 :public virtual Teacher17Grand {
public:int father1age;
};//让中间类虚继承爷爷
class Teacher17Father2 :public virtual Teacher17Grand {
public:int father2age;
};class Teacher17 :public Teacher17Father1, public Teacher17Father2 {
public:int teaage;
};void main() {//我们接着看,这些成员变量的值都是在哪里存储着?cout << sizeof(Teacher17Grand) << endl; // 8 一个vptr+ 一个intcout << sizeof(Teacher17Father1) << endl; // 16 一个vbptr + 一个自己int + 一个vptr + 一个父类 intcout << sizeof(Teacher17Father2) << endl; // 16 一个vbptr + 一个自己int + 一个vptr + 一个父类 intcout << sizeof(Teacher17) << endl; // 28 father1vbptr + father1age + father2vbptr + father2age +自己int + 爷爷vptr + 爷爷ageTeacher17 tea;tea.father1age = 1; tea.father2age = 2;tea.teaage = 3;tea.grandage = 4;cout << "断点在这里" << endl;}
整理