【C++】多态/虚表

news2024/9/27 12:17:33

目录

一、概念

二、虚表工作/运行原理

1.虚函数在一个类内存储的大小

2.对虚函数的访问(一维数组)

3.单继承

(1)虚函数继承情况

(2)单继承存储的大小

(3)基类子类调用情况

4.多继承

(1)存储的大小

(2)继承情况

(3)对虚函数的访问(二维数组)

三、多态

1.多态的条件

2.联编/绑定/捆绑

3.使用


一、概念

多态的4种状态:重载多态、包含多态、参数多态、强制多态

  • 重载多态:函数重载与运算符重载
  • 包含多态:含有虚函数的多态
  • 参数多态:模板——类模板、函数模板
  • 强制多态:强制类型转化——static_cast,cost_cast...

二、虚表工作/运行原理

1.虚函数在一个类内存储的大小

        如果一个类包含了虚函数,不管有多少的虚函数,则增加列一个指针的大小,该指针vptr叫虚指针,它指向一个虚表,该虚表中存放虚函数入口地址

  • 64位:8字节(指针大小)
  • 32位:4字节(指针大小)

如下A类,sizeof(A)= 4,无论几个f都是4个字节

普通函数不占类的空间大小 Class{ void fun()}; sizeof(A)=1

空类大小为1 Class A{}; sizeof(A)=1

 

2.对虚函数的访问(一维数组)

        类内有虚指针vptr,指针指向虚表vtable,虚表内存储本类中函数入口地址,存储的函数叫虚函数vfptr(f:函数)。对虚函数的范围可以通过先查找到虚表,然后在表内以数组名+下标的形式查数得到函数。

  • 存储:虚指针->虚表->虚函数
  • 访问:虚表->数组+下标

以如下形式进行范围:

int main()
{
    A a;
    typedef void(*FUN)();
    FUN pf=NULL;
    FUN* (*((int*)(*((int*)&a)));
    pf();
    FUN* (*((int*)(*((int*)&a))+1);//对下一个的范围
    pf();
}

解析:

  • 函数指针:FUN
  • pf函数指针类型,指向返回值为void 无参的函数FUN
  • a是A类型的对象
  • &a:取地址
  • (int*)&a 原A*类型转换为int*类型
  • *((int*)&a) 解引用,a的内容,虚表的地址
  • (int*)(*((int*)&a)) 强制转换类型
  • *((int*)(*((int*)&a)) 取内容,虚表首地址->第一个元素的地址——函数名
  • FUN*  (*((int*)(*((int*)&a)) )强转为FUN类型

3.单继承

(1)虚函数继承情况

继承情况与普通函数的一致,如下代码所示,B继承A,在B内对fa重写,gb是B自己的:

B继承流程:

  1. 全盘接收。B把A的虚表继承过来——内有三个x虚函数,A::fa,A::fb,A::bc
  2. 改写。同名同参虚函数fa被重写/覆盖,把内容修改为B::fa
  3. 添加。在后面添加B::gb,B::hb

=》B内总共5个函数:fa,fb,fc,gb,hb

也就是说,继承含有虚表时,该继承继承该改写改写与一般无虚表的继承无差别。

(2)单继承存储的大小

无论继承了多少个虚函数,还是一个指针的大小

(3)基类子类调用情况

B b;
b.fa;

👆输出的结果是B::fa

void test(A a)//子类对象不能接收基类,基类可接收子类(基类少,子类多)
{
    a.fa();
}
void main()
{
    B b;
    test(b);//将子类对象传给基类
}

👆此时输出结果为A::fa,因为此处没有产生多态。(多态在下面讲)

4.多继承

(1)存储的大小

存储大小为:继承的类个数*一个指针的大小

如下代码,sizeof(D)=12:3*4

class A
{
public:
    virtual void fa(){cout<<A::fa"<<endl;}
    virtual void ha(){cout<<A::ha"<<endl;}
}
class B
{
public:
    virtual void fb(){cout<<B::fb"<<endl;}
    virtual void hb(){cout<<B::hb"<<endl;}
}
class C
{
public:
    virtual void fc(){cout<<C::fc"<<endl;}
    virtual void hc(){cout<<C::hc"<<endl;}
}
class D
{
public:
    virtual void fd(){cout<<D::fd"<<endl;}
    virtual void hd(){cout<<D::hd"<<endl;}
}

单继承与多继承的字节大小?

  • 单继承时,无论内部几个虚表都是一个指针的大小
  • 多继承时,继承n个类,大小为n*指针的大小。有几个类就有几个虚指针

(2)继承情况

①子类的虚表跟在哪个子类的虚表后面?

  • 谁先继承就先跟在哪,D先继承A,就把D的虚表挂在A虚表后面,查看继承下来A的虚表内个数:

 如下图所示:

  • 其中A,B,C的虚表为:
  • D的虚表为:
  • 三个虚指针vptr指向三个虚表(基类有几个就有几个虚表)

因此,子类内有虚函数时,会把子类新添加的虚函数挂到第一个父类的虚函数后面

  • 笔试题如果问:该例虚表的运行原理?
  • 可以用上面代码+画图+一些语言描述回答

②如果基类们ABC有相同的函数f,D内重写f函数,重写了哪些类的?

基类的同名同参函数都会被改写

(3)对虚函数的访问(二维数组)

int main()
{
    D d;
    FUN pf=NULL;
    FUN* (*((int*)(*((int*)&d)));//0行0列
    pf();
    FUN* (*((int*)(*((int*)&a))+1);//0行1列
    pf();
    FUN* (*((int*)(*((int*)&a))+2);//0行2列
    pf();
    FUN* (*((int*)(*((int*)&a))+3);//0行3列
    pf();
    FUN* (*((int*)(*((int*)&a)+1));//1行0列
    pf();
    FUN* (*((int*)(*((int*)&a)+1)+1);//1行1列
    pf();
    FUN* (*((int*)(*((int*)&a)+2));//2行0列
    pf();
...
}

总结:

  • 对于多继承,在子类的对象中,每个父类都有自己的虚表,将最终子类的虚函数放在第一个父类的虚表中,使得不同父类类型指针的指向清晰。
  • 如果在子类中重写了父类们中的同名同参虚函数,那么虚表中同样修改。
  • 单继承时,无论内部几个虚表都是一个指针的大小
  • 多继承时,继承n个类,大小为n*指针的大小。有几个类就有几个虚指针

三、多态

1.多态的条件

  • 覆盖/重写(两个类之间必须是父子关系、最少两个类)
  • 同名同参虚函数
  • 基类指针或者引用指向基类对象或者派生类对象

2.联编/绑定/捆绑

定义:将函数名和函数调用联系的过程

联编种类:
早捆绑、晚捆绑

早期联编、晚期联编

早——编译时(编译:检查语法错误)

晚——运行时(运行:检查逻辑错误)

3.使用

如下代码中 ​​​​​​编译时输出:A::fn,B::fn

 

 如下代码输出为: A::fn A::fn

因为参数类型A类,aa:A类,因此用aa调用fn就是A类内fn

这是在编译时确定了调用类型,而不是通过参数传递,也就是说这种情况下没有联编上。那么如何联编上?

不能通过值传递--拷贝构造复制,没有传递b参数本身,需要修改为引用与指针

①修改为引用:

 

②指针

这种不同对象调用不同函数的现象,就叫多态

如果不是虚函数,就没有多态,结果都是A::fn。B内有2个fn,调用的是隐藏的。

为什么呢?

因为只有是虚函数才查虚表,不是的话就直接调基类内的函数

在调用时,被覆盖就调用子类的,没覆盖就基类的

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

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

相关文章

Actipro Windows Forms Controls 22.1.3 注册版

Actipro Windows Forms Controls 窗体控件 一组用于构建漂亮的 Windows 窗体桌面应用程序的 UI 控件 语法编辑器 语法高亮代码编辑器控件和解析套件。 为您自己的应用程序带来类似于 Visual Studio 的强大代码编辑体验&#xff0c;以及流行代码编辑器中的所有高级功能。大多数流…

二次封装 Spring Data JPA/MongoDB,打造更易用的数据访问层

本文正在参加「金石计划 . 瓜分6万现金大奖」 最近我在做一个新项目&#xff0c;由于我们项目组一直使用的是 MongoDB 数据库&#xff0c;所以新项目我就打算上 Spring Data MongoDB 尝试一下&#xff0c;虽然我早就用过了 Spring Data JPA&#xff0c;对 Spring Data 的相关 …

第四章《类与对象》第3节:方法的重载

方法的重载是Java语言中一项非常重要的机制。Java语言因为有了重载机制,使得程序员定义和调用方法都变得更加轻松。 4.3.1方法重载的概念及实现原理 4.2小节的Person类中定义了计算2个整数之和的add()方法,如果程序员为add()方法传递两个double型参数,则会因参数类型不兼容…

期末前端web大作业——HTML+CSS+JavaScript仿京东购物商城网页制作(7页)

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

聊一聊我对Restful的理解

概念 REST原则提倡按照HTTP的语义使用HTTP&#xff0c;如果一个系统符合REST原则&#xff0c;我们就说这个系统是Restful风格的。Restful是Web API设计中非常重要的一个概念&#xff0c;但是很多开发人员对于Restful的理解存在误区。 什么是Restful 在说什么是Restful 之前&…

数据结构之基数排序

基数排序 先把各个数以个位数不同分到不同的队列中 如果一个队列多个元素用链表连起来 第一趟分配 然后进行第一趟收集 应为我们想得到递减 所以我们从个位数高到低收集 然后第二趟分配 根据第一趟得到的结果 以十位数分配 这里注意&#xff01; 因为第一趟按个位分的&am…

ImmunoChemistry艾美捷高级钙素AM细胞活力试剂盒方案

ImmunoChemistry艾美捷ICT的Advanced Calcein AM Cell Viability Kit将Calcein AM与7-AAD相结合&#xff0c;可轻松同时标记单个样本中的活细胞、膜受损细胞和死细胞。钙黄绿素AM用于检测绿色荧光的活细胞&#xff0c;而7-AAD用于检测红色荧光的坏死或晚期凋亡细胞。可以使用流…

超详细curl新增支持openssl(https协议)支持

1、问题环境&#xff1a; os&#xff1a;Linux kali 5.5.0-kali2-amd64 #1 SMP Debian 5.5.17-1kali1 (2020-04-21) x86_64 GNU/Linux 2、涉及组件&#xff1a; curl当前版本&#xff1a;curl 7.85.0 openssl当前版本&#xff1a;OpenSSL 3.0.7 1 Nov 2022 (Library: OpenS…

学生个人网页设计作品 学生个人网页模板 简单个人主页成品 个人网页制作 HTML学生个人网站作业设计

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

遥感SCI期刊汇总

1. BOLETIM DE CIENCIAS GEODESICAS 《大地测量科学通报》http://ojs.c3sl.ufpr.br/ojs2/index.php/bcgSemiannual &#xff08;注&#xff1a;2008年开始被SCI收录&#xff09;ISSN: 1413-4853UNIV FEDERAL PARANA, CENTRO POLITECNICO, UFPR CENTRO POLITECNICO, CURSO POS…

如何使用云服务器以及宝塔面板快速搭建discuz论坛网站?

前面介绍了很多搭建个人网站&#xff0c;云网盘的方法&#xff0c;这篇文章将介绍使用discuz搭建一个论坛网站&#xff0c;采用的方式为轻量应用服务器搭配宝塔面板一键式部署discuz论坛网站&#xff0c;感兴趣的小伙伴可以跟着我一起搭建起来&#xff01; 说明&#xff1a; 云…

无线传感器网络:数据链路层,MAC

文章目录FramingByte CountFlag Byte MethodByte StuffingMedium Access Control (MAC)Static Channel AllocationDynamic Channel AllocationIndependent TrafficSingle ChannelObservable CollisionsContinuous or Slotted TimeCarrier Sense or No Carrier SensePure ALOHAS…

react的useState源码分析

前言 简单说下为什么React选择函数式组件&#xff0c;主要是class组件比较冗余、生命周期函数写法不友好&#xff0c;骚写法多&#xff0c;functional组件更符合React编程思想等等等。更具体的可以拜读dan大神的blog。其中Function components capture the rendered values这句…

JWT有状态登陆与无状态登陆

单点登录与JWT JWT 全称&#xff1a; Json Web Token 。作用&#xff1a; JWT 的作用是 用户授权(Authorization) &#xff0c;而不是用户的身份认证(Authentication) 。用户认证 指的是使用用户名、密码来验证当前用户的身份&#xff0c;即用户登录。用户授权 指用户登录成功后…

「从零单排canal 07」 parser模块源码解析

本文将对canal的binlog订阅模块parser进行分析。 parser模块(绿色部分)在整个系统中的角色如下图所示&#xff0c;用来订阅binlog事件&#xff0c;然后通过sink投递到store. parser模块应该来说是整个项目里面比较复杂的模块&#xff0c;代码非常多。 因此&#xff0c;本文根…

Nmap爆破MySQL弱口令漏洞:解决报错Accounts: No valid accounts found

nmap工具不仅仅能扫描&#xff0c;也可以暴力破解mysql&#xff0c;ftp&#xff0c;telnet等服务。 看到这里不要怀疑&#xff0c;在kali系统中查一下到底支持哪些暴力破解功能&#xff0c;命令如下 ls /usr/share/nmap/scripts |grep brute.nse 查询结果为 afp-brute.nse …

镜像底层原理详解和基于Docker file创建镜像

目录 一、镜像底层原理 1.联合文件系统(UnionFS) 2.镜像加载原理 3.为什么Docker里的centos的大小才200M? 二、Dockerfile 1.简介 2.Dockerfile操作常用命令 &#xff08;1&#xff09;FORM 镜像 &#xff08;2&#xff09;MAINTAINER 维护人信息 &#xff08;3&…

Vue的devtools安装教程

devtools是一个便于开发者调试Vue代码的插件 先确保你已经安装了node.js 点击此处去github上拉取工具包 安装yarn&#xff08;用npm在打包的时候会失败&#xff0c;使用yarn可以打包成功&#xff09; ① cmd输入&#xff1a;npm install -g yarn 全局安装yarn包管理工具   …

Nature Plants|植物基因组测序20年回顾与展望:三代HiFi基因组时代

2021年11月29日&#xff0c;美国密歇根州立大学在《Nature Plants》期刊在线发表题为“Representation and participation across 20 years of plant genomesequencing”综述&#xff0c;系统阐述了在过去的20年间&#xff0c;对陆地植物基因组学组装质量、已测序物种的分类和地…

Java的几大常用类

一、Object类 超类、基类&#xff0c;所有类的直接或者间接父类&#xff0c;位于继承树的最顶层。 任何类&#xff0c;如果没有写 extends 显示继承某个类&#xff0c;都直接默认继承 Object 类&#xff0c;否则为间接继承。 Object 类中所定义的方法&#xff0c;是所有对象…