当前位置: 首页 > news >正文

【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对象买票半价。

而在继承中构成多态有两个条件(牢记):

  1. 必须通过基类的指针或者引用调用虚函数
  2. 此时基类的指针或引用已经被赋值为了派生类的对象的地址,且被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行了重写

这中间出现了两个陌生的名词:虚函数和重写,因此我们首先要了解这两个词的意思是什么?

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; }
};

注意:

  1. 派生类进行重写时可以不加virtual,只要派生类里的函数和基类的虚函数的返回值类型、函数名字、参数列表完全相同,编译器会自动识别为基类虚函数的重写。但一般为了可读性还是加上
  2. 返回值类型、函数名字、参数列表完全相同。其中参数列表完全相同指的是参数类型+形参的名字完全相同,对于缺省值不作要求.
    virtual void func(int val = 3) {}
    virtual void func(int val = 4) {} 它们依然构造重写
  3. 虚函数的重写是对函数体进行重写
    基类里的虚函数:virtual void func(int val = 3) {cout << "基类" << val;}
    派生类的虚函数:virtual void func(int val = 4) {cout << "派生类" << val;}
    当你调用派生类的虚函数func()时,你会发现打印的结果是派生类3,即它会使用基类的虚函数头 + 派生类的虚函数体。后文有个面试题考察了这个知识。

虚函数重写的两个特例:

  1. 协变(基类与派生类虚函数返回值类型不同)
    上面说了虚函数重写需要返回值的类型相同,但是给了一个特例:基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时也可以是虚函数的重写,这种情况被称为协变
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;}
};
  1. 析构函数的重写(基类与派生类析构函数的名字不同)
    如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加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()?

  1. ppn的类型是基类person的指针满足多态的第一个条件
  2. ppn已经被赋值为派生类对象st的地址,且BuyTicket()对基类的BuyTicket进行了重写。满足第二个条件
    因此:ppn->BuyTicket会调用子类的虚函数。

2.4 C++11 override 和 final

final:修饰虚函数,表示该虚函数不能再被重写
使用场景:当你不想某个虚函数被重写时,可以加上final

在这里插入图片描述

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

在这里插入图片描述

2.5 重载,隐藏,重写

在这里插入图片描述


三、多态的原理

看完上面的内容,相信你会有以下的困惑:

  1. 为什么基类的对象或指针能调用到派生类的函数?
  2. 为什么限定为基类的指针或引用,基类的对象不行吗?

要解答这些问题,我们必须要了解定义虚函数时产生的虚函数表。

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个事实

  1. 多出来的内存存储了一个指针,32位下的指针是4字节,加上char a的1字节,最后在进行内存对齐,结果就是8字节。
  2. 这个指针指向的空间并不存储在对象里面,如果存储在对象里,那么对象的大小应该大于8字节。

此时我们可以画一个简图:
在这里插入图片描述


知晓了虚函数表的存在,随之而来的就有2个问题:

  1. 虚函数表是如何完成多态的功能?
  2. 虚函数表并没有存储在对象里,那它存储在什么地方?

同时加上前文提到的问题:为什么多态的构成条件要求是基类的指针或引用?

3.2 虚函数表如何完成多态的功能

在这里插入图片描述
通过监视窗口,我们可以看到:基类对象ps的虚表存储的值_vfptr[0] = 0x00bc15a5,而派生类对象st的基类的那一部分存储的虚表里的值_vfptr[0] = 0x00bc1596,二者值不相同,说明二者存储了不同的虚函数地址,一个存储的地址是person::BuyTicket, 另一个存储的是student::BuyTicket;除此之外,_vfptr都是存储在基类的那一部分。
据此,我们基本确定调用虚函数的过程如下:

  1. 基类对象和派生类对象都会创建虚函数表,基类对象的虚函数表存储基类的虚函数地址,派生类对象的虚函数表会存储派生类的虚函数地址。
  2. 当我们使用基类的对象的指针或引用去调用时,分别取指向对象的虚表去寻找。这就解释了为什么不能使用基类的对象,因为基类的对象里的虚表存储的是基类虚函数的地址,无法找到派生类的虚函数。

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 重载&#xff0c;隐藏&#xff0c;重写 三、多态的原理3. 1虚函数表3.2 虚函数表如何完成多态的功能3.3 虚函数表存储在内存空间的那个区域&#xff…...

uvm白皮书练习_ch2_ch221只有driver的验证平台之*2.2.1 最简单的验证平台

uvm白皮书练习 ch221 dut.sv 这个DUT的功能非常简单&#xff0c;通过rxd接收数据&#xff0c;再通过txd发送出去。其中rx_dv是接收的数据有效指示&#xff0c;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命令

目录 前言&#xff1a; CURL介绍&#xff1a; CURL的基本使用&#xff1a; CURL与PING命令的区别&#xff1a; CURL命令的应用&#xff1a; 总结&#xff1a; 前言&#xff1a; 当今互联网时代&#xff0c;与服务器进行数据交互成为了无法回避的需求。无论是获取Web…...

ICCV 2023|Occ2Net,一种基于3D 占据估计的有效且稳健的带有遮挡区域的图像匹配方法...

本文为大家介绍一篇入选ICCV 2023的论文&#xff0c;《Occ2Net: Robust Image Matching Based on 3D Occupancy Estimation for Occluded Regions》&#xff0c; 一种基于3D 占据估计的有效且稳健的带有遮挡区域的图像匹配方法。 论文链接&#xff1a;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地表水与地下水耦合

耦合模型被应用到很多科学和工程领域来改善模型的性能、效率和结果&#xff0c;SWAT作为一个地表水模型可以较好的模拟主要的水文过程&#xff0c;包括地表径流、降水、蒸发、风速、温度、渗流、侧向径流等&#xff0c;但是对于地下水部分的模拟相对粗糙&#xff0c;考虑到SWAT…...

工地临时用电之智慧用电:全方位保障用电安全

随着科技进步和智能化的发展&#xff0c;工地用电管理也迎来了智慧化的革新。智慧用电&#xff0c;作为智慧工地的重要组成部分&#xff0c;通过集中式管理和创新的技术手段&#xff0c;为工地提供了全方位的用电安全保障。 针对工地临时用 的现状及系统结构&#xff0c;力安科…...

JumpServer开源堡垒机与爱可生云树数据库完成兼容性认证

近日&#xff0c;中国领先的开源软件提供商FIT2CLOUD飞致云宣布&#xff0c;JumpServer开源堡垒机已经完成与爱可生云树数据库软件的兼容性认证。经过双方联合测试&#xff0c;云树数据库软件&#xff08;简称&#xff1a;ActionDB&#xff09;V1.0与杭州飞致云信息科技有限公司…...

信息化发展64

信息化体系 信息化代表了一种信息技术被高度应用&#xff0c;信息资源被高度共享&#xff0c;从而使得人的智能潜力以及社会物质资源潜力被充分发挥&#xff0c;个人行为、组织决策和社会运行趋于合理化的理想状态。 1997年召开的首届全国信息化工作会议&#xff0c;对信息化和…...

什么是全媒体整合营销?如何做好全媒体整合营销呢?

互联网发展进入深水区&#xff0c;目前营销大部分工作都与网络有关&#xff0c;网络营销形成各种分支&#xff0c;媒体平台的类型越来越多&#xff0c;如今的互联网发展背景下企业如何做好网络营销呢&#xff1f;小马识途营销顾问团队普遍认为企业当今应该开展的全媒体整合营销…...

系统集成|第十六章(笔记)

目录 第十六章 信息&#xff08;文档&#xff09;和配置管理16.1 文档管理16.2 配置管理 上篇&#xff1a;第十五章、采购管理 下篇&#xff1a;第十七章、变更管理 第十六章 信息&#xff08;文档&#xff09;和配置管理 16.1 文档管理 信息系统项目相关信息&#xff08;文档…...

hive数据库操作,hive函数,FineBI可视化操作

1、数据库操作 1.1、创建数据库 create database if not exists myhive;use myhive;1.2、查看数据库详细信息 desc database myhive;数据库本质上就是在HDFS之上的文件夹。 默认数据库的存放路径是HDFS的&#xff1a;/user/hive/warehouse内 1.3、创建数据库并指定hdfs存…...

信息学奥赛一本通 2075:【21CSPJ普及组】插入排序(sort) | 洛谷 P7910 [CSP-J 2021] 插入排序

【题目链接】 ybt 2075&#xff1a;【21CSPJ普及组】插入排序&#xff08;sort&#xff09; 洛谷 P7910 [CSP-J 2021] 插入排序 【题目考点】 1. 排序&#xff1a; 插入排序 插入排序示例&#xff1a; #include <bits/stdc.h> using namespace std; int main() {int…...

基于微信小程序的民宿短租酒店预订系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…...

Python第二次作业(2)【控制台界面】

要求&#xff1a;使用Python输出五个控制台界面 第一张&#xff1a; 代码如下&#xff1a; print(" 英雄联盟商城登录界面 ") print("~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*") print(" 1.用户登录 &q…...

conda创建环境在Collecting package metadata (current_repodata.json)时报错的解决

conda创建环境在Collecting package metadata (current_repodata.json)时报错的解决 报错信息&#xff1a; Collecting package metadata (current_repodata.json): - ERROR conda.auxlib.logz:stringify(171): Traceback (most recent call last): File “C:\Users\dandelion…...

卤制品配送经营商城小程序的用处是什么

卤制品也是食品领域重要的分支&#xff0c;尤其对年轻人来说&#xff0c;只要干净卫生好吃价格合理&#xff0c;那复购率宣传性自是不用说&#xff0c;而随着互联网发展&#xff0c;传统线下门店也须要通过线上破解难题或进一步扩大生意。 而商城小程序无疑是商家通过线上私域…...

信息化发展65

1.2现代化基础设施 基础设施包括交通、能源、水利、物流等以传统基础设施和信息网络为核心的新型基础设施&#xff0c;在国家发展全局中具有战略性、基础性、先导性作用。统筹推进传统基础设施和新型基础设施建设&#xff0c;打造系统完备、高效实用、智能绿色、安全可靠的现代…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

如何理解 IP 数据报中的 TTL?

目录 前言理解 前言 面试灵魂一问&#xff1a;说说对 IP 数据报中 TTL 的理解&#xff1f;我们都知道&#xff0c;IP 数据报由首部和数据两部分组成&#xff0c;首部又分为两部分&#xff1a;固定部分和可变部分&#xff0c;共占 20 字节&#xff0c;而即将讨论的 TTL 就位于首…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的

修改bug思路&#xff1a; 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑&#xff1a;async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...