【C++】多态,从使用到底层。
文章目录
- 前言
- 一、多态的概念
- 二、多太的定义和实现
- 2.1 多太的构造条件
- 2.2 虚函数
- 2.3 重写(覆盖)
- 2.4 C++11 override 和 final
- 2.5 重载,隐藏,重写
- 三、多态的原理
- 3. 1虚函数表
- 3.2 虚函数表如何完成多态的功能
- 3.3 虚函数表存储在内存空间的那个区域?
前言
一、多态的概念
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
举例:对于买票的这个行为来说,成年人买票全价,儿童买票半价,学生买票打折,军人优先买票……不同的人虽然都是进行买票的行为,但买票过程的细节不完全相同。而为了让不同的对象,进行同一行为,产生不同的状态。我们则需要采用面向对象的三大特性之一:多态。
下面看一段简单的多态代码,后文进行解释:
class Person
{
public:virtual void BuyTicket() { cout << "Person::BuyTicket()" << endl; }
};
class Student : public Person
{
public:virtual void BuyTicket() { cout << "Student::BuyTicket()" << endl; }
};
int main()
{Person pn;Student st;Person* ppn = &pn;ppn->BuyTicket(); //普通人买票ppn = &st;ppn->BuyTicket(); //学生买票return 0;
}
打印结果:

二、多太的定义和实现
2.1 多太的构造条件
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。就像上面的代码:Student继承了Person。Person对象买票全价,Student对象买票半价。
而在继承中构成多态有两个条件(牢记):
- 必须通过基类的指针或者引用调用虚函数。
- 此时基类的指针或引用已经被赋值为了派生类的对象的地址,且被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行了重写。
这中间出现了两个陌生的名词:虚函数和重写,因此我们首先要了解这两个词的意思是什么?
2.2 虚函数
虚函数:即被virtual修饰的类成员函数称为虚函数
比如BuyTicket()函数
class Person
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
2.3 重写(覆盖)
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同,称子类的虚函数重写了基类的虚函数。(重写也可叫作覆盖)
class Person
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person
{
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
注意:
- 派生类进行重写时可以不加virtual,只要派生类里的函数和基类的虚函数的返回值类型、函数名字、参数列表完全相同,编译器会自动识别为基类虚函数的重写。但一般为了可读性还是加上
- 返回值类型、函数名字、参数列表完全相同。其中参数列表完全相同指的是参数类型+形参的名字完全相同,对于缺省值不作要求.
virtual void func(int val = 3) {}
virtual void func(int val = 4) {} 它们依然构造重写- 虚函数的重写是对函数体进行重写,
基类里的虚函数:virtual void func(int val = 3) {cout << "基类" << val;}
派生类的虚函数:virtual void func(int val = 4) {cout << "派生类" << val;}
当你调用派生类的虚函数func()时,你会发现打印的结果是派生类3,即它会使用基类的虚函数头 + 派生类的虚函数体。后文有个面试题考察了这个知识。
虚函数重写的两个特例:
- 协变(基类与派生类虚函数返回值类型不同)
上面说了虚函数重写需要返回值的类型相同,但是给了一个特例:基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时也可以是虚函数的重写,这种情况被称为协变
class A{};
class B : public A {};
class Person {
public:virtual A* f() {return new A;}
};
class Student : public Person {
public:virtual B* f() {return new B;}
};
- 析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,因此所有的析构函数都满足函数名相同。
class Person {
public:virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:virtual ~Student() { cout << "~Student()" << endl; }
};
问题:为什么要对析构函数的名称进行处理?博客析构函数的名称为什么统一处理为destructor
知晓了这两个条件,我们来看上面的那段代码。
class Person
{
public:virtual void BuyTicket() { cout << "Person::BuyTicket()" << endl; }
};
class Student : public Person
{
public:virtual void BuyTicket() { cout << "Student::BuyTicket()" << endl; }
};
int main()
{Person pn;Student st;Person* ppn = &pn;ppn->BuyTicket(); //普通人买票ppn = &st;ppn->BuyTicket(); //学生买票return 0;
}
解释:为什么ppn能调用到派生类的BuyTicket()?
- ppn的类型是基类person的指针满足多态的第一个条件
- ppn已经被赋值为派生类对象st的地址,且BuyTicket()对基类的BuyTicket进行了重写。满足第二个条件
因此:ppn->BuyTicket会调用子类的虚函数。
2.4 C++11 override 和 final
final:修饰虚函数,表示该虚函数不能再被重写
使用场景:当你不想某个虚函数被重写时,可以加上final

override:帮助派生类检查是否完成重写

2.5 重载,隐藏,重写

三、多态的原理
看完上面的内容,相信你会有以下的困惑:
- 为什么基类的对象或指针能调用到派生类的函数?
- 为什么限定为基类的指针或引用,基类的对象不行吗?
要解答这些问题,我们必须要了解定义虚函数时产生的虚函数表。
3. 1虚函数表
class A
{
public:virtual void func1() {};virtual void func2() {};char a;
};
int main()
{ A A1;cout << sizeof(A1);return 0;
}
在32位的机器下,请问上面的打印结果是什么?
如果func的前面没有加virtual,结果很明显是1,但加上virtual后,结果变成了8.
那多出的内存放了些什么东西呢?
我们此时打开监视窗口:

发现A1中出现了一个指针_vfptr,我们猜测它代表的什么意思:v即virtual,f即function,ptr即指针,猜测它是虚函数指针。但它下面有【0】【1】,这又表明它可能是个数组。结合以下,即_vfptr是虚函数指针数组。
事实上,它还真是一个虚函数指针数组,只不过我们将这个数组叫做虚函数表,简称虚表。
这时我们可以确定2个事实
- 多出来的内存存储了一个指针,32位下的指针是4字节,加上
char a的1字节,最后在进行内存对齐,结果就是8字节。 - 这个指针指向的空间并不存储在对象里面,如果存储在对象里,那么对象的大小应该大于8字节。
此时我们可以画一个简图:

知晓了虚函数表的存在,随之而来的就有2个问题:
- 虚函数表是如何完成多态的功能?
- 虚函数表并没有存储在对象里,那它存储在什么地方?
同时加上前文提到的问题:为什么多态的构成条件要求是基类的指针或引用?
3.2 虚函数表如何完成多态的功能

通过监视窗口,我们可以看到:基类对象ps的虚表存储的值_vfptr[0] = 0x00bc15a5,而派生类对象st的基类的那一部分存储的虚表里的值_vfptr[0] = 0x00bc1596,二者值不相同,说明二者存储了不同的虚函数地址,一个存储的地址是person::BuyTicket, 另一个存储的是student::BuyTicket;除此之外,_vfptr都是存储在基类的那一部分。
据此,我们基本确定调用虚函数的过程如下:
- 基类对象和派生类对象都会创建虚函数表,基类对象的虚函数表存储基类的虚函数地址,派生类对象的虚函数表会存储派生类的虚函数地址。
- 当我们使用基类的对象的指针或引用去调用时,分别取指向对象的虚表去寻找。这就解释了为什么不能使用基类的对象,因为基类的对象里的虚表存储的是基类虚函数的地址,无法找到派生类的虚函数。
3.3 虚函数表存储在内存空间的那个区域?
A:栈
B:堆
C:代码段(常量区)
D:数据段(静态区)
先说答案 : 代码段(常量区)
验证如下:

思路:比较虚函数表内存储的地址与其他存储区域的地址进行对比,看谁更接近。
通过上面的结果可以看出:虚表的地址 与 常量区的地址最为接近。
如何提取虚表的地址:首先对象第一个存储的便是虚函数表指针,因此前4个字节(32位)存储便是虚函数表的地址,
(int*)&ps 即是 _vfptr的地址, 再解引用便是 _vfptr存储的地址,即虚函数表的地址
相关文章:
【C++】多态,从使用到底层。
文章目录 前言一、多态的概念二、多太的定义和实现2.1 多太的构造条件2.2 虚函数2.3 重写(覆盖)2.4 C11 override 和 final2.5 重载,隐藏,重写 三、多态的原理3. 1虚函数表3.2 虚函数表如何完成多态的功能3.3 虚函数表存储在内存空间的那个区域ÿ…...
uvm白皮书练习_ch2_ch221只有driver的验证平台之*2.2.1 最简单的验证平台
uvm白皮书练习 ch221 dut.sv 这个DUT的功能非常简单,通过rxd接收数据,再通过txd发送出去。其中rx_dv是接收的数据有效指示,tx_en是发送的数据有效指示。 module dut (clk,rst_n,rxd,rx_dv,txd,tx_en );input clk ; input rst_n ; in…...
服务断路器_Resilience4j超时降级
创建模块cloud-consumer-resilience4j-order80 POM引入依赖 <dependencies><!-- 引入Eureka 客户端依赖 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</a…...
【知识点随笔分析】我看看谁还不会用CURL命令
目录 前言: CURL介绍: CURL的基本使用: CURL与PING命令的区别: CURL命令的应用: 总结: 前言: 当今互联网时代,与服务器进行数据交互成为了无法回避的需求。无论是获取Web…...
ICCV 2023|Occ2Net,一种基于3D 占据估计的有效且稳健的带有遮挡区域的图像匹配方法...
本文为大家介绍一篇入选ICCV 2023的论文,《Occ2Net: Robust Image Matching Based on 3D Occupancy Estimation for Occluded Regions》, 一种基于3D 占据估计的有效且稳健的带有遮挡区域的图像匹配方法。 论文链接:https://arxiv.org/abs/23…...
leetcode - 14. Longest Common Prefix
Description Write a function to find the longest common prefix string amongst an array of strings. If there is no common prefix, return an empty string “”. Example 1: Input: strs ["flower","flow","flight"] Output: "…...
MySQL-查询语句语法(DQL)结构(查询操作 一)
SQL语句 编写顺序 - 执行顺序 1、SELECT 字段列表 4 2、FROM 表名列表 1 3、WHERE 条件列表 2 4、GROUP BY 分组字段列表 …...
SWAT-MODFLOW地表水与地下水耦合
耦合模型被应用到很多科学和工程领域来改善模型的性能、效率和结果,SWAT作为一个地表水模型可以较好的模拟主要的水文过程,包括地表径流、降水、蒸发、风速、温度、渗流、侧向径流等,但是对于地下水部分的模拟相对粗糙,考虑到SWAT…...
工地临时用电之智慧用电:全方位保障用电安全
随着科技进步和智能化的发展,工地用电管理也迎来了智慧化的革新。智慧用电,作为智慧工地的重要组成部分,通过集中式管理和创新的技术手段,为工地提供了全方位的用电安全保障。 针对工地临时用 的现状及系统结构,力安科…...
JumpServer开源堡垒机与爱可生云树数据库完成兼容性认证
近日,中国领先的开源软件提供商FIT2CLOUD飞致云宣布,JumpServer开源堡垒机已经完成与爱可生云树数据库软件的兼容性认证。经过双方联合测试,云树数据库软件(简称:ActionDB)V1.0与杭州飞致云信息科技有限公司…...
信息化发展64
信息化体系 信息化代表了一种信息技术被高度应用,信息资源被高度共享,从而使得人的智能潜力以及社会物质资源潜力被充分发挥,个人行为、组织决策和社会运行趋于合理化的理想状态。 1997年召开的首届全国信息化工作会议,对信息化和…...
什么是全媒体整合营销?如何做好全媒体整合营销呢?
互联网发展进入深水区,目前营销大部分工作都与网络有关,网络营销形成各种分支,媒体平台的类型越来越多,如今的互联网发展背景下企业如何做好网络营销呢?小马识途营销顾问团队普遍认为企业当今应该开展的全媒体整合营销…...
系统集成|第十六章(笔记)
目录 第十六章 信息(文档)和配置管理16.1 文档管理16.2 配置管理 上篇:第十五章、采购管理 下篇:第十七章、变更管理 第十六章 信息(文档)和配置管理 16.1 文档管理 信息系统项目相关信息(文档…...
hive数据库操作,hive函数,FineBI可视化操作
1、数据库操作 1.1、创建数据库 create database if not exists myhive;use myhive;1.2、查看数据库详细信息 desc database myhive;数据库本质上就是在HDFS之上的文件夹。 默认数据库的存放路径是HDFS的:/user/hive/warehouse内 1.3、创建数据库并指定hdfs存…...
信息学奥赛一本通 2075:【21CSPJ普及组】插入排序(sort) | 洛谷 P7910 [CSP-J 2021] 插入排序
【题目链接】 ybt 2075:【21CSPJ普及组】插入排序(sort) 洛谷 P7910 [CSP-J 2021] 插入排序 【题目考点】 1. 排序: 插入排序 插入排序示例: #include <bits/stdc.h> using namespace std; int main() {int…...
基于微信小程序的民宿短租酒店预订系统设计与实现(源码+lw+部署文档+讲解等)
文章目录 前言系统主要功能:具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…...
Python第二次作业(2)【控制台界面】
要求:使用Python输出五个控制台界面 第一张: 代码如下: print(" 英雄联盟商城登录界面 ") print("~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*") print(" 1.用户登录 &q…...
conda创建环境在Collecting package metadata (current_repodata.json)时报错的解决
conda创建环境在Collecting package metadata (current_repodata.json)时报错的解决 报错信息: Collecting package metadata (current_repodata.json): - ERROR conda.auxlib.logz:stringify(171): Traceback (most recent call last): File “C:\Users\dandelion…...
卤制品配送经营商城小程序的用处是什么
卤制品也是食品领域重要的分支,尤其对年轻人来说,只要干净卫生好吃价格合理,那复购率宣传性自是不用说,而随着互联网发展,传统线下门店也须要通过线上破解难题或进一步扩大生意。 而商城小程序无疑是商家通过线上私域…...
信息化发展65
1.2现代化基础设施 基础设施包括交通、能源、水利、物流等以传统基础设施和信息网络为核心的新型基础设施,在国家发展全局中具有战略性、基础性、先导性作用。统筹推进传统基础设施和新型基础设施建设,打造系统完备、高效实用、智能绿色、安全可靠的现代…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...
阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)
cd /home 进入home盘 安装虚拟环境: 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境: virtualenv myenv 3、激活虚拟环境(激活环境可以在当前环境下安装包) source myenv/bin/activate 此时,终端…...
Win系统权限提升篇UAC绕过DLL劫持未引号路径可控服务全检项目
应用场景: 1、常规某个机器被钓鱼后门攻击后,我们需要做更高权限操作或权限维持等。 2、内网域中某个机器被钓鱼后门攻击后,我们需要对后续内网域做安全测试。 #Win10&11-BypassUAC自动提权-MSF&UACME 为了远程执行目标的exe或者b…...
ABB馈线保护 REJ601 BD446NN1XG
配电网基本量程数字继电器 REJ601是一种专用馈线保护继电器,用于保护一次和二次配电网络中的公用事业和工业电力系统。该继电器在一个单元中提供了保护和监控功能的优化组合,具有同类产品中最佳的性能和可用性。 REJ601是一种专用馈线保护继电器…...
安宝特方案丨从依赖经验到数据驱动:AR套件重构特种装备装配与质检全流程
在高压电气装备、军工装备、石油测井仪器装备、计算存储服务器和机柜、核磁医疗装备、大型发动机组等特种装备生产型企业,其产品具有“小批量、多品种、人工装配、价值高”的特点。 生产管理中存在传统SOP文件内容缺失、SOP更新不及、装配严重依赖个人经验、产品装…...
