当前位置: 首页 > 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;打造系统完备、高效实用、智能绿色、安全可靠的现代…...

pytho实例--pandas读取表格内容

前言&#xff1a;由于运维反馈帮忙计算云主机的费用&#xff0c;特编写此脚本进行运算 如图&#xff0c;有如下excel数据 计算过程中需用到数据库中的数据&#xff0c;故封装了一个读取数据库的类 import MySQLdb from sshtunnel import SSHTunnelForwarderclass SSHMySQL(ob…...

处理飞书在线文档导出Word后无法自动编号问题

处理飞书在线文档导出Word后无法自动编号问题 解题思路&#xff1a;处理效果处理代码测试数据 最近工作中经常编写一些文档&#xff0c;有些文档需要多人协作完成。这两天需要完成一个可研的初稿&#xff0c;同事使用了飞书的在线文档。第一次使用飞书进行文档协作&#xff0c;…...

C++刷题 全排列问题

C刷题 全排列问题 题目描述思路讲解代码展示 题目描述 思路讲解 代码展示 #include <iostream>using namespace std;const int maxn 11;//P为当前排列&#xff0c;hashTable记录整数x是否已经在P中 int n, P[maxn], hashTable[maxn] {false};//当前处理排列的第index号…...

求数列a+aa+aaa+aaaa+......前n项和,a和n均由输入获得。

求数列aaaaaaaaaa…前n项和&#xff0c;a和n均由输入获得。 输入格式: 输入两个正整数a和n&#xff0c;两个数之间用逗号分隔。 输出格式: 输出"aaaaaaaaaa…和"的形式&#xff0c;详见输出样例。 输入样例: 在这里给出一组输入。例如&#xff1a; 3,6 输出样例:…...

ElementUI之首页导航+左侧菜单->mockjs,总线

mockjs总线 1.mockjs 什么是Mock.js 前后端分离开发开发过程当中&#xff0c;经常会遇到以下几个尴尬的场景&#xff1a; - 老大&#xff0c;接口文档还没输出&#xff0c;我的好多活干不下去啊&#xff01; - 后端小哥&#xff0c;接口写好了没&#xff0c;我要测试啊&#x…...

文心大模型写TodoList项目需求

大模型写TodoList项目需求 提示词 你是一名资深的互联网软件行业产品经理。 现在要设计一个todo-list项目,它有哪些功能和需求? 分条目写出需求大纲。 文心大模型输出 设计一个Todo-list项目时&#xff0c;需要考虑以下功能和需求&#xff1a; 基本功能&#xff1a; 创建任…...

使用applescript自动化trilium的数学公式环境(二)

9.23 ver1 没想到今天很有精神&#xff0c;在玩chatgpt的时候突然想到&#xff0c;为什么不让他帮我写一份代码呢&#xff1f;说干就干。但是&#xff0c;可能是因为我的英语不怎么样&#xff0c;chatgpt生成出来的整个东西实在是菜的抠脚。所以我觉得还是应该自己先想好一个大…...

机器学习与数据挖掘第三、四周

为什么第二周没有呢……因为刚换老师&#xff0c;自学要适应一段时间。 本课程作者之后的学习目标是&#xff1a;实操代码&#xff0c;至少要将作者参加数学建模中用到的数据处理方法都做一遍。 首先&#xff0c;作者复习一下李宏毅老师的两节课程。 机器学习概述 机器学习就…...

黎明加水印微信小程序源码 支持流量主接入

黎明加水印微信小程序源码&#xff0c;支持流量主接入。支持从聊天记录选择文件、相机拍摄、直接选择文件 支持白底、黑底的隐形水印&#xff0c;制作后&#xff0c;通过增加蒙版方能看到水印 纯前端&#xff0c;可嵌入任何项目。 部署教程 1、解压后得到项目文件夹 3、把…...

22 Python的argparse模块

概述 在上一节&#xff0c;我们介绍了Python的datetime模块&#xff0c;包括&#xff1a;datetime模块中一些常用的属性和函数。在这一节&#xff0c;我们将介绍Python的argparse模块。argparse模块是Python的一个标准库&#xff0c;用于编写命令行界面。它可以处理命令行参数和…...

在什么网站上做兼职靠谱/优化大师怎么下载

课程首页地址&#xff1a;http://blog.csdn.net/sxhelijian/article/details/7910565眼见到了期末&#xff0c;同学们各种忙与学期初、学期中完全不同。积累代码行的事情搁置一边可以理解&#xff0c;也不应在此时再抓了。第17周只要求一个项目&#xff0c;另外可以总结整个课程…...

展示型网站建设的标准/做好网络推广的技巧

最近早上地铁上一直看设计模式&#xff0c;代码中却使用不上&#xff0c;今天恰好碰到一个新需求&#xff0c;感觉和发布订阅模式有点相同&#xff0c;借用了一下理念&#xff0c;虽然代码写的很烂&#xff0c;但是还是第一次去试着用设计模式&#xff0c;记录一下成长的第一步…...

乐山市城乡规划建设局网站/济南seo优化公司助力网站腾飞

$http.post()比get方式多的一个参数为发送给后台的值&#xff0c;其他解释参照上一篇关于get方式解析&#xff0c;格式如下&#xff1a; <!DOCTYPE html> <html> <head lang"en"><meta charset"UTF-8"><title></title>…...

做网站的公司哪家强/关键词优化搜索排名

Topic modelling for humans 看着gensim这个标题都觉得霸气&#xff0c;从文本预处理&#xff0c;特征提出到主题聚类等&#xff0c;基本上包含了文本处理的所有功能。Corpora and Vector Spaces 语料与向量空间&#xff0c;这一节主要讲如何把文本从词变为bag-of-word。中文的…...

东莞网站建设lhznkj/seo外包网站

Linux中使用curl命令出现403错误的解决办法 403错误&#xff0c;是网站访问过程中&#xff0c;常见的错误提示&#xff0c;资源不可用&#xff0c; 服务器理解客户请求&#xff0c;但拒绝处理它&#xff0c;通常由于服务器上文件或目录的权限设置导致&#xff0c; 比如IIS或者a…...

北京智能网站建设制作/广州seo代理

上一篇我们主要学习了一维数组的操作&#xff0c;现实场景中&#xff0c;一维数组往往无法满足需要&#xff0c;本关将学习略微复杂的多维数组。多维数组一维数组只有行&#xff0c;二维数组相比一维数组多了列这个维度&#xff0c;而三维数组则类似多个二维数组堆叠在一起&…...