C++·多态

1. 多态的概念

        多态通俗讲就是多种形态,就是指去完成某个行为,当不同对象去做时会产生不同的结果或状态。

        比如买火车票这个行为,同样是买票的行为,普通成年人买到全价票,学生买到半价票,军人优先买票。这个买票就是一个多态行为,同样是买票的行为,不同人去买就会产生不同的状态。

2. 多态的定义与实现

        多态是在继承了相同父类的子类对象同父类对象之间,去调用同一函数,产生了不同的行为。比如父类是普通成年人,子类是学生、军人,在同时调用买票这一函数时,参数是普通成年人就输出买全价票,参数是学生就输出买半价票,参数是军人就输出买优先票。

        那么在继承中构成多态有两个必要条件

                1. 必须通过父类的指针或引用调用函数

                2. 被调用的函数必须是虚函数,且子类必须有对父类的虚函数进行了重写

2.1 虚函数

        虚函数就是用 virtual 修饰的函数。

        虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数 (及派生类虚函数与基类虚函数的返回值类型、函数名称、参数列表完全相同),称派生类的虚函数重写了基类的虚函数。

                

        在继承中有一个类似的概念叫 隐藏 ,指子类的同名函数会隐藏父类的同名函数,但是隐藏的构成只需要函数同名即可,所以可以说重写是一种特殊的隐藏。

        如果不是在两个不同但又有继承关系的类域中,函数重写的关系完全就是函数重载的关系。

        事实上,构成函数重写的时候只需要在父类中声明 virtual 子类中无所谓是否声明虚函数,但为了代码的可读性建议在子类中也加上 virtual 关键字。

2.2 多态实现

                        

        可以看到通过func函数我们成功完成了多态行为的验证。

        要注意必须符合多态形成的两个条件,及 虚函数重写 和 通过父类对象的引用或指针调用这个虚函数。

2.3 虚函数重写的两个例外

2.3.1 协变

        基类与派生类返回值类型不同。

        派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或引用时,成为协变。

                                

        这玩意儿没啥用知道有协变这么一说就好。

2.3.2 析构函数的重写

                ​​​​​​​        

        在正常情况下我们时用继承的析构函数没什么问题,但有一种特殊情况:

        ​​​​​​​        

        可以看到申请的是一块子类空间,但释放的时候只释放了父类空间。

        这因为传参时的参数是父类类型的因此选择父类的析构函数,这肯定是不可取的,我们要想办法用子类的析构方式释放掉这块空间。

        在C++设计的时候也意识到了这个问题,于是把这个地方设计称多态进行问题解决。

        实际上析构函数在编译器中处理的时候用的不是我们现在看到的这个黄色的函数名,而是被同一成了 destructor() (这就是为了用多态的方案解决问题)。

        也就是说p指针在 delete 的时候要调用一个 destruct() 函数,因为p指针类型是父类对象,同时这个要调用的 destruct() 函数又不是虚函数重写,所以自然就调用到了父类的析构函数。那么解决办法就是把析构函数进行虚函数重写。

        ​​​​​​​        ​​​​​​​        

        问题解决。

        说到这里就不得不提一下上节在说继承手时搓析构函数时的问题了,当时讲的是子类的析构函数中禁止调用父类的析构函数,原因不止那个要后调用父类析构,还因为,如果写了父类的析构的话,进了编译器析构函数名统一都会变成 destruct() 那在调用子类的析构函数时就进入了 destruct() 的无限递归了呀。

        如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名不同,但这只是表面上,在深层次中他们就是同名的,编译器对于析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成了destruct

2.4 C++11 override 和 final

        从之前的学习我们已经见识到了,C++对于函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致类似函数名少写给字母的错误而无法构成重载,而这种错误在编译期间是不会爆出的,只有在程序运行时没有得到预期结果才能看出来,因此C++11提供了 override 和 final 两个关键字可以帮助用户检测是否重写。

        override:检查派生类虚函数是否重写了基类某个虚函数,如果没有就报错

        

        final:修饰虚函数,表示该虚函数不能被重写

        

        final还可以用来修饰类,表示这个类是最终类,无法被别的类继承

2.5 重载、重写(覆盖)、隐藏(重定义) 的对比

3. 抽象类

3.1 概念

        在虚函数后面加上 =0 ,则这个函数属于纯虚函数包含虚函数的类叫抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承了抽象类后也不能直接实例化出对象,必须进行重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

        ​​​​​​​        

        这里Car就是一个拥有纯虚函数的抽象类,无法生成对象。而Benz和BWM在继承了Car后需要重写纯虚函数才能生成对象。

        在实际应用中,抽象类就是为这种情况出现的,用于描述有实际意义的抽象概念的接口。

3.2 接口继承和实现继承

        普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。

        虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

        我们看这段代码,结果是 A->1 还是 B->0 呢?事实上都不是,本题结果是 B->1

        B类继承了A类并对其虚函数进行了重写,同时使用了A类的指针调用了这个虚函数,多态的要求满足,此时形成多态,变量实际内容是B的对象,因此看似应该调用B类中的func函数,但虚函数的继承是接口继承,也就是说继承了A类的接口,使用B类的实现。

        我们再看下面两段代码:

        可以看到使用虚函数之后A类的大小明显变大,这是因为每个类中的为虚函数开设有一个虚表,存放虚函数们的地址,为函数重写备用,这就是为毛不写函数重写就不要写虚函数的原因,乱写虚函数会有消耗的。

        如果忘记了结构体或类中成员的内存位置配置,或者对齐数和对其规则,跳转:

C语言·自定义类型:结构体-CSDN博客文章浏览阅读959次,点赞25次,收藏22次。本节讲解了结构体的特殊声明、结构体的自引用(链表)、结构体在内存中的对齐规则、通过offsetof宏获取结构体成员的偏移量、通过#pragma修改默认对齐数、结构体传参、位段https://blog.csdn.net/atlanteep/article/details/134717687?spm=1001.2014.3001.5501

4. 多态的原理

        我们用下面一段代码讲解一下多态的原理

        我们结合着右边的图好讲一点,首先我们用Base类和Derive类实例化出两个对象b和d

        对于 b 对象来说,它里面只会存放成员变量和虚表地址(虚函数表地址),很遗憾,b没有成员变量,所以它里面只存了虚表的地址。虚表中存放着属于 Base类 的两个虚函数地址。

        对于 d 对象来说,因为它是继承了 Base类 而出生的,所以它身体中天生有属于 Base类 的内容,就是我用虚线框化出来的的地方,这块地中圈着Base类的一切能继承过来的内容,包括Base类的成员变量和Base类的虚表,而虚线框外的地方存放着属于 Derive类 自己的成员变量 。

        虚表地址中存放着 Base类 虚函数的地址,和 Derive类 自己的虚函数地址,但是如果这里面有形成函数重写的两个虚函数的情况的话,比如 Func1 那么此事虚表中指向 Func1 的指针的内容就是将父类的接口融合子类的实现的这么一个函数,也就是说 Deriver类 虚表中存了3个函数指针,分别指向被重写后的虚函数Func1,虚函数Func2,虚函数Func4。

        所以说重写这个操作又被叫做覆盖,就是本来虚表中存放的应该是父类的Func1,但是因为子类中重写了Func1的实现,所以就用子类的Func1的实现覆盖了父类Func1的实现。

        好了,这样多态的整个原理的线索就说完了,我们把这些线索串起来:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

        传参时传递的是Deriver的对象d,此时虚表已经准备好了(该重写的已经重写完毕),接收参数的变量类型是父类的指针,此时发生切割,将d中属于父类的部分切割了下来(虚线框中的部分),然后用这个部分中的虚表搜索并调用了Func1,此时的Func1就是被重写了之后的Func1。

        到此多态的实现原理是不是就通透了。

        观察这个继承的结构其实跟内部类其实挺像的,但是继承是一种虚假的内部类,他俩只是结构上相似,使用起来完全不是一回事,注意区分,关于内部类的内容传送至:

C++语言·类和对象(下)-CSDN博客文章浏览阅读806次,点赞15次,收藏22次。本文详细阐述了C++中构造函数、初始化列表、隐式类型转换、explicit关键字、静态成员、友元、内部类和匿名对象的概念,以及编译器在某些场景下的优化策略,帮助读者掌握这些关键概念和实践技巧。https://blog.csdn.net/atlanteep/article/details/137886869?spm=1001.2014.3001.5501        虚函数表,简称虚表,根普通函数和虚函数一样存储在常量区,虚函数表指针存储在对象里。

4.1 动态绑定与静态绑定

        1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如函数重载,函数模板。

        2. 动态绑定又称为后期绑定(晚绑定),在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。比如函重写。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NFT如何解决音乐版权的问题

音乐版权问题一直困扰着音乐产业。传统的音乐版权管理模式存在以下问题。需要注意的是,NFT在音乐版权领域仍处于早期发展阶段,存在一些需要解决的问题,例如技术标准不统一、应用场景有限、法律法规不明朗等。但随着技术的进步和市场的完善&am…

可重入锁深入学习(有码)

【摘要】 ​今天,梳理下java中的常用锁,但在搞清楚这些锁之前,先理解下 “临界区”。临界区在同步的程序设计中,临界区段活称为关键区块,指的是一个访问共享资源(例如:共享设备或是共享存储器&a…

路径规划 | 飞蛾扑火算法求解二维栅格路径规划(Matlab)

目录 效果一览基本介绍程序设计参考文献 效果一览 基本介绍 路径规划 | 飞蛾扑火算法求解二维栅格路径规划(Matlab)。 飞蛾扑火算法(Firefly Algorithm)是一种基于自然界萤火虫行为的优化算法,在路径规划问题中也可以应…

Nginx入门到精通三(反向代理1)

下面内容整理自bilibili-尚硅谷-Nginx青铜到王者视频教程 Nginx相关文章 Nginx入门到精通一(基本概念介绍)-CSDN博客 Nginx入门到精通二(安装配置)-CSDN博客 Nginx入门到精通三(Nginx实例1:反向代理&a…

子进程继承父进程文件描述符导致父进程打开设备文件失败

开发过程中有时会遇到需要在程序中执行三方程序或者shell脚本,一般会通过system(), popen(), exec簇来完成该功能。我们知道以上方法会通过fork创建子进程后在子进程中执行相应指令。如图1为某个示例流程,具体的程序执行流程如图2所示,线程my…

使用Python和MediaPipe实现手势控制音量(Win/Mac)

1. 依赖库介绍 OpenCV OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库。它包含了数百个计算机视觉算法。 MediaPipe MediaPipe是一个跨平台的机器学习解决方案库,可以用于实时人类姿势估计、手势识…

godis源码分析——database存储核心1

前言 redis的核心是数据的快速存储,下面就来分析一下godis的底层存储是如何实现,先分析单机服务。 此文采用抓大放小原则,先大的流程方向,再抓细节。 流程图 源码分析 现在以客户端连接,并发起set key val命令为例…

简单的SQL字符型注入

目录 注入类型 判断字段数 确定回显点 查找数据库名 查找数据库表名 查询字段名 获取想要的数据 以sqli-labs靶场上的简单SQL注入为例 注入类型 判断是数字类型还是字符类型 常见的闭合方式 ?id1、?id1"、?id1)、?id1")等,大多都是单引号…

微分方程的解法(Matlab)

微分方程分为刚性微分方程和非刚性微分方程,在数值解法中的表现和行为特性上存在显著差异。 刚性微分方程(Stiffness Equation)是指其数值分析的解只有在时间间隔很小时才会稳定,只要时间间隔略大,其解就会不稳定。这…

【BUG】Python3|COPY 指令合并 ts 文件为 mp4 文件时长不对(含三种可执行源代码和解决方法)

文章目录 前言源代码FFmpeg的安装1 下载2 安装 前言 参考: python 合并 ts 视频(三种方法)使用 FFmpeg 合并多个 ts 视频文件转为 mp4 格式 Windows 平台下,用 Python 合并 ts 文件为 mp4 文件常见的有三种方法: 调用…

项目范围管理-系统架构师(二十九)

1、(重点)软件设计包括了四个独立又相互联系的活动,高质量的()将改善程序结构的模块划分,降低过程复杂度。 A程序设计 B数据设计 C算法设计 D过程设计 解析: 软件设计包含四个,…

博客前端项目学习day01

这里写自定义目录标题 登录创建项目配置环境变量,方便使用登录页面验证码登陆表单 在VScode上写前端,采用vue3。 登录 创建项目 检查node版本 node -v 创建一个新的项目 npm init vitelatest blog-front-admin 中间会弹出询问是否要安装包&#xff0c…

R语言安装devtools包失败过程总结

R语言安装devtools包时,遇到usethis包总是安装失败,现总结如下方法,亲测可有效 一、usethis包及cli包安装问题 首先,Install.packages("usethis")出现如下错误,定位到是这个cli包出现问题 载入需要的程辑包…

Mac和VirtualBox Ubuntu共享文件夹

1、VirtualBox中点击设置->共享文件夹 2、设置共享文件夹路径和名称(重点来了:共享文件夹名称) 3、保存设置后重启虚拟机,执行下面的命令 sudo mkdir /mnt/share sudo mount -t vboxsf share /mnt/share/ 注:shar…

.快速幂.

按位与(Bitwise AND)是一种二进制运算,它逐位对两个数的二进制表示进行运算。对于每一位,只有两个相应的位都为1时,结果位才为1;否则,结果位为0。如:十进制9 & 5转化为二进制&am…

基于lstm的股票Volume预测

LSTM(Long Short-Term Memory)神经网络模型是一种特殊的循环神经网络(RNN),它在处理长期依赖关系方面表现出色,尤其适用于时间序列预测、自然语言处理(NLP)和语音识别等领域。以下是…

酒店管理系统小程序的设计

管理员账户功能包括:系统首页,个人中心,用户管理,酒店管理员管理,房间类型管理,房间信息管理,订单信息管理,系统管理 微信端账号功能包括:系统首页,房间信息…

智慧校园信息化大平台整体解决方案PPT(75页)

1. 教育信息化政策 教育部印发《教育信息化2.0行动计划》,六部门联合发布《关于推进教育新型基础设施建设构建高质量教育支撑体系的指导意见》,中共中央、国务院印发《中国教育现代化2035》。这些政策文件强调了教育的全面发展、面向人人、终身学习、因…

Linux vim文本编辑器

Vim(Vi IMproved)是一个高度可配置的文本编辑器,它是Vi编辑器的增强版本,广泛用于程序开发和系统管理。Vim不仅保留了Vi的所有功能,还增加了许多新特性,使其更加强大和灵活。 Vim操作模式 普通模式&#xf…

vue3.0 项目h5,pc端实现扫描二维码 qrcode-reader-vue3

qrcode-reader-vue3 插件简述 qrcode-reader-vue3插件,允许您在不离开浏览器的情况下检测和解码二维码。 🎥 访问设备摄像头并持续扫描传入帧。QrcodeStream🚮 渲染到一个空白区域,您可以在其中拖放要解码的图像。QrcodeDropZon…