C++多态的实现原理
【欢迎关注编码小哥,学习更多实用的编程方法和技巧】
1、类的继承
子类对象在创建时会首先调用父类的构造函数
父类构造函数执行结束后,执行子类的构造函数
当父类的构造函数有参数时,需要在子类的初始化列表中显式调用
Child(int i) : Parent("在子类构造函数中的初始化列表进行显式调用父类构造函数")
析构函数调用的先后顺序与构造函数相反
继承与组合的混搭
类中的成员变量可以是其它类的对象(组合)
口诀:先父母,后客人,再自己。
当子类中定义的成员变量与父类中的成员变量同名时
子类依然从父类继承同名成员
在子类中通过作用域分别符::进行同名成员区分
同名成员存储在内存中的不同位置
cout << "Parent::i = " << Parent::i << endl;
cout << "Child::i = " << Child::i << endl;
cout << "Parent::f = " << Parent::f << endl;
子类对象可以当作父类对象使用
子类对象在创建时需要调用父类构造函数进行初始化
子类对象在销毁时需要调用父类析构函数进行清理
先执行父类构造函数,再执行成员构造函数
在继承中的析构顺序与构造顺序对称相反
同名成员通过作用域分辨符进行区分
2、函数的重写
父类中被重写的函数依然会继承给子类
默认情况下子类中重写的函数将隐藏父类中的函数
通过作用域分辨符::可以访问到父类中被隐藏的函数
Parent *p = &child;
p->print();
Parent& rP = child;
rP.print();
打印输出的是父类的函数。
C++编译器支持静态联编,就是在编译阶段就可以确定下来的多态。
对于动态联编,只有在运行时才能确定的对象类型,编译器是不能作判断的,最稳妥的做法就是使用父类的类型进行操作。
C++与C相同,是静态编译型语言
在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象
所以编译器认为父类指针指向的是父类对象(根据赋值兼容性原则,这个假设合理)
由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象
从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数
面向对象的新需求
根据实际的对象类型来判断重写函数的调用
如果父类指针指向的是父类对象则调用父类中定义的函数
如果父类指针指向的是子类对象则调用子类中定义的重写函数
实现了以上的功能就是面向对象中的多态。
多态
根据运行时实际的对象类型表现出不同的行为状态,叫多态。
C++中通过virtual关键字对多态进行支持
使用virtual声明的函数被重写后即可展现多态特性
虚函数
在父类函数声明前面加上virtual关键字,使其成为虚函数。这时,函数就表现出多态性。
函数重载
必须在同一个类中进行
子类无法重载父类的函数,父类同名函数将被覆盖
重载是在编译期间根据参数类型和个数决定调用函数
函数重写
必须发生于父类与子类之间
并且父类与子类中的函数必须有完全相同的原型
使用virtual声明之后能够产生多态,子类在重写时自动成为虚函数
多态是在运行期间根据具体对象的类型决定调用函数
隐藏:
派生类中的函数与基类中的函数同名并且参数相同,但基类函数不是虚函数
派生类中的函数与基类中的函数同名,参数不同,不管基类函数是否是虚函数,基类函数都会被屏蔽。
child.Parent::func(); //使用作用域分别符可以调用
child.func(); //不可直接调用,因为父类的同名函数被隐藏。
3、C++中多态的实现原理
当类中声明虚函数时,编译器会在类中生成一个虚函数表
虚函数表是一个存储类成员函数指针的数据结构
虚函数表是由编译器自动生成与维护的
virtual成员函数会被编译器放入虚函数表中
存在虚函数时,每个对象中都有一个指向虚函数表的指针VPTR
每一个类都会由编译器自动生成一个虚函数表,而且每个类只有唯一一个表。此类生成的每一个对象里面都隐含着一个指向该表的指针。
调用虚函数时,会通过VPTR指针指向的虚函数表中查询该函数,找到入口地址,并调用。这个过程相对比较耗时,因此执行效率相对比较低,因此,没有必要把所有的函数都设计为虚函数。
对象在创建的时候由编译器对VPTR指针进行初始化
只有当对象的构造完全结束后VPTR的指向才最终确定
父类对象的VPTR指向父类虚函数表
子类对象的VPTR指向子类虚函数表
构造函数中调用虚函数无法实现多态。
纯虚函数
面向对象中的抽象类
抽象类可用于表示现实世界中的抽象概念
抽象类是一种只能定义类型,而不能产生对象的类
抽象类只能被继承并重写相关函数
抽象类的直接特征是纯虚函数
纯虚函数是只声明函数原型,而故意不定义函数体的虚函数。
统一的格式:
virtual 返回类型 函数名(参数列表)= 0;
抽象类与纯虚函数
包含着纯虚函数的类叫抽象类
抽象类不能用于定义对象
抽象类只能用于定义指针和引用
抽象中的纯虚函数必须被子类重写
class Shape{public:virtual double area() = 0;};class Rectangle : public Shape{public:Rectangle(double a, double b){m_a = a;m_b = b;}double area(){return m_a * m_b;}private:double m_a;double m_b;};class Circle : public Shape{private:double m_r;public:Circle(double r){m_r = r;}double area(){return 3.14 * m_r * m_r;}};void func(Shape *s){cout << s->area() << endl;}int main(int argc, char *argv[]){Rectangle rect(3,2);Circle c(4);func(&rect);func(&c);return EXIT_SUCCESS;}多态与数组class Parent{protected:int i;public:virtual void func(){cout << "Parent::func()" << endl;}};class Child : public Parent{protected:int j;public:Child(int a, int b){i = a;j = b;}void func(){cout << "i = " << i << " , j = " << j << endl;}};int main(int argc, char *argv[]){Parent *pp = NULL;Child *pc = NULL;Child ca[] = {Child(1,2),Child(3,4),Child(5,6),Child(7,8)};pp = ca;pc = ca;cout << "sizeof(Parent) = " << sizeof(Parent) << endl;cout << "sizeof(Child) = " << sizeof(Child) << endl;cout << setbase(16) << "pp = " << pp << endl;cout << setbase(16) << "pc = " << pc << endl;pp->func();pc->func();pp++;pc++;cout << setbase(16) << "pp = " << pp << endl;cout << setbase(16) << "pc = " << pc << endl;//pp->func();//pc->func();return EXIT_SUCCESS;}
注:Parent类对象占有8个字节,因为需要维护一个虚函数表的指针也占4个字节。同样的,Child对象除自身的变量j以外,还要从父类继承一个变量i,也要维护一个虚函数表的指针,一共12个字节。
不要将多态应用于数组
指针运算是通过指针的类型进行的 (编译时确定)
多态是通过虚函数表实现的 (运行时确定)
虚基类及多重继承
被实际开发经验抛弃的多继承
工程开发中真正意义上的多继承是几乎不被使用的
多重继承带来的代码复杂性远多于其带来的便利
多重继承对代码维护性上的影响是灾难性的
在设计方法上,任何多继承都可以用单继承代替
为了解决从不同途径继承来的同名数据成员造成的二义性问题 , 可以将共同基类设置为虚基类 。 这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝。
class B : virtual public A
{
};
class C : virtual public A
{
};
这就是虚基类的来源。
C++的接口设计
实际工程经验证明
多重继承接口不会带来二义性和复杂性问题
多重继承可以通过精心设计用单继承和接口代替
接口类只是一个功能说明,而不是功能实现 。
子类需要根据功能说明定义功能实现 。
绝大多数面向对象语言都不支持多继承,但都支持接口的概念
C++中没有接口的概念
C++中可以使用纯虚函数实现接口
class Interface
{
public:
virtual void func1() = 0;
virtual void func2(int i) = 0;
virtual void func3(int i, int j) = 0;
};
接口类中只有函数原型的定义,没有任何数据的定义。
相关文章:
C++多态的实现原理
【欢迎关注编码小哥,学习更多实用的编程方法和技巧】 1、类的继承 子类对象在创建时会首先调用父类的构造函数 父类构造函数执行结束后,执行子类的构造函数 当父类的构造函数有参数时,需要在子类的初始化列表中显式调用 Child(int i) : …...
[极客大挑战 2019]PHP--详细解析
信息搜集 想查看页面源代码,但是右键没有这个选项。 我们可以ctrlu或者在url前面加view-source:查看: 没什么有用信息。根据页面的hint,我们考虑扫一下目录看看能不能扫出一些文件. 扫到了备份文件www.zip,解压一下查看网站源代码…...
map用于leetcode
//第一种map方法 function groupAnagrams(strs) {let map new Map()for (let str of strs) {let key str ? : str.split().sort().join()if (!map.has(key)) {map.set(key, [])}map.get(key).push(str)} //此时map为Map(3) {aet > [ eat, tea, ate ],ant > [ tan,…...
CommonJS 和 ES Modules 的 区别
CommonJS 和 ES Modules 的 区别 1. CommonJS 和 ES Modules 区别?1.1 语法差异CommonJS:ES Modules: 1.2. 加载机制CommonJS:ES Modules: 1.3. 运行时行为CommonJS:ES Modules: 1.4. 兼容性和使用场景Com…...
科技为翼 助残向新 高德地图无障碍导航规划突破1.5亿次
今年12月03日是第33个国际残疾人日。在当下科技发展日新月异的时代,如何让残障人士共享科技红利、平等地参与社会生活,成为当前社会关注的热点。 中国有超过8500万残障人士,其中超过2400万为肢残人群,视力障碍残疾人数超过1700万…...
Flink四大基石之Time (时间语义) 的使用详解
目录 一、引言 二、Time 的分类及 EventTime 的重要性 Time 分类详述 EventTime 重要性凸显 三、Watermark 机制详解 核心原理 Watermark能解决什么问题,如何解决的? Watermark图解原理 举例 总结 多并行度的水印触发 Watermark代码演示 需求 代码演示ÿ…...
Spring WebFlux与Spring MVC
Spring WebFlux 是对 Spring Boot 项目中传统 Spring MVC 部分的一种替代选择,主要是为了解决现代 Web 应用在高并发和低延迟场景下的性能瓶颈。 1.WebFlux 是对 Spring MVC 的替代 架构替代: Spring MVC 使用的是基于 Servlet 规范的阻塞式模型…...
【深度学习基础】一篇入门模型评估指标(分类篇)
🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀深度学习_十二月的猫的博客-CSDN博客 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. 模…...
D80【 python 接口自动化学习】- python基础之HTTP
day80 requests请求加入headers 学习日期:20241126 学习目标:http定义及实战 -- requests请求加入headers 学习笔记: requests请求加入headers import requestsurlhttps://movie.douban.com/j/search_subjects params{"type":…...
⽂件操作详解
⽬录 一 文件操作的引入 1 为什么使⽤⽂件? 2 什么是⽂件? 3 文件分类(1 从⽂件功能的⻆度来分类:程序⽂件/数据⽂件 2根据数据的组织形式:为⽂本⽂件/⼆进制⽂件) 二 ⽂件的打开和关闭 1 …...
双高(高比例新能源发电和高比例电力电子设备)系统宽频振荡研究现状
1 为什么会形成双高电力系统 (1)新能源发电比例增加 双碳计划,新能源革命,可再生能源逐步代替传统化石能源,未来新能源发电将成为最终能源需求的主要来源。 (2)电力电子设备数量增加 为了实…...
TorchMoji使用教程/环境配置(2024)
TorchMoji使用教程/环境配置(2024) TorchMoji简介 这是一个基于pytorch库,用于将文本分类成不同的多种emoji表情的库,适用于文本的情感分析 配置流程 从Anaconda官网根据提示安装conda git拉取TorchMoji git clone https://gi…...
使用 Python 中的 TripoSR 根据图像创建 3D 对象
使用 Python 中的 TripoSR 根据图像创建 3D 对象 1. 效果图2. 步骤图像到 3D 对象设置环境导入必要的库设置设备创建计时器实用程序上传并准备图像处理输入图像生成 3D 模型并渲染下载.stl 文件展示结果3. 源码4. 遇到的问题及解决参考这篇博客将引导如何使用Python 及 TripoSR…...
Spring 框架中AOP(面向切面编程)和 IoC(控制反转)
在 Spring 框架中,AOP(面向切面编程)和 IoC(控制反转)是两个核心概念,它们分别负责不同的功能。下面我将通过通俗易懂的解释来帮助你理解这两个概念。 IoC(控制反转) IoC 是 Inver…...
电机瞬态分析基础(7):坐标变换(3)αβ0变换,dq0变换
1. 三相静止坐标系与两相静止坐标系的坐标变换―αβ0坐标变换 若上述x、y坐标系在空间静止不动,且x轴与A轴重合,即,如图1所示,则为两相静止坐标系,常称为坐标系,考虑到零轴分量,也称为αβ0坐标…...
Open3D (C++) 生成任意3D椭圆点云
目录 一、算法原理1、几何参数2、数学公式二、代码实现三、结果展示一、算法原理 1、几何参数 在三维空间中,椭圆由以下参数定义: 椭圆中心点 c = ( x 0 , y 0 , z...
5.利用Pandas以及Numpy进行数据清洗
1、缺失值处理 import pandas as pd import numpy as np#创建一张7行5列带有缺失值的表,表中的数据0-100随机生成,索引是python1. df pd.DataFrame(datanp.random.randint(0,100,size(7,5)), index [i for i in pythonl])df.iloc[2,3] Nonedf.iloc[4…...
@Bean注解详细介绍以及应用
目录 一、概念二、应用(一)代码示例1、首先创建一个简单的 Java 类User2、然后创建一个配置类AppConfig3、在其他组件中使用Bean创建的 bean4、通过 Spring 的ApplicationContext来获取UserService并调用其方法 (二)bean的方法名详…...
基于SpringBoot的预制菜销售系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…...
从 EXCEL 小白到 EXCEL 高手的成长之路
在职场与日常生活中,Excel 作为一款强大的数据处理与分析工具,扮演着不可或缺的角色。无论是初学者还是资深职场人士,掌握 Excel 技能都能极大地提高工作效率。那么,从一个 Excel 小白蜕变成为 Excel 高手,究竟需要多久…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...
