前言
上篇博客,我们学习了实现多态的两个条件
父类的指针或引用
子类重写父类的虚函数
还有,final,override,纯虚函数,抽象类等相关知识。
本篇我们将学习多态实现的底层原理
话不多说,马上开始今天的学习
一. 虚表指针
我们先来做一个小题
下面程序的运行结果是什么?也就是这个Base类的大小是多少?
class Base
{
public:
virtual void Func1()
{
cout << "Func1" << endl;
}
private:
int _b = 1;
char _ch;
};
int main()
{
cout << sizeof(Base) << endl;
Base b;
return 0;
}
答案是:
12
根据内存对齐的规则不是应该是8吗?怎么会是12呢?这是因为该类还有一个虚表指针
_vfptr就是虚表指针,virtual function table pointer,虚函数表指针
。
二. 多态原理
只有当一个类有虚函数的时候,才会有虚表指针。
class Person
{
public:
void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
class Student:public Person
{
public:
virtual void BuyTicket()
{
cout << "买票-半价" << endl;
}
};
void func(Person& people)
{
people.BuyTicket();
}
int main()
{
Person Tom;
func(Tom);
Student Jerry;
func(Jerry);
return 0;
}
Person::Tom内没有虚函数,内部没有虚表指针
Student::Jerry内有虚函数,内部有虚表指针
==================================================================================
我们首先看到,父类和子类都有虚函数,子类重写了父类的虚函数,在监视窗口,二者都有各自的虚表指针
,并且虚函数表内部内容不同
PS:虚函数表是在编译的时候就产生的
父类存储父类虚函数
子类存储子类虚函数
==================================================================================
接下里,我们查看实现多态的父类调用BuyTicket函数的汇编指令和没有实现多态的汇编指令
我们发现,满足多态时,虚函数的调用汇编指令变多了很多
这是因为满足多态与否,直接导致函数的编译不同
当不满足多态时,编译器直接调用函数的地址
满足多态时,编译器需要通过虚表
,找到相应函数的地址
这就是为什么子类需要重写父类的虚函数,只有重写了,才能有不同的内容
没有重写,子类虚表中的虚函数地址就还是父类的虚函数
==================================================================================
接下来,我们说明为什么需要父类的指针或者引用。
因为多态是让不同的对象使用多名接口,但却有不同的表现/内容
我们上述也提到,满足多态的父子类,实际调用函数,是通过虚表指针和虚表
完成调用的。
使用父类指针或引用指向子类,会发生切片
但父类指针或引用只是指向子类的父类部分,在多态里,那部分当然也包括虚表指针
这样在通过父类指针或引用调用函数时,实际函数通过子类的虚表指针和虚表
,所以调用的子类重写的虚函数
而使用父类对象,也会发生切片,但这个切片是将子类的父类部分单独拿出来赋回父类,但其中不包括子类的虚表指针和虚表
,因为虚表指针和虚表是类共有
的,不会因为赋值兼容而改变
三. 总结
- 总结一下子类的虚表生成:a. 先将
父类中的虚表内容拷贝一份到子类的虚表
中。b. 如果子类重写
了父类的中的某个虚函数,就用子类自己的函数覆盖虚表中父类的虚函数
c. 子类自己新增加的虚函数按其子类中的声明次序
增加到子类虚表的最后
- 虚函数存在哪?虚表存在哪?
在进程中,有
代码段,数据段(静态区),栈,堆等。
调用的函数,main函数等会在栈区进行压栈,动态申请的空间是在堆中申请。
代码段中存储的其实是代码转汇编再转二进制的那些二进制
虚表全称虚函数表
,虚表指针全称虚函数表指针
虚表本质是一个虚函数指针数组
,内部存储虚函数指针
,不是虚函数,虚函数和普通函数一样,都存储在代码段
,只是其指针在虚表中。
另外对象存储的也不是虚表,存的是虚表指针
,虚表实际也存储在代码段
同类的对象都共用一张虚表
总结语
本节博客仅学习多态的底层原理,和一些总结性的知识(三. 总结)
一些证明在下一篇C++的博客,敬请期待。
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。