多态的概念
多态是C++三大面向对象特性之一
多态分为两类:
1.静态多态:重载就是静态多态
2.动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:静态多态的函数地址是在编译时就确定的,而动态多态函数地址在运行时才确定。
以下是多态的案例:
//动物类
class Animal
{
public:
//动物运动函数
virtual void move()
{
cout << "动物在运动" << endl;
}
};
//鱼类继承动物类
class Fish : public Animal
{
public:
//鱼类运动函数重写
void move()
{
cout << "鱼在游" << endl;
}
};
//狗类继承动物类
class Dog :public Animal
{
public:
//狗类运动函数重写
void move()
{
cout << "狗在跑" << endl;
}
};
//通过函数调用类内函数
void doMove(Animal& animal)
{
animal.move();
}
int main()
{
Fish fish;
doMove(fish);
return 0;
}
运行后如图
当我们通过Animal指针调用同名函数时,最后调用的函数确是派生类(鱼类)的函数,这就是动态多态了。
多态底层原理
1.virtual修饰的函数叫做虚函数。
2.存在虚函数的类内会有一个指针,叫做vfptr(虚函数指针),这个指针维护一个表,叫做虚函数表,这张表存放着虚函数的地址,编译器通过这个指针去寻找和确定虚函数。
3.虚函数表由编译器维护。
4.类内的虚函数会被加入虚函数表中。
举例论证
下面我们通过几个例子,来探索这个虚函数指针和虚函数表的运行规则。
虚函数表就像是一张地图,通过虚函数指针,可以找到需要使用的虚函数。
回到上面的代码案例,我们来探讨为什么通过基类指针指向派生类调用虚函数会调用到派生类中的函数。
我们通过VS自带的Develoer Command Prompt for VS 来观察单个类的布局
首先把基类中的virtual去掉如图所示
这时候观察Animal类的布局,之后再观察有虚函数时Animal的布局
不出所料,总大小是一个字节,这是因为类和函数是分别存储的,所以大小和空类是一样的,现在我们把virtual加上去以后再观察单个类布局,如图所示:
这时候我们看了Animal与之前的不同
1.Animal空间大小变成了4个字节,还出现了名为vfptr的东西。
2.vfptr就是虚函数指针的英文缩写,因为是32位平台,指针的空间大小是4个字节,刚好对应类中的4个字节。
3.还出现了一张表,表中有move函数,这张表就是指针维护的虚函数表
至此,我们继续研究Fish派生类的类布局,一样通过Develoer Command Prompt for VS:
我们可以知道,当FIsh继承了Animal后,这个虚指针也是在的,它维护的是Fish类,所以,当派生类重写虚函数时,这个函数实际上也是虚函数,它有虚函数指针和虚函数表,来观察这个维护的虚函数表,它里面装的是 Fish类自己的虚函数,而不是继承下来的Animal中的move函数。
到这里,我们来回顾一下这个程序运行的过程,首先我们通过基类指针(Animal&)去调用派生类的函数,这时候调用出来的确实是派生类中的"鱼在游",这是因为当调用虚函数时,实际上是vfptr去找vftable中该函数地址的过程,那么哪怕你是通过基类去调用的函数,它的类实际上还是派生类,vfptr还是在此类中的vftable中去找该名字的函数,于是找到了&Fish::move,这时候调用出来的就是Fish下的move函数了。
总结
要发生动态多态,那么必须满足两点:
1.有继承关系。
2.子类重写父类的虚函数。
动态多态的本质就是函数运行时再通过vfptr指针去访问对应vftable中的函数地址从而调用函数。
希望大佬指点,多多点赞!