当前位置: 首页 > news >正文

C++——多态(下)

目录

引言

多态

4.多态的原理

4.1 虚函数表指针

4.2 多态的原理

5.单继承和多继承关系的虚函数表

5.1 单继承中的虚函数表

5.2 多继承中的虚函数表

结束语


引言

接下来我们继续学习多态。

没有阅读多态(上)的可以点击下面的链接哦~

C++——多态(上)

多态

4.多态的原理

4.1 虚函数表指针

我们先来看看这段代码:

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};int main()
{Base b;cout << sizeof(b) << endl;return 0;
}

输出结果为:

我们通过监视来观察一下:

通过观察测试我们发现b对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。它指向的对象就是虚函数表,一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表

对于大多数现代编译器,在32位系统上,虚指针通常占用4个字节,而在64位系统上则占用8个字节。

编译器可能会为对象添加一些填充以确保内存对齐,从而提高访问速度。因此,实际的对象大小可能会比这些值的简单相加要大。

这就解释了为什么上面的代码输出结果为16。

针对上面的代码我们对其进行改造:

class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};class Derive : public Base
{
public:virtual void Func1(){		cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main()
{Base b;Derive d;return 0;
}

通过监视来观察一下:

1. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。

2.基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。

3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。

4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。

5. 总结一下派生类的虚表生成:

a.先将基类中的虚表内容拷贝一份到派生类虚表中                                                                    b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数      c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

以下是对几个名词的补充:

虚函数: 虚函数是C++类中的成员函数,它们被声明为virtual,用于支持多态性。虚函数本身和普通函数一样,都是存储在程序的代码段中的。当我们调用一个虚函数时,实际上是在调用存储在代码段中的函数体。

虚表(vtable): 虚表是一个全局的或静态的表,它包含了指向类中所有虚函数的指针。这个表并不是存储在对象中的,而是存储在程序的某个全局或静态数据段中。每个包含虚函数的类都有一个与之对应的虚表。

虚表指针(vptr): 虚表指针是存储在对象实例中的一个特殊指针,它指向该对象所属类的虚表。这个指针是对象的一部分,通常位于对象的起始位置(但这也取决于编译器的具体实现和对象的内存布局)。当我们通过对象的指针或引用来调用虚函数时,编译器会使用这个虚表指针来查找并调用正确的虚函数。

4.2 多态的原理

在C++中,多态性允许我们通过基类指针或引用来调用派生类中的重写方法。为了实现这一点,编译器为每个包含虚函数的类生成一个虚函数表(vtable)。这个表包含了指向类中所有虚函数的指针。

每个对象实例中都含有一个指向其所属类的虚函数表的指针(虚表指针,vptr)。当通过基类指针或引用来调用虚函数时,编译器会使用这个虚表指针来查找并调用正确的虚函数。

(1)对于父类对象,其虚表指针指向基类的虚函数表。

(2)对于派生类对象,其虚表指针指向派生类的虚函数表。如果派生类重写了基类的虚函数,那么派生类的虚函数表中相应位置将指向派生类的重写函数;如果派生类没有重写某个虚函数,那么该位置将指向基类中的原始函数。

因此,当通过基类指针或引用来调用虚函数时,实际调用的函数取决于对象的实际类型(即对象的虚表指针指向的虚函数表)。这种机制允许C++在运行时根据对象的实际类型来确定应该调用哪个虚函数,从而实现多态性。

简单来说就是:

C++通过为每个包含虚函数的类生成一个虚函数表,该表存储了指向类中所有虚函数的指针。每个对象实例内部都含有一个指向其所属类虚函数表的指针。当使用基类指针或引用来调用虚函数时,编译器会利用这个虚表指针,在运行时查找并调用与对象实际类型相匹配的虚函数。

多态实现的两个条件:

(1) 继承(或基类与派生类的关系):继承(或基类与派生类的关系)是多态的基础。它允许一个类(派生类)继承另一个类(基类)的属性和方法。通过继承,派生类可以获取基类的所有公有和保护成员(注意,私有成员对派生类是不可见的,但它们在派生类对象的内存布局中仍然存在,只是不可直接访问)。在C++等语言中,继承还涉及虚函数表的继承。每个包含虚函数的类都有一个虚函数表,该表存储了类中所有虚函数的地址。派生类继承基类时,会继承基类的虚函数表,并可能根据需要对某些虚函数进行重写。

(2) 虚函数重写(动态绑定/后期绑定):虚函数是C++等语言中实现多态的关键机制。虚函数允许在运行时(而不是编译时)根据对象的实际类型来确定调用哪个函数。当派生类重写了基类的虚函数时,派生类对象的虚函数表中对应虚函数的地址会被更新为派生类实现的地址。这样,当通过基类指针或引用指向派生类对象并调用虚函数时,实际调用的是派生类实现的版本。虚函数重写是多态性的核心,它使得相同的函数调用可以根据对象的实际类型产生不同的行为。

综上所述,要实现多态,必须满足两个条件:一是通过继承(或基类与派生类的关系)建立类之间的层次关系;二是在派生类中重写基类的虚函数,以实现动态绑定(在运行时根据对象的实际类型调用正确的方法)。这两个条件共同作用,使得程序能够根据对象的实际类型来调用相应的方法,从而实现多态性。

5.单继承和多继承关系的虚函数表

5.1 单继承中的虚函数表

我们来看看以下代码:

class Base 
{
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }
private:int a;
};
class Derive :public Base 
{
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }virtual void func4() { cout << "Derive::func4" << endl; }
private:int b;
};int main()
{Base b;Derive d;return 0;
}

通过调试来观察一下:

我们可以发现:监视窗口并没有显示fun3和fun4。我们要如何查看d的虚表呢?我们可以通过下面的代码实现需求:

typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout << " 虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f(); }cout << endl;
}int main()
{Base b;Derive d;// 思路:取出b、d对象的头8bytes,就是虚表的指针,前面我们说了虚函数表本质是一个存虚函数// 指针的指针数组,这个数组最后面放了一个nullptr// 1.先取b的地址,强转成一个int*的指针// 2.再解引用取值,就取到了b对象头8bytes的值,这个值就是指向虚表的指针// 3.再强转成VFPTR*,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组。// 4.虚表指针传递给PrintVTable进行打印虚表// 5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最// 后面没有放nullptr,导致越界,这是编译器的问题。我们只需要点目录栏的 - 生成 - 清理解决方案,再// 编译就好了。// 这里我使用了reinterpret_cast来进行类型转换,这是更安全的做法,// 因为它明确指出了我们正在执行低级别的、可能依赖于实现的转换。VFPTR* vTableb = reinterpret_cast<VFPTR*>(*reinterpret_cast<intptr_t*>(&b));PrintVTable(vTableb);VFPTR* vTabled = reinterpret_cast<VFPTR*>(*reinterpret_cast<intptr_t*>(&d));PrintVTable(vTabled);return 0;
}

输出结果为:

5.2 多继承中的虚函数表

我们来了解多继承的虚函数表,来看看这段代码:

class Base1 
{
public:virtual void func1() { cout << "Base1::func1" << endl; }virtual void func2() { cout << "Base1::func2" << endl; }
private:int b1;
};class Base2 
{
public:virtual void func1() { cout << "Base2::func1" << endl; }virtual void func2() { cout << "Base2::func2" << endl; }
private:int b2;
};class Derive : public Base1, public Base2 
{
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }
private:int d1;
};typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout << " 虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}int main()
{Derive d;VFPTR* vTableb1 = reinterpret_cast<VFPTR*>(*reinterpret_cast<intptr_t*>(&d));PrintVTable(vTableb1);VFPTR* vTableb2 = *reinterpret_cast<VFPTR**>(reinterpret_cast<char*>(&d) + sizeof(Base1));PrintVTable(vTableb2);return 0;
}

输出结果为:

多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

结束语

把有关多态的一些基础内容写了一下。

感谢各位大佬的支持!!!

希望这篇文章对您理解C++多态有所帮助!!!

求点赞收藏评论关注!!!

相关文章:

C++——多态(下)

目录 引言 多态 4.多态的原理 4.1 虚函数表指针 4.2 多态的原理 5.单继承和多继承关系的虚函数表 5.1 单继承中的虚函数表 5.2 多继承中的虚函数表 结束语 引言 接下来我们继续学习多态。 没有阅读多态&#xff08;上&#xff09;的可以点击下面的链接哦~ C——多态…...

qsort函数详解+代码展示

文章目录 概要系列文章目录前言(1) 定义(2) 使用&#xff08;举例子 上代码&#xff09;1、定义数组&#xff1a;2、定义比较函数&#xff1a;3、调用 qsort&#xff1a;4、输出结果&#xff1a; (3) 注意事项 小结 概要 本篇博客将详细地介绍qsort排序函数&#xff0c;&#x…...

leetcode hot100【LeetCode 136. 只出现一次的数字】java实现

LeetCode 136. 只出现一次的数字 题目描述 给定一个非空整数数组&#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 …...

(免费送源码)计算机毕业设计原创定制:Java+ssm+JSP+Ajax SSM棕榈校园论坛的开发

摘要 随着计算机科学技术的高速发展,计算机成了人们日常生活的必需品&#xff0c;从而也带动了一系列与此相关产业&#xff0c;是人们的生活发生了翻天覆地的变化&#xff0c;而网络化的出现也在改变着人们传统的生活方式&#xff0c;包括工作&#xff0c;学习&#xff0c;社交…...

对抗攻击算法:FGSM和PGD

FGSM 传送门 FGSM 利用了梯度上升的思想&#xff0c;通过损失函数相对于输入图像的梯度来找到 最容易 迷惑网络的方向&#xff0c;并沿着这个方向对图像进行微小的扰动。 FGSM 的基本想法是&#xff0c;沿着这个梯度的符号方向对图像进行微调&#xff0c;以最大化损失函数。具…...

【八股文】小米

文章目录 一、vector 和 list 的区别&#xff1f;二、include 双引号和尖括号的区别&#xff1f;三、set 的底层数据结构&#xff1f;四、set 和 multiset 的区别&#xff1f;五、map 和 unordered_map 的区别&#xff1f;六、虚函数和纯虚函数的区别&#xff1f;七、extern C …...

xtu oj 众数

样例输入# 3 1 0 1 2 1 1 2 3 1 1 2 2样例输出# 1 2 3 解题思路&#xff1a;与数组大小有关&#xff0c;先排序 举个例子思考一下 n4 k2 数组为1 2 3 4 如果我们想让众数那个位的值为3(即max3)&#xff0c;3出现的次数为3&#xff0c;即众数为3&#xff0c;需要修改多少次…...

ENVI计算ROI分离度为灰色compute roi separability

我们在使用ENVI做影像分类的时候&#xff0c;需要采集样本兴趣区&#xff08;ROI&#xff09;&#xff0c;在采集完兴趣区需要计算样本ROI的分离度。 但是有时会发下你 计算ROI分离度的选项为灰色状态不能计算。 如果不是以下问题&#xff1a; “一个是必须首先选择或创建至少…...

Adaboost集成学习 | Python实现基于NuSVR-Adaboost多输入单输出回归预测

目录 效果一览基本介绍程序设计参考资料效果一览 基本介绍 基于NuSVR-Adaboost多输入单输出回归预测python代码 NuSVR是一种支持向量回归(SVR)算法的变体,用于解决回归问题。SVR是一种监督学习方法,它用于预测连续目标变量,而不是分类标签。NuSVR在SVR的基础上引入了一个…...

Python学习第十三天--面向对象,类和对象

一、面向过程和面向对象区别 面向过程&#xff1a;需要实现一个功能时&#xff0c;着重的是开发的步骤和过程&#xff0c;每个步都需要自己亲力亲为&#xff0c;需要编写代码&#xff08;自己来做&#xff09; 面向对象&#xff1a;需要实现一个功能时&#xff0c;不注重的是…...

AI运用落地思考:如何用AI进行系统运维?

1. 故障预测与预防 数据收集与分析&#xff1a;通过收集系统的各种运行数据&#xff0c;如服务器性能指标&#xff08;CPU使用率、内存占用、磁盘I/O等&#xff09;、网络流量数据、应用程序日志等。利用AI算法对这些海量数据进行分析&#xff0c;挖掘数据中的模式和相关性。例…...

springboot学习-分页/排序/多表查询的例子

最近喜欢上了springboot&#xff0c;真是个好的脚手架。今天继续学习分页/排序/多表查询等复杂功能。按步骤记录如下. 按步骤做的发现不可用&#xff0c;最终还是用的jdbctemplate解决。这也是一次经验。总计在最后。 1.maven依赖 首先从https://start.spring.io/ 选择需要的…...

windows 应用 UI 自动化实战

UI 自动化技术架构选型 UI 自动化是软件测试过程中的重要一环&#xff0c;网络上也有很多 UI 自动化相关的知识或资料&#xff0c;具体到 windows 端的 UI 自动化&#xff0c;我们需要从以下几个方面考虑&#xff1a; 开发语言 毋庸置疑&#xff0c;在 UI 自动化测试领域&am…...

ffmpeg命令详解

原文网址&#xff1a;ffmpeg命令详解_IT利刃出鞘的博客-CSDN博客 简介 本文介绍ffmpeg命令的用法。 命令示例 1.mp4和avi的基本互转 ffmpeg -i D:\input.mp4 E:\output.avi ffmpeg -i D:\input.avi E:\output.mp4 -i 表示input&#xff0c;即输入。后面填一个输入地址和一…...

【漏洞复现】CVE-2022-43396

漏洞信息 NVD - CVE-2022-43396 In the fix for CVE-2022-24697, a blacklist is used to filter user input commands. But there is a risk of being bypassed. The user can control the command by controlling the kylin.engine.spark-cmd parameter of conf. 背景介绍…...

文件的摘要算法(md5、sm3、sha256、crc)

为了校验文件在传输中保证完整性和准确性&#xff0c;因此需要发送方先对源文件产生一个校验码&#xff0c;并将该值传输给接收方&#xff0c;将附件通过ftph或http方式传输后&#xff0c;由接收方使用相同的算法对接收文件再获取一个新的校验码&#xff0c;将该值和发送方传的…...

如何借助AI生成PPT,让创作轻松又高效

PPT是现代职场中不可或缺的表达工具&#xff0c;但同时也可能是令人抓狂的时间杀手。几页幻灯片的制作&#xff0c;常常需要花费数小时调整字体、配色与排版。AI的飞速发展为我们带来了革新——AI生成PPT的技术不仅让制作流程大大简化&#xff0c;还重新定义了效率与创意的关系…...

云技术-docker

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团…...

对docker安装的mysql实现主从同步

1:分别安装mysql主,从数据库 将主库容器名称改为mysql_master,将从库容器名称改为mysql_slave 安装教程:docker安装mysql 2:配置主库的my.cnf挂载文件 [mysqld] #log-bin&#xff1a;表示启用binlog功能&#xff0c;并指定二进制日志的存储目录。 log-binmysql-bin #binlog_f…...

【不定长滑动窗口】【灵神题单】【刷题笔记】

采摘水果 fruits[i]表示第i棵树上的水果种类目的是尽可能多收集水果规矩: 只有两个篮子&#xff0c;且每个篮子只能装一种水果&#xff0c;但是每个篮子能装的总量没限制一旦开始采摘&#xff0c;就会连续采摘&#xff0c;把两个篮子都用掉也就是说&#xff0c;采摘到最后一颗…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

有限自动机到正规文法转换器v1.0

1 项目简介 这是一个功能强大的有限自动机&#xff08;Finite Automaton, FA&#xff09;到正规文法&#xff08;Regular Grammar&#xff09;转换器&#xff0c;它配备了一个直观且完整的图形用户界面&#xff0c;使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

React---day11

14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store&#xff1a; 我们在使用异步的时候理应是要使用中间件的&#xff0c;但是configureStore 已经自动集成了 redux-thunk&#xff0c;注意action里面要返回函数 import { configureS…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...