1. 多态概念
该词最初来源于希腊语,意思是同一事物具有多种形态,在C++语言中多态有着更广泛的含义。
2. 多态举例
例如演员在不同剧中演绎不同的角色,类似于同一事物的多种形态。
3. 多态分类
1. 静态多态(静态链编译,静态绑定,早绑定)
静态多态是编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误
a. 函数重载
b. 泛型编程
#include<iostream>
using namespace std;
//1.静态多态(静态链编译,静态绑定,早绑定
int Add(int left, int right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
int main()
{
Add(12, 34);
Add(12.0, 34.0);
return 0;
}
2. 动态多态(静态链编,动态绑定,晚绑定)
【动态多态的实现条件】
基类中必须包含虚函数,并且派生类一定要对基类中的虚函数进行重写
通过基类对象的指针或者引用调用虚函数
5. 代码实现多态
在程序运行时,根据基类的指针/引用指向的对象来确定具体调用哪个类的虚函数。
6. 重写
a. 基类中将被重写的函数必须是虚函数;
b. 基类和派生类中虚函数的原型(返回值类型,函数名,参数列表)保持一致。
两个例外:
1)协变:基类虚函数返回基类对象的指针/引用
派生类虚函数返回派生类对象的指针/引用
2)析构函数(基类和派生类中函数的名字不同)
c. 基类中虚函数和派生类中虚函数的访问限定符可以不同。
【面试题】
什么是函数重载,同名隐藏(重定义),重写(覆盖)?
哪些函数不能定义为虚函数?
抽象类
在成员函数(必须为虚函数)的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
//抽象类
#include<iostream>
using namespace std;
class Person
{
public:
virtual void GoToWashRoom() = 0;
};
class WoMan
{
public:
virtual void GoToWashRoom()
{
cout << "请上女厕!" << endl;
}
private:
string _strLady;
};
int main()
{
//Person p;//编译不通过,不允许使用抽象类类型"Person"的对象,"person"不能实例化对象
WoMan w;
return 0;
}
【总结】
(1)派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同(协变除外)
(2)基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性
(3)只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数
(4)如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加
(5)构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么 做,使用时容易混淆(?)
(6)不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整 的,可能会出现未定义的行为
(7)最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟 基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
(8)虚表是所有类对象实例共用的
7. 动态多态的调用原理
问题:为什么派生类对基类的虚函数重写后,通过基类对象的指针或引用调用该虚函数,就可以实现多态呢?、
8. 再探虚表&不同继承下带有虚函数的对象模型
1. 单继承
基类和派生类虚表的构建方式
基类虚表
将类中的虚函数按照在类中声明的先后顺序添加到虚表中
派生类虚表
1. 将基类虚函数表中的内容拷贝一份(未重写)
2. 若派生类重写了基类中的某个虚函数,用派生类自己的虚函数替换派生类虚表中相同偏移量位置的基表虚函数。
3. 将派生类自己新增加的虚函数增加到虚函数表的最后
虚函数调用原理
多态的条件已完全满足
1. 从对象的前4个字节取虚表的地址;
2. 传参(虚函数的形参 + this指针)
3. 从虚表中取对应的虚函数地址(虚表地址 + 偏移量)
4. 执行/调用当前虚函数。
2. 多继承
3. 菱形继承
4. 虚拟继承
菱形虚拟继承
9. 多态的缺陷
1. 对象多4个字节
2. 使程序的运行效率降低
通过虚表来调函数(在对象的前4个字节取虚表,在虚表中找地址掉函数)