C++:多态-虚函数

C++ 中的多态性是面向对象编程中的一个重要概念,它允许在运行时选择不同的函数实现,以适应不同类型的对象。

多态的种类
编译时多态性(Compile-time Polymorphism):也称为静态多态性或早期绑定,指在编译时确定程序应该调用的函数或运算符版本的能力;主要通过函数重载(Function Overloading)和运算符重载(Operator Overloading)来实现。
​
运行时多态性(Runtime Polymorphism):也称为动态多态性或晚期绑定,指在程序运行时根据对象的实际类型来确定调用的函数版本的能力,主要通过虚函数(Virtual Functions)和继承来实现,在运行时根据对象的实际类型来确定调用哪个函数。
​
参数多态性(Parametric Polymorphism):也称为泛型编程(Generic Programming);是指一种通用的编程技术,它允许在编写代码时不指定具体的数据类型,而是以一般的方式编写代码,稍后根据需要使用具体的类型实例化代码。

关于函数重载、运算符重载和继承/虚继承在之前的文章就已经有阐述过了,接下去说一下运行时多态性中的虚函数。

虚函数

虚函数(Virtual Function)是在基类中声明为虚函数的成员函数,它的特点是可以被派生类重写(覆盖)。虚函数为实现运行时多态性提供了基础,它允许在派生类中重新定义基类的函数,并通过基类指针或引用调用时动态地选择调用哪个函数版本。

以下是一个简单的示例:

代码定义了一个基类 Animal 和一个派生类 Cat,其中 CatAnimal 的子类。每个类都有构造函数和析构函数,并且 Animal 类中定义了一个 eat() 函数,Cat 类中重写了这个函数。

//父类
class Animal
{
public:Animal(){};~Animal(){};void eat(){std::cout << "Animal Eat 函数" << std::endl;};
};
​
//派生类
class Cat : public Animal
{
public:Cat(){};~Cat(){};void eat(){std::cout << "cat Eat 函数" << std::endl;};
}
​
int main() {
​Animal * catObj = new Cat;catObj->eat();system("pause");return 0;
}

main() 函数中,创建了一个指向 Cat 对象的 Animal 指针 catObj。这是因为派生类对象可以被赋值给基类指针,因为派生类对象包含了基类对象的所有成员。然后,通过这个指针调用 eat() 函数,因为这是一个Cat对象所以在结果出来之前我会认为运行的时Cat类中的eat()方法,但事实上此时程序的输出内容为:

可以看到此时输出的内容时Animal类中的eat()函数而不是Cat类中的eat()函数,这是由于 eat() 函数在基类中被声明为非虚函数,而派生类中重新定义了这个函数,所以在运行时,尽管 catObj 指向的是 Cat 对象,但实际上调用的是基类 Animal 中的 eat() 函数,而不是派生类 Cat 中的版本。这是因为非虚函数的调用是静态绑定的,编译器在编译时就已经确定了调用的函数版本。

这个结果很明显时不符合我们的预期的,这个时候如果希望运行的是子类中的方法,那么此时我们可以将父类中的eat()函数设置为虚函数:

在 C++ 中,将一个成员函数声明为虚函数的方法是在函数声明前面加上 virtual 关键字。
//父类
class Animal
{
public:Animal(){};~Animal(){};//虚函数virtual void eat(){std::cout << "Animal Eat 函数" << std::endl;};
};
​
//派生类
class Cat : public Animal
{
public:Cat(){};~Cat(){};void eat(){std::cout << "cat Eat 函数" << std::endl;};
}
​
int main() {
​Animal * catObj = new Cat;catObj->eat();system("pause");return 0;
}

将父类中的eat()方法设置为虚函数后,此时再进行程序的运行,得到的结果为:

因为 eat() 函数在基类中被声明为虚函数,而且派生类中重新定义了这个函数,所以在运行时,通过指向派生类对象的基类指针调用 eat() 函数时,实际上会调用派生类 Cat 中的版本。这是因为虚函数的调用是动态绑定的,会根据对象的实际类型来确定调用的函数版本。

虚函数的使用原理涉及到动态绑定(Dynamic Binding)和虚函数表(Virtual Function Table)。
动态绑定:
动态绑定是指在运行时确定应该调用的函数版本,而不是在编译时确定;当通过基类指针或引用调用虚函数时,实际调用的是对象的实际类型对应的函数版本。

虚函数表
虚函数表(Virtual Function Table,简称 vtable)是 C++ 实现运行时多态性的重要机制之一;每个含有虚函数的类都有一个虚函数表,其中存储了指向各个虚函数的指针,当对象被创建时,会包含一个指向正确虚函数表的指针,通过这个指针,程序能够在运行时根据对象的实际类型来确定调用的虚函数版本。

我们们可以描绘出虚函数表的示意图,以便更好地理解虚函数的工作原理。

虚函数表示例:

每个含有虚函数的类都有一个虚函数表,其中存储了指向各个虚函数的指针,其中的指针顺序与虚函数在类定义中的声明顺序相同。要注意:当一个虚函数在父类中声明为虚函数时,它会自动成为子类中的虚函数。

1.Animal类虚函数表
+----------------------------------------+
|             虚函数表 (Animal)          |
+----------------------------------------+
|  指向 Animal::eat() 的指针             |
+----------------------------------------+
|  指向 类中某虚函数 的指针                |
+----------------------------------------+
|               ....                   |
+----------------------------------------+
​
2.Cat类虚函数表
+----------------------------------------+
|             虚函数表 (Cat)             |
+----------------------------------------+
|  指向 Cat::eat() 的指针                |
+----------------------------------------+
|  指向 类中某虚函数 的指针                |
+----------------------------------------+
|                    ....              |
+----------------------------------------+

含有虚函数的类实例化的对象:对象的前四个字节存储空间存储的是指向虚函数表的指针(对指针取值即可获得到对应类虚函数表的地址)

Animal对象
+-----------------------+
|       Animal 对象      |
+-----------------------+
|  指向虚函数表 (Animal) |
+-----------------------+
|    其他成员变量        |
+----------------------+
​
​
Cat对象
+-----------------------+
|         Cat 对象       |
+-----------------------+
|  指向虚函数表 (Cat)     |
+-----------------------+
|    其他成员变量         |
+-----------------------+

现在让我们来解释一下虚函数调用的过程:

int main() {Animal * catObj = new Cat;catObj->eat();system("pause");return 0;
}
  1. 创建对象:首先,我们创建了一个 Cat 类的对象,并将其地址赋给了一个 Animal 类型的指针 catObj。由于 eat() 函数在基类 Animal 中声明为虚函数,因此在 Cat 类的对象中,会包含一个指向正确虚函数表的指针,这个指针会指向 Cat 类的虚函数表。

  2. 调用虚函数:当我们通过 catObj 指针调用 eat() 函数时,编译器会根据指针所指向的对象的实际类型来决定应该调用哪个虚函数版本。然后,程序会使用对象中存储的虚函数表指针来找到正确的虚函数表。

  3. 查找函数指针:在找到了正确的虚函数表后,程序会在虚函数表中查找 eat() 函数对应的函数指针。由于 Cat 类中重写了 eat() 函数,因此在 Cat 类的虚函数表中,指向 Cat::eat() 函数的指针会被存储在相应位置上。

  4. 调用函数:最后,程序会通过找到的函数指针来调用 Cat::eat() 函数,输出 "cat Eat 函数"。

根据上面的虚函数的调用过程和原理我们也可以不使用->调用符号,而通过手动地址寻找得到循行的函数。

int main() {
​Animal * catObj = new Cat;//手动调用虚函数typedef void(*MyEat)();MyEat  myeat = (MyEat)*(int *)*(int *)catObj;myeat();
​delete catObj;system("pause");return 0;
}
1.*(int *)*(int *)catObj;解释:

虚函数表指针通常位于对象的内存布局的开始位置(可能是第一个成员或对象的隐藏成员);

(int *)catObj:这一步是将指向对象的指针 catObj 进行了类型转换,将其转换为 int* 类型指针

*(int *)catObj:接着,我们对转换后的指针进行了解引用操作;根据 C++ 中的指针运算规则,解引用操作会取出指针所指向的虚函数表内存地址处的值。

(int *)*(int *)catObj:将虚函数表内存地址转化为int* 类型指针,此时指针指向虚函数表内存地址。

*(int *)*(int *)catObj:最后,我们对转换后的整数地址进行解引用操作,得到的是该地址存储的值,也就是Cat类虚函数表中的第一个虚函数eat()的函数指针地址。

因为我们通过上述方法获得到了虚函数的函数地址,所以此时需要使用一个函数指针去指向该地址,对该虚函数进行调用。

2.typedef void(*MyEat)();解析:

这段代码定义了一个函数指针类型 MyEat,它可以指向一个没有参数且返回类型为 void 的函数。

void(*MyEat)();:这是一个函数指针的声明。在 typedef 关键字后面,我们声明了一个名为 MyEat 的新类型,它是一个指向函数的指针。括号中的 *MyEat 表示这是一个指针类型,而括号外的 () 表示这个指针所指向的函数的参数列表。(如果指向的函数地址有参数,那么再进行函数指针类型定义的时候也需要跟上参数列表)
3.(MyEat)*(int *)*(int *)catObj;解析:

此时我们将上述获得到的虚函数eat()的函数指针地址(此时类型为整型)类型强制转化为函数指针类型MyEat

4.MyEat myeat = (MyEat)*(int *)*(int *)catObj;解析:

并声明一个函数指针类型MyEat对象myeat,接着将该指针指向虚函数eat()的函数指针地址。

5.myeat();解析:

最后运行myeat()函数获得最后的结果:

最后得到的结果也是cat对象的eat()方法。

再此处下断点,查看函数指针的地址值;

在反汇编窗口查看该地址值的相关汇编代码,可以看到该地址指向的就是Cat类的Eat函数。

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

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

相关文章

【Git】Git学习-13:Gitee和GitLab的使用

学习视频链接&#xff1a;【GeekHour】一小时Git教程_哔哩哔哩_bilibili​编辑https://www.bilibili.com/video/BV1HM411377j/?vd_source95dda35ac10d1ae6785cc7006f365780 流程 1. 创建仓库/已有仓库 2. 克隆到本地/在远程仓库关联 git clone 仓库地址 git remote add 仓库别…

[Kotlin]创建一个私有包并使用

1.创建Kotlin项目 创建项目&#xff1a; 在Android Studio或其他IDE中选择“Create New Project”。选择Kotlin和Gradle作为项目类型和构建系统。指定项目名称和位置&#xff0c;完成设置。 添加依赖: 如果你的库需要额外的依赖&#xff0c;可以在 build.gradle (Module: app…

Axure中继器介绍以及案例分享

中继器是 Axure 中一个比较高阶的应用&#xff0c;它可以让我们在纯静态网页中模拟出类似带有后台数据交互的增删改查的效果。 一、中继器的基本使用方法&#xff1a; 整体流程分为三个步骤 ☆创建中继器 我们先在 Axured画布中拖入一个中继器元件 双击中继器后的效果 打开之…

APP 在华为应用市场上架 保姆级别详细流程

1、作为一名干开发的程序员&#xff0c;第一次能把自己的APP 上架&#xff0c;对自己来说是多么有意义的一项成就 2、创建一个 华为的开发者账号 根据提示填写完注册的信息https://developer.huawei.com/consumer/cn/product/华为开发者产品 | 开发者平台 | 流量变现 | 华为开…

商家制作微信小程序有什么好处?微信小程序的制作有哪些步骤和流程

微信小程序全面指南 微信小程序是微信生态系统中一项革命性的功能&#xff0c;为希望与庞大的微信用户群体互动的企业提供了独特的融合便捷性和功能性的体验。本全面指南深入探讨了微信小程序的世界&#xff0c;强调了其重要性、工作原理以及实际用例&#xff0c;特别是针对企…

CCE云原生混部场景下的测试案例

背景 企业的 IT 环境通常运行两大类进程&#xff0c;一类是在线服务&#xff0c;一类是离线作业。 在线任务&#xff1a;运行时间长&#xff0c;服务流量及资源利用率有潮汐特征&#xff0c;时延敏感&#xff0c;对服务SLA 要求高&#xff0c;如电商交易服务等。 离线任务&…

DOTA-Gly-Asp-Tyr-Met-Gly-Trp-Met-Asp-Phe-NH2,1306310-00-8,是一种重要的多肽化合物

一、试剂信息 名称&#xff1a;DOTA-Gly-Asp-Tyr-Met-Gly-Trp-Met-Asp-Phe-NH2CAS号&#xff1a;1306310-00-8结构式&#xff1a; 二、试剂内容 DOTA-Gly-Asp-Tyr-Met-Gly-Trp-Met-Asp-Phe-NH2是一种重要的多肽化合物&#xff0c;其CAS号为1306310-00-8。该多肽包含一个DO…

Spring_概述

Spring 官网Spring Framework&#xff08;Spring&#xff09;文档位置重点内容Overview 官网 Spring官网 Spring Framework&#xff08;Spring&#xff09; 文档位置 重点 IoC容器AOP&#xff1a;面向切面编程AOT&#xff1a;ahead of time&#xff0c;提前编译Web 框架&…

C++中的异常处理方式

目录 一、异常 二、C语言中对错误的处理 三、C中的异常处理 四、异常的抛出和捕获 五、异常的重新抛出 六、C标准库中的异常体系 七、异常的规范 一、异常 在C中&#xff0c;异常是程序运行期间发生的意外或错误情况。这些情况可能会导致程序无法继续正常执行&#xff0c;…

[译文] 恶意代码分析:1.您记事本中的内容是什么?受感染的文本编辑器notepad++

这是作者新开的一个专栏&#xff0c;主要翻译国外知名安全厂商的技术报告和安全技术&#xff0c;了解它们的前沿技术&#xff0c;学习它们威胁溯源和恶意代码分析的方法&#xff0c;希望对您有所帮助。当然&#xff0c;由于作者英语有限&#xff0c;会借助LLM进行校验和润色&am…

IOT-9608I-L ADC端口的使用(连续采样ADC值)

目录 概述 1 硬件介绍 1.1 认识硬件 1.2 引脚信号定义 2 软件功能实现 2.1 查看iio:device0下的接口信息 2.2 实现连续采样ADC 2.2.1 功能描述 2.2.2 代码实现 2.2.3 详细代码 3 测试 概述 本文主要讲述IOT-9608I-L ADC端口的使用方便&#xff0c;其内容包括板卡上的…

自动化机器学习——贝叶斯优化

自动化机器学习——贝叶斯优化 贝叶斯优化是一种通过贝叶斯公式推断出目标函数的后验概率分布&#xff0c;从而在优化过程中不断地利用已有信息来寻找最优解的方法。在贝叶斯优化中&#xff0c;有两个关键步骤&#xff1a;统一建模和获得函数的优化。 1. 统一建模 在贝叶斯优…

Windows端之Python3.9及以上高版本工程打包得到的exe逆向工程解包得到pyc文件进而得到py文件的流程实现

参考来自 【python逆向 pyc反编译】python逆向全版本通杀_python反编译pyc-CSDN博客https://blog.csdn.net/zjjcxy_long/article/details/127346296Pyinstaller打包的exe之一键反编译py脚本与防反编译_pyinstaller防止反编译-CSDN博客https://blog.csdn.net/as604049322/artic…

嵌入式RTOS面试题目

用过哪些嵌入式操作系统&#xff1f;使⽤RTOS和裸机代码开发有什么区别&#xff08;优缺点&#xff09;&#xff1f; 之前的⼀个项⽬是采⽤裸机代码开发的&#xff0c;写起来还⾏&#xff0c;通过状态机来管理业务逻辑和各种外设。 但是随着外设的增加&#xff0c;任务之间的…

【数学建模】天然肠衣搭配问题

2011高教社杯全国大学生数学建模竞赛D题 天然肠衣&#xff08;以下简称肠衣&#xff09;制作加工是我国的一个传统产业&#xff0c;出口量占世界首位。肠衣经过清洗整理后被分割成长度不等的小段&#xff08;原料&#xff09;&#xff0c;进入组装工序。传统的生产方式依靠人工…

【深度学习】实验1 波士顿房价预测

波士顿房价预测 代码 import numpy as np import matplotlib.pyplot as pltdef load_data():# 1.从文件导入数据datafile D:\Python\PythonProject\sklearn\housing.datadata np.fromfile(datafile, sep )# 每条数据包括14项&#xff0c;其中前面13项是影响因素&#xff0c…

iOS xib布局

1.多次启动发现启动图和截屏的图片不一致,设置launch storyboard 不能到顶部 https://blog.csdn.net/u011960171/article/details/104053696/ 2.multipiler是比例&#xff0c;需要控制顺序1.视图&#xff0c;2父视图&#xff0c;选择宽度比例&#xff0c;默认是1 3.Aspect R…

CSS-伪类选择器

结构伪类选择器 作用&#xff1a;根据元素的结构关系查找元素 分类&#xff1a; 选择器说明元素名:first-child查找第一个元素元素名:last-child查找最后一个元素元素名:nth-child(N)查找第N名元素 <!DOCTYPE html> <html lang"en"> <head><me…

《Beginning C++20 From Novice to Professional》第十章 Function Templates

C Template 基础篇&#xff08;一&#xff09;&#xff1a;函数模板_函数模板的定义及使用-CSDN博客 这篇博客提到模板是泛型编程的基础&#xff0c;把类型也当做参数&#xff0c;这样使得静态类型语言对类型的处理更强大&#xff0c;提高了代码的可重用性&#xff0c;目标和软…

Java 框架安全:Spring 漏洞序列.(CVE-2022-22965)

什么叫 Spring 框架. Spring 框架是一个用于构建企业级应用程序的开源框架。它提供了一种全面的编程和配置模型&#xff0c;可以简化应用程序的开发过程。Spring 框架的核心特性包括依赖注入&#xff08;Dependency Injection&#xff09;、面向切面编程&#xff08;Aspect-Or…