【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指针使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表

构造函数不能是虚函数,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的

最好把基类的析构函数定义为虚函数,因为如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数,这会导致派生类部分的对象没有被正确析构,可能会引发资源泄露

对象在访问虚函数与普通函数速度的对比,如果是普通对象访问,两者一样快,如果是多态对象访问(指针对象或者引用对象),则调用普通函数更快,因为虚函数构成多态,运行时需要到虚函数表中去查找

虚函数表在编译阶段就生成了


今日分享就到这里了~

在这里插入图片描述

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

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

相关文章

开放式耳机哪个品牌好?分享几款不错的开放式蓝牙耳机

相信很多人戴入耳式耳机时间一久&#xff0c;就不是很舒服。经常会有闷热、不透气的感觉&#xff0c;甚至有的朋友会因为佩戴入耳式耳机滋生细菌&#xff0c;导致最后炎症的发生。总之&#xff0c;入耳式耳机真的不适合长时间佩戴&#xff0c;而且佩戴的场景也有很多限制。 那…

一文了解构建工具——Maven与Gradle的区别

目录 一、Maven和Gradle是什么&#xff1f; 构建工具介绍 Maven介绍 Gradle介绍 二、使用时的区别&#xff1a; 1、新建项目 Maven&#xff1a; Gradle&#xff1a; 2、配置项目 Maven&#xff1a; Gradle&#xff1a; 3、构建项目——生成项目的jar包 Gradle&…

Linux 信号详解

目录 一.前置知识 1.前台进程和后台进程 a.概念理解 b.相关指令 2.信号的前置知识 a.Linux 系统下信号的概念 b.进程对信号的处理方式 3.信号的底层机制 二.详解信号 1.信号的产生 a.键盘组合键 b.kill 指令和系统调用接口 ① kill 指令 ② kill() 系统调用接口 ③ raise() 系统…

TCP四次挥手过程详解

TCP四次挥手全过程 有几点需要澄清&#xff1a; 1.首先&#xff0c;tcp四次挥手只有主动和被动方之分&#xff0c;没有客户端和服务端的概念 2.其次&#xff0c;发送报文段是tcp协议栈的行为&#xff0c;用户态调用close会陷入到内核态 3.再者&#xff0c;图中的情况前提是双…

leetcode-链表篇3

leetcode-61 给你一个链表的头节点 head &#xff0c;旋转链表&#xff0c;将链表每个节点向右移动 k 个位置。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], k 2 输出&#xff1a;[4,5,1,2,3]示例 2&#xff1a; 输入&#xff1a;head [0,1,2], k 4 输出&#x…

栏目二:Echart绘制动态折线图+柱状图

栏目二&#xff1a;Echart绘制动态折线图柱状图 配置了一个ECharts图表&#xff0c;该图表集成了数据区域缩放、双Y轴显示及多种图表类型&#xff08;折线图、柱状图、象形柱图&#xff09;。图表通过X轴数据展示&#xff0c;支持平滑折线展示比率数据并自动添加百分比标识&…

从原理到代码:如何通过 FGSM 生成对抗样本并进行攻击

从原理到代码&#xff1a;如何通过 FGSM 生成对抗样本并进行攻击 简介 在机器学习领域&#xff0c;深度神经网络的强大表现令人印象深刻&#xff0c;尤其是在图像分类等任务上。然而&#xff0c;随着对深度学习的深入研究&#xff0c;研究人员发现了神经网络的一个脆弱性&…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 10月3日,星期四

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年10月3日 星期四 农历九月初一 1、 应急管理部&#xff1a;10月华北东南部等部分地区洪涝和风雹灾害风险较高&#xff0c;可能有1-2个台风登陆或明显影响我国。 2、 中国海警舰艇首次进入北冰洋&#xff0c;与俄海警展开联…

【MySQL 07】内置函数

目录 1.日期函数 日期函数使用场景&#xff1a; 2.字符串函数 字符串函数使用场景&#xff1a; 3.数学函数 4.控制流函数 1.日期函数 函数示例&#xff1a; 1.在日期的基础上加日期 在该日期下&#xff0c;加上10天。 2.在日期的基础上减去时间 在该日期下减去2天 3.计算两…

C#知识|基于反射和接口实现抽象工厂设计模式

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 01 应用场景 在项目的多数据库支持上、业务的多算法封装、以及各种变化的业务中&#xff1b; 02 抽象工厂组成 抽象工厂包括抽象产品&#xff08;即业务接口&#xff0c;可以通过抽象类或抽象接口设计&#xff09;…

C++和OpenGL实现3D游戏编程【连载13】——多重纹理混合详解

🔥C++和OpenGL实现3D游戏编程【目录】 1、本节要实现的内容 前面说过纹理贴图能够大幅提升游戏画面质量,但纹理贴图是没有叠加的。在一些游戏场景中,要求将非常不同的多个纹理(如泥泞的褐色地面、绿草植密布的地面、碎石遍布的地面)叠加(混合)起来显示,实现纹理间能…

基于微信的乐室预约小程序+ssm(lw+演示+源码+运行)

摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个…

信息安全工程师(24)网络安全体系建设原则与安全策略

一、网络安全体系建设原则 网络空间主权原则&#xff1a;维护网络空间主权是网络安全的首要原则。这要求国家在网络空间的管理、运营、建设和使用等方面具有完全自主的权利和地位&#xff0c;不受任何外部势力的干涉和侵犯。网络安全与信息化发展并重原则&#xff1a;网络安全与…

影视cms泛目录用什么程序?苹果cms二次开发泛目录插件

影视CMS泛目录一般使用的程序有很多种&#xff0c;&#xff08;maccmscn&#xff09;以下是其中几种常见的程序&#xff1a; WordPress&#xff1a;WordPress是一个非常流行的开源内容管理系统&#xff0c;可以通过安装一些插件来实现影视CMS泛目录功能。其中&#xff0c;一款常…

Win10之解决:设置静态IP后,为什么自动获取动态IP问题(七十八)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

vulnhub-Replay 1靶机

vulnhub&#xff1a;https://www.vulnhub.com/entry/replay-1,278/ 导入靶机&#xff0c;放在kali同网段&#xff0c;扫描 靶机在192.168.81.8&#xff0c;扫描端口 开启了三个端口&#xff0c;存在网站服务&#xff0c;访问 网页上有个超链接&#xff0c;点击后下载了这样一个…

Python开发环境配置(mac M2)

1. 前言 作为一名程序员&#xff0c;工作中需要使用Python进行编程&#xff0c;甚至因为项目需要还得是不同版本的Python如何手动管理多个版本的Python&#xff0c;如何给Pycharm&#xff08;IDE&#xff09;配置对应的interpreter等&#xff0c;都成为一个 “不熟练工” 的难…

Docker版MKVtoolnix的安装及中文显示

本文是应网友 kkkhi 要求折腾的&#xff0c;只研究了 MKVtoolnix 的安装及中文显示&#xff0c;未涉及到软件的使用&#xff1b; 什么是 MKVtoolnix &#xff1f; MKVToolnix 是一款功能强大的多媒体处理工具&#xff0c;用于在 Linux、其他 Unix 系统和 Windows 上创建、修改和…

Ks渲染做汽车动画吗?汽车本地渲染与云渲染成本分析

Keyshot是一款强大的实时光线追踪和全域光渲染软件&#xff0c;它确实可以用于制作汽车动画&#xff0c;包括汽车模型的渲染和动画展示。Keyshot的动画功能允许用户创建相机移动、物体变化等动态效果&#xff0c;非常适合用于汽车动画的制作。 至于汽车动画的渲染成本&#xff…

【设计模式-命令】

定义 命令模式&#xff08;Command Pattern&#xff09;是一种行为设计模式&#xff0c;它将请求封装为一个对象&#xff0c;从而使您能够使用不同的请求、排队请求或记录请求&#xff0c;并支持可撤销的操作。该模式通过将请求与其执行分离&#xff0c;使得请求者和接收者之间…