【C++进阶】多态

news2024/9/24 11:25:47

文章目录

  • 多态的概念
  • 多态的定义及实现
  • 抽象类
  • 多态的原理
  • 多继承和单继承关系中的虚表函数
  • 继承和多态常见的问题

多态的概念

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

多态的定义及实现

1.多态构成的条件

  • 1.1必须通过基类的指针或者引用调用虚函数
  • 1.2被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

2.虚函数

  • 虚函数:即被virtual修饰的类成员函数称为虚函数。

3.虚函数的重写

  • 虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
    在这里插入图片描述
    在这里插入图片描述

4.虚函数重写的两个例外

  • 4.1协变(基类与派生类虚函数返回值类型不同)
    派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。(了解)
    在这里插入图片描述
    在这里插入图片描述

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

5.C++11 override 和 final
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写

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

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

6.重载、覆盖(重写)、隐藏(重定义)的对比
在这里插入图片描述

抽象类

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

在这里插入图片描述
接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
接下来我们来看一道题
在这里插入图片描述
相信经过思考后很多人都想选D,但实际上选B这是为什么呢?
在这里插入图片描述
在这里插入图片描述
这就是接口继承,这里如果没有实现成虚函数,而是普通函数的话,就不会发生这种令人难以想象的情景。

多态的原理

在这里插入图片描述
在这里插入图片描述
通过观察测试我们发现b对象是16bytes(x64平台下),除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。
在这里插入图片描述
那么派生类中这个表放了些什么呢?我们接着往下分析
在这里插入图片描述
在这里插入图片描述
通过观察和测试,我们发现了以下几点问题:

  1. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
  2. 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
  3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表
  4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
  5. 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后
  6. 这里还有一个童鞋们很容易混淆的问题:虚函数存在哪的?虚表存在哪的? 答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段的,Linux g++下大家自己去验证?

多态原理
上面分析了这个半天了那么多态的原理到底是什么?还记得这里Func函数传Person调用的
Person::BuyTicket,传Student调用的是Student::BuyTicket
在这里插入图片描述

  1. 观察下图的红色箭头我们看到,p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket
  2. 观察下图的蓝色箭头我们看到,p是指向johnson对象时,p->BuyTicket在johson的虚表中找到虚函数是Student::BuyTicket
  3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。
  4. 反过来思考我们要达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调用虚函数。反思一下为什么?
  5. 再通过下面的汇编代码分析,看出满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。
    在这里插入图片描述

下面虚函数的调法,为上面红线的汇编代码下的调法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下面虚函数的调法,为上面蓝线的汇编代码下的调法:
在这里插入图片描述

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

下图为不是普通函数的调法:
不管传谁,调用的都是父类的函数,因为不看其它只看形参的类型。
在这里插入图片描述

多继承和单继承关系中的虚表函数

单继承中的虚函数表
在这里插入图片描述
观察下图中的监视窗口中我们发现看不见func3和func4。这里是编译器的监视窗口故意隐藏了这两个函数,也可以认为是他的一个小bug。那么我们如何查看d的虚表呢?下面我们使用内存窗口观看虚表中的函数。
在这里插入图片描述

多继承中的虚函数表
在这里插入图片描述
观察下图可以看出:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
在这里插入图片描述

继承和多态常见的问题

  1. 什么是多态?答:参考本博客内容
  2. 什么是重载、重写(覆盖)、重定义(隐藏)?答:参考本博客内容
  3. 多态的实现原理?答:参考本博客内容
  4. inline函数可以是虚函数吗?答:可以,不过编译器就看是否形成多态如何形成多态则忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去。
  5. 静态成员可以是虚函数吗?答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
  6. 构造函数可以是虚函数吗?答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。
  7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?答:可以,并且最好把基类的析构函数定义成虚函数。参考本博客内容
  8. 对象访问普通函数快还是虚函数更快?答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
  9. 虚函数表是在什么阶段生成的,存在哪的?答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。
  10. C++菱形继承的问题?虚继承的原理?答:参考继承之前继承的博客。注意这里不要把虚函数表和虚基表搞混了。
  11. 什么是抽象类?抽象类的作用?答:参考(抽象类)。抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系。

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

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

相关文章

我的2022年,一位双非生的平淡一年

没有白走的路,每一步都算数🎈🎈🎈 前言 一谈到年终总结,其实还是很多的遗憾,我和大部分人一样,走过这一年的路途,发现自己除去每年头发越来越少,脸色日渐不佳。好像没有什…

Web 和移动应用程序测试之间的区别

智能手机改变了人类与技术互动的方式。无论是旅行、健身、生活方式、视频游戏,甚至是服务,都只需触手可及(字面意思就是如此)。我们只需要看看越来越多的智能手机或平板电脑用户与桌面用户就可以掌握这一现实。 根据一项调查,从 2021 年 4 月…

Dubbo(尚硅谷)学习笔记3

这是我们正常启动: 现在我们去把zookeeper注册中心关掉。 此时我们的注册中心是用不了的。 但是我们的数据还有,也就是我们的消费者还是能调用我们的提供者中的方法。 那么我们现在来试一下dubbo直连,也就是没有注册中心,我们也可…

VideoRender和ImageRender中的一些c++知识点

1.inline C中的inline用法_路痴的旅行的博客-CSDN博客 1 引入inline关键字的原因在c/c中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数,栈空间就是…

深入理解计算机系统_静态链接和动态链接以及静态库和动态库

这篇文章记录静态链接和动态链接以及静态库和动态库的原理。 1 静态链接和动态链接 链接其实就是连接的意思,将所有相关的东西连接起来。 1.1 静态链接 什么是静态链接?编译时候的链接就是静态链接,所以ld/collect2链接程序,也…

公司让我一个人干数据中台,是不是可以准备找下家了

大数据群里,有个哥们问了下面这样一个问题,让刚刚阳康返工的群友们笑的心跳加速,直接炸锅。 开工有惊喜 一个人搞一个数据中台!这是啥神仙领导做出来的决策?是发烧的时候拍脑袋定的吗?热心的群友也都给出了…

vector类的使用

目录 ​一、vector类的组织形式 二、vector类的成员函数 1.默认成员函数 (1)构造函数、拷贝构造函数 (2)析构函数和赋值运算符重载 2.容量操作 3.迭代器(iterator) 4.元素访问 5.修改操作 一、vec…

web3调研:Dusk Network调研

在此声明,仅做分享,绝不存在倡导炒币行为 原文链接:Dusk 调研报告 web3产品调研系列 1、web3调研:Iron fish调研 2、web3调研:Dusk Network调研 目录web3产品调研系列一、背景概述二、项目介绍2.1 创始团队2.2 项目融…

【数据结构】三万字图文讲解带你手撕八大排序(附源码)

👑作者主页:进击的安度因 🏠学习社区:进击的安度因(个人社区) 📖专栏链接:数据结构 文章目录一、前言二、排序的概念和运用三、八大排序讲解及实现1、直接插入排序1.1 排序思路1.2 代…

Java重点源码回顾——ConcurrentHashMap1.7

1. 概述 HashMap在我们的日常生活中使用很多,但是它不是线程安全的。我们可以使用HashTable来代替,主要实现方式是在方法中加入synchronized,所以效率也比较低。因此,对于键值对,我们可以尝试使用ConcurrentHashMap来…

《Java 后端面试经》微服务篇

《Java 后端面试经》专栏文章索引: 《Java 后端面试经》Java 基础篇 《Java 后端面试经》Java EE 篇 《Java 后端面试经》数据库篇 《Java 后端面试经》多线程与并发编程篇 《Java 后端面试经》JVM 篇 《Java 后端面试经》操作系统篇 《Java 后端面试经》Linux 篇 《…

关于Linux 网络抓包的一些笔记整理

写在前面 遇到一个 ping 单通 的情况,需要抓包分析下,所以整理这部分笔记博文内容涉及: HTTP/TCP 抓包分析 DemoICMP 抓包分析 DemoNginx 抓包分析用户名密码 Demo 理解不足小伙伴帮忙指正 这世界的存在完全只是就它对一个其他事物的&#xf…

【自学Java】Java选择结构if

Java选择结构if Java语言if条件判断 在 Java 中,关键字 if 是用于测试某个条件(布尔型或逻辑型)的语句是否满足一定的条件,如果满足特定的条件,则会执行 if 后面的大括号 {} 括起来的代码块,如果没有代码…

hnu社交网络分析作业2

前言:前言:上的是林剑新老师的课程,还是比较有意思的,此博客用来记录作业的学习情况,答案为老师提供的 一、2 跳标签索引是一种在网络中进行距离查询时广泛使用的技术。算法 1 描述了一种在无向图中构造 2 跳标签索引…

Java 接口介绍和使用

1.什么是接口? 接口就是给出一些没有实现的方法,封装到一起,当某一个类要使用的时候再实现出来。 2.接口的语法 interface name{ attributes methods } 比如如下USB接口 public interface Usb {public void start();public void s…

Vue 中 CSS scoped 的原理

前言 在日常的Vue项目开发过程中&#xff0c;为了让项目更好的维护一般都会使用模块化开发的方式进行。也就是每个组件维护独立的template&#xff0c;script&#xff0c;style。主要介绍一下使用<style scoped>为什么在页面渲染完后样式之间并不会造成污染。 示例 搭…

FreeRTOS开发指南

1&#xff1a;任务模板 //任务优先级 #define XXX_TASK_PRIO 1 //任务堆栈大小 #define XXX_STK_SIZE 128 //任务句柄 TaskHandle_t XXXTask_Handler NULL; /* * 放在开始任务&#xff0c;只需要执行一次为了创建任务 */ void Create_XXX_Task(void) {BaseType_t xR…

编码器-解码器架构

“编码器&#xff0d;解码器”架构可以将长度可变的序列作为输入和输出&#xff0c;因此适用于机器翻译等序列转换问题。 编码器将长度可变的序列作为输入&#xff0c;并将其转换为具有固定形状的编码状态。 解码器将具有固定形状的编码状态映射为长度可变的序列。 机器翻译是…

2022 年我国的对外贸易行业发展如何?

2021年&#xff0c;在各种不确定因素的影响下&#xff0c;中国外贸人依然以其强大的韧性和实力取得了新的进出口成绩。去年进出口总值创历史新高&#xff0c;达到6.05万亿美元&#xff0c;一年内分别突破5万亿美元和6万亿美元。 在2021年成绩和经验的加持下&#xff0c;今年外…

SpringBoot+VUE前后端分离项目学习笔记 - 【08 SpringBoot实现分页查询】

手动实现分页功能 先理解分页查询原理 采用limit语句来实现分页 -- 页码PageNum 每页数据条目PageSize5 -- 第一页 PageNum0, limit 0,5 SELECT * FROM sys_user limit 0,5; -- 第二页 PageNum1, limit 5,5 SELECT * FROM sys_user limit 5,5; -- 公式&#xff1a; limit …