资讯专栏INFORMATION COLUMN

C++多态底层刨析(虚函数指针,虚函数表)

callmewhy / 3154人阅读

摘要:当子类继承了父类并且子类重写了父类的虚函数之后,我们可以看到此时子类中虚函数指针对应的虚函数表中存的是子类经过重写的函数了。

前言:相信小伙伴们在学习到C++面向对象特性之一的多态的时候,都或多或少有一些疑惑。搞不清楚多态在底层是如何实现的,今天我就带大家刨析一下多态的底层实现,了解一下虚函数指针和虚函数表到底是什么东西?(注意本文操作环境是VS2019 x86架构 32位机器)

1 、多态的定义和分类

1.1 多态的定义

1.1.1 定义:
多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
----------来自菜鸟教程

1.2 多态的分类

1.2.1 多态分为两类:
·静态多态:函数重载和运算符重载属于静态重载,复用函数名
·动态多态:派生类和虚函数实现运行时多态
1.2.2 静态多态和动态多态的区别:
·静态多态的函数地址早绑定–编译阶段确定函数地址
·动态多态的函数地址晚绑定–运行阶段确定函数地址

2、静态多态代码演示

2.1 静态多态代码:

#includeusing namespace std;class Father{public:    void speak() {        cout << "爸爸在说话!" << endl;    }};class Son :public Father{public:    void speak() {        cout << "儿子在说话!" << endl;    }};//执行说话函数//地址早被绑定 在编译阶段确定函数地址void doSpeak(Father &father)//父类引用接收子类对象{    father.speak();}void test01(){    Son son;    doSpeak(son);}int main(){    test01();    return 0;}

2.2 运行结果:

2.3 分析:
在此案例中,派生类和基类中都出现了speak函数,当用父类指针或者引用接收子类对象时,程序会执行基类中的同名函数,这是为什么呢?因为父类中的speak函数地址在编译期间就被绑定,所以在执行程序时无论传递的是哪种对象,执行的都是基类中的speak函数。这就是静态动态的弊端,那么如果想实现传入哪种对象就执行哪种类的函数,这就需要用到动态多态了。

3 、动态多态的代码刨析

动态多态满足条件:
1、有继承关系
2、子类重写父类的虚函数
动态多态的使用:
父类的指针或者引用指向子类对象

3.1 动态多态代码演示

3.1.1动态多态代码

#includeusing namespace std;class Father{public:    virtual void speak() {        cout << "爸爸在说话!" << endl;    }};class Son :public Father{public:    void speak() {        cout << "儿子在说话!" << endl;    }};//执行说话函数//地址早被绑定 在编译阶段确定函数地址void doSpeak(Father &father)//父类引用接收子类对象{    father.speak();}void test01(){    Son son;    doSpeak(son);}int main(){    test01();    return 0;}

3.1.2 运行结果:

3.1.3 分析:
静态多态变为动态多态只需要给父类的函数加上virtual关键字变为虚函数。

3.2 代码刨析

小知识:在C++中空类也占内存,占一个字节的空间

3.2.1
我们先来看一下父类的函数前加上virtual关键字,父类的内存占用有什么变化?

通过运行发现此时父类所占的空间变成了4个字节,那么这四个字节到底是存了什么????其实聪明的小伙伴们可能已经猜出来这四个字节是什么了,没错存的就是一个指针。这个指针就叫虚函数指针,简写为vfptr(virtual function pointer). 对于一个类来说,如果类中存在虚函数,那么编译器就会自动在类中加一条生成虚函数指针的语句(void * vfptr),并且在类的构造函数中为虚函数指针进行赋值(vfptr=&Father::vtal),此时这个虚函数指针就会指向虚函数表。所以,如果对象存在虚函数,那么编译器就会生成一个指向虚函数表的指针,所有的虚函数都存在于这个表中,虚函数表就可以理解为一个数组,每个单元用来存放虚函数的地址。
3.2.2
此时我们在父类中写两个虚函数
然后打开vs2019的调试功能查看一下对象father可以看到

此时可以发现父类对象确实有一个vfptr指针,这个指针对应的
表里就是储存着两个虚函数的地址,这两个函数都是属于父类的。
3.2.3
当子类继承了父类并且子类重写了父类的虚函数之后,我们可以看到:

此时子类中虚函数指针对应的虚函数表中存的是子类经过重写的函数了。所以当传入一个子类对象时通过查询子类vfptr找到对应的虚函数表,从而找到其中存的函数地址去执行,这也就是为什么动态多态可以根据传入对象的不同来执行不同的语句。

3.2.4 画图演示

4、使用VS自带的开发人员工具检验

4.1 找到VS2019开发人员工具

4.2 查看有虚函数的父类布局

4.3 查看子类重写虚函数之后子类的布局


由此证明,我们的结论是正确的!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/119812.html

相关文章

  • 谈谈Java的面向对象

    摘要:也就是说,一个实例变量,在的对象初始化过程中,最多可以被初始化次。当所有必要的类都已经装载结束,开始执行方法体,并用创建对象。对子类成员数据按照它们声明的顺序初始化,执行子类构造函数的其余部分。 类的拷贝和构造 C++是默认具有拷贝语义的,对于没有拷贝运算符和拷贝构造函数的类,可以直接进行二进制拷贝,但是Java并不天生支持深拷贝,它的拷贝只是拷贝在堆上的地址,不同的变量引用的是堆上的...

    ormsf 评论0 收藏0
  • C++基础语法(五)继承——万字总结,干货满满

    摘要:继承方式继承方式限定了基类成员在派生类中的访问权限,包括公有的私有的和受保护的。所以子类给父类引用赋值也是可以的,相当于给子类对象中继承的父类部分起了别名。如图成员函数也是如此,当子类与父类具有函数名相同的函数时,还是符合就近原则。 ...

    smartlion 评论0 收藏0
  • C++学习资料和视频

    摘要:博主在公众号后台设置了关键字回复,回复下面的里面的内容,可免费获得学习视频和资料。 博主在公众号后台设置了关键字回复, 回复下面的【】里面的内容, 可免费获得C++学习视频和资料。 如回复:C++基础                                    【C++】 【1】...

    wangshijun 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<