前言
多态主要分为两类:
- 静态多态:地址早绑定,即编译阶段确定函数地址,例如函数重载、运算符重载
- 动态多态:地址晚绑定,即运行阶段确定函数地址
动态多态
使用条件
父类指针或引用指向子类对象
基础语法
引入一段代码示例:
代码语言:javascript复制#include<iostream>
using namespace std;
class Animal
{
public:
void speak()
{
cout << "动物在叫" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "猫在叫" << endl;
}
};
void doSpeak(Animal& animal) //Animal& animal = cat
{
animal.speak(); //地址早绑定
}
void test01()
{
Cat cat;
doSpeak(cat); //父类引用/指针指向子类对象
cout << "Animal类占用内存:" << sizeof(Animal) << endl;
}
int main()
{
test01();
system("pause");
}
运行结果为:
程序期望输出Cat
类的speak
方法,也即输出内容为猫在叫
可以看到实际输出与期望不符,原因是speak()
在编译阶段已经确定了地址,无法通过父类指针指向子类对象,解决思路即是将早绑定改为晚绑定,让speak()
在运行阶段正确指向子类对象,将Animal
类的代码改成如下:
class Animal
{
public:
virtual void speak() //virtual修饰后变成虚函数
{
cout << "动物在叫" << endl;
}
};
修改后的运行结果为:
加入virtual关键词后,speak()
变为虚函数,子类中的speak()
无需添加virtual关键字(但也为虚函数)
此时speak()
正确指向Cat
类,由此可以得出多态满足的条件:
- 有继承关系
- 子类重写父类中的虚函数
修改之前的Animal
类占用内存为1字节,说明此时为空类(空类占用1字节),修改之后的占用变为4字节,可知加入virtual
关键词后,类内部结构发生了变化,不难猜出此时类中存在一个指针
实现原理
为了直观看到Animal
类内部结构,借助VS自带的命令提示工具,到源文件存放目录后运行cl /d1 reportSingleClassLayoutAnimal test.cpp
,结果如下:
可以看到类内存在一个大小为4的vfptr
,也即是virutal function pointer-虚函数(表)指针
,该指针指向下方的vftable
,也即是virtual function table-虚函数表
,该表中存放的speak()
实际地址是&Animal::speak
重复上述操作,继续查看Cat
类内部结构:
可以看到重写后,Cat
类中的虚函数表内实际存储的函数地址为&Cat::speak
图示
为方便理解,另附一张示意图