【C++进阶(九)】C++多态深度剖析

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C++从入门到精通⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习C++
  🔝🔝


在这里插入图片描述


多态

  • 1. 前言
  • 2. 多态的概念以及定义
  • 3. 多态的实例调用情况
  • 4. 构成多态的两个特例
  • 5. 多态的底层原理分析(一)
  • 6. 多态底层原理分析(二)
  • 7. 多态中的两个关键字
  • 8. 抽象类以及虚函数的几个结论
  • 9. 总结以及拓展

1. 前言

继承和多态这两兄弟常常一起出现
继承是实现多态的前提!

本章重点:

本篇文章着重讲解多态的概念以及
定义,多态的底层原理和析构函数重写
以及函数重写的两个例外条件
多继承中的虚函数表关系.其中,简单介绍
的部分有抽象类的概念以及定义和
继承与多态中的两个新增关键字

注:如果你不知道什么是继承,或继承
的知识掌握不牢固,请先阅读下面文章:

C++继承深度剖析


2. 多态的概念以及定义

概念: 通俗来说,多态就是多种状态
父子对象完成相同任务会产生不同的结果

比如:
学生和普通人都去买门票
学生是半价,而普通人是全价

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

  1. 必须通过基类的指针或引用调用虚函数
  2. 被调用的函数必须是虚函数
    并且子类的虚函数要被重写

现在的你可能有一万个问号
什么是虚函数?什么是重写?
没关系,我们一步一步讲!

关键字virtual加在成员函数前
这个成员函数就是虚函数!

在这里插入图片描述
虚函数的重写(也叫覆盖):

派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的,返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数

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

上面的代码中,BuyTicket函数就被重写了!

概念讲完,下一步进行实战!


3. 多态的实例调用情况

构成多态的条件就两个,一定要熟记!
一定要熟记!一定要熟记!重要的事情说三遍

下面是多态的实例:

class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};int main()
{Person* p1 = new Person;Person* p2 = new Student;p1->BuyTicket();p2->BuyTicket();return 0;
}

我们知道一个事实:
基类的指针或引用可以指向/引用
子类的对象,我们称为切片

p1和p2是基类指针,它们调用的
函数恰好还被重写了,所以这里符合
多态,p1指针指向的内容是Person
所以它调用Person中的函数,然而p2
指针指向的内容是Student,所以它
调用的是Student中的函数!

依次打印:"买票-全家","买票-半价"


4. 构成多态的两个特例

  1. 特例一: 子类的虚函数不写virtual
    依旧构成多态
class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:void BuyTicket() { cout << "买票-半价" << endl; }
};
Person* p1 = new Person;
Person* p2 = new Student;
p1->BuyTicket();
p2->BuyTicket();

这样写也是构成多态的!

  1. 特例二:基类与派生类虚函数返回值类型不同
    也可以构成多态(返回值必须满足某种条件)
class A{};
class B : public A {};class Person {
public:virtual A* f() {return new A;}
};
class Student : public Person {
public:virtual B* f() {return new B;}
};

父类的返回值要返回父类
子类的返回值要返回子类

  1. 注意事项1:父类不写virtual,而子类的同名
    函数写了virtual,这是不构成多态的!
class Person {
public:void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};

不构成多态!

  1. 注意事项2:在继承体系中,父子类的同名
    函数不构成重写就构成隐藏,不可能构成重载!

5. 多态的底层原理分析(一)

在这里插入图片描述

如果你单纯的认为Base类只有一个
整型变量占用空间的话,那你就上当啦!
事实上在32位机器下,这里的结果是8
在64位机器下,这里的结果是16!

这是因为它除了有一个变量外,还有
一个指针,此指针指向一个虚函数表

我们通过以下的代码来观察内存:

class A
{
public:virtual void func1(){cout << "父类func1";}
private:int _a;
};
class B : public A
{
public:virtual void func1(){cout << "子类func1";}
private:int _b;
};int main()
{A a;B b;return 0;
}

在这里插入图片描述

此指针叫虚表指针:vfptr,也就是
virtual function ptr

这个指针并不是直接指向虚函数的地址
而是指向一个虚函数表,可以理解位一个
数组,此数组中存放着此对象中所有的虚
函数的地址,它们的关系可以用下图表示:

在这里插入图片描述

注:不管有没有继承体系或多态
只要有虚函数就有虚表!


6. 多态底层原理分析(二)

现在得出一个结论:有虚函数的
类对象中还存放了一个虚表指针!

那么父类和子类的虚表指针和指向
的内容有什么不同或相同处吗?
形成多态现象的原理又是什么?
我来一一解答这些问题:

  1. 通过下面的代码来观察内存情况
    得出父子类虚表的关联:
class A
{
public:virtual void func1()cout << "父类func1";virtual void func2()cout << "父类func2";
private:int _a;
};
class B : public A
{
public:virtual void func1()cout << "子类func1";
private:int _b;
};
int main()
{A a;B b;return 0;
}

请看下图观察情况:

在这里插入图片描述

结论:

父类和子类的虚表指针是不同的
证明父子类各有一张虚函数表!
函数func1在子类中被重写了,所以
父子类虚表中的func1函数地址是不同的
函数func2没有被子类重写,所以
父子类虚表中的func2函数地址是相同的

拓展结论:同一个类的不同对象共用一个虚表

  1. 多态的原理深度剖析:

当一个函数A被重写时,它的父类虚表存放
父类函数A的地址,子类虚表存放的是子类
函数A的地址!

当父类的指针或引用指向子类空间时
调用虚函数时,会到指向对象的虚表中
中找到对应的虚函数地址,进行调用!

拓展结论: 父子类都只有A函数或无函数时

  1. 若父类写了虚函数A,而子类
    甚至没有写函数A,此时子类对象中
    存储的虚函数地址与父类相同

  2. 若父类甚至没有写函数A,而子类
    直接写了虚函数A,则父类对象中没有
    虚表,而子类对象中有虚表(存放A)


7. 多态中的两个关键字

  1. final:修饰虚函数,表示该虚函数不能被重写

在这里插入图片描述

  1. override:检查子类类虚函数是否重写了
    基类虚函数如果没有重写编译报错

在这里插入图片描述


8. 抽象类以及虚函数的几个结论

抽象类概念:

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写

抽象类的只需了解概念,实际中
使用到的场景很少

关于虚函数的几个小结论:

  1. 析构函数最好定义为虚函数
  2. 构造函数不能定义为虚函数
  3. 静态成员函数不能是虚函数
  4. 内联函数(inline)不能是虚函数

为什么说析构函数最好定义为虚函数?
请看下面的例子:

class Person {
public:virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生类Student的析构函数重写了Person的析构函数
//下面的delete对象调用析构函数,才能构成多态
//才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}

若析构函数不是虚函数,delete ptr2时
不符合多态,ptr2是Person类型指针
就只会调用Person类的析构,会有问题

若析构函数是虚函数,delete ptr2时
构成多态的条件,指针指向父类的对象
就调用父类的析构,指向子类的对象
就调用子类的析构,这样才是正确的!


9. 总结以及拓展

多态在校招的笔试面试中考察的
非常之多,很多面试官都喜欢在这
上面考察学生的掌握C++语法的程度
所以同学们请耐心学习!

拓展阅读:

多继承场景下的多态


🔎 下期预告:二叉搜索树 🔍

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 企业工程管理系统软件源码 自主研发 工程行业适用

工程项目管理软件&#xff08;工程项目管理系统&#xff09;对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营&#xff0c;全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&am…

英语——分享篇——每日200词——3001-3200

3001——ascertain——[ˌsəteɪn]——vt.查明&#xff0c;弄清——ascertain——a苹果(编码)s美女(编码)certain确定(熟词)——吃苹果的美女确定已查明此事——It can be difficult to ascertain the facts. ——可能难以查明事实真相。 3002——disrupt——[dɪsrʌpt]——…

spring6-国际化:i18n | 数据校验:Validation

文章目录 1、国际化&#xff1a;i18n1.1、i18n概述1.2、Java国际化1.3、Spring6国际化1.3.1、MessageSource接口1.3.2、使用Spring6国际化 2、数据校验&#xff1a;Validation2.1、Spring Validation概述2.2、实验一&#xff1a;通过Validator接口实现2.3、实验二&#xff1a;B…

Python 安装CSF(布料模拟滤波)的环境配置

一、环境配置 1.1 下载源码: Github下载CSF库源码 1.2 解压文件如下: 二、安装CSF库 2.1在解压文件中找到python文件夹所在目录 2.2 输入cmd并回车,来打开终端窗口 2.3激活虚拟环境 通过: activate +你的虚拟环境名称。来激活安装CSF库的虚拟环境。【不执行此

VM16Pro的Win10虚拟机安装Linux子系统Kali

VM16Pro的Win10虚拟机安装Linux子系统Kali 一、启用Windows功能二、配置WSL三、安装Kali四、安装kali基本工具包五、图形化六、适用的报错七、其他问题参考 一、启用Windows功能 启用后需重启二、配置WSL wsl --update #管理员启动Powershell执行&#xff0c;完成后将下面…

GitLab-访问返回403 forbidden问题处理

访问gitlab时报错forbidden 一般访问量大&#xff0c;密码错误频率高的时候&#xff0c;gitlab防爆机制启动了&#xff0c;对IP做了封禁&#xff0c;导致某些IP访问的是否返回 403 forbidden 1. 查看被封的IP /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/red…

vant组件是使用?

首先 在vue项目中使用的时候 要先下载组件 使用npm安装 # Vue 3 项目&#xff0c;安装最新版 Vant npm i vant# Vue 2 项目&#xff0c;安装 Vant 2 npm i vantlatest-v2 使用yarn安装或pnpm # 通过 yarn 安装 yarn add vant# 通过 pnpm 安装 pnpm add vant 在框架中引入即…

数据要素安全流通:挑战与解决方案

文章目录 数据要素安全流通&#xff1a;挑战与解决方案一、引言二、数据要素安全流通的挑战数据泄露风险数据隐私保护数据跨境流动监管 三、解决方案加强数据安全防护措施实施数据隐私保护技术建立合规的数据跨境流动机制 四、数据安全流通的未来趋势01 数据价值与产业崛起02 多…

2023-mac brew安装python最新版本,遇见的问题和处理方式

#### 创建Python3.11.6符号链接我现在遇见这个问题了&#xff1a;python --version -bash: python: command not found 192:bin wangyang$ python3 --version Python 3.9.6 192:bin wangyang$ /usr/local/bin/python3 --version Python 3.11.6我要怎么做&#xff0c;我才可以直…

Spring Boot自动配置原理揭秘

自动配置原理 概述原理Spring Boot Starterspring.factories 文件ConditionalOnX 注解配置 Bean配置属性 源码剖析 主页传送门&#xff1a;&#x1f4c0; 传送 概述 Spring Boot 是一个用于创建独立的、生产级别的 Spring 应用程序的框架。它极大地简化了 Spring 应用程序的开…

Spring MVC(一)【什么是Spring MVC】

重点 Spring&#xff1a;IOC 和 AOP 。 Spring MVC &#xff1a;Spring MVC 的执行流程。 SSM 框架的整合&#xff01; Spring 和 Mybatis 我们不建议使用太多注解&#xff0c;Spring MVC 建议全部使用注解开发&#xff01; 1、MVC 回顾 1.1、什么是MVC MVC是模型(Model)…

java实现多线程下载器

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final&#xff0c;一名热爱技术的在校学生。 &#x1f4dd;个人主页&#xff1a;个人主页1 || 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;项目专栏 &#x1f4e7;如果文章知识点有错误的地方&#xff0c;…

macrodata数据集在Python统计建模和计量经济学中的应用

目录 一、数据介绍二、应用三、statsmodels 统计模块四、使用 statsmodels 统计模块分析 macrodata.csv 数据集参考 一、数据介绍 macrodata.csv是一个示例数据集&#xff0c;通常用于统计分析和计量经济学中的教育和训练目的。这个数据集通常包括以下列&#xff1a; year&am…

远程监控高并发高吞吐java进程

文章目录 背景工具jconsole和jvisualvm 压测实战以太坊Java程序监控1.使用jconsole监控2.使用jvisualvm监控 问题分析堆内存使用异常通过调整内存策略来应对&#xff1a; 交易虚增问题 背景 作为使用java技术栈的金融类公司&#xff0c;确保Java程序在生产环境中的稳定性和性能…

基于PHP的宠物爱好者交流平台管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

对象属性的读写两种方法

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 对象属性的读写 两种方法 选择题 下列代码执行输出的结果是? class C(object): name hello c1 C() print("【执行】c1C()") print("【显示】print(c1.name)") print(c…

OpenGL —— 2.7、绘制多个自旋转的贴图正方体(附源码,glfw+glad)

源码效果 C源码 纹理图片 需下载stb_image.h这个解码图片的库&#xff0c;该库只有一个头文件。 具体代码&#xff1a; vertexShader.glsl #version 330 corelayout(location 0) in vec3 aPos; layout(location 1) in vec2 aUV;out vec2 outUV;uniform mat4 _modelMatrix; …

Java面试——RPC协议

涉及到分布式方面知识的话&#xff0c;RPC协议是逃不开的&#xff0c;所以在此记录一下RPC协议。 什么是RPC协议 RPC协议&#xff08;Remote Procedure Call&#xff09;远程过程调用&#xff0c;简单的来说&#xff1a;RPC协议是一种通过网络从远程计算机程序获取服务的协议…

智能矩阵系统解决的问题?

智能矩阵系统可以解决的问题多种多样&#xff0c;它主要通过人工智能技术应用于矩阵系统&#xff0c;解决一些传统方法难以处理的问题。 以下是一些常见的应用场景&#xff1a; 1. 数据管理&#xff1a;智能矩阵系统可以有效地管理大量的数据&#xff0c;包括数据的存储、检索…

Python学习基础笔记七十八——Socket编程1

现在的软件开发基本上都需要网络通讯。 不管是传统计算机软件&#xff0c;还是手机软件&#xff0c;还是物联网嵌入系统软件&#xff0c;这些都要和其他网络系统进行通讯。 而当今世界基本上都是使用TCP/IP协议进行通讯的。 TCP/IP协议是一种传输数据的方案。 收发信息的程序…