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

【C++】多态的认识和理解

在这里插入图片描述
个人主页

在这里插入图片描述

文章目录

  • ⭐一、多态的概念
  • 🎄二、多态的定义及实现
    • 1.多态的构成
    • 2.实现多态的条件
    • 3.虚函数的概念
    • 4.虚函数的重写和覆盖
    • 5.析构函数的重写
    • 6.协变
    • 7.override和 final关键字
    • 8.重载、重写/覆盖、隐藏这三者的区别
  • 🏠三、纯虚函数和抽象类的关系
  • 🏝️四、多态的原理
    • 1.虚函数表指针
    • 2.多态是如何实现的
    • 3.动态绑定和静态绑定
  • 🚀五、虚函数表
    • 1.概念
    • 2.虚函数和虚函数表两者的存储位置

⭐一、多态的概念

多态(polymorphism),简单的来说就是多种形态。但多态又可以分为两种:一种是编译是的多态,称为静态多态;还有一种是运行时的多态,称为动态多态。下面我们就更进一步去了解一下什么是静态多态和什么是动态多态。

1.静态多态
静态多态主要就是函数重载以及函数模板,它们传不同的类型参数就可以调用不同的函数,通过参数不同就能够达到多种形态。而至于为什么会被称为是编译时的多态,是因为它们的实参传递给形参的参数匹配是在编译时完成的。

2.动态多态
具体的来说,就是去完成某个行为(函数),可以通过传不同的对象去完成不同的行为,因此达到多种形态。 例如:当我们要去买票时,我们会发现普通人去买票时,是全价票;学生去买票时,是优惠票;而军人去买票时是优先买票。又或者同样是动物叫这一行为,传猫对象过去,就是”(>ω<)喵“,传狗对象过去,就是"汪汪"。

🎄二、多态的定义及实现

1.多态的构成

多态是⼀种继承关系的下的类对象,去调用同一函数,从而产生了不同的行为。
例如:Student继承了Person,Person对象买票为全价票,而Student对象买票则为优惠票。

2.实现多态的条件

实现多态有两个重要的条件:
• 必须是指针或者引用调用的函数。
• 被调用的函数必须是虚函数。

如果想要实现多态的效果,首先必须是基类的指针或引用,因为只有基类的指针或引用才能指向派生类对象。其次派生类必须对基类的虚函数进行重新或者覆盖,派生类才能有不同的函数,从而达到多态的效果。

3.虚函数的概念

虚函数,就是在类成员函数前面加上virtual修饰,那么这个成员函数就被称为虚函数。注意:非成员函数不能加上virtual进行修饰。

class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};

4.虚函数的重写和覆盖

概念:在派生类中有一个跟基类完全相同的虚函数(既派生类的虚函数与基类的虚函数的返回值类型、函数名以及参数列表完全相同),称派生类的虚函数重写了基类的虚函数。
注意:在重写基类的虚函数时,派生类的虚函数在不加上virtual的情况下,也能构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但这种写法不是很规范,不建议这样使用。

class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-打折" << endl; }
};void Func(Person* ptr)
{// 这⾥可以看到虽然都是Person指针Ptr在调⽤BuyTicket// 但是跟ptr没关系,⽽是由ptr指向的对象决定的。ptr->BuyTicket();
}int main()
{Person ps;Student st;Func(&ps);Func(&st);return 0;
}

在这里插入图片描述

通过上述代码以及运行结果,我们可以发现子类Student中的BuyTicket重写了基类Person的BuyTicket。

5.析构函数的重写

我们首先要了解虽然析构函数的名字看起来不一样,但实际上编译器对析构函数的名称做了特殊的处理,编译后的析构函数名称统一处理为destructor,因此析构函数的名称实际上都为destructor。

class A
{
public:virtual ~A(){cout << "~A()" << endl;}
};class B : public A 
{
public:~B(){cout << "~B()->delete:" << _p << endl;delete _p;}
protected:int* _p = new int[10];
};int main()
{A* p1 = new A;A* p2 = new B;delete p1;delete p2;return 0;
}

通过阅读上述代码,如果我们在~A之前不加virtual是否会发生报错呢?结果是肯定的,因为如果 ~A()前面不加上virtual,那么deletep2时只调用了A的析构函数,而没用调用B的析构函数,从而导致内存泄漏的问题,这就是为什么在基类中的析构函数设计为虚函数。

6.协变

协变就是派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用派生类虚函数返回派生类对象的指针或者引用时,称为协变。

class A {};
class B : public A {};class Person {
public:virtual A* BuyTicket(){cout << "买票-全价" << endl;return nullptr;}
};class Student : public Person {
public:virtual B* BuyTicket(){cout << "买票-打折" << endl;return nullptr;}
};void Func(Person* ptr)
{ptr->BuyTicket();
}int main()
{Person ps;Student st;Func(&ps);Func(&st);return 0;
}

在这里插入图片描述

7.override和 final关键字

C++对函数的重写要求比较严格,如函数名字写错了或参数写错等都无法构成重载,而这种错误在编译期间是不会报出的,只有在运行时才会出现错误。因此在C++11中提供了override,帮助用户检测是否完成重写。如果我们不想让派生类重写这个虚函数,那么就可以用final进行修饰。

1.override

class Car {
public:virtual void Dirve(){}
};
class Benz :public Car {
public:virtual void Drive() override { cout << "Benz-舒适" << endl; }
};
int main()
{return 0;
}

在这里插入图片描述
2.final

class Car
{
public:virtual void Drive() final {}
};
class Benz :public Car
{
public:virtual void Drive() { cout << "Benz-舒适" << endl; }
};
int main()
{return 0;
}

在这里插入图片描述

8.重载、重写/覆盖、隐藏这三者的区别

在这里插入图片描述

🏠三、纯虚函数和抽象类的关系

在虚函数的后面写上 =0 ,则这个函数就被称为纯虚函数。纯虚函数不需要定义实现(因为要被派生类进行重写),只需要声明即可。而包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象。如果派生类继承后不重写纯虚函数,那么派生类也是抽象类。 因此纯虚函数在某种程度上强制了派生类重写虚函数,因为不重写实例化不出对象。

class Car
{
public:virtual void Drive() = 0;
};class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};

🏝️四、多态的原理

1.虚函数表指针

我们先来看一道题:
下⾯编译为32位程序的运行结果是什么()
A. 编译报错 B. 运行报错 C. 8 D. 12

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

根据我们之前学习的知识,我们会觉得b的大小为8个字节。但实际上程序运行结果是12字节,这是为什么呢?这就是我们所要说的虚函数表指针。
在这里插入图片描述

Base类除了_b和_ch成员,还多⼀个_vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。⼀个含有虚函数的类中都至少都有⼀个虚函数表指针,因为⼀个类所有虚函数的地址要被放到这个类对象的虚函数表中,虚函数表也简称虚表

2.多态是如何实现的

我们以买票为例来探讨多态的实现。
在这里插入图片描述

通过下图我们可以看到,当满足多态条件后,底层不再是编译时通过调用对象来确定函数的地址,而是运行时到通过指向的对象的虚表中去确定对应的虚函数的地址,这样就实现了指针或引用指向基类去调用基类的虚函数,指向派生类去调用派生类对应的虚函数。
第⼀张图,ptr指向的Person对象,调用的是Person的虚函数;第⼆张图,ptr指向的Student对象,调用的是Student的虚函数。

在这里插入图片描述
在这里插入图片描述

3.动态绑定和静态绑定

不满足多态条件(指针或者引用+调用虚函数)的函数调用是在编译时绑定,也就是编译时确定调用函数的地址,叫做静态绑定。
在这里插入图片描述

满足多态条件的函数调用是在运行时绑定,也就是在运行时到指向对象的虚函数表中找到调用函数的地址,也就做动态绑定。
在这里插入图片描述

🚀五、虚函数表

1.概念

虚函数表本质是⼀个存虚函数指针的指针数组,⼀般情况这个数组最后面放了⼀个0x00000000标记。(这个C++并没有进行规定,各个编译器自行定义的,vs系列编译器会再后面放个0x00000000标记,g++系列编译不会放)

基类对象的虚函数表中存放基类所有虚函数的地址。

派生类的虚函数表中包含基类的虚函数地址,派生类重写的虚函数地址以及派生类自己的虚函数地址三个部分。

派生类由两部分构成,分别是继承下来的基类和自己的成员,⼀般情况下,继承下来的基类中有虚函数表指针,自己就不会再生成虚函数表指针。但是要注意的是这里继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同⼀个,就像基类对象的成员和派生类对象中的基类对象成员也独立的。

2.虚函数和虚函数表两者的存储位置

虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函数的地址又被存放到了虚表中。

虚函数表的存储位置在C++标准中并没有规定,取决于不同的编译器,在vs中,虚函数表是存放在代码段(常量区)的。

相关文章:

【C++】多态的认识和理解

个人主页 文章目录 ⭐一、多态的概念&#x1f384;二、多态的定义及实现1.多态的构成2.实现多态的条件3.虚函数的概念4.虚函数的重写和覆盖5.析构函数的重写6.协变7.override和 final关键字8.重载、重写/覆盖、隐藏这三者的区别 &#x1f3e0;三、纯虚函数和抽象类的关系&#…...

linux-安全管理-用户认证

Linux 安全管理&#xff1a;用户认证 一、概述 用户认证是 Linux 安全管理的核心部分&#xff0c;确保系统能够识别并验证合法用户&#xff0c;同时阻止未经授权的访问。Linux 提供了多种用户认证机制&#xff0c;包括用户名和密码认证、基于密钥的认证、双因素认证&#xff…...

webpack5 构建优化方案看这篇就够了!【Node.js进阶】

无论在面试还是内部晋升&#xff0c;webpack 构建优化方案 一直都是非常重要的部分。 webpack5构建加持 一、项目完成目标二、搭建项目1. 安装koa、koa/router &#xff08;如果已经配置可路过&#xff09;2. 创建入口文件3. 安装构建依赖4. 在项目根目录添加 .babelrc 文件5. …...

esp32-C2 对接火山引擎实现智能语音(一)

目录 一、火山引擎大模型简介 1)火山引擎网址: 2)首先需要先注册火山引擎账号 3)语音识别——即语音转为文本 一句话识别 流式语音识别 录音文件识别标准版 录音文件识别极速版 4)语音合成——文本转音频 一、火山引擎大模型简介 火山引擎的智能语音技术,基于业界先…...

【MySQL-初级】mysql基础操作(账户、数据库、表的增删查改)

概述 数据备份与恢复 数据库备份&#xff1a;在cmd下 root用户&#xff1a;sudo mysqldump -u root -p Test > Test.sql普通用户&#xff1a;mysqldump -u zzz -p db_name > db_name.sql 数据库恢复 先创建一个空的数据库在cmd下&#xff1a;sudo mysql -u root -p d…...

centos bash脚本一键运行安装go环境

复制到install_go.sh直接bash install_go.sh运行就完了 echo ----------安装go环境 wget https://go.dev/dl/go1.21.13.linux-amd64.tar.gz tar -zxvf go1.21.13.linux-amd64.tar.gzmkdir /srv cp -r go /srv/echo "PATH$PATH:/srv/go/bin ">> ~/.bashrc echo…...

vue2制作高复用页面

记录一下页面搭建记录&#xff0c;利用vue2组件化开发的思想。这个页面适合于大部分信息管理系统~。模板固定&#xff0c;每次使用&#xff0c;直接修改表单表格参数&#xff0c;api接口等。 以上图页面为例&#xff0c;一个基础数据信息页面可以分为&#xff0c;分类&#xff…...

Feed流系统重构:架构篇

重构对我而言&#xff0c;最大的乐趣在于解决问题。我曾参与一个C#彩票算奖系统的重构&#xff0c;那时系统常因超时引发用户投诉。接手任务时&#xff0c;我既激动又紧张&#xff0c;连续两天几乎废寝忘食地编码。结果令人振奋&#xff0c;算奖时间从一小时大幅缩短至十分钟。…...

Android 后台服务之Persistent 属性

在 Android 开发中,有时我们需要后台服务持续运行,以保持应用的某些功能。例如,音乐播放器需要在后台播放音乐,或者健康应用需要持续跟踪用户的运动数据。后台服务是 Android 中的一种组件,它不与用户界面交互,能够在后台执行长时间运行的任务。由于 Android 系统的资源管…...

STM32+ESP01连接到机智云

机智云,全球领先的智能硬件软件自助开发及物联网(iot)云服务平台。机智云平台为开发者提供了自助式智能硬件开发工具与开放的云端服务。通过傻瓜化的自助工具、完善的SDK与API服务能力最大限度降低了物联网硬件开发的技术门槛&#xff0c;降低开发者的研发成本&#xff0c;提升…...

电脑实时监控软件有哪些?七个电脑屏幕监控软件任你选择

电脑实时监控软件种类繁多&#xff0c;每款软件都有其独特的功能和适用场景。 以下是七个备受推荐的电脑屏幕监控软件&#xff0c;供您选择&#xff1a; 1.安企神&#xff1a; 功能&#xff1a;它是一款国内领先的企业级电脑监控解决方案&#xff0c; 提供实时屏幕监控、 文…...

信奥学习规划(CSP-J/S)

CSP-J组学习路线规划 CSP-S组学习规划...

【Linux取经之路】编译器gcc/g++的使用 调试器gdb的使用

目录 背景知识 编译器gcc/g的安装 编译器gcc/g的使用 调试器gdb的使用 cgdb 条件断点 背景知识 子曰&#xff1a;“温故而知新”。在谈gcc/g的使用之前&#xff0c;我们先来复习编译的4个阶段&#xff0c;也算是为下面的内容做一些铺垫&#xff0c;请看思维导图。 编译…...

自动化流程机器人(RPA)

自动化流程机器人&#xff08;RPA&#xff09;正逐渐成为企业提高效率和降低成本的强有力工具。 一、RPA的概念 自动化流程机器人&#xff08;Robotic Process Automation&#xff0c;简称RPA&#xff09;是一种利用软件机器人&#xff08;Robot&#xff09;模拟和执行复杂任务…...

Unity persistentDataPath使用案例

Unity persistentDataPath使用案例 一、Application.persistentDataPath 1、概念 persistentDataPath&#xff1a;此属性用于返回一个持久化数据存储目录的路径&#xff0c;可以在此路径下存储一些持久化的数据文件&#xff1b;是一个可读写的目录&#xff1b;此文件夹在Edi…...

Android 测试手册

1. 介绍 Android 测试是确保应用程序质量的重要步骤。它包括不同类型的测试&#xff0c;用于验证应用程序的功能、性能、安全性和用户体验。这个手册将指导你了解和实施 Android 测试的主要方法和工具。 2. 测试类型 2.1 单元测试 目的&#xff1a;验证单个组件&#xff08…...

各大平台统遭入侵??区块链市场遭攻击损失近3亿!

今年&#xff0c;全球发生多起骇人听闻的勒索入侵软件攻击事件&#xff0c;黑客组织利用各种手段和技术&#xff0c;不断试图突破网络安全防线&#xff0c;窃取敏感信息、破坏系统运行&#xff0c;甚至进行勒索和敲诈&#xff0c;使得网络安全问题日益凸显其重要性和紧迫性。 S…...

Java泛型(“代码模板”,一套代码套用各种类型)

1.什么是泛型 a.定义 i.如果不用泛型定义&#xff0c;在使用ArrayList时需要为每个class编写特定类型代码。 ii.泛型就是定义一种模板&#xff0c;既实现了编写一次&#xff0c;万能匹配&#xff0c;又通过编译器保证了类型安全。 iii.编写模板代码来适应任意…...

速响低代码平台:升级营销管理系统,开启高效无忧新体验!

当前日新月异的商业环境&#xff0c;企业面临着前所未有的挑战与机遇。随着市场竞争的日益加剧和企业业务的不断拓展&#xff0c;传统的营销方式和管理手段逐渐显露出其局限性&#xff0c;难以适应快速变化的市场需求。 数据收集难&#xff1a;传统的营销管理缺乏对客户数据的收…...

Gitlab升级14.0.12-->14.3.6遇到的gitlab-ctl reconfigure错误

问题描述 在按照官方文档升级路线11.0.2>17.2.2的过程中&#xff0c;升级14.0.12–》14.3.6时遇到一个错误&#xff1a; Running handlers: There was an error running gitlab-ctl reconfigure:rails_migration[gitlab-rails] (gitlab::database_migrations line 51) had…...

JDBC导图

思维歹徒 一、使用步骤 二、SQL注入 三、数据库查询&#xff08;查询&#xff09; 四、数据库写入&#xff08;增删改&#xff09; 五、Date日期对象处理 六、连接池使用 创建连接是从连接池拿&#xff0c;释放连接是放回连接池 七、事务和批次插入 八、Apache Commons DBUtil…...

飞思实验室与中飞院联合开发教学课程,校企联袂共绘教育蓝图

近日&#xff0c;飞思实验室与中国民用航空飞行学院&#xff08;以下简称“中飞院”&#xff09; 航空电子电气学院合作&#xff0c;共同开发《无人智能视觉导航控制技术》、《多旋翼无人飞行器集群系统》实验课程。这一举措旨在深化校企融合&#xff0c;学校通过引入企业带来的…...

Telephony Call

1、Telephony 架构 Telephony整体架构和Android架构一样的,包括APP层,框架层,HAL层,内核层。 其中HAL层不同平台实现方式不同,其中MTK SPRD 平台使用AT通道的方式实现,高通使用QMI方式实现。 2、通话业务介绍 APP包括Dialer.apk、TeleService.apk、Tele…...

Python--TCP/UDP通信

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 一.客户端与服务端通信原理 1. 服务器端 服务器端的主要任务是监听来自客户端的连接请求&#xff0c;并与之建立连接&#xff0c;然后接收和发送数据。 创建套接字&#xff1a;首先&#xff0…...

【已解决】请教 “Sa-Token 集成 xxl-job,报错:非 web 上下文无法获取 HttpServletRequest” 如何解决

1. xxl-job 报错日志 2024-09-11 17:19:04 [com.xxl.job.core.thread.JobThread#run]-[133]-[xxl-job, JobThread-3-1726046344528] <br>----------- xxl-job job execute start -----------<br>----------- Param: 2024-09-11 17:19:04 [com.xxl.job.core.thread…...

Redis——常用数据类型string

目录 常用数据结构&#xff08;类型&#xff09;Redis单线程模型Reids为啥效率这么高&#xff1f;速度这么快&#xff1f;&#xff08;参照于其他数据库&#xff09; stringsetgetMSET 和 MGETSETNX&#xff0c;SETEX&#xff0c;PSETEXincr&#xff0c;incrby&#xff0c;decr…...

架构设计:负责网络、定时、坐下、站起、重连等,支持多类游戏的无锁房间

本文首发在这里 重中之重就是想实现无锁&#xff01;无锁&#xff01;无锁&#xff01; 源码 servergolang 预计还会实现gate_server&#xff0c;接受并保持websocket长连接&#xff0c;按需双向流到game_server进行消息转发 未来上述服务均会以容器的形式由k8s自动化部署、…...

个人随想-gpt-o1大模型中推理链的一个落地实现

​首先祝大家中秋节快乐。 最近openai又推出了新的模型openai o1​还有它的mini版。官网的介绍&#xff0c;就是它的推理能力很强&#xff0c;比gpt-4o​有很大的提升。 最近也跟同行在聊这个o1&#xff0c;​看看落地方面有哪些可行性。在我们自己的实验上&#xff0c;把o1用…...

python解析ip范围,判断ip是否在ip范围内

目录 1. 背景 2. 代码使用示例 2.1 分割ip&#xff0c;横杠 &#xff08;-&#xff09; 的ip范围 2.2 判断ip在掩码&#xff08;/&#xff09;的范围内 2.3 判断ip在横杠&#xff08;-&#xff09;的范围内 2.3.1 格式&#xff1a;192.168.1.1-192.168.1.10 2.3.2 格式&…...

Springboot错误日志切面,找到post请求体被消费后的数据

问题记录&#xff1a;测试环境接口报错&#xff0c;日志里没有请求参数等信息&#xff0c;于是写了一个切面&#xff0c;但切面中获取不到 request的请求体&#xff0c;因为 post 请求体只能被消费一次&#xff0c;于是找解决办法 解决方法 既然 request 被消费了导致对应的请…...