3、C++11 override 和 final
- 引入:
C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的(编译器会按需实例化,只有实例化才会进行检查)
为此C++11提供了override和final两个关键字,可以帮助用户检测是否重写
final
修饰虚函数,表示该虚函数不能再被重写
示例:
class Car { public: virtual void Drive() final {} }; class Benz :public Car { public: virtual void Drive() { cout << "Benz-舒适" << endl; } };
override
检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
- 示例:
class Car{ public: virtual void Drive(){} }; class Benz :public Car { public: virtual void Drive() override {cout << "Benz-舒适" << endl;} };
4、重载/重写/重定义对比
- 对比示图:
三、抽象类
- 概念:
- 在虚函数的后面写上 =0 ,则这个函数为纯虚函数
- 包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象
- 派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象
- 纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承
- 示例:
class Car { public: virtual void Drive() = 0; }; class Benz :public Car { public: virtual void Drive() { cout << "Benz-舒适" << endl; } }; class BMW :public Car { public: virtual void Drive() { cout << "BMW-操控" << endl; } }; void Test() { Car* pBenz = new Benz; pBenz->Drive(); Car* pBMW = new BMW; pBMW->Drive(); }
接口继承和实现继承:
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现
虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口(如果不实现多态,不要把函数定义成虚函数)
注意:
虽然函数重写需要接口一样,但是对于参数的缺省值没有检查,如果基类虚函数和派生类重写函数的缺省值不同,按照基类虚函数的接口来走,也就是说不用管派生类重写函数的接口
四、多态的原理
1、虚函数表
- 例题:
// 这里常考一道笔试题:sizeof(Base)是多少? class Base { public: virtual void Func1() { cout << "Func1()" << endl; } private: int _b = 1; };
解释:
b对象是8bytes,除了_b成员,还多一个 _vfptr放在对象的前面
注意:
_vfptr一般存放在变量前(存放位置与平台有关)
对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)
一个含有虚函数的类中都至少都有一个虚函数表指针(因为虚函数的地址要被放到虚函数表中,虚函数表也称虚表)
示例:
// 针对上面的代码我们做出以下改造 // 1.我们增加一个派生类Derive去继承Base // 2.Derive中重写Func1 // 3.Base再增加一个虚函数Func2和一个普通函数Func3 class Base { public: virtual void Func1() { cout << "Base::Func1()" << endl; } virtual void Func2() { cout << "Base::Func2()" << endl; } void Func3() { cout << "Base::Func3()" << endl; } private: int _b = 1; }; class Derive : public Base { public: virtual void Func1() { cout << "Derive::Func1()" << endl; } private: int _d = 2; }; int main() { Base b; Derive d; return 0; }
说明:
d对象由两部分构成,一部分是父类继承下来的成员(虚表指针也就),存在部分的另一部分是自己的成员
对于派生类d对象,因为Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1(将继承的虚函数进行重写,而对应的在虚函数表上进行覆盖成自己的虚函数地址也叫作覆盖)(重写是语法的叫法,覆盖是原理层的叫法)
Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表
虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr(用来表示结束)(可能根据编译器而定)
派生类的虚表生成总结:
先将基类中的虚表内容拷贝一份到派生类虚表中
如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后
注意:
虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是函数的地址存到了虚表中,另外对象中存的是虚表指针,而虚表存在代码段中的(在vs平台下)(可以打印对象的地址与常量,变量进行比较)