当前位置: 首页 > news >正文

多态且原理

多态

多态image-20230311114543868

文章目录

  • 多态
    • 多态的定义和条件
      • 协变(父类和子类的返回值类型不同)
        • 函数隐藏和虚函数重写的比较
      • 析构函数的重写
      • 关键字final和override
    • 抽象类
    • 多态的原理
      • 单继承和多继承的虚函数表
        • 单继承下的虚函数表
        • 多继承下的虚函数表

多态的定义和条件

定义:多态是在不同继承关系的对象上,去调用同一函数,从而产生不同的行为。

在继承中构成多态还需要两个条件:

一是被调用的函数必须是虚函数(函数用virtual关键字修饰)。并且要求父类和子类的虚函数符合三同即函数名、参数、返回值类型相同,即为虚函数的重写/覆盖(子类的虚函数重写了父类虚函数)

二是必须是父类的指针、引用去调用虚函数

如图,有个Person类里面实现了一个BuyTicket函数,还有个student类里面也实现了一个BuyTicket函数。此时student类继承了Person类,两者的BuyTicket函数都是虚函数满足函数名、参数、返回值相同,并且在fun函数里参数是用的父类的引用去调用。此时Person和student的BuyTicket就构成了多态。通过不同的对象去调用同一个函数产生了不同的行为!

image-20230310093427991

另外,子类的函数可以不是虚函数,但父类的函数必须是虚函数

image-20230310111016688

协变(父类和子类的返回值类型不同)

三同中,返回值类型可以不同,但要求返回值是父子类关系的一个引用或者指针

image-20230310111956645

image-20230310112309224

就算用的别的类型也可以。这里我创建了一个父子类关系类型A,类型B用来做返回值

image-20230310112716213

函数隐藏和虚函数重写的比较

我们知道,父类和子类的函数名相同就构成了函数隐藏或者重定义。而多态的要求比隐藏更严格,虚函数的重写必须满足三同(函数名、参数、返回值类型相同),其中一个不相同即为函数隐藏。

函数重载函数隐藏/重定义函数重写/覆盖
同一作用域;函数名相同;参数列表不同(参数类型、个数、顺序);返回值不影响不同作用域(父类和子类);函数名相同;参数列表不同时,基类有无virtual修饰都是;参数列表相同时,基类没有virtual修饰是;返回值可以不同不同作用域(父类和子类);函数名相同;参数列表相同;返回值类型相同;基类必须要有virtual修饰;必须是由父类的引用或者指针调用虚函数;返回值类型不同时,返回值类型也必须是父子类关系的指针或者引用—协变

由此可见,多态调用与调用的对象有关,普通调用与调用的对象类型有关

析构函数的重写

这里是普通调用析构函数,目前没什么问题

image-20230310120153798

然而当有父类的指针指向或者父类的引用时,子类的析构函数没有执行,产生了内存泄漏。原因:子类的切片,指针或者引用指向父类那部分,所以子类就只调用了父类的析构函数。

image-20230310120400315

这时候就需要用到函数的重写。

只需要给父类的析构函数加上virtual修饰即可。编译后,编译器对父类和子类的析构函数名称都统一处理成destructor

image-20230310121153535

关键字final和override

前面都介绍的是如何实现函数的重写,那么一个虚函数不想被重写呢?

给虚函数加上关键词final加以修饰表示虚函数不能被重写

image-20230310163252125

那一个类不想被继承呢?

一是构造函数私有

image-20230310163942372

二是用final修饰,即可理解为最后的类

image-20230310164121328

override修饰子类函数可用来在编译期间检查子类函数是否对父类函数完成了重写

image-20230310164459296

抽象类

定义:在虚函数后面写=0,则这个函数为纯虚函数。包括纯虚函数的类称抽象类或接口类。抽象类不能实例化出对象,其子类也不能实例化出对象,除非子类重写了纯虚函数。纯虚函数规定了子类必须重写,即接口继承。

image-20230310171946833

image-20230310172204782

虚函数继承通过与普通函数的继承对比,普通函数继承为实现继承,派生类继承了基类,可以用基类的函数。而虚函数虚函数是一种接口继承。派生类继承的是接口,目的是为了重写,达到多态。

多态的原理

接下来看一个含有虚函数的类的大小

类A里有一个int类型和一个char类型,合计5个字节,加上虚函数dave,虚函数里有虚表指针4个字节(32位系统下),合计9个字节,内存对齐后是12个字节

image-20230310183256489

我们打开调试窗口可以看到有个指针_vfptr

image-20230310183757565

那如果类里多几个虚函数呢??

class A
{
public:virtual void dave1(){}virtual void dave2(){}virtual void dave3(){}
private:int _a;char _b;
};int main()
{A aa;cout << "带有虚函数的类的大小:" << sizeof(aa) << endl;return 0;
}

类里再多的虚函数也只有一个虚表指针,指针指向一个虚函数表,表里存放着指向各个虚函数的指针,该虚表本质上是函数指针数组。

image-20230310185721544

class A
{
public:virtual void Func1(){cout << "A::Func1()" << endl;}virtual void Func2(){cout << "A::Func2()" << endl;}void Func3(){cout << "A::Func3()" << endl;}
private:int _a = 1;
};
class B : public A
{
public:virtual void Func1(){cout << "B::Func1()" << endl;}
private:int _b = 2;
};
int main()
{A a;B b;return 0;
}

这里B类继承了A类,并完成了对虚函数fun1的重写,而没有对A类的虚函数fun2重写。可以看到两个虚函数都继承了下来,但fun1的地址该变了,而fun2的地址没有改变。可以猜测:子类在对父类函数的重写时,是先把父类的虚函数表拷贝一份,然后对要重写的函数进行覆盖。

image-20230310191955572

那普通调用和多态调用的原理有差别吗?

这里ptr指针对fun3函数调用为普通调用,而对fun1函数调用为多态调用

image-20230310193845725

调试时转到反汇编,可以看到普通调用是直接call函数,而多态调用则步骤很多,还用到了各种寄存器。

这里更加应证了普通调用为编译时绑定,即在编译期间就确定了程序的行为,也称静态多态,比如函数重载。

而多态调用为运行时绑定,在程序运行期间根据具体的类型确定程序的行为,调用具体的函数,也称动态多态。

image-20230310194240289

实际上,普通调用时,是根据指针指向的类型进行调用。ptr指向b对象的fun3是A类fun3的切片,跟ptr指向a对象的fun3无异。所以是直接call A类的fun3函数。

而多态调用是根据指针指向对象的类型有关。ptr指向b对象的fun1,**由于fun1是虚函数,该指向虚函数的指针进入了虚数表,那么指针就进入虚数表里找,找到的是类型B对类型A重写的fun1虚函数的指针,那么调用的就是重写的fun1函数,注意该切片部分是被重写的!**而ptr指向a对象的fun1也是进入虚数表里找,找到的调用的即是fun1虚函数本身。

而多态能完成指向谁调用谁其根本就是由于虚数表。

那虚表在哪里呢?

找到虚表存放的第一个指针的地址就能找到虚表的位置。

int main()
{int a = 0;cout << "栈:" << &a << endl;int* p1 = new int;cout << "堆:" << p1 << endl;const char* str = "hello world";cout << "代码段/常量区:" << (void*)str << endl;static int b = 0;cout << "静态区/数据段:" << &b << endl;A aa;cout << "虚表:" << (void*)*((int*)&aa) << endl;return 0;}

image-20230310201104960

通过测试,可以看到虚表的位置离代码段和静态区很近

并且同个类型的虚表是共享的。

image-20230310201806013

单继承和多继承的虚函数表

单继承下的虚函数表

接下来来看派生类对象的虚数表模型

typedef void(*vfptr)();//定义了函数指针
void PrintVtalbe(vfptr vtable[])//传函数指针数组
{for (int i = 0; vtable[i] != nullptr; i++){printf("[%d]:%p->", i, vtable[i]);vtable[i]();    }cout << endl;
}
class A {
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }
private:int _a;
};
class B :public A {
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }
private:int _b;
};
int main()
{A a;B b;vfptr* vtab1 = (vfptr*)(*((void**)&a));vfptr* vtab2 = (vfptr*)(*((void**)&b));PrintVtalbe(vtab1);PrintVtalbe(vtab2);return 0;
}

通过调试窗口,可以看到b对象只有继承下来的fun1和fun2而没有fun3

image-20230311095526878

通过调用内存窗口可以看到,b对象的第二个地址和a对象的第二个地址相同,推测那个就是fun2,而a对象的第三个地址就是空,b对象的第四个地址才是空,可以推测虚数表是以空结尾。那么b对象的第三个地址就是fun3,fun3进虚数表但是不在调试窗口上显示!

image-20230311095816467

通过打印就可以得到虚数表地址了

image-20230311100052995

多继承下的虚函数表

多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

另外,inline函数可以是虚函数吗?

inline在调用的展开,也就没有了地址,inline函数没有地址放到虚数表里。但多态调用inline函数是可以编译通过的,但忽略了inline的特性;而普通调用仍保持inline特性。

静态成员可以是虚函数吗?

不可以!静态成员没有this指针,且静态成员本身就不能实现多态。

构造函数可以是虚函数吗?
虚数表指针是在初始化列表时初始化,构造函数若是虚函数则虚数表无法初始化。

对象访问普通函数和虚函数谁更快?

如果是普通调用,则一样快。但如果是多态调用,则普通函数更快。运行时调用虚函数需要到虚数表里面去查找函数地址。

虚函数表在编译阶段生成,但虚函数表指针在运行时构造函数列表初始化。

相关文章:

多态且原理

多态 文章目录多态多态的定义和条件协变&#xff08;父类和子类的返回值类型不同&#xff09;函数隐藏和虚函数重写的比较析构函数的重写关键字final和override抽象类多态的原理单继承和多继承的虚函数表单继承下的虚函数表多继承下的虚函数表多态的定义和条件 定义&#xff1…...

动态库(二) 创建动态库

文章目录创建动态库设计动态库ABI兼容动态符号的可见性示例控制符号可见性通过-fvisibility通过strip工具删除指定符号创建动态库 在Linux中创建动态库通过如下过程完成&#xff1a; gcc -fPIC -c first.c second.c gcc -shared first.o second.o -o libdynamiclib.so 按照Li…...

opencv加水印

本文介绍opencv给图片加水印的方法。 目录1、添加水印1.1、铺满1.2、在指定区域添加1.3、一比一铺满1、添加水印 添加水印的原理是调低两张图片的透明度&#xff0c;然后叠加起来。公式如下&#xff1a; dst src1 * opacity src2 * (1 - opacity) gamma; opacity是透明度&a…...

Flume基操

Flume概述 Flume 定义 Flume 是 Cloudera 提供的一个高可用的&#xff0c;高可靠的&#xff0c;分布式的海量日志采集、聚合和传输的系统。Flume 基于流式架构&#xff0c;灵活简单。 Flume最主要的作用就是&#xff0c;实时读取服务器本地磁盘的数据&#xff0c;将数据写入到…...

图文详解红黑树,还有谁不会?

前言在MySQL中&#xff0c;无论是Innodb还是MyIsam&#xff0c;都使用了B树作索引结构(这里不考虑hash等其他索引)。本文将从最普通的二叉查找树开始&#xff0c;逐步说明各种树解决的问题以及面临的新问题&#xff0c;从而说明MySQL为什么选择B树作为索引结构。目录一、二叉查…...

多目标遗传算法NSGA-II原理详解及算法实现

在接触学习多目标优化的问题上&#xff0c;经常会被提及到多目标遗传算法NSGA-II&#xff0c;网上也看到了很多人对该算法的总结&#xff0c;但真正讲解明白的以及配套用算法实现的文章很少&#xff0c;这里也对该算法进行一次详解与总结。会有侧重点的阐述&#xff0c;不会针对…...

Spark 键值对RDD的操作

键值对RDD&#xff08;Pair RDD&#xff09;是指每个RDD元素都是&#xff08;key&#xff0c;value&#xff09;键值对类型&#xff0c;是一种常见的RDD类型&#xff0c;可以应用于很多的应用场景。 一、 键值对RDD的创建 键值对RDD的创建主要有两种方式&#xff1a; &#x…...

【SpringCloud】SpringCloud详解之Feign远程调用

目录前言SpringCloud Feign远程服务调用一.需求二.两个服务的yml配置和访问路径三.使用RestTemplate远程调用(order服务内编写)四.构建Feign(order服务内配置)五.自定义Feign配置(order服务内配置)六.Feign配置日志(oder服务内配置)七.Feign调优(order服务内配置)八.抽离Feign前…...

文档团队怎样使用GIT做版本管理

有不少小型文档团队想转结构化写作和发布&#xff0c;但是因为有限的IT技能和IT资源而受阻。本文为这样的小型文档团队而准备&#xff0c;描述怎样使用Git做内容的版本管理。 - 1 - 为什么需要版本管理 当一个团队进行协同创作内容时&#xff0c;有以下需要&#xff1a; 在对…...

【java】Java中-> 是什么意思?

先看一个例子 EventQueue.invokeLater(() -> {JFrame frame new ImageViewerFrame();frame.setTitle("ImageViewer");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setVisible(true);}); // 上面那一段可以看成如下: EventQueue.invokeLater(ne…...

网络类型部分实验

1.实验思路&#xff1a; 首先用DHCP 给四台PC配置上地址&#xff0c;配置成功后 其次底层IP地址的下发完成的同时&#xff0c;进行检测是否可以ping通 接着进行R1和R5之间使用PPP的PAP认证&#xff0c;R5为主认证方 主认证方ISP 被认证方R1 其次进行R2和R5使用PPP的CHAP认证&am…...

java教程--函数式接口--lambda表达式--方法引用

函数式接口 介绍 jdk8新特性&#xff0c;只有一个抽象方法的接口我们称之为函数接口。 FunctionalInterface ​ JDK的函数式接口都加上了FunctionalInterface 注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法&#xff0c;都是函数式接口。 如在Comparato…...

java——代理

什么是代理&#xff1a; 给目标对象一个代理对象&#xff0c;由代理对象控制着对目标对象的引用 为什么使用代理&#xff1a; ①&#xff1a;功能增强&#xff1a;通过代理业务对原有业务进行增强 ②&#xff1a;用户只能同行过代理对象间接访问目标对象&#xff0c;防止用…...

kubernetes中service探讨

文章目录序言kube-proxy代理模型userspace代理模型iptables代理模型ipvs代理模型修改代理模型Service资源类型ClusterIPNodePortLoadBalancerExternalName应用Service资源应用ClusterIP Service资源应用NodePort Service资源应用LoadBalancer Service资源外部IP序言 在Kuberne…...

Python3实现“美颜”功能

导语利用Python实现美颜。。。这是之前在GitHub上下载的一个项目。。。似乎有些日子了。。。所以暂时找不到原项目的链接了。。。今天抽空看了下它源代码的主要思想&#xff0c;似乎挺简单的。。。于是决定用Python3自己复现一下。。。T_T感觉还是挺有趣的。。。Just have a tr…...

【创建“待选项”按钮02计算坐标 Objective-C语言】

一、之前,我们已经把“待选项”按钮,创建好了,但是唯一的问题是,坐标都是一样的,所以都显示在一起了 1.下面,我们来设置一下,这些“待选项”按钮的坐标, 现在,“待选项”按钮的坐标,是不是都在同一个位置啊, 回忆一下,这个待选项按钮,是怎么生成的, 首先,是在…...

自组织( Self-organization),自组织临界性(Self-organized criticality)

文章目录1. 自组织概述原则历史按领域物理化学生物学2. 自组织临界性概述3. 自组织临界性的特征4. 自组织临界模型5. 自然界中的自组织临界6. 自组织临界性和优化7. 自组织临界性的控制7.1 方案7.2 应用1. 自组织 wiki: Self-organization 图 200 C 水热处理过程中微米级 Nb3O…...

Elasticsearch:集群管理

在今天的文章中&#xff0c;我们应该学习如何管理我们的集群。 备份和分片分配是我们应该能够执行的基本任务。 分片分配过滤 Elasticsearch 将索引配到一个或多个分片中&#xff0c;我们可以将这些分片保存在特定的集群节点中。 例如&#xff0c;假设你有多个数据集群节点&am…...

华为OD机试题 - 非严格递增连续数字序列(JavaScript)| 机考必刷

更多题库,搜索引擎搜 梦想橡皮擦华为OD 👑👑👑 更多华为OD题库,搜 梦想橡皮擦 华为OD 👑👑👑 更多华为机考题库,搜 梦想橡皮擦华为OD 👑👑👑 华为OD机试题 最近更新的博客使用说明本篇题解:非严格递增连续数字序列题目输入输出示例一输入输出说明Code解题…...

lc23. 合并K个升序链表

题目描述给你一个链表数组&#xff0c;每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。示例 1&#xff1a;输入&#xff1a;lists [[1,4,5],[1,3,4],[2,6]]输出&#xff1a;[1,1,2,3,4,4,5,6]解释&#xff1a;链表数组如下&…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程&#xff0c;然后使用强化学习的Actor-Critic机制&#xff08;中文译作“知行互动”机制&#xff09;&#xff0c;逐步迭代求解…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)

LeetCode 3309. 连接二进制表示可形成的最大数值&#xff08;中等&#xff09; 题目描述解题思路Java代码 题目描述 题目链接&#xff1a;LeetCode 3309. 连接二进制表示可形成的最大数值&#xff08;中等&#xff09; 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...