C++入门12——详解多态2

上篇文章(C++入门12——详解多态1)中,我们介绍了C++多态的概念和用法,但是只知其然而不知其所以然是万万不行的,所以本篇文章将从探案的角度详细介绍多态的原理。


 1. 虚函数表

想要弄懂多态的原理,首先要了解一下虚函数表。

先来做一道笔试题:下面代码的运行结果是多少?

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};int main()
{Base b;cout << sizeof(Base) << endl;return 0;
}

(vs2019下的x86程序)答案是:8字节

我们把上述代码的virtual关键字注释掉得到的答案却是:4字节

这是为什么呢?打开监视窗口看一下b对象:

发现b对象里不仅存在_b,还有一个_vfptr,这个_vfptr是什么东西呢?

其实,多出的一个_vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。

为了更好的理解这段话中的概念,在如上代码的基础上,我们再添加一个虚函数Func2和一个正常函数Func3:

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}virtual void Func2(){cout << "Func2()" << endl;}void Func3(){cout << "Func3()" << endl;}
private:int _b = 1;
};

 

知道了基类的虚函数表里面存放了什么,下面我们再来探究一下派生类的这个表里面又存放的什么:

在此代码的基础上:①增加一个派生类Base1继承基类Base;②Base1中重写Func1

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}virtual void Func2(){cout << "Func2()" << endl;}void Func3(){cout << "Func3()" << endl;}
private:int _b = 1;
};
class Base1:public Base
{
public:virtual void Func1(){cout << "子类Func1()" << endl;}
private:int _d = 2;
};
int main()
{Base b;Base1 d;return 0;
}

从监视的结果来看:

①子类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员(Func1、Func2、_b),虚表指针也正是存这部分的,另一部分是自己的成员(_d);

②基类b对象和派生类d对象虚表是不一样的,这里的原因是Func1完成了重写,所以d的虚表中存的是重写的Base1::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法;

③另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但不是虚函数,所以不会放进虚表;

④虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr; 

派生类虚表的生成总结:

a.先将基类中的虚表内容拷贝一份到派生类虚表中;

b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数;

c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。


另外需要分清的是:虚函数、虚函数表都是存在哪里的(堆、栈、常量区)?

既然我们知道常量是存在代码区(常量区);静态变量是存在静态区;局部变量存在栈区;动态开辟的变量存在堆区,那我就以此设计一个程序看一下虚函数表是存在哪里的:

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}virtual void Func2(){cout << "Func2()" << endl;}void Func3(){cout << "Func3()" << endl;}
private:int _b = 1;
};
class Base1 :public Base
{
public:virtual void Func1(){cout << "子类Func1()" << endl;}
private:int _d = 2;
};
int main()
{Base b;Base1 d;int i = 0;static int j = 0;int* p1 = new int;const char* p2 = "xxxxxxxxx";printf("栈区:%p\n", &i);printf("静态区:%p\n", &j);printf("堆区:%p\n", p1);printf("常量区:%p\n", p2);//虚函数表的地址存在对象b、d的前4个byte上,那怎么取到对象的前4个byte呢?//很显然Base类型无法强转为int类型//那我们就取b、d的地址,将b、d的地址强转为int*//然后再解引用int*,此时就取到了b、d对象的前4个byteBase* p3 = &b;Base1* p4 = &d;printf("Base的虚表地址:%p\n", *(int*)p3);printf("Base1的虚表地址:%p\n", *(int*)p4);return 0;
}

 

很明显,结果显示虚表地址与常量区的地址是最近的,所以我们可以判断出虚表地址是存放在代码段的(常量区)。 

 

⑤注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样,都是存在代码段(常量区)的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。 

如果说虚函数存在虚表,虚表存在对象,那么这种说法就是错误的,正确的说法应该为:

虚函数的指针存在虚表,虚表的指针存在对象!


2. 多态的原理

说了这么个老半天,我还是虚头八脑,多态的原理到底是什么呢?

class Luck_Peo
{
public:virtual void Red_Packet(){cout << "五块红包" << endl;}
};class Unluck_Peo:public Luck_Peo
{
public:virtual void Red_Packet(){cout << "五毛红包" << endl;}
};void Func(Luck_Peo& p)
{p.Red_Packet();
}int main()
{Luck_Peo lp;Unluck_Peo up;Func(lp);Func(up);return 0;
}

在这串代码里:Func函数传lp调用的是Luck_Peo::Red_Packet,传up调用的是Unluck_Peo::Red_Packet,怎么就这么神?怎么就做到指向父类调父类,指向子类调子类?what!?

1. 上图的红色箭头我们看到,p是指向lp对象时,p->Red_Packet在lp的虚表中找到的虚函数是Luck_Peo::Red_Packet。

2. 上图的蓝色箭头我们看到,p是指向up对象时,p->Red_Packet在up的虚表中找到的虚函数是Unluck_Peo::Red_Packet。

3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。

另外我们需要了解的是:

满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中去找的。不满足多态的函数调用时是编译时确认好的。

即:

多态调用:运行时,去虚函数表中找函数的地址进行调用,所以指向父类调的是父类虚函数,指向子类调的是子类虚函数;

普通调用:编译时,通过调用者类型确定函数地址。

 3.动态绑定与静态绑定

1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态, 比如:函数重载;

2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体 行为,调用具体的函数,也称为动态多态

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

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

相关文章

数据结构与算法学习day22-回溯算法-分割回文串、复原IP地址、子集

一、分割回文串 1.题目 131. 分割回文串 - 力扣&#xff08;LeetCode&#xff09; 2.思路 分割回文串可以抽象为一棵树形结构。 递归用来纵向遍历&#xff0c;for循环用来横向遍历&#xff0c;切割线&#xff08;就是图中的红线&#xff09;切割到字符串的结尾位置&#xf…

STM32F407单片机编程入门(十三) 单片机IAP(在应用编程)详解及实战源码

文章目录 一.概要二.STM32F407VET6单片机IAP介绍1.STM32F407VET6单片机IAP基本原理2.STM32F407VET6单片机IAP基本流程 三.配置一个BOOT工程四.配置一个APP工程五.工程源代码下载六.小结 一.概要 STM32单片机程序升级方法有很多种&#xff0c;主要有以下几种&#xff1a; 1.将…

【LeetCode】146. LRU缓存

1.题目 2.思想 3.代码 3.1 代码1 下面这是一版错误的代码。错误的原因在于逻辑不正确导致最后的代码也是不正确的。 class LRUCache:def __init__(self, capacity: int):self.time 0 # 用于全局记录访问的时间self.num2time {} # 数字到时间的映射self.key2val {} # 数字…

如何理解MVCC

MVCC是什么&#xff1f; MVCC&#xff0c;是MultiVersion Concurrency Control的缩写&#xff0c;翻译成中文就是多版本并发控制&#xff0c;多个事务同时访问同一数据时&#xff0c;调控每一个事务获取到数据的具体版本。和数据库锁一样&#xff0c;它也是一种并发控制的解决…

实时同步 解决存储问题 sersync

目录 1.sersync服务 2.sersync同步整体架构 ​编辑 3.rsync服务准备 4.sersync部署使用 5.修改配置文件 6.启动sersync 7.接入nfs服务 8.联调测试 1.sersync服务 sersync服务其实就是由两个服务组成一个是inotify服务和rsync服务组成 inotify服务用来监控那个…

Infineon——TC397 Multicore简介

文章目录 前言一、TC397简介二、命名规则三、多核开发建议 前言 AURIX™ TC3xx微控制器架构具有多达6个独立的处理器内核CPU0…CPU5, 可在一个统一平台上无缝托管多个应用程序和操作系统. 由于实现了具有独立读取接口的多个程序Flash模块, 该架构支持进一步的实时处理. AURIX™…

自学笔记之TVM编译器框架 ,核心特性,模型优化概述,AI应用落地

最近在学习一些和芯片 AI相关的知识&#xff0c;重点了解了一下TVM&#xff0c;我自己认为TVM在AI应用落地类似的项目中&#xff0c;用途还是非常广泛的&#xff0c;现在把一些重要的笔记贴在下面&#xff0c;有两篇原帖链接也附上&#xff0c;感兴趣的同学可以学习一下。 TVM…

小球轻重的测量

设有12个小球。其中11个小球的重量相同&#xff0c;称为好球&#xff1b;有一个小球的重量与11个好球的重量不同&#xff08;或轻或重&#xff09;&#xff0c;称这个小球为坏球。试编写一个算法&#xff0c;用一个无砝码的天平称三次找出这个坏球&#xff0c;并确定其比好球轻…

SpringCloud入门(五)Nacos注册中心(上)

国内公司一般都推崇阿里巴巴的技术&#xff0c;比如注册中心&#xff0c;SpringCloudAlibaba也推出了一个名为Nacos的注册中心。Dynami Naming and Configuration Service。是阿里巴巴2018年7月开源的项目。 Nacos是阿里巴巴的产品&#xff0c;现在是SpringCloud中的一个组件。…

智谱清影 - CogVideoX-2b-部署与使用

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 体验地址&#xff1a;[丹摩DAMODEL官网](https://www.damodel.com/console/overview) CogVideoX 简介本篇将详细介绍使用丹摩服务器部…

网络通信——OSI七层模型和TCP/IP模型

OSI模型 一.OSI七层模型 OSI&#xff08;Open System Interconnect&#xff09;七层模型是一种将计算机网络通信协议划分为七个不同层次的标准化框架。每一层都负责不同的功能&#xff0c;从物理连接到应用程序的处理。这种模型有助于不同的系统之间进行通信时&#xff0c;更…

KamaCoder 103. 水流问题

题目要求 N*M的矩阵&#xff0c;数值代表位置的相对高度。矩阵模拟了一个地形&#xff0c;当雨水落上时&#xff0c;会根据地形倾斜向低处流动。但是只能从较高或等高的地点流向较低或等高并且相邻的地点&#xff0c;我们的目标是确定那些单元格&#xff0c;从这些单元格出发的…

Vue(14)——组合式API①

setup 特点&#xff1a;执行实际比beforeCreate还要早&#xff0c;并且获取不到this <script> export default{setup(){console.log(setup函数);},beforeCreate(){console.log(beforeCreate函数);} } </script> 在setup函数中提供的数据和方法&#xff0c;想要在…

101. 对称二叉树(共含三道leetcode题)

文章目录 101. 对称二叉树递归法迭代法 小结100.相同的树572.另一个树的子树 101. 对称二叉树 101. 对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#…

Administration Console后台弱⼝令登录

1.环境搭建 cd vulhub-master/iboss/CVE-2017-12149 docker-compose up-d 2.访问登录页面 JBoss AS 6 Admin Consolehttp://47.121.211.205:8080/admin-console/login.seam?conversationId4用户名admin 密码vulhub 3.上传war文件 4.访问上传文件并进行连接 访问上传文件 使…

kubectl 执行一条命令之后发生了什么?

kubectl 是与 Kubernetes 集群交互的命令行工具&#xff0c;用户通过它可以对集群资源进行操作和管理。你有没有想过&#xff0c;当我们执行一条 kubectl 命令之后&#xff0c;背后都发生了什么&#xff1f; 详细过程 kubectl -> kube-api-server 根据通信类型&#xff0…

算法题之宝石与石头

宝石与石头 给你一个字符串 jewels 代表石头中宝石的类型&#xff0c;另有一个字符串 stones 代表你拥有的石头。 stones 中每个字符代表了一种你拥有的石头的类型&#xff0c;你想知道你拥有的石头中有多少是宝石。 字母区分大小写&#xff0c;因此 "a" 和 "…

EECS498 Deep Learning for Computer Vision (一)软件使用指南

#最近开始学习深度学习的相关基础知识&#xff0c;记录一下相关笔记及学习成果# learning&#xff1a;building artificial systems that learn from data and experience deep learning(a set of machine learning): hierarchical learning algorithms with many "laye…

制作一个rabbitmq-sdk以及rabbitmq消费者实现定时上下线功能

目录结构 pom.xml <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">&l…

低版本JMX Console未授权

1.环境搭建 cd vulhub-master/jboss/CVE-2017-7504 docker-compose up -d 2.访问漏洞网站 http://47.121.211.205:8080/jmx-console/http://47.121.211.205:8080/jmx-console/ 3.然后找到jboss.deployment (jboss ⾃带得部署功能) 中的flavorURL,typeDeploymentScanner点进 …