C++·多态

news2024/11/15 13:26:17

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.coloradmin.cn/o/1925358.html

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

相关文章

Qt MV架构-视图类

一、基本概念 在MV架构中,视图包含了模型中的数据项,并将它们呈现给用户。数据项的表示方法,可能和数据项在存储时用的数据结构完全不同。 这种内容与表现分离之所以能够实现,是因为使用了 QAbstractItemModel提供的一个标准模…

Go语言---TCP服务端以及客服端的实现

TCP的C/S架构 TCP服务器的实现 监听的底层实现 func Listen(network, address string) (Listener, error) {var lc ListenConfigreturn lc.Listen(context.Background(), network, address) }type Listener interface {// Accept waits for and returns the next connection …

Jenkins中Node节点与构建任务

目录 节点在 Jenkins 中的主要作用 1. 分布式构建 分布式处理 负载均衡 2. 提供不同的运行环境 多平台支持 特殊环境需求 3. 提高资源利用率 动态资源管理 云端集成 4. 提供隔离和安全性 任务隔离 权限控制 5. 提高可扩展性 横向扩展 高可用性 Jenkins 主服务…

【深度学习入门篇 ⑤ 】PyTorch网络模型创建

【🍊易编橙:一个帮助编程小伙伴少走弯路的终身成长社群🍊】 大家好,我是小森( ﹡ˆoˆ﹡ ) ! 易编橙终身成长社群创始团队嘉宾,橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官…

注册中心与配置中心,是分?是合?

一零年代初,我还自己动手写过socket连接来做服务注册发现,后来有了zookeeper就方便多了,那时候zookeeper也直接做配置中心使用。后来出现了一些专门的服务注册发现组件如Eureka\etcd\consul,专门的配置中心比如spring cloud confi…

2024年上半年信息系统项目管理师——综合知识真题题目及答案(第1批次)(2)

2024年上半年信息系统项目管理师 ——综合知识真题题目及答案(第1批次)(2) 第21题:在一个大型信息系统项目中,项目经理发现尽管已经建立了沟通机制,但团队间的沟通依然不畅,项目风险…

【JavaScript】解决 JavaScript 语言报错:Uncaught SyntaxError: Unexpected token

文章目录 一、背景介绍常见场景 二、报错信息解析三、常见原因分析1. 缺少必要的语法元素2. 使用了不正确的字符或符号3. JSON 格式错误4. 字符串未正确闭合 四、解决方案与预防措施1. 检查语法元素2. 正确使用符号和字符3. 修正 JSON 格式4. 字符串闭合 五、示例代码和实践建议…

C++基础语法:STL之迭代器

前言 "打牢基础,万事不愁" .C的基础语法的学习 引入 C基础:STL概述-CSDN博客 上一篇梳理了一些同STL有关的概念.同时对理解迭代器需要的类包含,内部类,链表等内容做了分析,这篇从<C Prime Plus> 6th Edition(以下称"本书")的P684,大标题16.4泛型编…

Sortable.js板块拖拽示例

图例 代码在图片后面 点赞❤️关注&#x1f64f;收藏⭐️ 页面加载后显示 拖拽效果 源代码 由于js库使用外链&#xff0c;所以会加载一会儿 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name&qu…

Pycharm 导入 conda 环境

使用时经常在此处卡壳&#xff0c;在此做个记录。 这个位置选择 conda 安装路径下的 python.exe 文件即可

书生浦语大模型实战营---Linux 基础知识

创建开发机 开建开发机首先填写开发机名称&#xff0c;选择镜像和配置资源&#xff0c;最后选择立即创建 ssh免密钥登陆 1、本地执行ssh-keygen -t rsa&#xff0c;然后一路回车就可以了 2、执行cat ~/.ssh/id_rsa.pub 3、回到开发机平台&#xff0c;在首页点击配置SSH Key…

手机数据恢复篇:如何从 Android 设备内恢复数据

如何从 Android 内部存储恢复数据&#xff1f; 要从 Android 内部存储恢复已删除的文件&#xff0c;您需要一个 Android 内部存储恢复应用或程序。请继续阅读以获取可靠的 Android 数据恢复软件&#xff0c;并让它帮助您从 Android 手机的内部存储恢复数据。 是否有可能恢复 An…

工业大数据是什么?应用工业大数据时面临哪些挑战?

在当今快速发展的工业领域&#xff0c;大数据已成为推动企业转型升级的核心动力。工业大数据&#xff0c;以其独特的价值和潜力&#xff0c;正逐渐改变着传统的生产、管理和决策模式。然而&#xff0c;伴随着大数据的快速发展&#xff0c;一系列挑战也随之浮现。本文将深入探讨…

刷题之单词规律同构字符串(leetcode)

同构字符串 单词规律 两个都是映射关系&#xff0c;用两张哈希表记录互相映射就可以了 同构字符串&#xff1a; class Solution { public:bool isIsomorphic(string s, string t) {//用两张哈希表做映射if(s.size()!t.size()){return false;}unordered_map<char,char&…

零基础学JS之APIS

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 非常期待和您一起在这个小…

算法day04 位运算 插入排序 二分法 对数器

位运算: 1&#xff09;有一个数组只包含这样的数&#xff0c;有几个数出现偶数次&#xff0c;有1个数出现奇数次&#xff0c;要求时间复杂度不超过o(n),怎么求出现奇数次的数。 使用 ^ 异或运算整个数组&#xff0c;偶数次运算结果为0&#xff0c;只留下最后一个奇数次的数。 …

【零基础】学JS之APIS第四天

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 非常期待和您一起在这个小…

网络层重点协议—IP协议

在复杂的网络环境中确定一个合适的路径 协议头格式如下&#xff1a; 4位版本号(version) 指定协议的版本&#xff08;IPV4-4,IPV6-6&#xff09; 4位首部长度(header length) IP头部的长度是多少个32bit&#xff0c;也就是length*4的字节数。4bit表示最大的数字是15&#x…

记一次TIDB开启TLS失败导致PD扩容失败案例

作者&#xff1a; Dora 原文来源&#xff1a; https://tidb.net/blog/8ee8f295 问题背景 集群之前由于TIUP目录被删除导致TLLS证书丢失&#xff0c;后续需要重新开启TLS 在测试环境测试TLS开启步骤&#xff0c;导致后续两台PD扩容失败&#xff0c;步骤如下&#xff1a; …

Linux Win 10 Windows上安装Ollama部署大模型qwen2 7b/15配置启动 LangChain-ChatChat 0.2.10进行对话

Win 10 Window安装Ollama部署qwen2 7b LangChain-ChatChat 环境说明 Win 10 Python 3.11.9 LangChain-ChatChat 0.20 Ollama 0.2.10 Qwen2 1.5b/7b Windows 安装Ollama 下载并安装Windows版Ollama https://ollama.com/download#/ 下载大模型qwen2:1.5b或者qwen2:7b 在命令…