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 多继承中的虚函数表 结束语 引言 接下来我们继续学习多态。 没有阅读多态(上)的可以点击下面的链接哦~ C——多态…...

qsort函数详解+代码展示
文章目录 概要系列文章目录前言(1) 定义(2) 使用(举例子 上代码)1、定义数组:2、定义比较函数:3、调用 qsort:4、输出结果: (3) 注意事项 小结 概要 本篇博客将详细地介绍qsort排序函数,&#x…...
leetcode hot100【LeetCode 136. 只出现一次的数字】java实现
LeetCode 136. 只出现一次的数字 题目描述 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。 …...

(免费送源码)计算机毕业设计原创定制:Java+ssm+JSP+Ajax SSM棕榈校园论坛的开发
摘要 随着计算机科学技术的高速发展,计算机成了人们日常生活的必需品,从而也带动了一系列与此相关产业,是人们的生活发生了翻天覆地的变化,而网络化的出现也在改变着人们传统的生活方式,包括工作,学习,社交…...

对抗攻击算法:FGSM和PGD
FGSM 传送门 FGSM 利用了梯度上升的思想,通过损失函数相对于输入图像的梯度来找到 最容易 迷惑网络的方向,并沿着这个方向对图像进行微小的扰动。 FGSM 的基本想法是,沿着这个梯度的符号方向对图像进行微调,以最大化损失函数。具…...
【八股文】小米
文章目录 一、vector 和 list 的区别?二、include 双引号和尖括号的区别?三、set 的底层数据结构?四、set 和 multiset 的区别?五、map 和 unordered_map 的区别?六、虚函数和纯虚函数的区别?七、extern C …...

xtu oj 众数
样例输入# 3 1 0 1 2 1 1 2 3 1 1 2 2样例输出# 1 2 3 解题思路:与数组大小有关,先排序 举个例子思考一下 n4 k2 数组为1 2 3 4 如果我们想让众数那个位的值为3(即max3),3出现的次数为3,即众数为3,需要修改多少次…...
ENVI计算ROI分离度为灰色compute roi separability
我们在使用ENVI做影像分类的时候,需要采集样本兴趣区(ROI),在采集完兴趣区需要计算样本ROI的分离度。 但是有时会发下你 计算ROI分离度的选项为灰色状态不能计算。 如果不是以下问题: “一个是必须首先选择或创建至少…...

Adaboost集成学习 | Python实现基于NuSVR-Adaboost多输入单输出回归预测
目录 效果一览基本介绍程序设计参考资料效果一览 基本介绍 基于NuSVR-Adaboost多输入单输出回归预测python代码 NuSVR是一种支持向量回归(SVR)算法的变体,用于解决回归问题。SVR是一种监督学习方法,它用于预测连续目标变量,而不是分类标签。NuSVR在SVR的基础上引入了一个…...
Python学习第十三天--面向对象,类和对象
一、面向过程和面向对象区别 面向过程:需要实现一个功能时,着重的是开发的步骤和过程,每个步都需要自己亲力亲为,需要编写代码(自己来做) 面向对象:需要实现一个功能时,不注重的是…...

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

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

windows 应用 UI 自动化实战
UI 自动化技术架构选型 UI 自动化是软件测试过程中的重要一环,网络上也有很多 UI 自动化相关的知识或资料,具体到 windows 端的 UI 自动化,我们需要从以下几个方面考虑: 开发语言 毋庸置疑,在 UI 自动化测试领域&am…...
ffmpeg命令详解
原文网址: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,即输入。后面填一个输入地址和一…...

【漏洞复现】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)
为了校验文件在传输中保证完整性和准确性,因此需要发送方先对源文件产生一个校验码,并将该值传输给接收方,将附件通过ftph或http方式传输后,由接收方使用相同的算法对接收文件再获取一个新的校验码,将该值和发送方传的…...

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

云技术-docker
声明! 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团…...
对docker安装的mysql实现主从同步
1:分别安装mysql主,从数据库 将主库容器名称改为mysql_master,将从库容器名称改为mysql_slave 安装教程:docker安装mysql 2:配置主库的my.cnf挂载文件 [mysqld] #log-bin:表示启用binlog功能,并指定二进制日志的存储目录。 log-binmysql-bin #binlog_f…...
【不定长滑动窗口】【灵神题单】【刷题笔记】
采摘水果 fruits[i]表示第i棵树上的水果种类目的是尽可能多收集水果规矩: 只有两个篮子,且每个篮子只能装一种水果,但是每个篮子能装的总量没限制一旦开始采摘,就会连续采摘,把两个篮子都用掉也就是说,采摘到最后一颗…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能
指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

从零开始了解数据采集(二十八)——制造业数字孪生
近年来,我国的工业领域正经历一场前所未有的数字化变革,从“双碳目标”到工业互联网平台的推广,国家政策和市场需求共同推动了制造业的升级。在这场变革中,数字孪生技术成为备受关注的关键工具,它不仅让企业“看见”设…...