C++当中的多态(三)

  (六)虚表的本质

  其实我们大家应该都已经猜到了:我们虚表的本质就是一个函数指针数组。通过访问这个函数指针数组就可以得到我们想要的虚函数的地址,之后通过这个地址就可以调用我们相应的虚函数。我们这个函数指针数组是以nullptr结尾的,也就是全0。跟上图中显示的效果也完全相同。

  那么我们就可以根据这个特定编写出如下的代码进行验证我们最后一个地址到底是不是我们在派生类当中新加入的虚函数的地址了。所示代码如下:

  经过这种方式就可以打印出我们虚函数表当中所有存储的虚函数了。很明显之后的新加入的指针就是我们在派生类当中创建的虚函数。但是我们在验证的时候需要进行注意:实际上我们的验证是不符合规范的。对于我们类当中成员函数的访问都是通过this指针进行的,但是我们是通过函数的地址进行强制访问,有的编译器会产生报错。提示我们访问的规则不符合规范。在这里我们仅作为了解即可。

  需要我们注意的是:在派生类对基类进行继承的时候,我们的虚函数表也会一同被继承下来。但是这个继承所进行的操作是先创建一个函数指针数组,之后将我们基类当中虚函数表所存储的虚函数的地址全部复制到我们新创建的函数指针数组当中。

  之后经过派生类对构成重写的虚函数地址的覆盖以及新创建虚函数地址的添加操作之后就得到了一张新的虚函数表。我们需要注意的是:虚函数表当中存储的数据可能相同,但是虚函数表的地址并不相同,所以并不是共用一张表。

  对于不同的类来说,虚函数表是不相同的,虚函数表当中存储的数据可能相同。对于一个类所创建的多个不同对象来说,其公用同一张虚函数表。

  (七)虚表在内存当中存储的位置

  那么我们创建好的新的虚函数表存储在什么位置呢?我们先进行思考:存放在栈区?由于我们的很多数据都是存放在栈区的,在栈区当中存储的数据也会进行压栈,很难进行扩大容量等行为。很明显存放在栈区并不是我们想要的结果。

  存放在堆区吗?也不是没有可能,如果是存放在堆区上,那么我们创建虚表的时候进行的操作大概就是向堆区申请一块空间,之后有新的虚函数产生就重新申请一块更大的空间进行存储我们虚函数的地址。但是反复的重新申请又会增加很大的系统负担,如果一下子开辟很多空间又会造成内存空间浪费的现象,还有更好的选择吗?

  存放在静态区吗?存放在静态区的概率似乎比存放在堆区的概率还大。因为我们的成员函数就是存放在一块公共的空间便于我们进行调用的。如果存放在堆区就跟我们成员函数的性质相一致,还不会有堆区跟栈区的困扰。

  存放在常量区吗?对于我们不允许进行修改的数据我们可以将数据存储在常量区当中。思考一下:我们的虚表可以进行修改吗?似乎我们没有对虚表进行修改过,但是在派生类进行重写的时候系统自动改变了我们虚表当中的内容,这样算不算是修改了呢?虚表会存储在常量区吗?

  我们经过代码对上述的疑问进行验证:

  经过验证我们会发现,我们虚表的地址跟我们常量区存储数据的地址更加接近所以虚表的地址应该是存储在常量区上的。

  (八)静态绑定和动态绑定

  所谓的动态绑定跟静态绑定实际上就是我们多态的形式。多态的形式可以分为两种:一种是静态的多态,一种是动态的多态。

  1.静态多态

  对于我们静态的多态来说实际上就是函数的重载。静态的多态在编译的时候就已经确定好了想要调用的函数的地址。多态的形式表现为我们调用相同的函数名,通过传不同参数的形式实现不同的效果。

  2.动态多态

  对于我们动态多态来说其实就是我们本章节讲的多态的形式。动态的多态在运行的时候通过查找相应的虚函数表调用相应的函数地址,进而实现多态的作用。其主要的实现方式就是虚函数的重写。

  (九)纯虚函数和抽象类

  所谓的纯虚函数其实就是在我们设置的虚函数的后面加上=0的形式,这种类型的函数就叫做纯虚函数。含有纯虚函数的类叫做抽象类,抽象类不能实例化产生相应的对象,因此只能通过继承之后才可以使用。所以纯虚函数和抽象类其实是共同存在的,作用就是强制我们进行虚函数的重写。通常情况下我们会将一个没有实际实例的基类设置成为抽象类。所示代码如下:

  我们会发现当我们将基类设置成为抽象类之后,我们定义的全虚函数就会被编译器强制要求重写,如果不进行重写系统就会产生报错。我们尝试将上述的函数进行重写:

  我们会发现经过重写之后代码就可以正常运行了。

  实际上抽象类的作用经常被用来定义某种事务的基本参数,也就是这种事物一定要具有的属性和参数。例如:我们的汽车类就规定了我们不同类型的汽车在创建对象的时候就一定要具有自己的速度信息和容量信息等等。

  (十)多继承当中虚函数的重写

  之前我们说到的继承都是单继承的形式,对于我们的派生类来说只有一个基类,但是我们的继承不仅仅有单继承还有多继承。那么多继承当中的虚函数重写是什么样的呢?

  在单继承当中派生类继承基类之后就会创建一张自己的虚函数表,之后将基类虚函数表当中的数据复制一份,再对我们复制下来的虚函数表进行适当的覆盖和添加,之后就形成了派生类当中的虚函数表。其实再多继承当中进行的操作跟我们单继承当中所进行的操作很相似。

  当我们不对多继承当中的虚函数进行重写的时候就会发现多继承的继承模式实质上跟我们单继承的继承模式完全相同。对于A类我们会创建一张属于A类的虚函数表,对于B类我们会创建一张属于B类的虚函数表。当我们使用C类同时继承A类和B类的时候,我们这两张虚函数表也同时被继承下来。编译器会自动将两张虚函数表当中的虚函数复制一份添加到我们的派生类当中相对应的虚函数表当中。

  当我们对独属于类A或类B当中的虚函数进行重写的时候实际上就是进行两份单继承多态重写的方式。编译器会自动将继承A的虚函数表当中对应的函数地址进行覆盖得到一个完整的派生类虚函数表A,同样的对于读书与B类的虚函数进行重写的时候,也会对相应的函数地址进行覆盖得到一个完整的派生类虚函数表B。对于没有进行重写的虚函数会保持原本基类当中的函数地址不变。

  但是如果多继承基类当中存在多个相同函数名的虚函数,并且我们在派生类当中对该虚函数进行了重写操作的时候就会产生不同的作用。

  首先我们对类A和类B进行相同函数名的虚函数进行重写操作。 之后使用两个两个基类的指针尝试访问构成多态的函数。

  使用基类A的指针进行访问C类重写的函数,调用的应该是C类当中的重写之后的虚函数,使用基类B的指针进行访问C类重写的函数,调用的应该也是C类当中重写之后的虚函数。这两次调用的函数应该相同。我们运行的结果也正如我们预期中的那样产生了两次相同的结果。

  但是通过观察C类当中的虚函数表我们会有新的疑问,为什么我们虚函数表当中对应的位置函数的地址不相同呢?不是调用的是同一个虚函数吗?

  我们可以通过反汇编的形式进行分析出现上述问题的原因。

  通过反汇编的形式我们可以发现:当我们使用类A进行调用虚函数的时候,我们只需要通过一次call指令和一次jump指令就可以跳到我们重写虚函数的位置。但是对于使用类B进行调用虚函数的时候我们得需要通过两次jump函数才可以找到我们重写虚函数的地址。中间的一次jump操作我们进行了一个寄存器当中的减操作。

  根本原因其实是因为在多继承当中类存放的位置不同。

  我们可以发现在C类对象当中由于我们先继承A类所以会先在C类当中构建出一个A类,之后才会构建出我们的B类对象。这也就造成了对于我们的C类对象来说其中的初始地址跟我们C类中的A类的初始地址是相同的。

  当我们使用A类指针去接受一个C类的对象的指针的时候,我们是可以直接进行使用的。但是对于我们的B类来说就不行了,我们需要先计算出前面A类的大小,之后跳过A类的地址才可以得到我们B类的地址。

  而我们中间一次jump操作所带来的寄存器的减操作,实际上就是调整好我们传入的地址,即传入B类当中的this指针。eax是保存this指针的寄存器。我们可以进行验证一下。A类当中仅仅只存储有一个虚函数表,这个是一个指针类型的数据。所以占4个字节。减去刚好等于我们B类在C类当中的位置。如果我们对A类加上一个成员变量的话,我们寄存器的见操作数值也会相应的变化。

  我们会发现经过测试程序运行的现象跟我们预期的结果刚好相符。

  所以在多继承的多态当中对同名虚函数进行重写所对应的虚函数的地址不同,这是为了更正我们传入的对象的地址所进行的封装。当然我们也可以在底层完全隐藏,这样的话也有可能会在窗口上显示相同的虚函数的地址,这仅仅是不同编译器所带来的不同的效果而已。

  (十一)菱形虚拟继承当中的虚函数

  实际上菱形虚拟继承当中的虚函数很简单,仅仅是将两个看起来很复杂的对象进行了嵌套而已。回顾一下菱形继承的存储方式:为了解决数据冗余和二义性的问题。我们将中间的派生类设置为虚继承的形式。被设计成虚继承的类会将基类部分分离出来,由一张虚基表进行管理。而在我们中间的派生类当中仅仅保存着新加的数据和我们的虚基表的地址。在虚基表第二个指针的位置上会记录派生类和基类地址的相对偏移量。

  所以我们的重合的派生类当中会存储两张虚基表,加上一些新添加的数据。

  当我们加上多态的概念之后就变成了复合的形式。

  但是需要我们注意的是:在中间类进行重写的时候,我们的重合类也需要进行重写操作。如果不进行重写的话,那么我们在通过重合的派生类调用函数的时候就会产生歧义。因为我们有两个平等的调用对象,也就是中间类当中的B和C类。因此在中间派生类进行重写的时候,我们重合节点对应的类一定要进行重写,否则系统就会进行报错。

  当我们对重合的派生类进行重写之后程序恢复正常。

  那么我们的虚继承的虚函数表是怎样进行存储的呢?

  当我们在中间的派生类对虚函数进行重写的时候由于新创建的虚函数主体并不相同,所以会创建一个单独的虚函数表进行相应数据的记录。所以中间会生成两张虚函数表。同时我们A的虚函数表被D类所继承,如果D类对A类进行虚函数的重写操作,会直接使用新的虚函数地址覆盖我们原有的虚函数的地址。那么我们进行存储的一共有三张虚函数表。

  综上所述,对于菱形虚拟继承当中的多态来说我们一共需要创建五张表。对于这一部分的知识在实际的生活当中几乎不会被使用,所以我们仅仅作为了解即可。

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

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

相关文章

如何使用python运行Flask开发框架并实现无公网IP远程访问

文章目录 1. 安装部署Flask2. 安装Cpolar内网穿透3. 配置Flask的web界面公网访问地址4. 公网远程访问Flask的web界面 本篇文章主要讲解如何在本地安装Flask,以及如何将其web界面发布到公网进行远程访问。 Flask是目前十分流行的web框架,采用Python编程语…

Vue3如何查看项目是否安装了vue-router路由依赖,及安装方法

查找vue3项目的依赖 如果当前的vue3项目是基于vite构建工具创建的,那么在创建的过程中会询问是否需要安装一些依赖,一般情况下可以根据项目的需求自定义安装。 如果在vue3的项目创建完之后,确实忘记了自己之前都安装过哪些项目的依赖&#…

云服务器部署DB-GPT项目

本文收录于《DB-GPT项目》专栏,专栏总目录: 点击这里。 文章目录 项目介绍 一、登录云服务器 1. 进入控制台 2.点击容器实例(点数字) 二、创建容器实例 1. 等待容器实例创建好,创建好的容器实例如下:…

海康威视相机在QTcreate上的使用教程

文章目录 前言:基础夯实:效果展示:图片展示:视频展示: 参考的资料:遇到问题:问题1:int64 does not问题2:LNK2019配置思路(这个很重要)配置关键图片:配置具体过…

erlang学习: Mnesia Erlang数据库3

Mnesia数据库删除实现和事务处理 -module(test_mnesia). -include_lib("stdlib/include/qlc.hrl").-record(shop, {item, quantity, cost}). %% API -export([insert/3, select/0, select/1, delete/1, transaction/1,start/0, do_this_once/0]). start() ->mnes…

Beyond Homophily Reconstructing Structure for Graph-agnostic Clustering

发表于:ICML23 推荐指数: #paper/⭐ (个人不太喜欢他的行文方法,有部分内容有点让人看不懂) 总结:很常见的套路:构造同配视图异配视图,进行同配传播异配传播,然后利用类对比损失,类重…

【Linux】Linux 可重入函数

文章目录 Linux 可重入函数1. 什么是可重入函数?2. 可重入函数的特点3. Linux 中的可重入函数示例4. 如何编写可重入函数?5. 注意事项 Linux 可重入函数 在编写并发或多线程程序时,理解可重入函数的概念非常重要。可重入函数(Ree…

dp+观察,CF 1864 D. Matrix Cascade

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 https://codeforces.com/problemset/problem/1864/D 二、解题报告 1、思路…

Python | Leetcode Python题解之第394题字符串解码

题目&#xff1a; 题解&#xff1a; class Solution:def decodeString(self, s: str) -> str:def dfs(s, i):res, multi "", 0while i < len(s):if 0 < s[i] < 9:multi multi * 10 int(s[i])elif s[i] [:i, tmp dfs(s, i 1)res multi * tmpmulti…

flex布局子元素设置字体大小导致对齐问题解决

原代码 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head&g…

AI应用 | 超好玩的“汉语新解“ - 文末有Prompt

最近群里玩“汉语新解”的文字卡片贼多 感觉很新颖 本来AI是无法生成固定的图的 但是使用html格式&#xff0c;来生成固定图片的想法还是很不错的 看看效果 使用很简单 把提示词喂给Ai即可 随便一个大模型都可以&#xff0c;比如ChatGPT、通义千问、kimi等等 提示词(Prompt)如下…

llama.cpp本地部署大模型

llama.cpp 是一个C库&#xff0c;用于简化LLM推理的设置&#xff0c;它使得在本地机器上运行大模型&#xff08;GGUF格式&#xff09;成为可能。 官网&#xff1a;https://github.com/ggerganov/llama.cpp 模型库&#xff1a; https://huggingface.co/ HF-Mirror 魔搭社区…

共享IP可以被清理为纯净IP吗?

在网络世界中&#xff0c;IP地址是连接各个网络设备的桥梁&#xff0c;它们扮演着至关重要的角色。共享IP&#xff0c;作为多用户共同使用的IP地址&#xff0c;因其经济性和便利性而广受欢迎。然而&#xff0c;随着网络环境的日益复杂&#xff0c;共享IP也带来了一系列问题&…

基于SpringBoot+Vue+MySQL的房屋租赁管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不可跨域的鸿沟&#xff0c;信息的…

基于SpringBoot+Vue的超市外卖管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

8.Bug流程管理,禅道的使用(包含笔试/面试题)

一、bug的生命周期&#xff08;重点&#xff09; bug的生命周期就是从bug被发现到bug被关闭的整个过程。 1.bug生命周期&#xff1a; 新建&#xff08;提交bug&#xff09; - 指派 - 已解决 - 待验 - 关闭 new&#xff08;新建&#xff09; - assign额的&…

Python语言开发学习之使用Python预测天气

什么是wttr&#xff1f; 使用Python预测天气的第一步&#xff0c;我们要了解wttr是什么。wttr.in是一个面向控制台的天气预报服务&#xff0c;它支持各种信息表示方法&#xff0c;如面向终端的ANSI序列(用于控制台HTTP客户端(curl、httpie或wget))、HTML(用于web浏览器)或PNG(…

SprinBoot+Vue在线考试系统的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质…

React实现虚拟列表的优秀库介绍

在 React 中&#xff0c;有一些优秀的库可以帮助你实现高效的虚拟列表渲染。以下是几个常用的库&#xff1a; 1. react-window react-window 是一个轻量级的虚拟列表库&#xff0c;适用于大多数虚拟列表需求。它提供了简单易用的 API 和良好的性能。 安装 npm install reac…

Rabbitmq中得RPC调用代码详解

文章目录 1.RPC客户端2.RabbitMQ连接信息实体类3.XML工具类 本文档只是为了留档方便以后工作运维&#xff0c;或者给同事分享文档内容比较简陋命令也不是特别全&#xff0c;不适合小白观看&#xff0c;如有不懂可以私信&#xff0c;上班期间都是在得 直接上代码了 1.RPC客户端 …