C++进阶——多态

news2024/9/20 18:42:37

什么是多态?通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生不同的状态。

举个栗子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人 买票时是优先买票。接下来,我们来讲讲他其中的内容及用法。

一、多态的定义及其实现

1.多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了 Person。Person对象买票全价,Student对象买票半价。

那么在继承中要构成多态还有两个条件: 1. 必须通过基类的指针或者引用调用虚函数(必须是基类) 2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

2.虚函数与重写

什么叫虚函数?通过之前提到的虚继承我们可以类比,虚函数就是用virtual修饰的成员函数(必须是成员函数)(二者无关联)

那什么是重写呢?我们来看下面这个场景:

子类和父类都写了一个同名的虚函数,按道理来讲它们构成了我们之前讲过的隐藏关系,但不是,这里是重写(或覆盖),隐藏是针对子类和父类的同名变量而言的。重写的结果也是和隐藏一样,把父类的函数屏蔽。(当然也可以理解为隐藏)(重写包含于隐藏关系,但重写函数返回类型,函数名,函数列表均虚相同,更加严格)

这就是一个标准的多态行为(买票情景,p就是全价,s就是半价)。这样我们来看多态的构造条件还是蛮严格的,缺一不可(父类的virtual必须写,子类的可以不写,建议都写)。如果把上面的&去掉就不是多态。

多态调用,看指向对象的类型,调用指向对象的函数;普通调用(非多态),看调用者的类型,调用调用者的函数。也就是说,如果上面不构成重写关系,那么即使我把s传给了函数作为参数,其最后也会调用person的函数(全价)(因为调用者的类型是person)。

3.虚函数的例外

通过以上对多态的介绍我们暂时觉得这个虚函数与多态在实际应用中有非常大的帮助,但这个虚函数重写也有大坑(例外):

(1)协变(基类与派生类虚函数返回值类型不同) :派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。(了解即可,实际用处并不大)

这里的返回值不同不是简单的将前面的一个void改成int或char即可,而是改成父类(子类)的指针或引用

这样虽然他们返回值类型不同但仍构成多态。(不是一定要是A.B类型,只要是父类或者子类就行)

(2)析构函数的重写(基类与派生类析构函数的名字不同)(很重要)

我们先来看下面这个场景:

现在如果我们创建一个Student变量st,根据之前继承的内容它会先调用子类的析构,然后自动的调用父类析构,也就是说运行结果是~Person()\n ~Student(),但我们现在如果delete p1,猫腻就出来了,结果发现它只调用了父类的析构,此时如果我们子类开辟空间的情况下就会造成内存泄露的问题,那到底为什么没有调用子类的析构呢?

此时我们没有加virtual的情况下是不构成多态的,所以它调用析构函数的类型取决于你调用者的类型(因为p1的类型是person,那最后会调用父类的析构而不是子类,这是产生问题的原因,为了解决这个问题我们实现多态来解决)我们在之前提到过,delete函数的运行分为析构+operator delete(),但是实际上,析构函数的名字不是~类名,而是destructor(),也就是说,此时子类和父类的析构函数同名,构成了隐藏。此时如果我们在父类前加virtual就构成了重写(同名的条件在上面已解释),此时调用析构函数的类型就是看指向对象的类型了。

常考问题:析构函数是否建议析构成虚函数? 一定建议!原因如上

4.final与override关键字

override是专门帮你检查你的虚函数是否写好,一般写在派生类的函数之后即可

virtual void Drive() override {cout << "已写好" << endl;}

final关键字的位置与override相同,它的作用是告诉别人这个虚函数不想再被重写,重写就会报错。

除此之外,final还可以用来修饰类(写在类后),表明这个类无法被继承。

二、抽象类

纯虚函数:在虚函数后面加一个=0的函数(好草率)

包含纯虚函数的类叫抽象类,抽象类的特点是不能实例化出对象。那我们实践中到底要他干啥呢?

其实在实践中如果一个类型在现实没有实体对象,不想实例化出对象,就可以设计成抽象类。比如设计一个car类,你实例化不出来,因为你也不知道是什么牌子的车,所以就需要用某牌车的类去继承,但此时仍无法实例化(因为继承了car类就把纯虚函数继承下来了)为了解决这个问题我们就可以在派生类重写虚函数

补充一个知识点,我们在计算一个类的大小时,除了考虑结构体内存对齐的情况,还要多一个指针的大小(如果类中有虚函数的话),这个指针是指向虚函数表的。我们在下面的内容会提到。

三、多态的原理

1.虚函数表

我们先来看下面这个问题,sizeof(Base)的大小是多少?

正常来看应该是只有一个int的大小(4字节),通过观察测试我们发现b对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中(指针数组),虚函数表也简称虚表。这个指针指向了虚函数表的地址而不是这个虚函数的地址,这也是虚函数和普通成员函数的区别之一。那问题来了,一个类中不可能所有函数都是虚函数,那么在调用函数的时候,虚函数可以通过找虚函数表来找到函数的地址,那普通函数呢?

我们下面讲讲是如何找到虚函数表中的函数以及如何去找普通函数的地址。

2.多态的原理

我们先给一段代码

class Base
{
public:
       virtual void func1()
       {
          cout<<"Base::func1()"<<endl;
       }
       virtual void func2()
       {
          cout<<"Base::func2()"<<endl;
       }
       void func3()
       {
          cout<<"Base::func3()"<<endl;
       }
private:
       int _b=1;
};
class Derive:public Base
{
public:
       virtual void func1()
       {
          cout<<"Derive::func1()"<<endl;
       }
private:
       int _d=2;
}
void func(Base*p)
{
  p->func1();
  p->func3();
}
int main()
{
 Base b;
 Derive d;
 func(&b);
 func(&d);
return 0;
}
  

经过测试我们发现:

对象d中也有一个和b不一样的虚表指针,即基类b对象和派生类d对象的虚表是不一样的,我们发现func1完成了重写,所以d的虚表冲存放的是重写的Derive::func1,故重写也叫做覆盖,重写是语法的叫法,覆盖是原理层的叫法。

另外func2继承下来后是虚函数,放进了虚表,func3也继承下来,但不会放进虚表。

虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。

总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生 类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己 新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是 他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那虚表存在哪里了呢?经过测试我们发现在常量区代码段。

我们用一张图来表示上图代码的虚函数之间的关系:

那么多态的原理就是,我指向哪个对象,那么调用函数时就会进入哪个虚表进而调用该虚表内对应的函数(指向derive调用func1,那就会调用derive::func1而不是base::func1,但要是调用func2的话就是base::func2,因为没有构成重写)至于func3,普通函数的调用就不会有区别了,直接调用就好了。

3.动态绑定与静态绑定

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

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

像上面的func3就是静态绑定,不满足多态,编译时即绑定,在符号表中找函数地址,与指向的对象没有关系。func1,func2就是动态绑定,在运行时,根据所指向的对象在对应的虚表调用函数。

————

本文完,查缺补漏的小伙伴可以看看下面的问题哦,求点赞!

1. 什么是多态? 2. 什么是重载、重写(覆盖)、重定义(隐藏)? 3. 多态的实现原理?4. 什么是抽象类?抽象类的作用?

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

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

相关文章

数据库进阶:2.索引

1.数据库中的索引 1.1索引的概念 介绍&#xff1a;索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构&#xff08;有序&#xff09;。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#x…

AI绘图怎么变现?想做点副业的小白必看!

在科技飞速发展的今天&#xff0c;AI绘图作为一种新兴技术&#xff0c;不仅改变了艺术创作的方式&#xff0c;也为创作者提供了多种变现途径。本文将详细探讨几种常见的AI绘图变现方式&#xff0c;帮助创作者更好地利用这一技术实现经济收益。 更多实操教程和AI绘画工具&#x…

第四届“长城杯”网络安全大赛 暨京津冀网络安全技能竞赛(初赛) 全方向 题解WriteUp

战队名称&#xff1a;TeamGipsy 战队排名&#xff1a;18 SQLUP 题目描述&#xff1a;a website developed by a novice developer. 开题&#xff0c;是个登录界面。 账号admin&#xff0c;随便什么密码都能登录 点击头像可以进行文件上传 先简单上传个木马试试 测一下&…

保姆级别带你了解个股场外期权的交易买卖流程

今天期权懂带你了解保姆级别带你了解个股场外期权的交易买卖流程。个股场外期权的交易方式主要包括电话或电子邮件协商、中介平台撮合、电子交易系统、合约签订和定制化服务。 场外个股期权交易买卖流程 第一&#xff0c;个人想要参与交易个股场外期权需要通过合法的金融机构…

最新篇 接口测试工具Postman 企业常规面试题出炉~(附答案)

面试题目录 说下你对Postman的了解&#xff1f; Postman你在工作中使用流程是什么样的&#xff1f; Postman 你使用了哪些功能&#xff1f; Postman 里面如何管理测试环境&#xff1f; Postman如何设置关联&#xff1f;postman参数化有哪几种方式&#xff1f; 在postman中&…

Qt/C++ Mysql数据库用户登录分配软件(源码分享)

功能简介&#xff1a; 这是一个基于 Qt/C 实现的简单 MySQL 用户权限管理系统&#xff0c;能够通过控制台交互输入进行数据库连接、用户创建、权限分配以及用户查询的自动化操作。用户可以通过该软件登录到 MySQL 服务器&#xff0c;选择数据库&#xff0c;并根据需求创建新用…

拍摄录制剪辑太麻烦?一键生成3D文旅视频的AI神器来了!

AI正在改变世界。 从Chat GPT的横空出世到大语言模型的爆发&#xff0c;AI开始融入人们的生活&#xff0c;人类与AI的对话无处不在&#xff1b; Midjournery等文生图工具&#xff0c;让“神笔马良”走进现实&#xff0c;每个人都能用AI创作出生动的图片&#xff1b; Sora更是…

引入sqlite作为云盒和硬盒子驱动的日志记录功能

1.下载源码 wget https://www.sqlite.org/2024/sqlite-autoconf-3460100.tar.gz 2.解压安装与引用 注意&#xff1a;不能将源码下载到Windows和虚拟机的共享文件夹下&#xff0c;自动构建的时候会导致一系列的问题 ./configure CCgcc --prefix/usr/lib

VMware虚拟机上安装openfileresa开源的NAS存储管理解决方案和ISCSI共享磁盘存储

目录 文章目录 目录说明下载安装镜像创建虚拟机安装 openfileresa 操作系统重新启动系统使用 root 用户登录使用内置的用户 openfiler 登录 创建 ISCSI 的共享存储1、添加存储硬盘2、查看添加的磁盘3、对硬盘分区4、创建卷组(Volume Group)5、创建数据卷(Volume)6、启动 ISCSI …

解决 蚁剑AntSword 连接后 ,命令回显 有问题 (ret=127) 的方法

在渗透测试过程中&#xff0c;我们在 连接上蚁剑 后&#xff0c;想要 进行 命令执行查看一些信息&#xff0c; 发现无论输入任何指令&#xff0c;返回的信息 只有 ret127&#xff0c;要解决这种问题&#xff0c;为大家提供一个解决方案。 蚁剑 软件&#xff0c;命令回显 只有…

深入了解以太坊

1. 以太坊编程语言和操作码 以太坊中智能合约的代码以高级语言编写&#xff0c;如 Serpent、LLL、Solidity 或 Viper,并可转换为 EVM 可以理解的字节码&#xff0c;以便执行。 Solidity 是为以太坊开发的高级语言之一&#xff0c;它具有类似 JavaScript 的语法&#xff0c;可以…

【踩坑】Vue3项目正常跑动后页面空白问题

近期踩了个坑&#xff0c;Vue3搭建的项目能够正常跑动&#xff0c;但是页面却是空白的&#xff0c;控制台也不报错&#xff0c;只留下一行警告&#xff1a; 发现是 router 入口文件&#xff08;一般是在 router 文件夹下的 index 里面&#xff09;的写法和 vite 版本不匹配的问…

AI算力池化技术在银行业的最佳实践荣获“科学普及达人奖”

纵观半个多世纪以来的金融行业发展历史&#xff0c;每一次技术升级与商业模式变革依赖科技赋能与理念创新的有力支撑&#xff0c;以人工智能为代表的新技术给金融机构了带来巨大效益。 近日&#xff0c;由中国人民银行科技司指导、北京金融科技产业联盟支持、《金融电子化》杂…

AlmaLinux 9 上配置静态 IP 地址

在 Rocky Linux 9 中&#xff0c;密钥文件的新默认存储位置在 /etc/NetworkManager/system-connections 中 cd /etc/NetworkManager/system-connections默认dhcp配置 ~ …

数集相等定义推翻2300年直线公理

黄小宁 2300年前的古人认为凡懂什么是直线的人都知过两异点只能画一条直线从而有初中的2300年直线公理&#xff0c;继而有平行公理和平面公理等。然而数集相等概念凸显直线公理使数学一直将无穷多各异直线误为同一线。 变量x所取各数也均由x代表。设集A&#xff5b;x&#xf…

CENet及多模态情感计算实战(论文复现)

CENet及多模态情感计算实战&#xff08;论文复现&#xff09; 本文所涉及所有资源均在传知代码平台可获取 文章目录 CENet及多模态情感计算实战&#xff08;论文复现&#xff09;概述研究背景主要贡献论文思路主要内容和网络架构数据集介绍性能对比复现过程&#xff08;重要&am…

数据集成在搭建“智慧校园”中的使用

智慧校园是一种新型校园数字化建设方式&#xff0c;目前被全国高校使用。 智慧校园利用现代信息技术&#xff0c;如物联网、云计算、大数据等&#xff0c;搭建集成硬件设施和数据平台&#xff0c;实现校园管理高度信息化&#xff0c;提高校园管理的效率。 如何搭建“智慧校园…

SPI驱动学习五(如何编写SPI设备驱动程序)

目录 一、SPI驱动程序框架二、怎么编写SPI设备驱动程序1. 编写设备树2. 注册spi_driver3. 怎么发起SPI传输3.1 接口函数3.2 函数解析 三、示例1&#xff1a;编写SPI_DAC模块驱动程序1. 要做什么事情2. 硬件2.1 原理图2.2 连接 3. 编写设备树4. 编写驱动程序5. 编写app层操作程序…

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息&#xff0c;即可快速实现设备配网&#xff0c;是一种兼顾高效性、可靠性和安全性的配网方式。2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递&#xff0c;利用特有的NAN协议实现。利用手机和智能设备…

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码&#xff1a; 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口&#xff0c;https的端口号为443 url为统一资源定位符。CSDN…