一、抽象类
在虚函数后面加 =0 , 这个虚函数就是纯虚函数。(此函数只声明不实现)
有纯虚函数的类是抽象类。(无法实例化对象)
上图Car是一个抽象类,他的子类由于继承了纯虚函数,如果在子类重写纯虚函数也会实例化不出对象。
只有重写继承的虚函数之后子类才能实例化对象。
虽然父类无法实例化对象,但是父类能定义指针,指向子类就能完成调用。
意义:强制派生类去完成重写,override 是已经重写了虚函数检查是否重写正确。
二、单继承和多继承的对象模型
1、单继承
class Base {
public :virtual void func1() { cout<<"Base::func1" <<endl;}virtual void func2() {cout<<"Base::func2" <<endl;}
private :int a;
};
class Derive :public Base {
public :virtual void func1() {cout<<"Derive::func1" <<endl;}virtual void func3() {cout<<"Derive::func3" <<endl;}virtual void func4() {cout<<"Derive::func4" <<endl;}
private :int b;
};
Derive对象模型如此下
2、多继承
class Base1 {
public:virtual void func1() { cout << "Base1::func1" << endl; }virtual void func2() { cout << "Base1::func2" << endl; }
private:int b1;
};class Base2 {
public:virtual void func1() { cout << "Base2::func1" << endl; }virtual void func2() { cout << "Base2::func2" << endl; }
private:int b2;
};class Derive : public Base1, public Base2 {
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }
private:int d1;
};
Derive对象模型如下
三、虚表与虚基表
虚继承中的表叫虚基表,存的是当前位置距离虚基类位置的偏移量。解决菱形继承问题。
虚表是虚函数表,存的是虚函数地址,目的是实现多态。
虚函数和普通函数一样存在代码段。
虚函数表存在常量区(代码段)
四、菱形继承和菱形虚拟继承的对象模型
1、菱形继承
在32位下,D对象大小是28字节。
对象模型如下图
2、菱形虚拟继承
在32位下,D对象大小36字节。
对象模型如下图
五、多态基础知识总结
1、同类型实例化的对象虚表一样。
2、多态分类
多态分类 | 多态类型 |
编译时多态 | 重载,模板(早期绑定) |
运行时多态 | 重写,虚函数(晚期绑定,动态绑定) |
3、函数如果参数是对象,则参数是哪个对象,就调用哪个对象中的函数,不管你是不是虚函数重写了。
函数参数如果是父类的指针或引用,且传入的是子类对象,完成重写后调用子类虚函数。
4、派生类没有虚表指针!
如果是完成子类重写的虚函数,那么子类虚函数地址是覆盖继承父类的那个虚表中原来的虚函数地址。
如果是子类没有完成重写的虚函数,虚函数地址存在第一个继承下来的父类的虚表中。
所以不管有没有虚函数的重写,派生类没有必要拥有自己的虚表指针。
5、虚函数的继承是接口的继承,重写函数实现,得到子类中不同于父类的函数。
普通函数的继承是复用父类的成员函数。
6、构造的顺序是声明的顺序,不是初始化类表的顺序,菱形虚拟继承的父类只会构造一次,因为父类共享。
7、虚表是在编译时生成。