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

C++ ——多态 下 (图解多态原理、虚函数的再认知)

目录

一、抽象类

1)抽象类定义

 2)抽象类的继承

3)抽象类实现多态

4)抽象类的好处

二、多态的实现原理

1)虚函数的存储方式

2)子类中虚函数的存储方式

① 子类将基类中的虚表原封不动的拷贝到自己的虚表中

② 如果子类中重写了基类中的虚函数,则将子类虚表中基类的虚函数替换为子类自己重写了的虚函数地址

③ 如果子类有新增的虚函数,则接着将子类新增的虚函数按照其在子类中的声明顺序依次增加到子类虚表的尾部

 3)代码检验虚函数在虚表中的先后位置

 4)对于多态的原理的剖析

① 当不满足多态的条件时

② 满足多态的条件(通过指针或者引用调用)

 三、其他继承方式下的虚表模型

1)单继承方式

 2)多继承方式

 源码:(注意调用TestPrintB1与TestPrintB2方法获取类中虚表的顺序)


一、抽象类

在多态上那篇文章中,写了这样一个类Shape,然后通过子类圆、矩形、三角形分别继承该Shape类基类,通过重写基类中的Print与GetPerimeter这俩个虚函数然后达到了多态的效果。

可是这里仍然有一些问题,就是这个基类的函数Print与GetPerimeter这俩个函数的执行对于基类对象来说,用Shape定义一个对象s,这个s是个啥呢?它是一个图形?它是个啥图形?不免得引发争议,这个Shape就不应该定义对象,它的功能函数也不应该被实现!!!

class Shape
{
public:virtual void Print(){cout << "未知图形" << endl;}virtual double GetPerimeter(){cout << "未知图形,无法获得周长" << endl;}
};

这个Shape就是一个抽象类,形状这个概念在问题领域中不是直接存在的,它只是一个抽象概念。

1)抽象类定义

在虚函数的后面写上=0,表示该函数为纯虚函数,包含纯虚函数的类称为抽象类(也叫接口类),抽象类不能实例化出对象。

class Shape
{
public:virtual void Print() = 0;virtual double GetPerimeter() = 0;
};

 2)抽象类的继承

抽象类可以被继承,但子类中如果没有对基类所有的纯虚函数进行重写,则子类也无法进行实例化对象。(即子类也是一个抽象类)

 对基类中的所有纯虚函数进行重写之后才能完成子类实例化对象:

class Shape
{
public:virtual void Print() = 0;    // 限定为纯虚函数,表示在子类中必须要对其进行重写virtual int GetPerimeter() = 0;
};class Triangle : public Shape
{
public:Triangle(int a, int b, int c): _a(a), _b(b), _c(c){}virtual void Print()override    // 养成给基类中需要重写的虚函数加上override的习惯{cout << "△" << endl;}virtual int GetPerimeter()override{return _a + _b + _c;}
private:int _a;int _b;int _c;
};
int main()
{Triangle t(3, 4, 5);    // 正常编译
}

3)抽象类实现多态

上面说了抽象类无法实例化对象,但是抽象类也是一种类型,所以它可以创建指针或者引用。

那么就可以用到多态来传递参数

void Test(Shape& s)    // 传入Shape类型的引用
{s.Print();cout << s.GetPerimeter() << endl;
}int main()
{Triangle t(3, 4, 5);Test(t);    // 实现多态Shape s;	// 实例化对象报错 !!!Shape* ptrs = &t;	// 定义一个指针  OKShape& rs = t;	// 定义一个引用  OK  // 基类的指针或引用可以指向子类return 0;
}

4)抽象类的好处

        ①  代码更加符合逻辑——有些类就是无法创建对象,所以将它声明为抽象类

        ② 不用花费时间来考虑纯虚函数中的代码应该怎么写

        ③ 抽象类实际规范了:后续子类要实现的虚函数的原型 ==》将接口规范化

二、多态的实现原理

这里使用的是VS2019 x86 32位的编译环境下进行研究(一个指针为4个字节)

1)虚函数的存储方式

class D
{
public:D(int d, int b): _d(d){cout << "B::B()" << endl;}virtual void func1(){cout << "D::func1()" << endl;}virtual void func2(){cout << "D::func2()" << endl;}virtual void func3(){cout << "D::func1()" << endl;}int _d;
};int main()
{cout << sizeof(D) << endl;    // 结果为 8D d(1,2);return 0;
}

 创建一个D类,类中存在三个虚函数,这三个虚函数在类中的模型是如何呢?

① 调试并查看用D创建出来的对象d:发现d中前四个字节存放的是一个类似于地址的东西,接着存放的是_d的值也占用四个字节,所以sizeof(D)为8个字节

② 那么这四个字节的地址是什么呢?

展开这个地址发现里面又存放了三个地址,而这三个地址都指向了我定义的三个虚函数!

 

③ 调用内存窗口看一下&d,然后再打开另外一个窗口查看一下d中前四个字节的地址存放的是啥

 ④ 对比这内存窗口与监视窗口的这三个地址,发现就是②中所说的,这块地址指向了三个虚函数的地址。

 

 ⑤ 模型推导

 ⑥ 同一个类中定义俩个对象,它们的虚表地址是相同的。(共用同一张虚表)

 

结论:

        如果一个类中存在虚函数(类的大小多个4个字节)

        ① 则编译时编译器会为类中的虚函数创建一个虚表(编译器一定会生成类的构造方法)

        ② 虚表中的内容存放的是各个虚函数的入口地址,

        ③ 并且虚表中虚函数的先后次序和类中定义的虚函数的先后次序一致。

        ④ 同一个类中的多个对象共用同一张虚表

2)子类中虚函数的存储方式

上面了解了关于类中虚函数是如何存储的,那么下面通过继承来学习子类中的虚函数的存储方式

① 子类将基类中的虚表原封不动的拷贝到自己的虚表中

 如图将B中的俩个虚函数继承下来后置入自己的虚表中。函数访问时通过虚表指针来进行访问。

class B{
public:int _b;virtual void func1(){cout << "B::func1()" << endl;}virtual void func2(){cout << "B::func2()" << endl;}
};class D : public B{
public:int _d;
};

② 如果子类中重写了基类中的虚函数,则将子类虚表中基类的虚函数替换为子类自己重写了的虚函数地址

D子类重写了基类的虚函数func1():

③ 如果子类有新增的虚函数,则接着将子类新增的虚函数按照其在子类中的声明顺序依次增加到子类虚表的尾部

通过内存监视窗口了解虚表内的函数存储(D::func3()为在子类D中新增的虚函数,D::func1为子类重写了基类的虚函数,B::func2为基类继承下来的虚函数)

 

class B
{
public:int _b;virtual void func1(){cout << "B::func1()" << endl;}virtual void func2(){cout << "B::func2()" << endl;}
};class D : public B
{
public:int _d;virtual void func1(){cout << "D::func1()" << endl;}virtual void func3(){cout << "D::func3()" << endl;}
};

3)代码检验虚函数在虚表中的先后位置

通过上面的学习可以知道,通过一个对象的内存模型可以知道其前四个字节为虚表指针,那么通过这个虚表指针就可以访问到存储虚函数入口地址的函数指针数组,接着通过这个函数指针数组来访问每个函数的入口地址

 ① 获取虚表指针内容

使用一个四个字节大小的int类型的指针来进行强转d对象,则这个接收结果的p现在就指向了0xcd9b64这个地址,如果这时候对这个p解引用则就可以得到虚表指针的内容

② 对p进行解引用

解引用后的值指向了虚表指针数组(可是这里的data是一个int类型的整形数据——下面看到0xcd9b64是我通过设计将整数16进制转化看到的)

 

 ③ 对data整形数据进行类型转换

首先看一下函数指针如何取别名,对void(*)()取别名为 

typede void(*PVft)();

PVft* fp = (PVft*) data;

 ④ 从上到下依次打印虚表中的内容

	while (*fp){(*fp)();fp++;}

 4)对于多态的原理的剖析

① 当不满足多态的条件时

例如下面使用基类的对象来直接进行调用函数

编译器在编译阶段就可以确定调用哪个类的函数,因为此时编译器编译时看到的是对象的静态类型,是哪个类的对象就去哪个类里面调用它的成员函数。

② 满足多态的条件(通过指针或者引用调用)

直白的,当了解了子类虚函数的存储结构后,多态其实也就是一种特殊的函数调用机制,它通过传入的对象指针判断访问的是哪个对象的类型,然后通过该类型的虚表确定该如何来调用入口函数

 三、其他继承方式下的虚表模型

1)单继承方式

单继承方式下的虚表模型就是上面我们一直所探讨的用例

父类与子类中各有一张虚表(当然是在有虚函数的前提下)

 2)多继承方式

如以下继承体系:B1(func1、func2)、B2(func3、func4)为俩个基类,D继承自这俩个基类,D中重写了B1中的func1、B2中的func4、以及新增虚函数func5

在多继承中,子类中存在俩个虚表,子类新增的虚函数地址按次序放到第一张虚表后面

 源码:(注意调用TestPrintB1与TestPrintB2方法获取类中虚表的顺序)

class B1
{
public:int _b1;virtual void func1(){cout << "B1::func1()" << endl;}virtual void func2(){cout << "B1::func2()" << endl;}
};class B2
{
public:int _b2;virtual void func3(){cout << "B2::func3()" << endl;}virtual void func4(){cout << "B2::func4()" << endl;}
};
class D : public B1, public B2
{
public:int _d;virtual void func1()// 重写父类B1的虚函数{cout << "D::func1()" << endl;}virtual void func4()// 重写父类B2的虚函数{cout << "D::func4()" << endl;}virtual void func5()// 子类新增虚函数{cout << "D::func5()" << endl;}
};typedef void(*VFPT)();
void TestPrintB1(B1& b, const string& info)
{cout << info << endl;VFPT* fp = (VFPT*)*(int*)&b;while (*fp){(*fp)();fp++;}cout << "======================" << endl;
}
void TestPrintB2(B2& b, const string& info)
{cout << info << endl;VFPT* fp = (VFPT*)*(int*)&b;while (*fp){(*fp)();fp++;}cout << "======================" << endl;
}int main()
{D d;d._b1 = 1;d._b2 = 2;d._d = 3;TestPrintB1(d, "D of B1");TestPrintB2(d, "D of B1");
}

相关文章:

C++ ——多态 下 (图解多态原理、虚函数的再认知)

目录 一、抽象类 1&#xff09;抽象类定义 2&#xff09;抽象类的继承 3&#xff09;抽象类实现多态 4&#xff09;抽象类的好处 二、多态的实现原理 1&#xff09;虚函数的存储方式 2&#xff09;子类中虚函数的存储方式 ① 子类将基类中的虚表原封不动的拷贝到自己的…...

cocos creater 3.x 构建QQ小游戏

一、目前 cocos creater 不支持直接构建QQ小游戏&#xff0c;需要构建成微信小游戏&#xff0c;然后修改成QQ小游戏 二、构建QQ小游戏不能勾选 分离引擎 的选项&#xff0c;勾选分离引擎的选项&#xff0c;需要安装cocos微信小游戏引擎插件&#xff0c;这个插件似乎目前只支持微…...

ArcGIS笔记3_如何编辑、修改和导出散点数据

本文目录前言Step 1 在ArcGIS中添加并显示坐标点Step 2 将坐标数据保存成shp文件Step 3 编辑或修改坐标数据Step 4 导出修改后的数据&#xff1a;法一&#xff1a;通过转换工具导出Step 5 导出修改后的数据&#xff1a;法二&#xff1a;通过dBASE表导出前言 本博文更多针对Arc…...

Computer Graphics From Scratch - Chapter 8

系列文章目录 简介&#xff1a;Computer Graphics From Scratch-《从零开始的计算机图形学》简介 第一章: Computer Graphics From Scratch - Chapter 1 介绍性概念 第二章&#xff1a;Computer Graphics From Scratch - Chapter 2 基本光线追踪 第三章&#xff1a;Computer Gr…...

金三银四”不香了?

“金三银四”不香了&#xff1f; “金三银四”这个词&#xff0c;放在三年前&#xff0c;勾勒的是无数踌躇满志的年轻人涌向职场&#xff0c;大中小企业血液更新与流动的鲜活画面。 尤其是互联网行业&#xff0c;这个在过去20多年里极大改变文化交流方式与商业形态的领域&…...

个人开源PCB开发板列表汇总

个人开源PCB开发板列表汇总✨首先感谢立创EDA的免费打样和立创一起开源的广大网页。 &#x1f530;STC单片机为主控开源PCB开发板列表 &#x1f4cc;STC15F2K60S2开发板&#xff1a;https://oshwhub.com/perseverance51/stc15f2k60s2-ji-tong-ban &#x1f4cc;STC15W408AS系…...

2023美国大学生数学建模竞赛(美赛)思路代码

2023美国大学生数学建模竞赛&#xff08;美赛&#xff09;思路&代码报名时间节点比赛说明问题A&#xff08;数据分析题&#xff09;&#xff1a;收干旱影响的植物群落&#xff08;MCM&#xff09;第一问第二问问题B&#xff08;仿真建模题&#xff09;&#xff1a;重塑马赛…...

makefile简易教程

makefile简易教程 一、学习目标 达到多文件快速编译的需求&#xff0c;相关符号的意思&#xff0c;以及其它注意事项。 二、快速入门 2.1 基本概念 Makefile 是一个在Unix和Linux操作系统上使用的构建工具&#xff0c;用于自动化编译和构建源代码。 2.2 用处 通过Makefi…...

快速入门nginx

目录 1.nginx前言 2.什么是nginx 3.Nginx作用&#xff1f; 1.正向代理 2.反向代理 3.轮询 4.加权轮询 4.Nginx的安装 1.windows下安装 2.linux下安装 5.Nginx常用命令 1.nginx前言 我们公司项目刚刚上线的时候&#xff0c;并发量小&#xff0c;用户使用的少&#…...

甘特图:项目管理工具,轻松简化工作流程

项目规模越大&#xff0c;管理就越复杂&#xff0c;有时候甚至一个项目经理需要管理多个项目&#xff0c;当多个项目、多条任务同时进行&#xff0c;项目所涉及的范围广&#xff0c;内容越来越复杂&#xff0c;使得项目越难以把控&#xff0c;好的管理工具&#xff0c;可以提升…...

刷题专练之翻转题练习

文章目录一、 编写函数实现字符串翻转二、轮转数组总结一、 编写函数实现字符串翻转 描述 编写一个函数&#xff0c;实现字符串的翻转 输入描述&#xff1a; 输入一个字符串 输出描述&#xff1a; 输出翻转后的字符串 写法一&#xff1a; 这种方法是定义begin和end&#xff0…...

【Java】死锁

一、什么是死锁 死锁指多个线程在执行过程中&#xff0c;因争夺资源造成的一种相互等待的僵局。 进程死锁是指两个或两个以上的进程在执行过程中&#xff0c;由于竞争资源或者由于彼此通信而造成的一种阻塞的现象&#xff0c;若无外力作用&#xff0c;它们都将无法推进下去。…...

DS图—图的最短路径(无框架)迪杰斯特拉算法

目录 题目描述 AC代码 题目描述 给出一个图的邻接矩阵&#xff0c;输入顶点v&#xff0c;用迪杰斯特拉算法求顶点v到其它顶点的最短路径。 输入 第一行输入t&#xff0c;表示有t个测试实例 第二行输入顶点数n和n个顶点信息 第三行起&#xff0c;每行输入邻接矩阵的一行&…...

【笔记】数据异常检测与修复总结

文章目录一、异常种类1. 对于移动对象的数据异常2. 对于时序数据的异常检测二、异常数据清洗流程三、数据预处理四、异常检测算法五、异常修复算法六、漂移数据清洗一、异常种类 不同的研究对象&#xff0c;有着不同的异常分类方式 1. 对于移动对象的数据异常 异常数据信息&…...

算法笔记(七)—— 图的相关知识及算法

图的存储方式 1. 邻接表&#xff08;记录关于某点的直接相邻点&#xff09; 2. 邻接矩阵&#xff08;一定是正方形的矩阵&#xff0c;对点进行编号&#xff0c;点到点的权值由距震中的值表示&#xff0c;无直接相连记为正无穷&#xff09; 图的模板 unordered_map<int,No…...

ssh配置互信时错误解决方法

之前项目中遇到有关配置ssh互信免密登录问题&#xff0c;为避免以后踩坑&#xff0c;现记录一下避坑指南。 1、提示如下错误&#xff1a; Permission denied (publickey,gssapi-keyex,gssapi-with-mic). 问题分析&#xff1a;可能是ssh配置问题。 查看日志/var/log/secure&…...

SQL69 返回产品并且按照价格排序

描述有Products 表prod_idprod_nameprod_pricea0011egg3a0019sockets4b0019coffee15【问题】编写 SQL 语句&#xff0c;返回 Products 表中所有价格在 3 美元到 6 美元之间的产品的名称&#xff08;prod_name&#xff09;和价格&#xff08;prod_price&#xff09;&#xff0c;…...

vue+elementUI 实现设置还款日字母弹窗组件

1、业务背景 还款业务&#xff0c;设置每月还款日&#xff0c;选每月几号扣款&#xff0c;不需要29、30、31&#xff0c;因为不是每个月都有这三天的 2、预期效果图 3、代码实现 3.1 初始化vue项目 地址&#xff1a;https://cn.vuejs.org/guide/introduction.html 3.2 在项…...

【JavaGuide面试总结】Redis篇·中

【JavaGuide面试总结】Redis篇中1.Redis 单线程模型了解吗&#xff1f;2.Redis6.0 之后为何引入了多线程&#xff1f;3.Redis 是如何判断数据是否过期的呢&#xff1f;4.过期的数据的删除策略了解么&#xff1f;5.Redis 内存淘汰机制了解么&#xff1f;6.什么是 RDB 持久化&…...

Python:每日一题之全球变暖(BFS连通性判断)

题目描述 你有一张某海域 NxN 像素的照片&#xff0c;"."表示海洋、"#"表示陆地&#xff0c;如下所示&#xff1a; ....... .##.... .##.... ....##. ..####. ...###. ....... 其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿…...

VUE -- defineExpose

defineExpose定义demo定义 defineExpose定义&#xff1a;用于组件通信中父级组件调用操作子组建方法和响应式属性参数能力 在使用definExpose前需要了解两个拷贝对象函数 对象copy&#xff1a;shallowReactive 与 数据 copy&#xff1a;shallowRef 这两个都是vue包里面的 简…...

实用调试技巧【下篇】

&#x1f534;本文章是在 Visual Studio 2022&#xff08;VS2022&#xff09;编译环境下进行操作讲解 文章目录3.2.调试的时候查看程序当前信息3.2.1.查看临时变量的值3.2.2.查看内存信息3.2.3.查看调用堆栈3.2.4.查看汇编信息&#x1f973;4.调试实例&#x1f973;5.如何写出&…...

【数据结构期末例题】

前言 本文是博主自己在准备学校数据结构考试时的总结&#xff0c;各个知识点都贴有对应的详细讲解文章以供大家参考&#xff1b;当然文中还有许许多多的截图&#xff0c;这些是博主对主要内容的摘取&#xff0c;对于那些基础较好的同学可以直接看截图&#xff0c;减少跳转对应文…...

管理物理和快照备数据库(Physical and Snapshot Standby Databases)

1&#xff0e;打开物理备数据库 物理备数据库可以打开做只读访问&#xff0c;用于从主数据库卸载查询负载。 如果已经购买Oracle Active Data Guard选项的授权&#xff0c;当数据库打开时Redo Apply可以是激活的&#xff0c;因此允许查询返回与从主数据库返回的完全相同的结果…...

双目立体视觉:SAD算法

算法原理SAD(Sum of absolute differences)是一种图像匹配算法。基本思想&#xff1a;差的绝对值之和。此算法常用于图像块匹配&#xff0c;将每个像素对应数值之差的绝对值求和&#xff0c;据此评估两个图像块的相似度。该算法快速、但并不精确&#xff0c;通常用于多级处理的…...

海外问卷调查答题技巧,纯干货分享,新手小白看过来

海外问卷调查为什么别人赚得盆满钵满而我却连通过都不行&#xff1f;是不是经常有人发出这种疑问&#xff0c;东哥作为一个结交过很多做问卷调查行业的跨境人士&#xff0c;也了解到很多做这一行的去答题的时候都是掌握一定技巧的&#xff0c;而不是去乱答。今天东哥就来说说国…...

【NGINX入门指北】Nginx Web 架构实验

Nginx Web 架构实验 文章目录Nginx Web 架构实验一、动态网站结构二、LNMP 动态网站环境部署三、fastcgi & php-fpm&#xff1a;四、php-fpm初始化配置五、Nginx Location、六、Nginx Rewrite七、CA&HTTPS八、Nginx 的平滑升级一、动态网站结构 资源 资源文件识别——…...

rtt-nano移植

nano其他功能移植 添加finsh组件打开宏实现rt_hw_console_getchar函数添加finsh组件到工程总结问题1. 移植到stm32G0过程中出现Undefined symbol rt_hw_interrupt_disable (referred from clock.o)??2. “implict declaration of function ‘ ‘ is invalid in c99??3. 关于…...

cnn+transformer

好的,下面是使用 Transformer 加 CNN 实现语义分割的代码,使用的数据集是 Semantic Segmentation Drone Dataset。 首先,我们需要导入必要的 Python 库和模块。我们将使用 PyTorch 深度学习框架来实现模型: #python import torch import torch.nn as nn import torch.nn.fu…...

Python fileinput模块:逐行读取多个文件

前面章节中&#xff0c;我们学会了使用 open() 和 read()&#xff08;或者 readline()、readlines() &#xff09;组合&#xff0c;来读取单个文件中的数据。但在某些场景中&#xff0c;可能需要读取多个文件的数据&#xff0c;这种情况下&#xff0c;再使用这个组合&#xff0…...

商务网站建设考试/sem运营

基于MATLAB联合站原油加热模糊控制(程序)(课题申报表,任务书,开题报告,中期检查表,外文翻译,论文24500字,程序,答辩PPT)摘 要我国普遍采用长距离管道输送的方式输送原油&#xff0c;由于我国原油大部分为“三高”原油&#xff0c;即具有含腊高&#xff0c;凝固点高和粘度高的特…...

学校怎么做网站/今日热点新闻事件2022

Happy Chrismas&#xff01; 转载于:https://www.cnblogs.com/allenblogs/archive/2010/12/22/1913568.html...

政府制作网站收费/八百客crm系统登录入口

往期热门文章&#xff1a;1、牛客网&#xff1a;为什么不能将实数作为 HashMap 的 key&#xff1f; 2、RedisJson发布官方性能报告&#xff0c;性能碾压ES和Mongo 3、分布式数据一致性思考-B端系统一致性 4、Java字符串拼接的五种方法&#xff0c;哪种性能最好&#xff1f; 5、…...

酒店 深圳 网站制作/站长之家最新域名查询

本文转载自&#xff1a; https://www.cnblogs.com/dongrenwen/p/6226246.html 作者&#xff1a;dongrenwen 转载请注明该声明。vuejs取得URL中参数的值 地址&#xff1a;http://localhost:3333/#/index?id001 结果&#xff1a;001 console.log(this.$route.query.id)...

电商网站建设运城/推广软件免费

A 题意就是求两个字符串的缩写&#xff0c;具体规则就是两个字符串&#xff0c;每个字符串的缩写可以是0-字符串的长度的任意长&#xff0c;然后合并起来&#xff0c;求字母顺序最小的那个#include <iostream> #include <cstring> #include <cstdio> #includ…...

做网站办公室图片/北京网站seo服务

第三篇&#xff1a;第一个能在开发板上运行的程序日期&#xff1a;2009-3-6程序非常的简单&#xff0c;如下所示&#xff1a;#include int main(void){printf("hello,linux!");return 0;}[rootlocalhost sin]# arm-linux-gcc -o hello –c hello.c编译提示错误[rootl…...