C++学习笔记(22)——多态

news2025/1/23 13:08:41

目录

    • @[TOC](目录)
  • 比喻与理解
  • 1. 多态的概念
  • 2. 多态的定义及实现
    • 2.1多态的构成条件
    • 2.2 虚函数
    • 2.3虚函数的重写
    • 2.3.1 虚函数重写的两个例外:
      • 1. 协变(基类与派生类虚函数返回值类型不同)
      • 2. 析构函数的重写(基类与派生类析构函数的名字不同)
    • 2.4 C++11 override 和 final
    • 2.5 重载、覆盖(重写)、隐藏(重定义)的对比
  • 3. 抽象类
    • 3.1 概念
    • 3.2 接口继承和实现继承
  • 4. 多态的原理
    • 4.1虚函数表
    • 4.2多态的原理
    • 4.3 动态绑定与静态绑定
  • 5. 单继承和多继承关系中的虚函数表
    • 5.1 单继承中的虚函数表
    • 5.2 多继承中的虚函数表
    • 5.3. 菱形继承、菱形虚拟继承

在这里插入图片描述

比喻与理解

多态就是餐馆里面吃席,大人小孩分桌,各吃各的,只允许小孩窜桌吃大人那桌的菜,大人不许吃小孩那桌;

  1. 得先有大人带着,小孩才能进餐馆。
  2. 如果小孩要吃大人的菜,必须先找他对应的大人要;
  3. 如果小孩想吃某个自己想出来的特制菜,必须先找他对应的大人点菜,特制菜来了后,然后小孩吃小孩特制菜,大人吃大人的普通菜;
  4. 如果小孩不点菜,大人就会自己点普通菜,小孩桌上也摆普通菜。

1. 多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
简而言之,多态就是一个函数面对子类和父类会有选择性的方法应对,选择方法的根据取决于父子关系,有的特供给父,有的特供给子;

2. 多态的定义及实现

2.1多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。
那么在继承中要构成多态还有两个条件:

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

即,我们可以自定义哪些函数有多态,父类有虚函数是允许多态的前提;
在这里插入图片描述

2.2 虚函数

虚函数:即被virtual修饰的类成员函数称为虚函数。
在这里插入图片描述

2.3虚函数的重写

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

子类重写父类,目的是,让子类调用函数时函数把专供子类的方法拿出来,这个专供子类的方法的内容是子类自己阐述的;
在这里插入图片描述

2.3.1 虚函数重写的两个例外:

1. 协变(基类与派生类虚函数返回值类型不同)

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

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

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

在这里插入图片描述

2.4 C++11 override 和 final

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

  1. final:修饰虚函数,表示该虚函数不能再被重写

即,大人告诉小孩哪些菜小孩不能特制。
在这里插入图片描述

  1. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

即,大人告诉小孩哪些菜可以特制。
在这里插入图片描述

2.5 重载、覆盖(重写)、隐藏(重定义)的对比

重载:大人小孩混餐,自助餐;
重写:小孩点了特制菜炸鸡,也上了大人的烤鸡,但小孩只吃自己点的特制菜炸鸡,如果没点特制菜,就吃大人的普通菜烤鸡;
重定义:小孩点了特制菜炸鸡,只让小孩吃炸鸡,烤鸡上桌了但罩着不让吃,小孩不知道烤鸡的存在,小孩吃自己点的特制菜炸鸡;

在这里插入图片描述

3. 抽象类

3.1 概念

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

简而言之,纯虚函数可以让我们提前指定哪些类不能直接实例化,也是让我们提前指定哪些类必须重写。提前规避了一些编程过程中的混乱产生。

在这里插入图片描述

3.2 接口继承和实现继承

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

我的理解是:实现继承与接口继承的区别在于:是否使用了足额内存空间去存储逻辑上逻辑上被拷贝的那个。实现继承使用了足够多的内存去存储应被继承的内容,接口继承仅仅象征性的使用一点内存去存储一个媒介(如指针)去找被继承的类里面的成员。

4. 多态的原理

4.1虚函数表

在这里插入图片描述

通过观察测试我们发现b对象是8bytes,除了_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++下大家自己去验证.
    在这里插入图片描述

我的理解是:“注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的。”——虚表就是菜单,菜在后厨已经炒好了。

4.2多态的原理

上面分析了这个半天了那么多态的原理到底是什么?还记得这里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. 再通过下面的汇编代码分析,看出满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的
    我的理解是:多态为我们提供了派生类可以自己选择性的自定义一个函数的方法内容,换言之,就是允许派生类在继承基类的方法时,可以自定义一个方法代替基类方法起作用。
    在这里插入图片描述

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

4.3 动态绑定与静态绑定

  1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
  2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态
  3. 本小节之前(5.2小节)买票的汇编代码很好的解释了什么是静态(编译器)绑定和动态(运行时)绑定

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

需要注意的是在单继承和多继承关系中,下面我们去关注的是派生类对象的虚表模型,因为基类的虚表模型前面我们已经看过了,没什么需要特别研究的

5.1 单继承中的虚函数表

在这里插入图片描述

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

5.2 多继承中的虚函数表

**加粗样式**

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

5.3. 菱形继承、菱形虚拟继承

实际中我们不建议设计出菱形继承及菱形虚拟继承,一方面太复杂容易出问题,另一方面这样的模型,访问基类成员有一定得性能损耗。所以菱形继承、菱形虚拟继承我们的虚表我们就不看了,一般我们也不需要研究清楚,因为实际中很少用。

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

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

相关文章

【Git教程】(二十)外包长历史记录 — 概述及使用要求,执行过程及其实现,替代解决方案 ~

Git教程 外包长历史记录 1️⃣ 概述2️⃣ 使用要求3️⃣ 执行过程及其实现3.1 外包项目历史3.2 链接到当前活动版本库 Git 版本库会随着时间积累越来越大,会影响它的内存管理效率。通常在版本库中只有源 代码文件情况下,这点效率影响可以忽略不计。在现…

新火种AI|倒反天罡!美国名校斯坦福AI团队抄袭中国大模型

作者:一号 编辑:美美 中国大模型被抄袭,怎么不算是某种层面上的国际认可呢? 5月29日,斯坦福大学的一个AI研究团队发布了一个名为「Llama3V」的模型,号称只要 500 美元就能训练出一个 SOTA 多模态模型&am…

精酿啤酒新风尚,FENDI CLUB盛宴启幕,品质生活触手可及

随着现代人对生活品质的追求日益提升,精酿啤酒作为一种新兴的生活方式,正逐渐引领潮流。在这个背景下,FENDI CLUB的盛宴盛大开启,为广大消费者带来了一场别具一格的品质生活体验。 一、精酿啤酒的崛起 精酿啤酒以其独特的口感、…

手机卡不缴纳违约金就不给注销?实用的处理方法大全!

我手机卡都不用了,为何不能注销?而且要缴纳违约金?简直是无法无天!小编在回复粉丝问题的时候,经常遇到这种情况,现在就给大家系统整理下如何处理这个问题,希望能帮助到大家! 在处理不…

段子照进现实!裁员裁到大动脉,理想被传召回被裁员工…?

你一定看过类似这样的段子吧!「公司高层换血,各个部门丢裁了个遍,终于要对财务下手,财务总监走之前,让公司补了六百万税」 还有类似这样的:「某公司裁员把一个销售主管裁了,那销售上午刚谈了个1…

vue动态加载组件import引入组件找不到组件(Error: Cannot find module)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码: h…

【杂谈】AIGC之Stable Diffusion:AI绘画的魔法

Stable Diffusion:AI绘画的魔法 引言 在AI的世界里,Stable Diffusion就像一位魔法师,它能够将我们脑海中的幻想,用画笔一一描绘出来。今天,就让我们一探这位魔法师的奥秘,看看它是如何从无到有&#xff0…

Java驱动的工程项目管理系统:实现高效协作与精准管理

在工程行业的现代管理实践中,有效地协同工作和信息共享对于提高工作效率和降低成本至关重要。本文将深入探讨一款基于Java技术的工程项目管理系统,该系统采用前后端分离的架构,功能全面,旨在满足不同角色的需求,从项目…

【一小时学会Charles抓包详细教程】Charles 弱网测试与实战篇 (10)

🚀 个人主页 极客小俊 ✍🏻 作者简介:程序猿、设计师、技术分享 🐋 希望大家多多支持, 我们一起学习和进步! 🏅 欢迎评论 ❤️点赞💬评论 📂收藏 📂加关注 Charles 弱网测…

Message forwarding mechanism (消息转发机制)

iOS的消息转发机制 iOS的消息转发机制是在消息发送给对象时,找不到对应的实例方法的情况下启动的。消息转发允许对象在运行时处理无法识别的消息,提供了一种动态的、灵活的消息处理方式。 消息转发机制主要分为三个阶段: 动态方法解析快速…

基于振弦采集仪的土木工程安全监测技术研究

基于振弦采集仪的土木工程安全监测技术研究 随着土木工程的发展,安全监测成为了非常重要的一部分。土木工程的安全监测旨在及早发现结构的变形、位移、振动等异常情况,以便及时采取措施进行修复或加固,从而保障工程的安全运行。振弦采集仪作…

2024第26届大湾区国际电机博览会暨发展论坛

2024第二十六届大湾区国际电机博览会 暨发展论坛 2024第26届大湾区国际电机博览会暨发展论坛 The 26th Greater Bay Area International Motor Expo and Development Forum 时间:2024年12月4-6日 地址:深圳国际会展中心(宝安新馆&#x…

【Vue】普通组件的注册使用-全局注册

文章目录 一、使用步骤二、练习 一、使用步骤 步骤 创建.vue组件&#xff08;三个组成部分&#xff09;main.js中进行全局注册 使用方式 当成HTML标签直接使用 <组件名></组件名> 注意 组件名规范 —> 大驼峰命名法&#xff0c; 如 HmHeader 技巧&#xf…

zdppy_api 中间件请求原理详解

单个中间件的逻辑 整体执行流程&#xff1a; 1、客户端发起请求2、中间件拦截请求&#xff0c;在请求开始之前执行业务逻辑3、API服务接收到中间件处理之后的请求&#xff0c;和数据库交互&#xff0c;请求数据4、数据库返回数据5、API处理数据库的数据&#xff0c;然后给客户…

【线性代数】SVDPCA

用最直观的方式告诉你&#xff1a;什么是主成分分析PCA_哔哩哔哩_bilibili 奇异值分解singular value decomposition&#xff0c;SVD principal component analysis,PCA 降维操作 pca就是降维后使得信息损失最小 投影在坐标轴上的点越分散&#xff0c;信息保留越多 pca的实现…

Springboot二屯村钓鱼场管理系统的设计-计算机毕业设计源码58167

摘 要 在互联网时代的来临&#xff0c;电子商务的骤起&#xff0c;一时间网络进行购物这一形式备受欢迎&#xff0c;到现在&#xff0c;网购更是普及。现如今各个行业也通过网购的方式来进行拓展业务&#xff0c;增加企业的知名度以及提升业绩&#xff0c;满足了用户像网购一样…

懒人开发者的福音,轻松开发应用无需搭建服务!

近日&#xff0c;一款轰动开发圈的神器正以“太硬核了&#xff01;疯传开发圈&#xff01;”的口碑迅速走红&#xff0c;那就是Memfire Cloud&#xff01;这款一站式开发应用&#xff0c;不仅让懒人开发者尽享便利&#xff0c;更为开发者们带来了前所未有的开发体验。 对于懒人…

windows操作系统提权之服务提权实战rottenpotato

RottenPotato&#xff1a; 将服务帐户本地提权至SYSTEM load incognito list_tokens –u upload /home/kali/Desktop rottenpotato.exe . execute -Hc -f rottenpotato.exe impersonate_token "NT AUTHORITY\SYSTEM" load incognito 这条命令用于加载 Metasploi…

【安装笔记-20240529-Windows-Wireshark 网络协议分析工具】

安装笔记-系列文章目录 安装笔记-20240529-Windows-Wireshark 网络协议分析工具 文章目录 安装笔记-系列文章目录安装笔记-20240529-Windows-Wireshark 网络协议分析工具 前言一、软件介绍名称&#xff1a;Wireshark主页官方介绍 二、安装步骤测试版本&#xff1a;Wireshark-4…

力扣11. 盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。 示例 …