C++是如何实现多态的

C++是如何实现多态的

结论:C++通过虚函数来实现多态的,根本原因是派生类和基类的虚函数表的不同

构成多态的必要条件有如下3点:

  • 存在继承关系
  • 基类存在虚函数,且派生类有相同原型的函数遮蔽它
  • 存在基类类型的指针指向派生类对象,且该指针调用了存在遮蔽关系的虚函数

如下图,就是一个简单的多态的例子:(实验环境:vs2019)
在这里插入图片描述

在这里插入图片描述大猪眉头一皱,觉得事情并不简单。

针对上述实验结果,有个疑点:

  • 基类指针是如何知道要调用派生类的f函数,而不是基类的f函数的呢?

这其中的奥秘,还是要从内存模型开始探索。谈到内存模型,那么我们先探讨最简单的——这个对象有多大吧(或者说这个对象占几个字节的内存吧)。
在这里插入图片描述
如上图所示,A的大小在32位编译模式下是4字节,在64位编译模式下是8字节,由此可见,A内存模型里面肯定有个指针!(原因:https://blog.csdn.net/qq_18138105/article/details/105209406)

那么继续深入分析对象的内存模型,这个指针到底指向什么呢?

实际上,这个指针指向一个数组,数组中的每个元素都是虚函数的入口地址,这个数组也就是虚函数表(存在于C++内存模型中的常量区)!由于虚函数表和对象在内存上是分开存储的(虚函数在C++内存模型中的代码区,对象在C++内存模型中的堆区,指向对象的指针在C++内存模型的栈区),因此,就需要在对象中需要安插一个指向这个虚函数表的指针!

我们再看一个例子:
在这里插入图片描述
各个对象的内存模型如下(我用cocos creator画的):
在这里插入图片描述
如上图所示,对象内存和虚函数表内存分开的,对象的*vfptr指向对应的虚函数表。(注意:和很多博客画的图不同,为了直观!我是把处于内存高地址的放上面,处于内存低地址的放下面)

仔细观察派生类B的虚函数表

  • 派生类如果存在对基类有遮蔽关系的虚函数,则在虚函数表中则取派生类的这个虚函数的入口地址,如&B::f
  • 对于未被派生类遮蔽的基类的虚函数,派生类的虚函数表则取基类的这个虚函数的入口地址,如&A::g
  • 派生类新增的虚函数,依次往虚函数表后面加,如&B::h

因此,我们通过指针调用虚函数时,先根据指针找到对象里的vfptr来定位到虚函数表,然后通过虚函数在虚函数表中的索引值来得到虚函数的入口地址。

比如 a->f(), 实际上编译器会这么处理 (*(*(a+0)+0))(a)

  • a+0 是 a对象 vfptr 的地址
  • *(a+0)是 vfptr的值, 又 vfptr 是指向虚函数表的指针,因此 *(a+0) 也是虚函数表的首地址
  • 由于 A::f 函数在虚函数表中的索引是0,因此 (*(a+0)+0)就是获取 A::f 函数的入口地址
  • 知道了A::f 函数的地址,*(*(a+0)+0) 就是对 A::f 的调用
  • 把a对象的指针传入 A::f, 就是a作为 A::f 的 this指针!

同理,调用 b->f() ,也是一样的,只不过访问的是 B的虚函数表,最后调用的是 B::f, 而不是 A::f, 这就解释了 “基类指针是如何知道要调用派生类的f函数,而不是基类的f函数” 的问题,就是因为虚函数表的不同

疑问虽然已经解决了,但是我们还是要继续细探究竟!经过下面的实验,得出的 结果和内存模型完全相符!

#include <iostream>
using namespace std;class A {
public:A() : a(100) {}virtual void f() {cout << "A::f" << endl;}virtual void g() {cout << "A::g" << endl;}
protected:int a;
};class B : public A {
public:B() : b(50) {}virtual void f() {cout << "B::f" << endl;}virtual void h() {cout << "B::h" << endl;}
protected:int b;
};// 定义一个 参数为 A*类型 返回值是 void 的 函数指针 Fun
typedef void (*Fun)(A*);// 指针值类型(64位编译模式下是long long, 32位编译模式下是int)
#ifdef _WIN64
#define ptr_value long long
#else
#define ptr_value int
#endif
// 根据对象指针和偏移量 获取 指针值类型的指针
#define ptr(obj, offset) ((ptr_value*)obj+offset)int main() {A* a = new A;A* b = new B;// 接下来探究 a对象 和 b对象 的 内存模型 //ptr_value a_vfptr_value = *ptr(a, 0); // a_vfptr的值 即 b的虚函数表的首地址((Fun)*ptr(a_vfptr_value, 0))(a); // A::f((Fun)*ptr(a_vfptr_value, 1))(a); // A::g    ptr_value a_a = *ptr(a, 1); // a对象 的 int a成员cout << (int)a_a << endl; // 100ptr_value b_vfptr_value = *ptr(b, 0); // b_vfptr的值 即 b的虚函数表的首地址((Fun)*ptr(b_vfptr_value, 0))(b); // B::f((Fun)*ptr(b_vfptr_value, 1))(b); // A::g    ((Fun)*ptr(b_vfptr_value, 2))(b); // B::hptr_value b_a = *ptr(b, 1); // b对象 的 int a成员cout << (int)b_a << endl; // 100ptr_value b_b = *ptr(b, 2); // b对象 的 int b成员cout << (int)b_b << endl; // 50return 0;
}

因此,只要理解了虚函数表,C++的多态自然就迎刃而解了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337403.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言实现通讯录附详细代码(动态+静态)

点击蓝字关注我们一、通讯录简介实现一个通讯录&#xff1b;通讯录可以用来存储1000个人的信息&#xff0c;每个人的信息包括&#xff1a;姓名、性别、年龄、电话、住址提供方法&#xff1a;添加联系人信息删除指定联系人信息查找指定联系人信息修改指定联系人信息显示所有联系…

Lua协程Coroutine是什么

Lua协程Coroutine是什么协程和线程不同&#xff1a; 同一时刻&#xff0c;一个多线程程序可以用多个线程同时执行&#xff1b;而协程只能有一个在执行多线程是抢占式的&#xff1b;而协程是非抢占式的&#xff0c;只有协程显示被挂起&#xff0c;才会被挂起 协程和线程的相同…

C++程序的内存分区模型-栈区堆区

点击蓝字关注我们1、栈区&#xff1a;由编译器自动分配释放&#xff0c;存放函数的参数值&#xff0c;局部变量等&#xff08;由编译器管理其“生死”&#xff09;注意事项&#xff1a;不要返回局部变量的地址&#xff0c;栈区开辟的数据由编译器自动释放栈区代码演示&#xff…

CocosStudio的节点如何使用自定义shader

CocosStudio的节点如何使用自定义shader问题&#xff1a;我想对CocosStudio 的 某个UI 里的 某个图片&#xff08;如下图所示的Image类型&#xff09;使用自定义shader。但是&#xff0c;我把 对传统的cc.Sprite应用自定义shader的方式 应用于它时&#xff0c;并不生效&#xf…

excel随机抽取_简单随机抽样及其进阶分层随机抽样方法展示

一、分享简单随机抽样的几种方法1、抽样分析工具抽样2、INDIRECTRANDBETWEEN函数抽样3、RAND排序抽样4、SAS抽样二、分层抽样方法1、Python分层抽样2、SAS分层抽样3、EXCEL函数及功能分层抽样简单随机抽样的几种方法方法一抽样分析工具抽样如果你的EXCEL尚未安装数据分析&#…

为什么存在动态内存分配,动态内存函数(malloc函数,free函数,calloc函数,realloc函数)...

点击蓝字关注我们1.当前我们知道的内存的使用方法2.为什么存在动态内存分配如上我们已学的开辟空间的方式有两个特点&#xff1a;空间开辟的大小是固定的必须指定数组的长度所以就产生了空间开大了浪费开小了不够用的问题&#xff0c;所以使用动态内存分配3.动态内存函数&#…

C++ vector类的模拟实现

点击蓝字关注我们1.前言vector和string虽然底层都是通过顺序表来实现的&#xff0c;但是他们利用顺序表的方式不同&#xff0c;string是指定好了类型&#xff0c;通过使用顺序表来存储并对数据进行操作&#xff0c;而vector是利用了C中的泛型模板&#xff0c;可以存储任何类型的…

visual studio源文件的编译顺序是依据什么?

问题&#xff1a;visual studio源文件的编译顺序是依据什么&#xff1f; 结论&#xff1a;依据 .vcxproj 文件里 指定了ClCompile的ItemGroup &#xff0c;如下图所示&#xff0c;就是这么简单粗暴。

功能齐全的屏幕截图C++实现详解

点击蓝字关注我们1、概述要使用屏幕截图&#xff0c;其实很容易&#xff0c;装一款聊天软件或者办公软件就可以了&#xff0c;比如QQ、企业微信、钉钉、飞书等。但要开发出类似这些软件的屏幕截图模块&#xff0c;则没那么容易。其实实现屏幕截图的技术并不复杂&#xff0c;主要…

如何判断exe文件是debug还是release编译生成的

如何判断exe文件是debug还是release编译生成的结论&#xff1a; 用IDA工具打开exe&#xff0c;然后看Imports里面的依赖库是否有带d或D结尾的&#xff0c;如果有就说明是Debug的 实验&#xff1a;&#xff08;实验环境 vs2017&#xff0c; IDA工具&#xff09; &#xff08;0&…

大屏可视化分配率是什么意思_什么是分配率?

大屏可视化分配率是什么意思诸如“不可持续的分配率”和“您需要保持较低的分配率”之类的短语似乎仅属于Java Champions的词汇表。 复杂&#xff0c;恐怖并被魔术光环包围。 经常发生的情况是&#xff0c;当您更仔细地查看概念时&#xff0c;魔术会随着抽烟消失。 这篇文章试…

C/C++语言动态开辟的杨辉三角

点击蓝字关注我们问题引入杨辉三角相必大家并不陌生&#xff0c;第1行有1列、第二行有2列…第n行有n列&#xff0c;且每行行首和行尾的值都为1&#xff0c;其余的值为上一行两数相加我们在C语言阶段&#xff0c;第一次碰到的杨辉三角应该都是用常规的二维数组存储&#xff0c;可…

git gui 历史版本_这些Git命令都不会,还是不要去面试了

前言以下&#xff0c;项目中经常使用的Git命令&#xff0c;汇总到这里以便与你能快速的学习和掌握Git命令&#xff0c;在文章最后有惊喜哟&#xff0c;一定要看到最后啊&#xff01;使用的 Git版本&#xff1a;git version 2.24.0命令git log# 输出概要日志,这条命令等同于# gi…

java restful_Java EE中的RESTful计时器

java restful在这篇文章中...。 EJB计时器旋风之旅 通过带有示例实现的简单REST接口即时使用EJB计时器 更新&#xff08;2015年7月14日&#xff09; 该应用程序的前端现在可以在OpenShift上使用 。 由于我是前端新手&#xff0c;因此我在其他来源的帮助下组装了此HTML5 Ang…

c# 联合halcon 基于相关性 模板匹配_机器视觉之halcon入门(5)-字符识别exe生成...

2.3.2 第二个halcon程序转EXE程序&#xff1a;字符识别老规矩&#xff0c;每一段halcon代码得用C#二次开发下。根据上一节所教的&#xff0c;我们配置下C#的环境&#xff0c;顺便添加好控件&#xff0c;如下图(2-3-2-1)。图 2-3-2-1控件基本跟上一节一样&#xff0c;只是少了一…

C语言数据的存储和取出(超详细讲解)

点击蓝字关注我们整形的储存我们知道一个整形的存储是以补码的形式储存取出是原码的形式。比如&#xff1a;int a 5;的二进制是101那它的原码应该是&#xff1a;00000000 00000000 00000000 00000101正数的原反补相同那它存进去和取出来都是&#xff1a;00000000 00000000 000…

打印pdf就一页_PDF 文件转换工具

是将 PDF 文件转换为完全可编辑的 Windows 文档最好的转换软件。无论您需要您的内容是 Microsoft Word、Excel、PowerPoint、HTML 还是仅需要文本&#xff0c; 总会给您一个简单的方法&#xff0c;快捷地获取您要的内容。可转换整个文档或选择内容。亦可创建 PDF 文件。PDF 转换…

C++类的this指针,静态成员,友元函数友元类

点击蓝字关注我们1. this指针在上篇讲C中类&#xff0c;对象&#xff0c;封装&#xff0c;继承&#xff08;派生&#xff09;&#xff0c;多态的时候&#xff0c;this指针出现在成员函数中&#xff0c;并使用->成员提取符操作成员变量。在 C 中&#xff0c;每一个对象都能通…

批量提取文件创建时间_批量采集新浪微博用户内容

有时我们需要把某些用户的微博数据全部采集下来用作分析&#xff0c;每条信息复制的工作量是非常低效的&#xff0c;必须要借助工具。今天给大家介绍一款采集软件&#xff1a;微风采集器。打开软件&#xff0c;选择模板&#xff0c;下拉框选&#xff1a;批量提取指定用户微博内…

C++异常的规则

点击蓝字关注我们异常是指存在于程序运行时的异常行为&#xff0c;这些行为超出了函数正常功能的范围&#xff0c;当程序的某部分检测到一个无法处理的问题时&#xff0c;就需要用到异常处理。1. C语言中传统的处理错误方式终止程序&#xff1a;如assert&#xff0c;当发生错误…