【C++】多态(下)
个人主页~
多态(上)~
多态
- 四、多态的原理
- 1、虚表的存储位置
- 2、多态的原理
- 3、动态绑定和静态绑定
- 五、单继承和多继承关系的虚函数表
- 1、单继承中的虚函数表
- 2、多继承中的虚函数表
- 六、多态中的一些小tips
四、多态的原理
1、虚表的存储位置
class A {
public:virtual void func1() {cout << "A::func1" << endl; }virtual void func2() {cout << "A::func2" << endl; }
private:int _a;
};void func()
{cout << "void func()" << endl;
}int main()
{A a1;A a2;static int a = 0;int b = 0;int* p1 = new int;const char* p2 = "hello world";printf("静态区:%p\n", &a);printf("栈:%p\n", &b);printf("堆:%p\n", p1);printf("代码段:%p\n", p2);printf("虚表:%p\n", *((int*)&a1));printf("虚函数地址:%p\n", &A::func1);printf("普通函数地址:%p\n", func);return 0;
}
被static修饰的变量a存放在静态区,局部变量b存储在栈区,指针p1指向在堆上开辟出的对象,常量字符串的指针存放在代码段
虚表这里,因为a1是一个类对象,它的地址存放了虚表指针和内置类型_a两部分,虚表指针是一个void类型的指针,占4个字节,把它强制转换成int*类型的指针,再解引用,得到的是虚表的指针
虚函数地址就是固定用法,要把是哪个类的虚函数标注出来,然后用取地址符号
从上图我们可以观察到,虚函数和普通函数存放位置接近,代码段和虚表存放位置接近,而虚表和虚函数相对于静态区栈区以及堆区来说还是离代码段更近近,也就是说,虚函数和普通函数以及虚表存放在代码段
2、多态的原理
class A
{
public:virtual void D(){cout << "A : virtual void D()" << endl;}
};class B : public A
{
public:virtual void D(){cout << "B : virtual void D()" << endl;}
};void func(A& ra)
{ra.D();
}void test()
{A a;B b;func(a);func(b);
}
当ra为A对象时,函数调用时在A的虚表中找到func,当ra为B对象时,函数调用时在B的虚表中找到func,然后调用,这样就实现出了不同对象去完成同一行为时,展现出不同的形态
我们要达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调用虚函数
class A
{
public:virtual void D(){cout << "A : virtual void D()" << endl;}
};class B : public A
{
public:virtual void D(){cout << "B : virtual void D()" << endl;}
};void func(A* p)
{p->D();
}void test()
{A a;func(&a);a.D();
}
对于多态调用:
p中存的是A对象的指针,将p移动到eax中:
00382571 mov eax,dword ptr [p]
[eax]就是取eax值指向的内容,这里相当于把mike对象头4个字节(虚表指针)移动到了edx:
00382574 mov edx,dword ptr [eax]
[edx]就是取edx值指向的内容,这里相当于把虚表中的头4字节存的虚函数指针移动到了eax:
0038257B mov eax,dword ptr [edx]
call eax中存虚函数的指针,这里可以看出满足多态的调用,不是在编译时确定的,是运行起来以后到对象的中取找的
对于普通调用:
因为不满足多态调用所以是普通函数调用,直接call地址
3、动态绑定和静态绑定
静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态
五、单继承和多继承关系的虚函数表
1、单继承中的虚函数表
class A
{
public:virtual void func1() {cout << "A::func1" << endl; }virtual void func2() {cout << "A::func2" << endl; }
private:int _a;
};
class B :public A
{
public:virtual void func1() {cout << "B::func1" << endl; }virtual void func3() {cout << "B::func3" << endl; }virtual void func4() {cout << "B::func4" << endl; }
private:int _b;
};int main()
{A a;B b;return 0;
}
图中的监视窗口中我们发现看不见func3和func4,这里是编译器的监视窗口故意隐藏了这两个函数
那我们如何查看整个b的虚表呢
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()
{A a;B b;VFPTR * vTablea = (VFPTR*)(*(int*)&a);PrintVTable(vTablea);VFPTR* vTableb = (VFPTR*)(*(int*)&b);PrintVTable(vTableb);return 0;
}
PrintVTable函数:
核心点就是这个函数指针VFPTR,这是一个函数指针,指向的类型是void*,参数为(),也就是无参,也就是说这个指针可以指向任意一个返回类型为void*并且无参的函数
PrintVTable函数的参数也可以写成VFPTR* vTable,虚表的地址就是指针vTable,后加[]就是对表中的指针进行访问,打印出它们的指针,并且将这些指针指向的函数调用表示出来,让我们可以看到这个地址对应的是哪个函数
main函数:
取出a、b对象的头4bytes,就是虚表的指针,虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr
1.先取a的地址,强转成一个int*的指针
2.再解引用取值,就取到了a对象头4bytes的值,这个值就是指向虚表的指针
3.再强转成 VFPTR* ,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组
4.虚表指针传递给PrintVTable进行打印虚表
5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最后面没有放nullptr,导致越界,这是编译器的问题,我们只需要点重新生成解决方案就行
2、多继承中的虚函数表
class A1
{
public:virtual void func1() {cout << "A1::func1" << endl; }virtual void func2() {cout << "A1::func2" << endl; }
private:int a1;
};
class A2
{
public:virtual void func1() {cout << "A2::func1" << endl; }virtual void func2() {cout << "A2::func2" << endl; }
private:int a2;
};
class B : public A1, public A2
{
public:virtual void func1() {cout << "B::func1" << endl; }virtual void func3() {cout << "B::func3" << endl; }
private:int b;
};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()
{B b;VFPTR* vTablea1 = (VFPTR*)(*(int*)&b);PrintVTable(vTablea1);VFPTR* vTablea2 = (VFPTR*)(*(int*)((char*)&b + sizeof(A1)));PrintVTable(vTablea2);return 0;
}
多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中,也就是func3函数,第一个继承基类就是最左边继承的这个基类
六、多态中的一些小tips
内联函数可以是虚函数,但是如果被inline修饰的函数是虚函数,那么inline特性将会消失,被修饰的函数相当于没被修饰
静态成员不可以是虚函数,因为静态成员没有this指针使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表
构造函数不能是虚函数,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的
最好把基类的析构函数定义为虚函数,因为如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数,这会导致派生类部分的对象没有被正确析构,可能会引发资源泄露
对象在访问虚函数与普通函数速度的对比,如果是普通对象访问,两者一样快,如果是多态对象访问(指针对象或者引用对象),则调用普通函数更快,因为虚函数构成多态,运行时需要到虚函数表中去查找
虚函数表在编译阶段就生成了
今日分享就到这里了~
相关文章:
【C++】多态(下)
个人主页~ 多态(上)~ 多态 四、多态的原理1、虚表的存储位置2、多态的原理3、动态绑定和静态绑定 五、单继承和多继承关系的虚函数表1、单继承中的虚函数表2、多继承中的虚函数表 六、多态中的一些小tips 四、多态的原理 1、虚表的存储位置 class A {…...
基于四种网络结构的WISDM数据集仿真及对比:Resnet、LSTM、Shufflenet及CNN
在上节中,我们已经详细介绍了WISDM数据集及如何使用CNN网络训练,得到了六个维度的模型仿真指标及五个维度的可视化分析,那么现在我们将训练模型推广到其他网路结构中去,通过仿真实验来对比一下不同网络之间对于WISDM数据集的训练效…...
【蚂蚁HR-注册/登录安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…...
【分布式微服务云原生】详解Redis的主从模式,主服务器挂了如何从多个从服务器选出新的主服务器
深入探索Redis主从模式:架构、故障转移与最佳实践 摘要: 本文深入探讨了Redis的主从复制模式,包括其工作原理、故障转移机制以及如何配置和管理这一模式。文章通过清晰的结构和实例代码,帮助读者理解如何在实际项目中应用Redis主…...
Android Context是什么?有很多的context他们之间有什么区别?什么时候该使用哪个?
目录 一、Context是什么? 在Android中,Context是一个抽象类 ,它代表了应用程序的当前状态,包括资源和类加载器等,它提供了一个应用运行所需的信息,比如我们要获取资源 ,那么需要她,…...
数字解调同步技术
一些概念 载波同步 载波同步是一个过程,通过该过程,接收机使其本地载波振荡器的频率和相位与接收信号的频率和相位相适应。 载波相位同步 Carrier Phase Synchronization载波频率同步 Carrier Frequency Synchronization 帧同步 待更新 位同步 待…...
k8s搭建一主三从的mysql8集群---无坑
一,环境准备 1.1 k8s集群服务器 ip角色系统主机名cpumem192.168.40.129mastercentos7.9k8smaster48192.168.40.130node1centos7.9k8snode148192.168.40.131node2centos7.9k8snode248192.168.40.132node3centos7.9k8snode348 k8s集群操作请参考《K8s安装部署&…...
Oracle架构之物理存储中各种文件详解
文章目录 1 物理存储1.1 简介1.2 数据文件(data files)1.2.1 定义1.2.2 分类1.2.2.1 系统数据文件1.2.2.2 撤销数据文件1.2.2.3 用户数据文件1.2.2.4 临时数据文件 1.3 控制文件(Control files)1.3.1 定义1.3.2 查看控制文件1.3.3…...
AR 领域的突破——微型化显示屏为主流 AR 眼镜铺平道路
概述 多年来,增强现实 (AR) 技术一直吸引着人们的想象力,有望将数字信息与我们的物理世界无缝融合。通过将计算机生成的图像叠加到现实世界的视图上,AR 有可能彻底改变我们与环境的互动方式。从增强游戏体验到协助手术室的外科医生ÿ…...
Web安全 - 构建全面的业务安全保护防御体系
文章目录 业务安全概述业务安全 vs. 基础安全业务安全的防护业务安全的防护策略1. 用户资源对抗的技术实现与优化2. IP资源对抗的技术实现与优化3. 设备资源对抗的技术实现与优化4. 操作资源对抗的技术实现与优化实际应用场景中的策略 典型场景业务场景 1:新用户注册…...
机器学习(2):机器学习的相关术语
场景示例: 你周日约了小李、老王打牌,小李先来了,老王没来。你想打电话叫老王过来。小李说:“你别打电话啦,昨天老王喜欢的球队皇马输球了,他的项目在上个礼拜也没成功上线,再加上他儿子期末考…...
Leecode热题100-75.颜色分类
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 必须在不使用库内置的 sort 函数的情况下解…...
408算法题leetcode--第22天
200. 岛屿数量 200. 岛屿数量时间:O(mn);空间:O(min(m, n)),队列最大入队个数,可以想象从左上到右下,第一次入队1个,第二次出队1,入队2,第三次出队2,入队3……...
dubbo微服务
一.启动nacos和redis 1.虚拟机查看是否开启nacos和redis docker ps2.查看是否安装nacos和redis docker ps -a3.启动nacos和redis docker start nacos docker start redis-6379 docker ps二.创建三个idea的maven项目 1.第一个项目dubboapidemo 2.1.1向pom.xml里添加依赖 …...
如何在 DAX 中计算多个周期的移动平均线
在 DAX 中计算移动聚合很容易。但是,计算一段时间内的移动平均值时会有一些陷阱。由于其中一些陷阱是定义问题,因此我们必须小心,不要选择错误的方法。让我们看看细节。欢迎来到雲闪世界。 添加图片注释,不超过 140 字(…...
微信小程序 图片的上传
错误示范 /*从相册中选择文件 微信小程序*/chooseImage(){wx.chooseMedia({count: 9,mediaType: [image],sourceType: [album],success(res) {wx.request({url:"发送的端口占位符",data:res.tempFiles[0].tempFilePath,method:POST,success(res){//请求成功后应该返…...
软件测试人员发现更多程序bug
软件测试人员发现更多程序bug 1. 理解需求和业务,需求评审时候发现bug 熟悉了产品的业务流程、才能迅速找出软件中存在的一些重要的缺陷,发现的软件缺陷才是有价值的。否则即使你能找到一些软件缺陷,那也是纯软件的缺陷,价值不大…...
Nagle 算法:优化 TCP 网络中小数据包的传输
1. 前言 在网络通信中,TCP(传输控制协议)是最常用的协议之一,广泛应用于各种网络应用,如网页浏览、文件传输和在线游戏等。然而,随着互联网的普及,小数据包的频繁传输成为一个不容忽视的问题。…...
C#入门教程
目录 1.if分支语句 2.面向对象 3.static简单说明 1.if分支语句 我们的这个C#里面的if语句以及这个if-else语句和C语言里面没有区别,就是打这个输出上面的方式不一样,c#里面使用的是这个console.writeline这个指令,其他的这个判断逻辑都是一…...
【MySQL报错】---Data truncated for column ‘age‘ at row...
目录 一、前言二、问题分析三、解决办法 一、前言 欢迎大家来到权权的博客~欢迎大家对我的博客进行指导,有什么不对的地方,我会及时改进哦~ 博客主页链接点这里–>:权权的博客主页链接 二、问题分析 问题一修改表结构 XXX 为 not n…...
Go基础学习08-并发安全型类型-通道(chan)深入研究
文章目录 chan基础使用和理解通道模型:单通道、双通道双向通道单向通道单向通道的作用 缓冲通道和非缓冲通道数据发送和接收过程缓冲通道非缓冲通道 通道基本特性通道何时触发panicChannel和Select结合使用Select语句和通道的关系Select语句的分支选择规则有那些Sel…...
some 蓝桥杯题
12.反异或01串 - 蓝桥云课 (lanqiao.cn) #include "bits/stdc.h" #define int long long using namespace std; char c[10000000]; char s[10000000]; int cnt,Ans,mr,mid; int maxi; int p[10000000],pre[10000000]; signed main() {ios::sync_with_stdio(0);cin.t…...
[linux 驱动]input输入子系统详解与实战
目录 1 描述 2 结构体 2.1 input_class 2.2 input_dev 2.4 input_event 2.4 input_dev_type 3 input接口 3.1 input_allocate_device 3.2 input_free_device 3.3 input_register_device 3.4 input_unregister_device 3.5 input_event 3.6 input_sync 3.7 input_se…...
2023_Spark_实验十:Centos_Spark Local模式部署
参考这篇博客:【Centos8_配置单节点伪分布式Spark环境】_centos8伪分布式环境搭建-CSDN博客...
pyecharts-快速入门
pyecharts文档:渲染图表 - pyecharts - A Python Echarts Plotting Library built with love. pyecharts-gallery文档:中文简介 - Document (pyecharts.org) 一、快速入门案例 from pyecharts.charts import Barbar Bar() bar.add_xaxis(["衬衫…...
vue3打包疯狂报错
打包的时候报错很多Cannot find name ‘xxx‘ 。 但是npm run dev 是运行正常的。 解决方法:package.json中的vue-tsc --noEmit 删掉就可以了。 例如: 这是原来的 {"scripts": {"dev": "vite","build": &quo…...
STM32 软件触发ADC采集
0.91寸OLED屏幕大小的音频频谱,炫酷! STM32另一个很少人知道的的功能——时钟监测 晶振与软件的关系(深度理解) STM32单片机一种另类的IO初始化方法 ADC是一个十分重要的功能,几乎任何一款单片机都会包含这个功能&a…...
Android SystemUI组件(08)睡眠灭屏 锁屏处理流程
该系列文章总纲链接:专题分纲目录 Android SystemUI组件 本章关键点总结 & 说明: 说明:本章节持续迭代之前章节的思维导图,主要关注左侧上方锁屏分析部分 睡眠灭屏 即可。 Power按键的处理逻辑最终是由PhoneWindowManager来完…...
C# 表达式与运算符
本课要点: 1、表达式的基本概念 2、常用的几种运算符 3、运算符的优先级 4、常见问题 一 表达式 表达式是由运算符和操作数组成的。、-、*和/等都是运算符,操作数包括文本、常量、变量和表达式等。 二 算术运算符 2.1 算术运算符的使用 三 常见错误 …...
SpringBoot--最大连接数和最大并发数
原文网址:SpringBoot--最大连接数和最大并发数-CSDN博客 简介 本文介绍SpringBoot的最大连接数和最大并发数。 配置 SpringBoot默认使用tomcat处理请求。tomcat可以指定连接数、线程数等配置。 server:tomcat:# 请求处理线程都在使用中时,新连接请求…...
网站建设wordpress/搜索引擎营销的四种方式
原标题:鸿蒙正式发布!快看你的手机啥时候升级?华为P50也曝光了…6月2日晚,华为正式发布HarmonyOS 2(鸿蒙),这意味着“鸿蒙手机”已经变成面向市场的正式产品。这个新的操作系统将打通手机、电脑、平板、电视、无人驾驶…...
巴中交通建设有限公司网站/软文是什么样子的
分表和表分区的目的就是减少数据库的负担,提高数据库的效率,通常点来讲就是提高表的增删改查效率 什么是分表?分表是将一个大表按照一定的规则分解成多张具有独立存储空间的实体表,我们可以称为子表,每个表都对应三个文…...
网站建设万户网络/seo技术团队
简介 技术论坛: http://bbs.chinaunix.net/forum-240-1.html 资源地址: https://sourceforge.net/projects/fastdfs/ 源码资源: https://github.com/happyfish100 FastDFS 是一个开源的轻量级分布式文件系统,它对文件进行管理…...
用卫生纸做的礼物街网站/怎么推广自己的产品
1. 获取磁盘总空间大小 2. 获取磁盘可用空间大小 3. 获取指定路径下某个文件的大小 4. 获取文件夹下所有文件的大小 5. 获取字符串(或汉字)首字母 6. 将字符串数组按照元素首字母顺序进行排序分组 使用如下: 输出结果如下: 7. 获取当前时间 8. 计算上次日期距离现在多久, 如 xx…...
wordpress 加速 谷歌/保定seo推广外包
select ROW_NUMBER()OVER(ORDER BY 用来排序的列的列名),XXX,XXX from XXX 按照原始顺序: ROW_NUMBER()OVER(ORDER BY (select 0)) 转载于:https://www.cnblogs.com/xcxcxcxc/p/5541204.html...
做生意在哪个网站做/谷歌浏览器网页
每次技术变革企业包括个人都需要做出适应,现在我们处于新一轮实际革命的时代节点上,从小数据时代到大数据时代的前叶.那么企业面对大数据需要做出什么样的变革呢? 又存在什么样的挑战呢?首先, 决策方式的改变,传统运作管理在变成大数据管理,越来越多的传统决策在变成基于数据…...