首先由一个简单的例子引出多态(为了演示方便使用了struct关键字来定义类)
#include <iostream>
using namespace std;
struct Cat
{
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
struct Dog
{
void speak() {
cout << "Dog::speak()" << endl;
}
void run() {
cout << "Dog::run()" << endl;
}
};
struct Pig
{
void speak() {
cout << "Pig::speak()" << endl;
}
void run() {
cout << "Pig::run()" << endl;
}
};
void walk(Dog* dog) {
dog->speak();
dog->run();
}
void walk(Cat* cat) {
cat->speak();
cat->run();
}
void walk(Pig* pig) {
pig->speak();
pig->run();
}
int main(){
walk(new Dog());
walk(new Cat());
walk(new Pig());
return 0;
}
假如新增一个动物类,又得重载一个walk函数,导致代码越写越多,有什么好的方法能解决这个问题吗?
我们一开始想到的是把共同的函数都放到一个父类中,再由子类继承该父类然后在其类中重写该函数,最后借助父类指针可以指向子类对象的性质,将walk函数中的参数改为父类指针
于是我们很高兴地写出了方案二的代码
#include <iostream>
using namespace std;
struct Animal
{
void speak() {
cout << "Animal::speak()" << endl;
}
void run() {
cout << "Animal::run()" << endl;
}
};
struct Cat : Animal
{
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
struct Dog : Animal
{
void speak() {
cout << "Dog::speak()" << endl;
}
void run() {
cout << "Dog::run()" << endl;
}
};
struct Pig : Animal
{
void speak() {
cout << "Pig::speak()" << endl;
}
void run() {
cout << "Pig::run()" << endl;
}
};
void walk(Animal* animal) {
animal->run();
}
int main(){
walk(new Dog());
walk(new Cat());
walk(new Pig());
return 0;
}
当我们以为我们写的代码؏؏☝ᖗ乛◡乛ᖘ☝؏؏的时候,现实却给了我们一记重拳
什么?竟然不行,是哪里出了什么问题吗?为什么打印出来的结果都是父类中的结果呢?
我们在walk函数处下断点,然后按F5快捷键进入调试模式,在调试模式下按下alt+g快捷键进入反汇编模式,可以看到以下汇编代码
从图中我们可以看出,代码是直接写死的,直接就是call Animal::speak和call Animal::run,所以调用的当然是父类中的speak和run函数啦
我们需要的效果是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,此时多态出场了
即在父类函数中使用virtual关键字加在共有的函数前
此时我们就写出了第三版代码
#include <iostream>
using namespace std;
struct Animal
{
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::run()" << endl;
}
};
struct Cat : Animal
{
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
struct Dog : Animal
{
void speak() {
cout << "Dog::speak()" << endl;
}
void run() {
cout << "Dog::run()" << endl;
}
};
struct Pig : Animal
{
void speak() {
cout << "Pig::speak()" << endl;
}
void run() {
cout << "Pig::run()" << endl;
}
};
void walk(Animal* animal) {
animal->run();
}
int main(){
walk(new Dog());
walk(new Cat());
walk(new Pig());
return 0;
}
此时调试模式下的汇编代码如下图所示(注意黄色框里的内容,call eax)
多态三要素
- 子类override父类虚成员函数
- 父类指针指向子类对象
- 利用父类指针调用override的虚成员函数
重头戏来了,多态其底层是怎么实现的?虚指针+虚表
虚表里面存储着最终需要调用的虚函数地址,这个虚表也叫虚函数表
下面将配合汇编代码来让大家快速的理解其本质
测试时用的代码如下(注意:以下代码是在x86环境下进行的测试,但原理都一样)
#include <iostream>
using namespace std;
struct Animal
{
int m_age;
virtual void speak() {
cout << "Animal::speak()" << endl;
}
virtual void run() {
cout << "Animal::run()" << endl;
}
};
struct Cat : Animal
{
int m_life;
void speak() {
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
int main(){
Animal* cat = new Cat();
cat->m_age = 23;
cat->speak();
cat->run();
return 0;
}
F5进入调试模式
调试时的内存布局如下
cat对象地址0x00F019D0
虚表地址0x00B3BD9C
虚表内容
0x00b219f1
0x00b219c4
按下alt+g进入反汇编模式
一直按F11,执行到下图红色方框处再按一次F11
就可以看到下图了,注意红色方框内的内容,再对比下上面写的虚表内容
然后再按一次,即可看到跳入到Cat类中执行speak函数
汇编代码解析
好了,到此就结束啦~相信大家对于多态又有了新的认识了吧