概念
多种形态,在完成某个行为时,当不同的对象去完成时会产生不同的形态。多态又分为动态多态和静态多态。
静态多态(静态绑定,早绑定):编译器在编译时确定调用哪个函数(体现:函数重载、模板)。
动态多态(动态绑定,晚绑定):编译时无法确定具体调用哪个函数,只有在代码运行时才知道具体应该调用哪个函数(体现:虚函数+重写)。
C++中实现多态的条件
1.在继承体系中,基类中必须要有虚函数,派生类必须对基类进行重写。
2.必须通过基类的指针或引用去调用虚函数。
上面两个条件缺一不可。
但是也有两个例外:协变和析构函数。
协变:返回值类型不同。基类虚函数返回基类的指针或引用,派生类虚函数返回派生类的指针或引用。基类虚函数也可以返回另一个继承体系基类的指针或引用,其派生类虚函数返回与基类返回的指针或引用相同继承体系的派生类指针或引用。
析构函数:只要基类中的析构函数被virtual关键字修饰,如果其派生类的显式提供了自己的析构函数,则形成重写。
重写、同名隐藏与重载
重写:派生类重写基类的虚函数,必须保证派生类虚函数与基类函数的原型(返回值类型、函数名字、参数列表)完全相同。
重载:只要参数列表(参数个数,参数顺序,参数类型)不同,就构成重载,与返回值无关。
同名隐藏:基类与派生类具有相同名称的成员,派生类中只要成员名称相同就构成同名隐藏。
同名隐藏 | 重写 |
---|---|
必须在继承体系中 | 必须在继承体系中 |
基类与派生类中具有相同名称的成员 | 被重写的函数在基类中必须是虚函数 |
(成员函数、 成员变量),只要名字相同就构成同名隐藏,与成员函数参数类型以及返回值类型是否相同无关 | 派生类重写基类的虚函数时,必须要与基类的虚函数原型完全相同(返回值类型以及参数列表完全相同,例外:协变、析构函数重写)。 |
对基类函数是否为虚函数没有要求 | 基类函数必须为虚函数 |
多态实现原理
如果类中包含有虚函数,编译器会给该类的对象多增加4字节,在这新增加的4个字节中存放指向虚函数表格(虚表)的指针(虚表指针)。虚表中的虚函数按照其在类中声明的先后顺序存放,同一个类的所有不同对象在底层共用该类的虚表。此外,只要类中存在虚函数,即使派生类未对基类中虚函数进行重写,该派生类也有属于自己的虚表(与基类没有共用)。但是派生类虚表中的内容与基类中虚表的内容完全相同。
多态实现过程:
- 将基类虚函数表中内容拷贝一份放在派生类虚表中。
- 如果派生类重写了基类中的虚函数,编译器将会使用派生类自己的虚函数替换基类中相同偏移量的函数。
- 对于派生类新增加的一个或多个虚函数,按照其在派生类中声明的次序添加到虚表的最后。
- 运行时从对象前四个字节中获取基表地址,从虚表中获取到虚函数的地址并进行调用。
扩展
final:修饰虚函数,表示该虚函数不能再被继承。
override:检测修饰的函数有没有重写,只能修饰派生类虚函数。
抽象类:类中虚函数都写成纯虚函数,即虚函数=0。抽象类不能实例化,但可以创建抽象类的指针。如果派生类没有重写基类的纯虚函数,派生类仍为抽象类。