波奇学C++:多态知识点

多态中函数的重写(基类指针访问派生类函数),只重写函数的实现,而不重写声明。

class Person
{
public:virtual void fun(int i = 0){cout << "Person"<<" "<<i;}
};
class Student:public Person
{
public:virtual void fun(int i = 1){cout << "student" <<" "<<i;}
};
int main()
{Person p;Student st;Person* pp = &st;pp->fun();return 0;
}

结果是 student 0 原因在于重写时只重写函数的实现,就是说相当于Person的fun的声明和Student的函数实现的拼在一起所以缺省值是0。

为什么多态调用(重写)只能用父类的指针和引用,不能子类指针或者引用,不能是父类对象?

如果是子类指针或者引用就不是多态调用了只是单存子类对父类的重定义,隐藏函数。

上一篇文章提到的,多态的本质就是基类和派生类的虚表中保存的虚函数地址被覆盖了,多态调用意味着访问的必须是子类的虚表而不是父类的。

子类对象直接赋值父类不会拷贝虚表虚函数的地址

上图为赋值前,下图为赋值后,如图他们的__vfptr始终不同,所以父类对象必然无法访问子类对象的虚函数的地址。

为什么指针和引用可以?

父类型指针表示它范围的范围是父类,所以它指向子对象时,本质上依然说访问子类的父类部分,虚表依然是子类的虚表。

引用同理,相当于切割出子类中父类的部分。本质上依然是子类的虚表。

为什么父类指针可以指向子类对象?可以指向意味着结构相似,原因在于,继承相当于把父对象一整个拷贝放在子对象中,结构相似也是向上转换的基础

虚表的存储在代码段

证明思路:输出各个区的地址和虚表的地址,进行比较,字节相差较少说明在哪个区。

int main()
{Person p;Student st;int a = 1;printf("栈上:%x\n", &a);int* b = new int;printf("堆上:%x\n", b);static int c = 0;printf("静态区:%x\n", &c);const char* d = "abcde";printf("代码段:%x\n", d);printf("虚表1:%x\n", *((int*)&p));printf("虚表2:%x\n", *((int*)&st));return 0;}

注意打印对象是打印对象的成员变量的值,这里是因为__vptr内置变量(保存虚表地址)的在成员变量首位,所以可以打印出来,同时int* 是只取虚表的地址后四个字节(小端机),%x也是只打印地址的后4位字节。

通过比较可发现,虚表和代码段的位置更近,所以虚表在代码段中。

派生类新的虚函数保存在虚表中,原有虚函数的地址的下面

class Person
{
public:virtual void fun(int i = 0){cout << "Person"<<" "<<i;}int _a;
};
class Student:public Person
{
public:virtual void fun(int i = 1){cout << "student" <<" "<<i;}virtual void fun1(){cout << "new virtual fun1()";}int _b;
};

fun1()是Student的虚函数,fun1保存在子函数的虚表中

证明:虚表保存函数指针地址,虚表可以看成指针数组,所以我们可以把虚表的函数指针打印出来。

typedef void(*FUNC_PTR) ();//重定义函数指针类型
//形参是数组,实参为数组指针
void PrintVFT(FUNC_PTR table[])
{//vs会在虚表末尾保存一个空指针,所以循环到nullptr为止for (size_t i = 0; table[i] != nullptr; i++){printf("[%d]:%p\n", i, table[i]);}
}
int main()
{Person ps;Student st;int vft1 = *((int*)&ps);
//86位机器地址是32位转换成int*PrintVFT((FUNC_PTR*)vft1);int vft2 = *((int*)&st);PrintVFT((FUNC_PTR*)vft2);return 0;
}

 如图上面为父类虚表保存的地址,下面为派生类虚表保存的指针地址。重写的虚函数覆盖了原有的地址,并且新地址在虚表内。

静态多态:指的是函数重载,指的是编译的时候函数地址确定了

动态多态:继承,虚函数重写,调用的函数地址的确定是在运行时去虚表中确定的

多继承的多态问题

typedef void(*FUNC_PTR) ();
void PrintVFT(FUNC_PTR table[])
{for (size_t i = 0; table[i] != nullptr; i++){printf("[%d]:%p", i, table[i]);FUNC_PTR f = table[i];f();printf("\n");}
}
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;
};
int main()
{Derive d;cout << sizeof(d) << endl;int vft1 = *((int*)&d);Base2* ptr = &d;int vft2 = *((int*)ptr);PrintVFT((FUNC_PTR*)vft1);PrintVFT((FUNC_PTR*)vft2);return 0;}

下面代码的Derive继承了Base1和Base2,其中两个fun1()都被继承了。

打印结果

 为什么是20?

 因为是一整个对象继承,所以会存在两个虚表,base1,base2虚表指针+int变量 8+8+4=20

由上面的结果图可知fun1在两个虚表中被重写,且都调用了同一个函数。但是地址却不一样,

实际上调用虚表2的fun()的地址,会改变指针位置和虚表1fun()指针相同,再调用函数。

反汇编证明

b1,b2指针分别调用fun1(),反汇编,call指令进入func1函数,此时

注意此处fun1()的地址是0C92840h

调用base2的fun1虚表地址,此时地址是0C94670h

进入call指令,ecx-8,再jump向0C91244h地址最后到base1虚表的地址。

 

 简单来说指向base2部分的指针,先指向base1的,再调用指针1保存的重写函数的地址。

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

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

相关文章

命令执行漏洞(附例题)

一.原理 应用有时需要调用一些执行系统命令的函数&#xff0c;如PHP中的system、exec、shell_exec、passthru、popen、proc_popen等&#xff0c;当用户能控制这些函数的参数时&#xff0c;就可以将恶意系统命令拼接到正常命令中&#xff0c;从而造成命令执行攻击。 二.利用条…

Linux平台如何实现采集音视频数据并注入轻量级RTSP服务?

技术背景 好多开发者&#xff0c;问我们最多的问题是&#xff0c;为什么要设计轻量级RTSP服务&#xff1f;轻量级RTSP服务&#xff0c;和RTSP服务有什么区别&#xff1f; 针对这个问题&#xff0c;我们的回答是&#xff1a;轻量级RTSP服务解决的核心痛点是避免用户或者开发者…

dll修复精灵,dll修复工具下载方法分享,mfc140u.dll缺失损坏一键修复

今天&#xff0c;我将为大家分享一个关于mfc140u.dll的问题。首先&#xff0c;我想问一下在座的网友们&#xff0c;有多少人知道mfc140u.dll是什么&#xff1f;又有多少人知道它的作用以及如何解决这个问题呢&#xff1f;在接下来的演讲中&#xff0c;我将详细介绍mfc140u.dll的…

C#使用proto

写多了go代码&#xff0c;被go mod tidy惯坏了&#xff0c;还以为全天下的都很好用呢&#xff0c;结果发现并不是这样。尤其是项目组的proto还是又封了个工具直接就能跑得&#xff0c;导致以为没那么复杂的事情变得复杂了起来。是有两套生成的规则&#xff0c;时间有点晚&#…

虹科分享 | 解决外科医生的担忧:AR让技术自己开口说话

在手术室中&#xff0c;分心可能导致严重错误和伤害&#xff0c;这凸显了在手术过程中减少对外科医生干扰的重要性。对于外科医生来说&#xff0c;在长时间的手术过程中&#xff0c;引入新技术设备时需要考虑多种因素。根据Vuzix对500多名外科医生的综合调查显示&#xff0c;使…

LeetCode 865. Smallest Subtree with all the Deepest Nodes【树,DFS,BFS,哈希表】1534

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

机械零件保养3d模拟演示打消客户购买顾虑

复杂机械的工作运转是复杂的&#xff0c;想要对机械有深度的理解和迭代&#xff0c;必须了解它的运转原理及参数&#xff0c;复杂机械运行原因教学存在着不可视、系统庞杂及知识点多等弊病&#xff0c;3D虚拟展示是基于web3d网页运行的三维页面&#xff0c;可以将复杂机械运行过…

Java复习-20-接口(3)- 代理设计模式

代理设计模式(Proxy) 功能&#xff1a;可以帮助用户将所有的开发注意力只集中在核心业务功能的处理上。 代理模式(Proxy Pattern)是一种结构性模式。代理模式为一个对象提供了一个替身&#xff0c;以控制对这个对象的访问。即通过代理对象访问目标目标对象&#xff0c;可以在目…

uni-app开发小程序时ucharts图表如何使用

在此不会具体告诉大家怎么做&#xff0c;我只告诉大家方法&#xff1a; 第一步&#xff0c;推荐使用组件方法进行绘图&#xff0c;首先去官网下载这个ucharts的插件&#xff1a; 秋云 ucharts echarts 高性能跨全端图表组件 - DCloud 插件市场 下载完毕导入到所需要用到的项目…

数据结构与算法基础-学习-33-归并排序

目录 一、基本思想 二、算法思路 1、合并两个有序序列 2、分治法 三、算法源码 1、MergeSortTwoSortData 2、TwoWayMergeSortRecurtionSentryQueue 四、算法效率分析 五、Linux环境编译测试 六、小感慨 排序的其他相关知识点和源码分享可以参考之前的博客&#xff1a…

Docker入门,Docker是什么?有什么用?该怎么用?

目录 1. 项目部署时的复杂性&#xff1f; 2. Docker是如何解决依赖兼容问题的&#xff1f; 3. 众多Linux操作系统发行版的区别 4. Docker 是如何实现跨系统运行的&#xff1f; 5. Docker与虚拟机的差别 6. 镜像(Image)与容器(Container) 7. DockerHub 8. Docker 架构 …

安防监控/视频汇聚/云存储/AI智能视频分析平台EasyCVR显示CPU过载,该如何解决?

视频云存储/安防监控/视频汇聚平台EasyCVR基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防视频监控系统EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云…

Java知识总结(持续更新)

一、JDK、JRE、JVM三者之间的关系&#xff1f; 1. **JDK (Java Development Kit)**&#xff1a; JDK 是 Java 开发工具包&#xff0c;它包含了用于开发 Java 应用程序的所有必要工具和库。这包括 Java 编译器&#xff08;javac&#xff09;、Java 核心类库、开发工具&#x…

《React vs. Vue vs. Angular:2023年的全面比较》

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

【LeetCode-简单题】844. 比较含退格的字符串

文章目录 题目方法一&#xff1a;单指针方法二&#xff1a;双指针方法三&#xff1a;栈 题目 方法一&#xff1a;单指针 首先每次进入循环处理之前需要对第一个字符进行判断&#xff0c;若是退格符&#xff0c;直接删掉&#xff0c;结束此次循环fast从0开始&#xff0c;如果fa…

每日一练 | 网络工程师软考真题Day32

阅读以下说明&#xff0c;答复以下【问题1】至【问题5】 【说明】 某公司内部效劳器S1部署了重要的应用&#xff0c;该应用只允许特权终端PC1访问&#xff0c;如图4-1所示。为保证通信平安&#xff0c;需要在S1上配置相应的IPSec策略。综合考虑后&#xff0c;确定该IPSec策略如…

pdf.js 微信公众号不显示问题

问题1&#xff1a; 在浏览器中能够正常显示&#xff0c; 但是在微信浏览器中不行&#xff01;解决&#xff1a; 这个是pdf.js 版本问题&#xff0c; 用2.4版本&#xff0c;微信打开就没问题了 问题2&#xff1a; 如何关闭侧边栏&#xff1f; 修改这个地方&#xff0c; 将 -1 改…

QLineEdit 类(行编辑器)

1、 QLineEdit 类是 QWidget 类的直接子类&#xff0c;该类实现了一个单行的输入部件&#xff0c;即行编辑器&#xff1b; 2、验证器(QValidator 类)和输入掩码简介&#xff1a;主要作用是验证用户输入的字符是否符合验证器 的要求&#xff0c;即限制对用户的输入&#xff0c;比…

发UPS国际快递到墨西哥的收费标准

UPS国际快递是目前全球范围内最为知名和可靠的快递服务提供商之一&#xff0c;无论是个人还是企业都可以通过UPS将包裹快速送达世界各地&#xff0c;其中包括墨西哥。所以&#xff0c;对于许多人来说&#xff0c;了解到发UPS国际快递到墨西哥的收费标准是十分重要的。 发UPS国际…

Linux常见进程类别

目录 常见进程类别 守护进程&精灵进程 任务管理 进程组 作业 作业 | 进程组 会话 w命令 守护进程 守护进程的创建 setsid()函数 daemon()函数 模拟实现daemon函数 前台进程 | 后台进程 僵尸进程 | 孤儿进程 僵尸进程的一些细节 守护进程 | 后台进程 守护…