目录
一、多态的定义及实现
⑴ 多态:同一种行为,不同对象来完成会产生不同结果。
⑵ 虚函数:被virtual修饰的类成员函数称为虚函数。
⑶ 重写:派生类虚函数与基类虚函数的返回值类型、函数名、参数列表完全相同(除缺省值,this)
重写有两个特例:
(一) 协变:基类虚函数返回基类对象的指针或者引用,而派生类虚函数返回派生类对象的指针或者引用
(二) 需要重写的函数,派生类可以不添加virtual,但是基类必须添加virtual
⑷ 构成条件:
① 必须通过基类的指针或者引用调用虚函数
② 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
二、析构函数的重写
保证p1和p2指向的对象正确的调用析构函数,需要进行重写。在编译阶段,析构函数名会被统一殊处理成destructor,来保证能够实现多态从而正确的调用析构函数。
class A
{
public:
A()
{}
~A()
{}
private:
int _a;
int _b;
};
三、哪些函数不可以为虚函数
⑴ 构造函数不可以为虚函数:虚函数就是通过这个虚指针调用的。如果构造函数是虚函数,就需要通过虚函数指针来调用,对象此时还没有实例化,也就是还没有分配内存空间,也没有虚指针。
⑵ 静态函数不可以为虚函数:静态成员函数属于类,没有隐藏的this指针。而虚函数的调用就是通过this指针找到虚函数表指针,再找到虚函数地址进行调用的。
⑶ 友元函数不可以是虚函数:虚函数必须是类的成员函数,而友元函数不是类的成员函数。
⑷ 内联函数不可以时虚函数:内联函数在编译期间就展开了,而虚函数时在运行时才确定的。
四、多态的实现原理
① 类里面有了虚函数后,会在对象的前面会存放一个指针,这个指针我们叫做虚函数表指针。(有些平台可能会放到对象的最后面,这个跟平台有关)
②实现多态的过程是先将基类中的虚表内容拷贝一份到派生类虚表中,如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 (这里的覆盖是指针的覆盖),而派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后,然后满足多态的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的(是指直接调用该函数,如派生类的某个虚函数不符合重写(算是派生类新增加的虚函数),父类指针调用时依旧会去虚表里去找,但这样是降低了性能)。
③ 虚函数和普通函数一样的,都是存在代码段的,虚函数指针表(简称虚表)是存放的是虚函数的指针,对象的前面存放的是虚表的指针。
④上面能够说明重写是语法的叫法,覆盖是原理层的叫法。
五、编译时多态和运行时多态
① 编译时多态是通过函数重载、运算符重载和模板实现的,在编译阶段就能决定调用哪个函数。
② 运行时多态是通过虚函数实现的,使用方法就是基类的指针或引用调用重写的虚函数,在运行时决定调用哪个虚函数(父类或者子类)。
六、抽象类
① 在虚函数的后面写上 =0 ,则这个函数为纯虚函数。
② 包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
③ 在设计模式中抽象类是非常关键点,依靠抽象类来实现多个模式。
七、多继承时的虚表
① 多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中。
② 构成重写的虚函数,两个虚表中对应的虚函数都会被覆盖,哪怕是两个父类中有相同的函数被重写(不要被函数名所迷惑)。
八、重写、隐藏和重载的对比
① 函数重载是在同一作用域,只要函数名/参数(类型、顺序)相同。
② 隐藏是发生基类和派生类两个不同作用域上,只要函数名相同,不构成重写就构成隐藏。
③ 重写是发生基类和派生类两个不同作用域上,要函数名、参数(类型、顺序)、返回值相同。
九、override和final (C++11提供)
① override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
② final:修饰虚函数,表示该虚函数不能再被重写