learn C++ NO.18——多态

什么是多态?

多态是C++面向对象编程中的一个核心概念,它允许程序在执行过程中,根据对象的实际类型来调用适当的函数。多态性主要通过继承和虚函数来实现,这使得代码更加灵活和可扩展。多态的条件如下:1、调用函数是重写的虚函数。2、基类指针或者引用。

虚函数的概念

被virtual修饰的成员函数就是虚函数。
在这里插入图片描述

虚函数的重写

虚函数重写是指派生类重新定义(或称为覆盖)了基类中的虚函数。当派生类中存在一个与基类中虚函数具有三同,即相同名称、参数列表和 返回值(在C++11及以后版本中,还包括const属性和volatile属性)的函数时,该函数就重写了基类中的虚函数。

下面通过一个代码样例来简单看一看
在这里插入图片描述
在这里插入图片描述

在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样写。

在这里插入图片描述

协变是虚函数重写的一个特例。协变是指派生类重写虚函数时,与基类虚函数返回值类型不同。但是基类虚函数返回值类型是基类的指针或引用。派生类虚函数的返回值类型是派生类的指针或引用。

在这里插入图片描述
析构函数的重写也是一个特殊的例子。如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

为什么需要重写虚函数呢?通过下面场景便可以明白。

在这里插入图片描述
可以看到在释放Student的切片时,编译器没有去调用派生类的析构函数释放派生类对象部分,这导致了内存泄露问题。因为delete底层是会去让p对象去调用它的析构函数,然后调用operator delete来释放空间。在这个场景中用户期望调用析构函数的行为是一个多态调用。所以,我们需要重写析构函数以达到正常释放派生类对象的目的。

在这里插入图片描述
C++11标准提供了两个关键字,override 和 final。可以用于帮助用户检测重写情况。

final:修饰虚函数,表示该虚函数不能再被重写。
在这里插入图片描述

final修饰的类不能被继承。
在这里插入图片描述

想让一个类不能被继承不仅仅可以用final修饰这个类,还可以通过私有构造函数来实现。不过私有构造函数后需要对外提供一个静态函数以实例化类对象。私有化析构函数也可以做到让类不能被继承。但是需要提供一个对外的清理资源的接口以供用户释放资源。下面以私有化构造函数为例。
在这里插入图片描述

override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
在这里插入图片描述

多态的原理

虚函数表

在C++中,虚函数表是一种用于支持多态性的机制。当一个类中含有虚函数时,编译器会为该类生成一个虚函数表,这个表存储了该类的虚函数在代码段中的地址。在运行时,通过虚函数表,程序能够正确地调用指向派生类对象的基类指针或引用所调用的虚函数。下面通过样例简单看一看。

在这里插入图片描述
sizeof(Base)的值是多少呢?答案是8,因为Func1是一个虚函数,编译器会生成一个虚函数表来保存虚函数的地址。_b为一个字节,虚函数表占四个字节。根据内存对齐的原则,所以sizeof(Base)为8字节。

多态的原理

先通过调试窗口简单看一看基类和派生类究竟干了些啥。
在这里插入图片描述
在这里插入图片描述
通过调试窗口可以发现派生类对象Johnson也有一份虚函数表。并且虚函数表的内容与基类的内容不一样。这是因为派生类的虚函数完成了重写,将原本基类的虚函数进行了覆盖。所以虚函数的重写也称为覆盖。重写通常是语言层面的叫法,覆盖是底层实现层面的叫法。

虚函数表本质是一个存虚函数指针的指针数组,在VS平台下,一般情况这个数组最后面放了一个nullptr。g++平台不会这样处理。
在这里插入图片描述

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

vftptr也许会因为成员变量被定义在栈区或事堆区上,而存储在栈区或堆区上,但是vftptr所指向的虚函数表是存储在代码段上的。在VS平台下,虚函数存储和普通函数一样存储在代码段中。虚函数表也是存储在代码段中。需要注意的是虚函数表存储的不是虚函数,而是虚函数指针,即虚函数在代码段(常量区)中的地址。g++平台下虚函数表和虚函数都是存放代码段(常量区)中。
在这里插入图片描述
在这里插入图片描述

下图的Func函数如何做到多态调用呢?如果传递的是基类对象,它会直接在运行时通过基类的虚函数表找到对应的虚函数进行调用。派生类也是同理。而普通调用则是在编译时,编译器确定地址调用。
在这里插入图片描述
下面通过反汇编简单看一看多态调用与普通调用的区别。

在这里插入图片描述
在这里插入图片描述
通过上图可以看到,无论传递的是基类对象还是派生类对象,多态调用的指令都是一样的。基类多态调用,运行时编译器会去基类的虚函数表中找到虚函数地址,然后调用。而派生类传递的是基类的切片(切割),所以派生类中基类的部分,虚函数表会进行重写。编译器会去重写后的虚函数表调用派生类的虚函数。

动态绑定与静态绑定

静态绑定指的是程序在编译阶段就确定了程序的动作。这也称之为前期绑定。一般函数重载就是一种静态的多态。
在这里插入图片描述
动态绑定又称为后期绑定,指的是程序在运行后才能确定程序的动作。虚函数的重写就是一种动态绑定的行为。

关于继承多态的试题

下面做一个题来提升一下对于多态的理解。
在这里插入图片描述

看代码依次来进行分析,首先,主函数中new了一个B对象。然后通过这个对象调用test函数。test()函数一定是由this指针进行调用,所以可以理解成A* ->func()。但是func是一个重写的虚函数。所以应该调用的是B ::func()。这里最具迷惑性的坑来了,就是基类和派生类的func函数都给了默认参数。而这个默认参数是取基类的默认参数。因为虚函数的重写的是实现。所以这里的返回值、函数名、参数列表部分都是用的基类。最终输出的结果是 B->1

为什么基类对象不能构成多态呢?

若出现派生类对象赋值给基类对象切片时,并不会拷贝虚函数表。这是因为若基类对象构成多态势必拷贝虚函数表,那基类对象调用虚函数时,就会调用派生类的虚函数。那就乱套了。

基类对象不能构成多态是因为方法调用在编译时就已经确定,且不会通过虚函数表进行动态查找,所以无法调用派生类中的覆盖方法。要实现多态,必须使用基类指针或引用指向派生类对象。

在这里插入图片描述

打印虚函数表的程序

在这里插入图片描述
简单说明一下,上图的情况,基类Person有一个虚函数BuyTicket,两个成员函数Func1和Func2。派生类Student重写了BuyTicket()和一个虚函数Func3。通过监视窗口可以看到,Student对象的虚函数表没有Func3。下面,写一个程序来验证一下我们的猜想。
在这里插入图片描述
这里利用了VS平台的虚函数表的结尾时nullptr的特性对虚函数表进行了打印遍历。通过程序可以发现,Func3就是紧跟在Student类对象的虚函数表后面的。

关于多继承多态的问题

这里以下面的多继承为例。
在这里插入图片描述
这里有两个基类分别是Base1和Base2,分别有一个整型成员变量和两个虚函数func1和func2,有一个派生类Derive继承了Base1和Base2,也有一个整型成员变量,和一个重写的func1和虚函数func3。下面简单来看一下Derive定义的对象模型。

在这里插入图片描述
所以Derive对象的大小是20字节。下面通过一段代码,看看VS平台对于多继承的派生类的多态调用是如何进行处理的。

在这里插入图片描述
乍一看好像没有什么特别的,但是实际在底层汇编处理时,别有洞天。下面通过监视窗口简单瞅一瞅。

在这里插入图片描述
通过监视窗口可以看到,Base1的虚函数表中func1的地址和Base2中的func1的地址不一样。那为什么上面程序的结果是Base1对象的切片和Base2对象的切片调用的是同一个func1呢?下面通过反汇编一探究竟。
在这里插入图片描述
通过反汇编可以清晰的看到,VS平台编译器对Base2的虚函数表做了特殊处理,让派生类继承Base2的成员部分的虚函数表存的是对于this偏移量的处理的地址。本质上也是和Base1和d对象同一个func1的地址。至于为什么要这样处理?我推测根对象模型有关。由于先继承的基类会在后继承的基类的上面。而VS平台下,这里的Base1的虚函数表刚好就是在d对象的前四个字节。而Base2虚标则存储在d对象中的第8-11个字节的位置上。编译器会先去Base2虚函数表地址中存储的this指针中 - 8的位置找func1。而Base1和d的虚函数表恰好处于首地址。所以不需要想Base2那样处理。

抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

接口继承和实现继承

普通的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。**虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。**所以如果不实现多态,不要把函数定义成虚函数。这样会浪费资源。

inline函数可以是虚函数吗?

答案是可以,但是,在VS平台下,虚函数用inline修饰,这时候编译器会忽略inline属性,那这个函数就不再是内联函数。因为虚函数要存放在虚函数表中。
在这里插入图片描述

静态成员函数可以是虚函数吗?

答案是不行。有以下几种原因。
从对象与虚函数表层面看,静态成员没有this指针。它不与类对象关联,只与类关联。因此它不能被存放在虚函数表中。
从多态性和动态绑定层面上看,静态成员函数,在编译时即确定函数地址。这与虚函数在执行时确认函数地址来说是相违背的。因此静态成员函数与动态绑定即只相违背。

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

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

相关文章

[翟旭发射器]python-推导式-列表list表达式练习

# 简单的列表生成 numbers00[x for x in range(1,11)] print(numbers00) # 带条件的列表生成 numbers01[x for x in range(1,11) if x%20] print(numbers01) # 带表达式的列表生成 numbers10[x**2 for x in range(1,11)] print(numbers10) # 嵌套循环的列表生成 coordinates[(x…

基于SpringBoot+Vue+MySQL的美食点餐管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 在数字化快速发展的今天,餐饮行业也迎来了转型升级的重要机遇。传统餐饮管理方式面临效率低下、顾客体验不佳等问题。为此,开发一款基于SpringBootVueMySQL架构的美食点餐管理系统显得尤为重要。该系统旨…

在MySQL中,要查询所有用户及其权限,您可以使用以下命令:

文章目录 1、查询所有用户1.1、登录数据库1.2、select user,host from mysql.user; 2、查看用户的权限 1、查询所有用户 1.1、登录数据库 [rootlocalhost ~]# docker exec -it spzx-mysql /bin/bash rootab66508d9441:/# mysql -uroot -p123456 mysql: [Warning] Using a pas…

网络层协议——IP

目录 IP层 IP报文格式 IP的理解 运营商 分片与组装 IP层 传输层的TCP或者UDP协议能直接将数据发送到网络中吗?显然不能,封装完的TCP报文还是需要向下交付,经过协议栈,从链路层发送到物理层也就是网路中。 那么tcp做了什么工…

HTML5好看的水果蔬菜在线商城网站源码系列模板2

文章目录 1.设计来源1.1 主界面1.2 商品列表界面1.3 商品详情界面1.4 其他界面效果 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/142059220 HTML5好看的水果蔬菜在线商城…

全面详尽的 PHP 环境搭建教程

目录 目录 PHP 环境搭建概述 在 Windows 上搭建 PHP 环境 使用集成环境 XAMPP 安装步骤 配置和测试 常用配置 手动安装 Apache、PHP 和 MySQL 安装 Apache 安装 PHP 安装 MySQL 配置 PHP 连接 MySQL 在 Linux 上搭建 PHP 环境 使用 LAMP 方案 安装 Apache 安装 …

网络安全科普系统小程序的设计

管理员账户功能包括:系统首页,个人中心,用户管理,知识分类管理,科普知识管理,案例分析管理,建议反馈,试题内容管理,交流论坛,系统管理 微信端账号功能包括&a…

基于DAMODEL——Faster-RCNN 训练与测试指南

Faster-RCNN 训练与测试指南 前言 今天我们要来实现一个经典的目标检测模型:Faster-Rcnn。我们使用DAMODEL云平台来实现,这是个很强大的云端平台,功能众多,你可以投你所好去进行你想做的事情。 1. 环境与工具准备 1.1 远程连接…

docker搭建个人网盘,支持多种格式,还能画图,一键部署

1&#xff09;效果 2&#xff09;步骤 2.1&#xff09;docker安装 docker脚本 bash <(curl -sSL https://cdn.jsdelivr.net/gh/SuperManito/LinuxMirrorsmain/DockerInstallation.sh)docker-compose脚本 curl -L "https://github.com/docker/compose/releases/late…

①无需编程 独立通道 Modbus主站EtherNet/IP转ModbusRTU/ASCII工业EIP网关串口服务器

Modbus主站EtherNet/IP转ModbusRTU/ASCII工业EIP网关串口服务器https://item.taobao.com/item.htm?ftt&id743840591638 EtherNet/IP 串口网关 EtherNet/IP 转 RS485 型号 2路总线EIP网关 MS-A1-2021 4路总线EIP网关 MS-A1-2041 4路总线EIP网关&#xff08;双网口&am…

轻量型js聊天框架Botui(附示例源码)

最近在捣鼓typecho博客&#xff0c;无意中发现一个好玩的小插件botui&#xff0c;可以实现简单的自定义聊天对话&#xff0c;因此通过"抽丝剥茧“提取出来一个简单的示例代码&#xff0c;就是一些js&#xff0c;css之类的。非常适合移植作为博客插件。 1.Botui介绍 官方文…

记录linux环境下搭建本地MQTT服务器实现mqtt的ssl加密通讯

1、ubuntu安装mosquitto sudo apt-get update//安装服务端 sudo apt-get install mosquitto//安装客户端 sudo apt-get install mosquitto-clients 2、安装openssl 3、mqtts/tls加密传输 mosquitto原生支持了TLS加密&#xff0c;TLS&#xff08;传输层安全&#xff09;是SSL&…

怎么录制游戏视频?精选5款游戏录屏软件

对于热爱游戏的你来说&#xff0c;记录游戏中的精彩瞬间并分享给朋友或粉丝&#xff0c;无疑是一种享受。然而&#xff0c;在众多录屏软件中&#xff0c;如何选择最适合你的那一款&#xff1f;今天&#xff0c;我们就为大家精选了五款游戏录屏软件&#xff0c;需要的朋友快来选…

编译原理3——词法分析

3.1词法分析器的作用 词法分析是编译的第一阶段。词法分析器的主要任务是读入源程序的输入字符、将它们组成词素&#xff0c;生成并输出一个词法单元序列&#xff0c;每个词法单元对应于一个词素。 但在这个过程中&#xff0c;词法分析器还要和语法分析器进行交互。交互&…

Redis: 特点,优势,与其他产品的区别以及高并发原理

入门Redis概述 1 &#xff09;选择Redis是因为其高性能 因为 Redis 它数据存储的机制是存在内存中的&#xff0c;减少了传统关系数据库的磁盘IO它是单线程的保证了原子性&#xff0c;它还提供了事务&#xff0c;锁等相关的机制 2 &#xff09;Redis 环境安装配置 linux 或 d…

学习制作第一个LC带通滤波器的初步认识与总结

作为业余爱好&#xff0c;之前没有接触过射频方面的知识。 收音机&#xff0c;fm调频话筒等等想必是很多人都想制作的一个入门制作。但是这个里面的振荡&#xff0c;谐振&#xff0c;滤波&#xff0c;虽然在电子报上面频频看见&#xff0c;对于普通爱好者&#xff0c;如果没有…

在视频上绘制区域:使用Vue和JavaScript实现交互式画布

在数字时代&#xff0c;交互式媒体内容的创建和消费变得越来越普遍。特别是视频内容&#xff0c;它不仅提供了视觉信息&#xff0c;还允许用户与之互动&#xff0c;从而增强了用户体验。本文将介绍如何使用Vue.js框架和JavaScript创建一个交互式组件&#xff0c;该组件允许用户…

【Docker】Docker快速入门

Docker学习笔记 一、Docker概述 为什么会出现Docker? 安卓开发流程&#xff1a;apk(java开发的)发布到应用商店&#xff0c;用户安装apk即可使用。 后端开发流程&#xff1a; jar(java开发的)带上环境发布到Docker仓库&#xff0c;用户从Docker仓库拉取镜像并部署。 总结…

Android 如何实现搜索功能:本地搜索?数据模型如何设计?数据如何展示和保存?

目录 效果图为什么需要搜索功能如何设计搜索本地的功能&#xff0c;如何维护呢&#xff1f;总结 一、效果图 二、为什么需要搜索功能 找一个选项&#xff0c;需要花非常多的时间&#xff0c;并且每次都需要指导客户在哪里&#xff0c;现在只要让他们搜索一下就可以。这也是模…

低代码平台后端搭建-阶段完结

前言 最近又要开始为跳槽做准备了&#xff0c;发现还是写博客学的效率高点&#xff0c;在总结其他技术栈之前准备先把这个专题小完结一波。在这一篇中我又试着添加了一些实际项目中可能会用到的功能点&#xff0c;用来验证这个平台的扩展性&#xff0c;以及总结一些学过的知识。…