【C++笔记】C++多态

【C++笔记】C++多态

  • 一、多态的概念及实现
    • 1.1、什么是多态
    • 1.2、实现多态的条件
    • 1.3、实现继承与接口继承
    • 1.4、多态中的析构函数
    • 1.5、抽象类
  • 二、多态的实现原理

一、多态的概念及实现

1.1、什么是多态

多态的概念:

在编程语言和类型论中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。
计算机程序运行时,相同的消息可能会送给多个不同的类别之对象,而系统可依据对象所属类别,引发对应类别的方法,而有不同的行为。简单来说,所谓多态意指相同的消息给予不同的对象会引发不同的动作。
多态也可定义为“一种将不同的特殊行为和单个泛化记号相关联的能力”。
多态可分为变量多态与函数多态。变量多态是指:基类型的变量(对于C++是引用或指针)可以被赋值基类型对象,也可以被赋值派生类型的对象。函数多态是指,相同的函数调用界面(函数名与实参表),传送给一个对象变量,可以有不同的行为,这视该对象变量所指向的对象类型而定。因此,变量多态是函数多态的基础。

多态的概念:通俗来说,就是多种形态, 具体点就是去完成某个行为,当不同的对象去完成时会
产生出不同的状态 。
举个例子:比如 买票这个行为 ,当 普通人 买票时,是全价买票; 学生 买票时,是半价买票; 军人
买票时是优先买票。

1.2、实现多态的条件

实现多态的两个条件:

1、被调用的函数必须是虚函数,子类对父类的虚函数进行重写 (重写:三同(函数名/参数/返回值)+虚函数)
2、父类指针或者引用去调用虚函数。

举个例子:
在这里插入图片描述
这时候就实现了多态,即指向子类对象就调用子类对象的函数,如果指向的是父类对象,就调用的是父类对象的函数:
在这里插入图片描述
其实C++这里还有一个特殊情况,就是如果父类的同名函数加上了virtual修饰了,那么子类的同名函数就算不加virtual也是虚函数了,即也构成多态:
在这里插入图片描述
但我个人感觉函数加上的好,因为可能会形成误导。

强调:一定要是父类的指针或引用调用,如果是对象就变成了普通调用了:
在这里插入图片描述
此外虚函数的重写也还要满足三同:函数名、参数、返回值相同,只要有其中一个不满足也会变成普通调用。

但是这里还有非常尴尬的例外:“协变”,含义是虚函数的返回值类型可以不同,但又一个条件:子类和父类的返回值类型也必须是父子关系指针和引用。
在这里插入图片描述
其实“协变”也是C++常常被诟病的一点,因为它的应用场景实在太局限了,我也是感觉它有点儿多余了,我们只需要了解一下即可。

1.3、实现继承与接口继承

普通函数的继承实际上是一种实现继承,也就是继承了函数的逻辑:
例如:
在这里插入图片描述
这里继承的是函数的实现,所以变量_a改变了,输出的结果也就改变了。
而虚函数继承的是函数的接口,也就是父类和子类的接口是一样的,只是实现的逻辑不一样。其目的主要是为了重写,达成多态。
例如:
在这里插入图片描述
因为这里继承的只是接口,而实现逻辑是不同的,所以打印出来的内容也就不同。也就实现了多态。
之所以说是子类继承了父类的接口,是因为如果我们改变子类中的虚函数的默认参数是不起作用的:
在这里插入图片描述
所以这也就解释了为什么子类的虚函数没有加virtual也依然是虚函数,因为其接口就是继承了父类的。

1.4、多态中的析构函数

我们先来看一个现象:
在这里插入图片描述
相信大家都能看出这段代码的问题,这很明显值是一个普通调用。但是它new了一个B对象却只调用了A类的析构函数,这岂不是有内存泄漏的风险?
那该怎么解决这个问题呢?如果要将析构函数也实现成多态的调用的话,那子类和父类的析构函数名不可能相同啊,不是冲突了?

C++正是为了解决这个问题,对构造函数进行了一些处理:
因为多态的原因,编译器在底层会将析构函数的函数名统一处理成destructor()。
所以我们表面上看到的析构函数是是不同名的,实际在底层他们都叫destructor(),所以也就能实现多态了:
在这里插入图片描述

1.5、抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
抽象类还有以下三个注意点:

1、子类继承抽象类后也不能实例化出对象,只有重写纯虚函数,子类才能实例化出对象。
2、父类的纯虚函数强制了派生类必须重写,才能实例化出对象。
3、纯虚函数也可以写实现{ },但没有意义,因为是接口继承,而子类被强制了重写纯虚函数,所以{ }中的实现会被重写;父类没有对象,不能调用父类的实现,所以父类实现纯虚函数也就没有意义了。

其实各种抽象的事物都可以定义成抽象类,比如人、动物、汽车、水果……,也就是它不具体指哪一个事物,只是抽象的代表默写事物的总体特征。

比如说动物:
在这里插入图片描述

二、多态的实现原理

这里有一个类,我们试试来求一下它的大小:
在这里插入图片描述
首先要说一点,不管是普通成员函数还是虚函数都是不储存在类里面的,都是存在代码段的。
可这里的类的大小为什么是8字节呢?不应该是4字节吗?
说明类里面一定还存了别的什么东西,我们可以到监视窗口看看:
在这里插入图片描述
我们会发现除了成员_a之外还多了一个_vfptr的东西,这个其实是一个虚表指针,它的本质是一个数组指针,指向一个函数指针数组,而被指向的这个函数指针数组就是虚表。
由于平台的不同,虚表的位置也有可能不同,有的实在类的最前面有的可能是在类的最后面。
一个含有虚函数的类至少有一个虚表指针。
我们可以到内存中去仔细的看看A类的结构:
在这里插入图片描述
然后我们可以来看看虚表中到底有什么:
在这里插入图片描述
所以我们可以来打印一下虚表中的内容,看看它们是不是函数的地址,如果是的话试试调用它:
在这里插入图片描述
从结果来看确实是函数的地址,因为所有的虚函数的地址都会存进虚表,所以这里会打印四个。

有了上面的铺垫我们就可以来解释多态的真正原理了。
我们先让一个B类继承A类,并重写func函数:
在这里插入图片描述
然后我们再取出A类和B类的虚表对比看看:
在这里插入图片描述
我们发现两个对象中的虚表里,只有被重写的func()函数的地址不同,而没有重写的print1()的地址则相同。所以虚函数的重写也被称为是虚函数的覆盖(其实是虚函数表的覆盖)。

有了以上的铺垫,在我提出以下结论的时候,才会逻辑自掐:
多态的实现机制其实就是,傻傻地通过虚表指针找到虚表,再找到对应的虚函数。

之所以这种“傻傻”的行为能成功,是因为在父类指针或引用指向子类的时候会发生“切片”:
在这里插入图片描述
A类的指针只会指向B对象中A类部分的内容,所以也就只会在A类部分的虚表中查找。就算B类有多张虚表(当B类有多个直接父类时候就会有多张虚表)。父类A的指针通过切片之后也只会指向A类部分。
且因为,虚表的位置在某个类中都是固定的,所以偏移量也都是固定的,所以B类有多少个直接父类,他们父类的指针的寻址操作也都是统一的。

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

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

相关文章

DNS 域名解析系统

文章目录 前言什么是 DNS 域名解析系统为什么需要 DNS 域名解析DNS 是如何发展的hosts 文件维护域名和IP的映射关系DNS 系统(服务器)DNS 镜像系统 前言 前面为大家分享了关于计算机网络中应用层——自定义协议、传输层——UDP、TCP 协议、网络层——IP协…

Java修仙传之Flink篇

大道三千:最近我修Flink 目前个人理解: 处理有界,无界流的工具 FLINK: FLINK定义: Flink特点 Flink分层API 流的定义 有界数据流(批处理): 有界流:数据结束了,程序也…

大数据之LibrA数据库系统告警处理(ALM-12005 OKerberos资源异常)

告警解释 告警模块对Manager中的Kerberos资源的状态进行监控,当Kerberos资源异常时,系统产生此告警。 当Kerberos资源恢复时,且告警处理完成时,告警恢复。 告警属性 告警参数 对系统的影响 Manager中的Kerberos资源异常&#…

ceph高可用、手动安装

操作系统:centos8 三台服务器: 192.168.6.22:mon,mgr,mds,osd 192.168.6.23:mon,mgr,mds,osd 192.168.6.24:mon,mgr,mds,osd 正式环境osd和mon不应该在一个节点,建议osd单独服务器安装。 ceph版本&am…

element表格自定义筛选

文章目录 前言一、简介二、效果展示三、源码总结 前言 提示:这里可以添加本文要记录的大概内容: …待续 提示:以下是本篇文章正文内容,下面案例可供参考 一、简介 修改el-table的筛选…待续 二、效果展示 三、源码 使用方法…

sentinel规则持久化-规则同步nacos-最标准配置

官方参考文档&#xff1a; 动态规则扩展 alibaba/Sentinel Wiki GitHub 需要修改的代码如下&#xff1a; 为了便于后续版本集成nacos&#xff0c;简单讲一下集成思路 1.更改pom 修改sentinel-datasource-nacos的范围 将 <dependency><groupId>com.alibaba.c…

[SUCTF 2019]EasySQL 1

题目环境&#xff1a; 把你的旗子给我&#xff0c;我会告诉你旗子是不是对的。 判断注入类型1回显结果 不是字符型SQL注入 1回显结果 数字型SQL注入 查所有数据库,采用堆叠注入1;show databases;查看所有数据表1;show tables;尝试爆Flag数据表的字段1;show columns from Flag; …

LeetCode | 27. 移除元素

LeetCode | 27. 移除元素 OJ链接 这道题有一个方法是要删除的数据直接后一个数据挪动到前一个数据&#xff0c;这个方法好不好&#xff1f;最坏的情况下时间复杂度是O(N^2) 还有一个方法 定义一个src定义一个dst&#xff0c;原地直接进行赋值&#xff0c;不进行挪动&#xf…

Qt程序执行编译输出内容解释

以这个为例&#xff1a; D:\SoftwareInstall\Qt6\Tools\QtCreator\bin\jom\jom.exe -f Makefile.Debug cd AuthorizeTools\ && ( if not exist Makefile D:\SoftwareInstall\Qt6\5.15.2\msvc2019_64\bin\qmake.exe -o Makefile E:\Coding\project\DigitalCamera\digita…

Vue使用 IndexDB vue操作IndexDB数据库 Vue操作IndexDB数据库

Vue使用 IndexDB vue操作IndexDB数据库 Vue操作IndexDB数据库 Vue使用 IndexDB vue操作IndexDB数据库 Vue操作IndexDB数据库安装 IndexDB类库引入 localForage测试 新增数据、获取数据 Vue使用 IndexDB vue操作IndexDB数据库 Vue操作IndexDB数据库 大部分场景使用 LocalStore都…

Linux | 进程终止与进程等待

目录 前言 一、进程终止 1、进程终止的几种可能 2、exit 与 _exit 二、进程等待 1、为什么要进程等待 2、如何进行进程等待 &#xff08;1&#xff09;wait函数 &#xff08;2&#xff09;waitpid函数 3、再次深刻理解进程等待 前言 我们前面介绍进程时说子进程退出…

pytorch复现4_Resnet

ResNet在《Deep Residual Learning for Image Recognition》论文中提出&#xff0c;是在CVPR 2016发表的一种影响深远的网络模型&#xff0c;由何凯明大神团队提出来&#xff0c;在ImageNet的分类比赛上将网络深度直接提高到了152层&#xff0c;前一年夺冠的VGG只有19层。Image…

uniapp 关于 video 组件的缩放比例问题

在 container 样式的 padding-bottom 设置比例值 9/16 比例值&#xff1a;56.25% 3/4 比例值&#xff1a;75% <view class"container"><video class"video-box" src"xxx.mp4" /> </view> .container {position: relative;wid…

Redis(01)| 数据结构

这里写自定义目录标题 Redis 速度快的原因除了它是内存数据库&#xff0c;使得所有的操作都在内存上进行之外&#xff0c;还有一个重要因素&#xff0c;它实现的数据结构&#xff0c;使得我们对数据进行增删查改操作时&#xff0c;Redis 能高效的处理。 因此&#xff0c;这次我…

作为20年老程序员,我如何使用GPT4来帮我写代码

如果你还在用google寻找解决代码bug的方案&#xff0c;那你真的out了&#xff0c;试试gpt4, save my life. 不是小编危言耸听&#xff0c;最近用gpt4来写代码极大地提高了代码生产力和运行效率&#xff0c;今天特地跟大家分享一下。 https://www.promptspower.comhttps://www.…

测开 (Junit 单元测试框架)

目录 了解 Junit 引入相关依赖 1、Junit注解 Test BeforeEach、BeforeAll AfterEach && AfterAll 2、断言 1、Assertions - assertEquals 方法 2、Assertions - assertNotEquals 方法 3、Assertions - assertTrue && assertFalse方法 4、Assertions…

Microsoft365个人版与家庭版有哪些功能区别?

Microsoft 365个人版与家庭版均能享受完整的Microsoft 365功能与权益&#xff0c;稍有不同的是&#xff0c;Microsoft 365家庭版可供6人使用&#xff0c;而个人版是仅供一人使用。 个人版可以同时登入5台设备&#xff0c;家庭版每人也可以登入5台设备&#xff0c;每个人都可以享…

【Linux】centos安装配置及远程连接工具的使用

前言 CentOS 是什么&#xff1f; CentOS社区企业操作系统&#xff08;Community Enterprise Operating System&#xff09; CentOS 是众多 Linux 发行版中的一种。全称&#xff1a; The Community ENTerprise Operating System 。 她是将 Red Hat Enterprise Linux &#xff…

sitespeedio.io 前端页面监控安装部署接入influxdb 到grafana

1.docker部署influxdb,部署1.8一下&#xff0c;不然语法有变化后面用不了grafana模板 docker run -d -p 8086:8086 --name influxdb -v $PWD/influxdb-data:/var/lib/influxdb influxdb:1.7.11-alpine docker exec -it influxdb_id bash #influx create user admin with pass…

Yakit工具篇:WebFuzzer模块之重放和爆破

简介 Yakit的Web Fuzzer模块支持用户自定义HTTP原文发送请求。为了让用户使用简单&#xff0c;符合直觉&#xff0c;只需要关心数据相关信息&#xff0c;Yakit后端(yaklang)做了很多工作。 首先我们先来学习重放请求的操作&#xff0c;在日常工作中可以使用 Web Fuzzer进行请…