C++从入门到入土(八)——多态的原理

server/2025/3/19 12:53:49/

目录

前言

多态的原理

动态绑定与静态绑定

虚函数表

小结


前言

在前面的文章中,我们介绍了C++三大特性之一的多态,我们主要介绍了多态的构成条件,但是对于多态的原理我们探讨的是不够深入的,下面这这一篇文章,我们将着重介绍C++多态的实现原理。

相关参考文章如下:

C++从入门到入土(七)——多态

多态的原理

我们在前面的文章中了解到多态的实现条件有以下两个:

1.必须是基类的指针或引用调用虚函数

2.被调用的函数必须是虚函数,并且完成了重写

看到上面两个条件,我们不禁会发出疑问,为什么必须是通过基类的指针或引用调用虚函数呢?为什么不能通过其他条件构成多态呢?那么我们通过下面代码来讨论这两个问题:

class Base
{
public:virtual void Func(){cout << "基类调用" << endl;}
protected:int _base;
};class A:public Base
{
public:virtual void Func(){cout << "派生类调用" << endl;}
private:int _a;
};void Print(Base& a)
{a.Func();
}int main()
{Base b;Print(b);A a;Print(a);return 0;
}

首先我们看到上述一段简单的代码,其运行结果如下所示:

我们进入调试观察一下它的内部:

我们可以看到,相比于没有实现多态的类,其底层多了一个_vfptr的对象,那这个对象是什么呢?直接讲结论:这个对象是虚函数表(v表示virtual,f表示function,ptr表示指针),其本质是一个指针数组。

那么我们此时就可以猜测,是不是因为虚函数表的出现导致多态行为的发生呢?我们再仔细观察一下会发现,派生类的虚函数表保存在基类base之下,但是他们的地址是不同的,那么虚函数表中存储的是什么呢?实际上虚函数表中存的是虚函数的地址。当我们满足多态的条件后,我们在编译时不再通过调用对象来确定函数的地址,而是运行时到指向对象的虚函数表中确定虚函数的地址,这样就实现了基类的指针或引用调用不同函数的目的

动态绑定与静态绑定

我们在粗略理解了多态的原理后,接下来我们引入动态绑定与静态绑定的概念帮助我们更加深入地理解多态地原理.。

首先我们要理解什么是动态与静态绑定。

静态绑定:在编译时就确定函数的地址就叫静态绑定,换句话说就是不满足多态的函数调用,例如:函数重载、模板等。

动态绑定:在运行时通过虚函数表来确定函数的地址就叫动态绑定,例如:多态。

所以可以这么说,动态绑定是多态的特点之一。

虚函数表

还是以上面的例子,我们发现,派生类和基类在实例化的过程中分别产生了不同的虚函数表,即使派生类继承了基类的对象,但是派生类的虚函数表中并没有保存基类虚函数的地址,我们将虚函数表的地址输入到内存窗口中查看:

 我们可以看到,基类和派生类的虚函数表中保存的地址是不同的,不同的类的虚表也是不同的。

我们给派生类再加一个虚函数,进入调试,观察一下:

当我们给基类添加了一个虚函数后发现,虚函数表中又存储了一个地址,但是对于基类而言只有一个虚函数的地址,那么我们可以得出下面的结论:

派生类的虚函数表中包含<1.>基类虚函数的地址 <2.>派生类重写的虚函数的地址的覆盖 <3.>自己的虚函数的地址

综上所述,对于虚函数表的结论如下:

1.基类对象的虚函数表存放基类所有虚函数的地址,同类型的对象共用一张虚函数表,不同类型的对象各有自己的虚函数表,即:基类和派生类各有自己的虚函数表

2.虚函数表中包含:基类虚函数的地址;  派生类重写的虚函数的地址的覆盖; 派生类自己的虚函数

3.虚函数表本质是一个函数指针数组,存放虚函数的地址

4.虚函数存放在代码段,虚函数的地址存放在虚函数表,虚函数表指针存放在对象中

小结

本篇文章我们介绍了多态的实现原理,动态绑定以及虚函数表,通过本篇博客的阅读相信您对多态的认识会更加清楚,如果本篇文章对您有所帮助的话希望能够点赞、关注加转发,您的支持就是我创作的最大动力。  


http://www.ppmy.cn/server/176242.html

相关文章

【CXX-Qt】1.5 使用CMake构建

在本示例中&#xff0c;我们将演示如何使用CMake将CXX-Qt代码集成到C应用程序中。Cargo将CXX-Qt代码构建为静态库&#xff0c;然后CMake将其链接到C可执行文件中。 我们首先需要修改项目结构&#xff0c;以分离项目的不同部分。 tutorial cpp qml rust将Rust项目移动到rust文…

【云原生之kubernetes实战】在k8s环境中部署Jirafeau文件共享工具

【云原生之kubernetes实战】在k8s环境中部署Jirafeau文件共享工具 前言一、Jirafeau 介绍1.1 Jirafeau 工具简介1.2 主要特点1.3 主要使用场景二、相关知识介绍2.1 本次实践存储介绍2.2 k8s存储介绍三、本次实践介绍3.1 本次实践简介3.2 本次环境规划3.3 部署前需准备工作四、检…

docker run 命令常用参数

docker run 命令 用于从镜像创建并启动一个新的容器。 基本语法&#xff1a; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]常用选项分类说明 容器配置 --name 为容器指定名称&#xff08;默认随机生成&#xff09;。 示例&#xff1a; docker run --name my_container …

java项目40分钟后token失效问题排查(40分钟后刷新页面白屏)

项目40分钟后token失效问题排查&#xff08;40分钟后刷新页面白屏&#xff09; 经过我 对比失效前token 可以正常访问接口&#xff0c;失效后的token 不能访问系统&#xff0c; 得出结论&#xff0c;我系统对接第三方 sso 系统&#xff0c;token 失效时间 在他们那边配置&…

C++《红黑树》

在之前的篇章当中我们已经了解了基于二叉搜索树的AVL树&#xff0c;那么接下来在本篇当中将继续来学习另一种基于二叉搜索树的树状结构——红黑树&#xff0c;在此和之前学习AVL树类似还是通过先了解红黑树是什么以及红黑树的结构特点&#xff0c;接下来在试着实现红黑树的结构…

Git 面试问题,解决冲突

1.问题描述 在多人协作开发中&#xff0c;当多个开发者在同一文件的同一部分进行修改并提交时&#xff0c;Git 无法自动合并这些更改&#xff0c;从而产生代码冲突&#xff08;Conflict&#xff09;。冲突的代码会被 Git 标记出来&#xff0c;需要开发者手动解决。 冲突原因 多…

golang函数与方法的区别

1.调用方式的区别 函数的调用方式&#xff1a;函数名&#xff08;参数...&#xff09; 方法的调用方式: 变量.方法名&#xff08;参数...&#xff09; 2.函数的使用 package mainimport "fmt" //函数参数为值类型&#xff0c;调用的时候只能传递值类型数据&#…

【Deepseek进阶篇】--4.科研运用

DeepSeekDeepResearch ,让科研像聊天一样简单 通过百度网盘分享的文件&#xff1a;AI学术工具公测版.exe 链接:https://pan.baidu.com/s/1kPrFGhpWuwB2eiGuP33Qjg?pwd0417 目录 1. 能做什么 1.1.爬虫数据采集 1.2.撰写文章标题 1.3.中-英、英-中 1.4.中文学术写作润色指…