C++程序设计——多态:虚函数、抽象类、虚函数表
注:以下示例均是在VS2019环境下
一、多态的概念
通俗来讲,多态就是多种形态,当不同的对象去完成某个行为时,会产生出不同的状态。即不同继承关系的类对象,去调用同一函数时,产生不同的行为。
比如”叫“这个行为,不同的动物,发出的声音是不同的。
二、多态的定义及实现
1.多态的构成条件
(1)必须是在继承环境下。
(2)被调用的函数必须是虚函数,且派生类中必须对该虚函数进行重写。
(3)必须通过基类的指针或引用去调用虚函数。
2.虚函数
被virtual修饰的类成员函数。
3.虚函数的重写
3.1虚函数的重写(覆盖)
派生类中有一个跟基类中完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名、参数列表都完全相同),称为子类的虚函数重写了基类的虚函数。
注意:
(1) 在重写基类虚函数时,派生类的虚函数不加virtual关键字修饰时,也可以构成重写(因为基类的虚函数被继承下来了,在派生类中依旧保持虚函数属性),但是这种写法不太规范,不建议这样使用。
(2)基类和子类虚函数的访问权限可以不同,但是一般都会将基类的虚函数设置为public。
3.2虚函数重写的两个例外
(1)协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数的返回值类型不同。即基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或引用时,称为协变。
注意:
基类和派生类必须属于同一继承体系;基类的返回值和派生类的返回值必须属于同一继承体系。但是返回值和基类、派生类不一定属于同一继承体系。
(2)析构函数的重写(基类与派生类析构函数名不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否使用virtual关键字修饰,都与基类析构函数构成重写,但是析构函数名不同。
函数名不同,看起来似乎违背了重写的规则,单其实是编译器对析构函数名做了特殊处理,编译后析构函数名称统一处理成了destructor。
4.C++11——override和final
在某些情况下,由于疏忽可能会导致函数名字不同而无法构成重写,而这种错误在编译期间是不会报错的,只有在程序运行时没有得到预期结果才通过调试寻找错误,代价较高。
因此C++11提供了override和final两个关键字,可以帮助用户检测是否重写。
4.1override关键字
检查派生类虚函数是否重写了基类某个虚函数,若没有则编译报错。

4.2final关键字
只能修饰虚函数。
修饰虚函数,表示该虚函数不能再被重写。

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

注意:
(1)重写和重定义必须在继承体系内。
(2)重写只能是成员函数,而且是重写基类的虚函数;重定义既可以是成员函数,也可以是成员变量。
三、抽象类
1.抽象类概念
(1)在虚函数后面加上=0,则该函数为纯虚函数。
(2)包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化对象。
(3)派生类继承抽象类后,也不能直接实例化对象,必须对基类所有纯虚函数进行重写后才能实例化对象,否则派生类也是抽象类。
(4)纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口的继承。

注意:纯虚函数可以不用写函数体,写了不影响但没有意义。
2.接口继承和实现继承
2.1实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
2.2接口继承
虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。
所有若不需要实现多态,不要把成员函数定义为虚函数。
四、多态的原理
1.虚函数表
只要类中存在虚函数,对象大小就会多4个字节:

该指针指向的是一段连续的空间,即虚函数表,里面存放的是虚函数入口地址,且顺序与定义顺序相同。
对象模型:

基类虚表:
(1)虚函数入口地址存放顺序与虚函数定义顺序相同。
(2)一个类的多个对象公用同一个虚表
2.虚函数与虚表存放位置
(1)虚函数和普通函数一样存放在代码段,虚表存放的是虚函数指针。
(2)对象中存放的是虚表指针,不是虚表。
(3)在vs环境下,虚表是存放在代码段的。
3.静态多态与动态多态
3.1静态多态
在程序编译阶段,就已经确定了程序的行为,也叫静态绑定、前期绑定(早绑定),比如:函数重载、模板。
3.2动态多态
在程序运行时,根据程序拿到的具体类型确定程序的具体行为,调用具体的函数,比如根据基类的指针或引用指向不同类的对象,选择对应的虚函数进行调用。也叫动态绑定、后期绑定(晚绑定)。
五、单、多继承中的虚函数表
5.1单继承中的虚函数表
基类虚表:
(1)按虚函数声明顺序存放入口地址。
(2)一个类的多个对象公用同一个虚表
子类虚表:
(1)子类有自己独立的虚表,不与父类共用。
(2)子类会将基类虚表拷贝一份放入自己的虚表中。
(3)如果子类重写了基类的某个虚函数,就会用子类自己虚函数的地址去覆盖虚表中被重写的基类虚函数的地址。
(4)子类自己定义的虚函数,其地址入口会按声明顺序依次存放在虚表的末尾。
5.2多继承中的虚函数表
基类虚表:
(1)按虚函数声明顺序存放入口地址。
(2)一个类的多个对象公用同一个虚表
子类虚表:
(1)子类会将基类虚表拷贝一份放入自己的虚表中。
(2)如果子类重写了基类的某个虚函数,就会用子类自己虚函数的地址去覆盖虚表中被重写的基类虚函数的地址。
(3)子类自己新增的虚函数,会将其入口地址添加在第一张虚表之后,第一张虚表即子类第一个继承的基类所拷贝的虚表。
六、常见问题
1.什么是多态?
2.什么是重载、重写、重定义?
3.多态的实现原理?
虚表的构造?
虚函数的调用原理:
(1)获取对象虚函数表指针(对象前4个字节)
(2)传递this指针
(3)从虚表中找到对应虚函数的入口地址
(4)调用该虚函数
4.inline函数可以是虚函数吗?
语法上可以。不过编译器会忽略inline属性,这个函数就不再是inline,因为虚函数的地址入口需要放入到虚表中去。
5.静态成员可以是虚函数吗?
不可以。因为静态成员函数没有this指针,使用“类名::成员函数”的调用方式无法访问虚函数表,所以静态成员不能放入虚函数表。
6.构造函数可以是虚函数吗?
不可以。因为对象的虚函数表指针是在构造函数初始化列表阶段才初始化的。
7.析构函数可以是虚函数吗?什么场景是?
可以。最好是把基类的析构函数定义为虚函数。场景:在继承体系中,基类的析构函数最好设置为虚函数,防止用基类的指针去销毁派生类对象时,只调用基类的析构函数而不调用子类的析构函数。
8.对象访问普通函数快还是虚函数快?
若是普通对象,则一样快;若是指针对象或引用对象,则调用普通函数快。因为通过指针或引用访问虚函数时,需要在运行过程中去查询虚表才能确定函数入口地址,而普通对象在编译时就已经确定了函数的入口地址。
9.虚函数表是在哪个阶段生成的,存放在哪儿?
虚函数表在编译阶段生成,一般情况存放在代码段(常量区)。
10.菱形继承的问题?虚拟原理?
注意不要混淆虚函数表和虚基表。
11.什么是抽象类?抽象类的作用?
……
作用:抽象类规范了派生类必须重写纯虚函数,另外纯虚函数更体现出了接口的继承。
相关文章:
C++程序设计——多态:虚函数、抽象类、虚函数表
注:以下示例均是在VS2019环境下 一、多态的概念 通俗来讲,多态就是多种形态,当不同的对象去完成某个行为时,会产生出不同的状态。即不同继承关系的类对象,去调用同一函数时,产生不同的行为。 比如”叫“这…...
OpenMMLab AI实战营 第6课 语义分割与MMSegmentation
第6课 语义分割与MMSegmentation 1. 语义分割简介 任务:将图像按照物体的类别分割成不同的区域,等价于对每个像素进行分类应用 无人驾驶人像分割智能遥感医疗影像分析 语义分割 vs 实例分割 vs 全景分割 语义分割:仅考虑像素的类别…...
产业互联网是对互联网的衍生和进化,也是一次重塑和再造
互联网并不仅仅只是充当撮合和中介的角色,它应当具备更多的功能和意义。只有这样,它的发展才能够真正全面和完善。产业互联网的衍生和出现,正是在互联网进化的基础之上出现的。这是我们看到之所以会有那么多的互联网玩家投身到产业互联网的浪…...
Shell脚本之——Hadoop3单机版安装
目录 1.解压 2.文件重命名 3.配置环境变量 4.hadoop-env.sh 5.core-site.xml 6. hdfs-site.xml 7. mapred-site.xml 8.yarn-site.xml 9.完整脚本代码(注意修改主机名) 10.重启环境变量 11.初始化 12.启动服务 13.jps查询节点 1.解压 tar -zxf /opt/install/hadoo…...
代码随想录NO39 |0-1背包问题理论基础 416.分割等和子集
0-1背包问题理论基础 分割等和子集1. 0-1背包问题理论基础(二维数组实现)2. 0-1背包问题理论基础 二(一维数组实现)1. 0-1背包问题理论基础(二维数组实现) 背包问题一般分为这几种: 0-1背包问题:有n件物品和一个最多能背重量为w…...
FITC-PEG-FA,荧光素-聚乙二醇-叶酸,FA-PEG-FITC,实验室科研试剂,提供质量检测
FITC-PEG-FA,荧光素-聚乙二醇-叶酸 中文名称:荧光素-聚乙二醇-叶酸 英文名称:FITC-PEG-FA 英文别名:Fluorescein-PEG-Folic Acid 性状:基于不同的分子量,呈白色/类白色固体,或粘稠液体。 溶…...
简洁易懂:源码+实战讲解Redisson并发锁及看门狗自动续期
1 缘起 有一次同事问Redisson存储的键是否为hash? 我当时,没有看Redisson的相关源码,只知道应用, 所以没有办法回答,于是开始看看Redisson实现的源码, 顺便写了一个单机Redisson测试, 发现Redi…...
TCP 三次握手和四次挥手
✏️作者:银河罐头 📋系列专栏:JavaEE 🌲“种一棵树最好的时间是十年前,其次是现在” 目录TCP 建立连接(三次握手)为啥不能是 4 次?为啥不能是 2 次?三次握手的意义:TCP 断开连接(四…...
JavaWeb复习
JavaWeb复习一.概述1.概念2.B/S和C/S 架构二.HTTP通信协议概述1.概念2.HTTP1.0 与 HTTP1.1 版本3.HTTP 协议组成4.常见状态码5.GET 与 POST 请求方式三.Tomcat1.Web服务器介绍2.安装(Windows)3.Tomcat目录结构4.server.xml部分配置解释四.Servlet1.概念2…...
P14 PyTorch AutoGrad
前言:激活函数与loss的梯度PyTorch 提供了Auto Grad 功能,这里系统讲解一下torch.autograd.grad系统的工作原理,了解graph 结构目录:1: require_grad False2: require_grad True3: 多层bakcward 原理4: in…...
前端报表如何实现无预览打印解决方案或静默打印
在前端开发中,除了将数据呈现后,我们往往需要为用户提供,打印,导出等能力,导出是为了存档或是二次分析,而打印则因为很多单据需要打印出来作为主要的单据来进行下一环节的票据支撑, 而前端打印可…...
Operating System Course 2 - My OS
Computer Startup process上一篇:http://t.csdn.cn/XfUKt 讲到这个启动设备的第一个扇区:引导扇区。那么引导扇区的代码长什么样子?这里得看引导扇区代码源文件bootsect.s(.s后缀文件为用汇编语言编写的源代码文件)。另…...
离散数学 课时一 命题逻辑的基本概念
1 命题 1、命题:可以判断其真值的陈述句 2、真值:真或者假(1或者0) 3、真命题:真值为真的命题 4、假命题:真值为假的命题 5、原子命题:不可以再被分解成更简单的命题 6、复合命题:由原子命题通过联结词联结…...
Word文档带有权限密码怎么办?
Word文档的权限密码指的是什么?其实这是Word文档的保护方法之一,具体指Word文档的编辑、修改受到了限制,需要输入密码才能进行。 设置了权限密码的Word文档还是可以直接打开,只有当需要编辑或者修改内容的时候,才会发…...
C++多态
1. 多态的概念1.1 概念多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态举个例子:比如买票这个行为,当普通人买票时,是全价买票;…...
访问学者如何申请美国J1签证?
一、申请美国J1签证的步骤: 第一步:填写I901表。 填写I901表会收取SERVIS费用180美元,可以用VISA/Master卡直接网上支付。填完后打印收据单或者存成PDF后续再打印,记下I901收据编号。 第二步:DS-160表填写。 填写DS-…...
使用gitlab ci/cd来发布一个.net 项目
gitlab runner的安装和基本使用:https://bear-coding.blog.csdn.net/article/details/120591711安装并给项目配置完gitlab runner后再操作后面步骤。实现目标:master分支代码有变更的时候自动构建build。当开发人员在gitlab上给项目打一个tag标签分支的时候自动触发…...
笔试题-2023-蔚来-数字芯片设计【纯净题目版】
回到首页:2023 数字IC设计秋招复盘——数十家公司笔试题、面试实录 推荐内容:数字IC设计学习比较实用的资料推荐 题目背景 笔试时间:2022.08.24应聘岗位:校招-芯片逻辑综合工程师-智能硬件笔试时长:90min笔试平台:nowcoder牛客网题目类型:不定项选择题(15道)、填空题…...
ThreadLocal 详解
ThreadLocal简介JDK源码对ThreadLocal类的注释如下:ThreadLocal提供线程局部变量,使得每个线程都有自己的、独立初始化的变量副本ThreadLocal实例通常是类中的private static字段,用于将状态与线程相关联,如用户ID、事务ID只要线程…...
【Java 面试合集】重写以及重载有什么区别能简单说说嘛
重写以及重载有什么区别能简单说说嘛 前述 这是一道非常基础的面试题,我们在回答的过程中一定要逐一横向比较。 从方法的 修饰符,返回值,方法名,含义,参数等方面进行逐一分析来比较不同。 话不多话,看下…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...
