一、分类:
1、静态多态:在编译时完成,例如:函数重载,泛型编程。
2、动态多态:程序执行期间,例如:虚函数{(1)一定要在派生类中对基函数进行重写。(2)基类的指针或引用调用虚函数}
二、虚函数:{virtual 返回值类型 函数名 (参数列表){}}
1、同名隐藏:如果基类与派生类具有相同的函数,则在调用函数时优先调用派生类的成员函数。
2、重写(覆盖):在继承体系中,如果基类中有虚函数,在派生类中有和基类虚函数原型完全相同的函数,则会发生函数重载。
注:(1)若返回类型不同,则不能构成重写,但若都返回本类的指针或引用也可以构成重写。
(2)析构函数也可以构成重写。【建议:最好将基类中析构函数写成虚函数】
3、纯虚函数:在成员函数的形参列表后面写上“=0”;
virtual 返回值 函数名(参数列表)=0 {}
4、如果一个函数具有虚函数,则在内存中存放的时候会将前四个字节变成虚表指针,后面是成员变量。
虚表指针指向虚表,里面存放着虚函数地址。
5、虚函数的调用:
(1)取对象地址
(2)取对象的前4个字节的内容,-->虚表指针
(3)相对应的虚函数
6、虚函数的缺点:
(1)对象多了4个字节
(2)效率低
7、构造函数不能做虚函数:
因为调用构造函数后才可以形成虚表以及虚表指针,如果将构造函数变成虚函数,则必须要先调用虚表才能调用构造函数,但因为没有调用构造函数就没有虚表,所以,构造函数就不能变成虚函数。
8、友元函数静态成员函数不能做虚函数:因为没有this指针。
总结:不同对象的虚表函数表不一样,所以实现了动态多态。
三、虚表(虚函数地址表)
1、基类中的虚函数按照按照在类中的声明次序,派生类中自己的虚函数则放在基类虚函数后,也按照顺序存放,如下图所示:
2、派生类:
(1)单继承:
a.检测基类中是否对基类虚函数进行重写,若重写则调用重写后的,反之,调用基类函数。
b.派生类中自己的虚函数在基类后,并按定义顺序放。
(2)多继承:
先继承的虚表在前,若派生类有单独的虚函数,则放在第一张虚表后。
3、总结虚拟机承德虚表格式:
四、几个小问题:
1、Base b1,b2,b3; Derived d1,d2,d3;
b1,b2,b3是否共用一张虚表? 共用
d1,d2,d3是否与b1,b2,b3共用一张虚表? 不共用
2、为什么下述代码会实现多态?
void Test(Base &b)
{b.Test();}
因为b是一个引用,所以会调用虚表
3、Base *pd=(Base*)&d1;(d1是派生类变量)调用谁的虚表?
答:调用的是派生类的虚表,因为d1里面的虚表指针指向的是派生类的虚表,尽管取得是Base类大小的空间但是虚表指针指向的是派生类的虚表指针,所以最终调用的是自己的虚表。