【C++】“多态”特性

在这里插入图片描述

文章目录

  • 一、多态的概念
  • 二、多态的定义实现
    • 1. 多态的构成条件
      • 1.1 虚函数
      • 1.2 虚函数的重写
    • 2. 多态的调用
    • 3. 虚函数重写的其他问题
      • 3.1 协变
      • 3.2 析构函数的重写
  • 三、override和final关键字
  • 四、重载/重写/隐藏的对比
  • 五、纯虚函数和抽象类
  • 六、多态的原理

C++的三大主要特性:封装(类和对象)、继承、多态。前两者我们已经学习过了,今天最后来认识一下“多态”特性。

一、多态的概念

多态,就是“多种形态”,指函数的行为可以有多种形态。多态一般分为编译时多态(静态多态)和运行时多态(动态多态)。其中,编译时多态主要就是之前讲的函数重载和函数模板,它们传不同类型的参数就可以调用不同的函数,通过参数的不同达到不同的形态效果,之所以叫编译时多态,是因为传参匹配这一过程是在编译时期完成的。
运行时多态,具体指一个函数传不同的对象就会完成不同的行为,达到多种形态。比如写一个买火车票行为,传普通人类对象是“全价”,传学生类对象是“打折”,传军人类对象就是“优先”,一个函数(行为),根据传的对象类型不同,就有不同作用。今天谈的“多态”指的主要就是运行时多态。

二、多态的定义实现

1. 多态的构成条件

(运行时)多态是一个继承体系下的类对象,去调用同一函数,而产生不同的行为。比如,Person类为基类,Student类继承了Person类,Soldier类继承了Person类,那么这三类的对象买票行为就有不同的效果。

实现多态还有两个重要的条件:

  • 必须是基类的指针类型或引用类型去调用虚函数。
  • 被调用的函数必须是虚函数,并且完成了虚函数的重写(覆盖)。

我们依次来说明:
要实现多态效果,首先必须是基类的指针或引用去调用,因为只有这样才既能指向基类对象又能指向派生类对象(的基类的切片)。
第二,派生类必须对基类的虚函数完成重写,重写了,基类和派生类才能有这个函数的不同实现方法,多态的不同形态效果才能达到。

1.1 虚函数

类的成员函数前加关键字virtual修饰,那么这个成员函数被称为虚函数,非成员函数不能加virtual修饰。如:

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

虚函数的存在就是为了给多态服务的。

1.2 虚函数的重写

虚函数的重写指:派生类中有一个跟基类虚函数“三同”的虚函数,则称派生类的这个虚函数完成了对基类虚函数的重写。“三同”指的是,函数名相同、返回类型相同、参数类型。
在派生类中重写基类虚函数时,派生类的虚函数前可以不加virtual修饰,也可以构成重写,因为继承后基类的虚函数在派生类中仍保持虚函数属性。但是这种写法不规范,实际使用还是建议派生类的虚函数前写上virtual(但基类的虚函数前是必须写virtual的)。不过在考试中也有可能故意埋这个坑,注意判断。
举例:

class Person
{
public:virtual void BuyTicket(){cout << "全价" << endl;}
};class Student : public Person
{
public:// 注意保证“三同”virtual void BuyTicket(){cout << "打折" << endl;}
};

2. 多态的调用

利用多态的特性,我们可以模拟出简单的买火车票行为:传普通人类对象是“全价”,传学生类对象是“打折”,传军人类对象就是“优先”:

class Person
{
public:virtual void BuyTicket(){cout << "全价" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "打折" << endl;}
};class Soldier : public Person
{
public:virtual void BuyTicket(){cout << "优先" << endl;}
};

但是记得刚才说的多态的第一个条件,必须是基类的指针类型或引用类型去调用虚函数,比如:
在这里插入图片描述

3. 虚函数重写的其他问题

3.1 协变

派生类重写基类虚函数时,重写时虚函数返回类型也可以不一样。基类虚函数可以返回自己或其他基类对象的指针或引用,派生类虚函数可以返回自己或其他派生类对象的指针或引用。这种方式称为卸变,但是它的实际意义并不大,简单了解即可。

class Person
{
public:virtual Person* BuyTicket(){cout << "全价" << endl;return nullptr;}
};class Student : public Person
{
public:virtual Student* BuyTicket(){cout << "打折" << endl;return nullptr;}
};

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;}
};

3.2 析构函数的重写

析构函数十分特殊,编译器会对析构函数的名字进行特殊处理——编译后析构函数的名称会统一处理成destructor,再加上析构函数都是没有返回、没有参数的。因此,基类和派生类的析构函数总会构成“三同”,如果不加virtual修饰成虚函数,那么基类和析构函数既然是“同名”的,就构成隐藏关系了所以,基类的析构函数一定要加virtual修饰成虚函数,派生类析构函数的virtual可写可不写

比如:

class A
{
public:~A(){cout << "~A()" << endl;}
};class B : public A
{
public:~B(){cout << "~B()" << endl;delete p;}
protected:int* p = new int[10];
};

在这里插入图片描述
可见,如果~A()不加virtual,那么delete p2时只会调用A的析构函数,没有调用B的析构函数,导致了内存泄漏问题。

这样就没问题了。
在这里插入图片描述

总而言之,基类的析构函数建议设计为虚函数

三、override和final关键字

C++提供了两个新的关键字:

  • override:
    C++对虚函数重写的要求是很严格的,但是有时候我们可能粗心没有满足多态的要求,但是语法没问题编译也不会有问题,只有在运行结果不是预期情况下我们才能发现问题。因此C++11提供了override关键字,写在派生类的想要重写的虚函数后,帮助用户检测是否完成了重写
    在这里插入图片描述
  • final
    之前继承提到过,如果一个类不想被继承,就在类名后加final修饰。除此之外final还有一个功能,如果我们不想让基类的虚函数被重写,就在这个虚函数()后加final修饰在这里插入图片描述

四、重载/重写/隐藏的对比

这三个概念比较相近,要注意区别:

函数重载:

  • 两个函数在同一作用域
  • 函数名相同,参数的类型或个数不同,返回值可同可不同

虚函数重写:

  • 两个函数分别在一个继承体系的基类和派生类中
  • 函数名、参数、返回值相同,协变除外
  • 两个函数必须都是虚函数

隐藏:

  • 两个函数分别在一个继承体系的基类和派生类中
  • 函数名相同
  • 两个函数只要不构成重写,就是隐藏关系
  • 基类和派生类的成员变量相同也构成隐藏关系

五、纯虚函数和抽象类

如果在一个虚函数的()后写上= 0,则这个虚函数称为纯虚函数,纯虚函数不需要定义实现(可以实现但是没有意义),只要声明即可。
包含纯虚函数的类称为抽象类,抽象类不能实例化出对象,如果一个抽象类的派生类继承后不重写纯虚函数,则派生类也是抽象类。可以认为,纯虚函数一定程度上强制派生类重写虚函数,因为不重写不实例化出对象。

在这里插入图片描述

六、多态的原理

还是看一开始的例子:

class Person
{
public:virtual void BuyTicket(){cout << "全价" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "打折" << endl;}
};class Soldier : public Person
{
public:virtual void BuyTicket(){cout << "优先" << endl;}
};
int main()
{Person per;Student stu;Soldier sol;Person* ptr;ptr = &per;ptr->BuyTicket(); ptr = &stu;ptr->BuyTicket();ptr = &sol;ptr->BuyTicket();return 0;
}

调试观察,可以发现:
在这里插入图片描述
每一个对象的第一个成员都是一个叫__vfptr的指针(有些平台可能会把它放在最后一个),这个指针叫做虚函数表指针,指向这个类的虚函数表。一个含有虚函数的类中都至少有一个虚函数表指针,一个类所有虚函数的地址都会存在一个虚函数表中,虚函数表实际上就是函数指针数组,也称虚表。

  • 基类和每个派生类都有自己独立的虚函数表。派生类会继承基类的虚函数表指针,但是这个虚函数表指针和基类的虚函数表指针不是同一个指针,指向的虚表也就不是同一个。
  • 派生类重写了基类的虚函数后,派生类的虚表中对应的原基类虚函数就会被覆盖成派生类重写的虚函数地址。这也是为什么重写也可以称为覆盖。

基于这样的原理,基类的指针或引用调用基类和派生类的同一个虚函数时,其实就是从它们各自的__vfptr找到各自的虚函数表,再找到这个虚函数。但由于派生类的虚函数表中这个虚函数已经被重写(覆盖),调用后也就有不同的结果了。这就是多态实现的原理。

本篇完,感谢阅读。

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

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

相关文章

2025.5.27学习日记 linux三剑客 sed与正则表达式

sed是Stream Editor(字符流编辑器)的缩写,简称流编辑器。 sed是操作、过滤和转换文本内容的强大工具。 常用功能包括结合正则表达式对文件实现快速增删改查 , 其中查询的功能中最常用的两大功能是过 滤 ( 过滤指定字符串)、取行(取出指定行)。 注意sed和awk使用单引号,双引号…

文科小白学习Linux系统之安全管理

目录 前言 一、SELinux安全上下文 1、SELinux 简介 2、基础操作命令 1. 查看SELinux状态 2. 切换工作模式 3、安全上下文&#xff08;Security Context&#xff09; 1. 查看上下文 2. 修改上下文 chcon命令 semanage 命令 4、SELinux布尔值&#xff08;Booleans&am…

企业内训系统源码开发详解:直播+录播+考试的混合式学习平台搭建

在企业数字化转型的大潮中&#xff0c;员工培训早已不再是传统教室中的一场场“走过场”&#xff0c;而是通过技术驱动的“系统化能力提升”。尤其在知识更新换代加速、竞争压力日益激烈的背景下&#xff0c;企业越来越倾向于建设自主可控、功能灵活、支持多种学习形态的内训平…

智能化报销与精细化管理:购物小票识别系统全面提升企业运营效率

在现代企业管理中&#xff0c;购物小票的处理一直是财务和运营管理中的一项挑战。尤其在企业费用报销、会员管理、库存监控等环节&#xff0c;手动整理与核对小票不仅耗时费力&#xff0c;还容易产生错误。随着人工智能技术的发展&#xff0c;企业亟需一种高效、智能的解决方案…

毫秒级数据采集的极致优化:如何用C#实现高性能、无冗余的实时文件写入?

在工业控制、通信系统或高频交易领域&#xff0c;毫秒级数据采集的精度直接决定系统性能。但一个棘手问题常被忽视&#xff1a;如何处理同一毫秒内的重复数据&#xff1f; 若简单写入所有数据&#xff0c;会导致文件臃肿、分析效率骤降&#xff1b;若处理不当&#xff0c;又可能…

NLua性能对比:C#注册函数 vs 纯Lua实现

引言 在NLua开发中&#xff0c;我们常面临一个重要选择&#xff1a;将C#函数注册到Lua环境调用&#xff0c;还是直接在Lua中实现逻辑&#xff1f; 直觉告诉我们&#xff0c;C#作为编译型语言性能更高&#xff0c;但跨语言调用的开销是否会影响整体性能&#xff1f;本文通过基准…

go并发与锁之sync.Mutex入门

sync.Mutex 原理&#xff1a;一个共享的变量&#xff0c;哪个线程握到了&#xff0c;哪个线程可以执行代码 功能&#xff1a;一个性能不错的悲观锁&#xff0c;使用方式和Java的ReentrantLock很像&#xff0c;就是手动Lock&#xff0c;手动UnLock。 使用例子&#xff1a; v…

【HarmonyOS5】DevEco Studio 使用指南:代码阅读与编辑功能详解

⭐本期内容&#xff1a;【HarmonyOS5】DevEco Studio 使用指南&#xff1a;代码阅读与编辑功能详解 &#x1f3c6;系列专栏&#xff1a;鸿蒙HarmonyOS&#xff1a;探索未来智能生态新纪元 文章目录 前言代码阅读代码导航功能代码折叠语法高亮跨语言跳转代码查找 快速查阅API接口…

【Python 深度学习】1D~3D iou计算

一维iou 二维 import numpy as npdef iou_1d(set_a, set_b):# 获得集合A和B的边界 x1, x2 set_ay1, y2 set_b# 计算交集的上下界low max(x1,y1)high - min(x2, y2)# 计算交集if high - low < 0:inter 0else:inter high - low# 计算并集union (x2 -x1) (y2 - y1) - in…

SpringBoot Controller接收参数方式, @RequestMapping

一. 通过原始的HttpServletRequest对象获取请求参数 二. 通过Spring提供的RequestParam注解&#xff0c;将请求参数绑定给方法参数 三. 如果请求参数名与形参变量名相同&#xff0c;直接定义方法形参即可接收。(省略RequestParam) 四. JSON格式的请求参数(POST、PUT) 主要在PO…

智能防护实战:从攻击成本看企业安全降本增效

1. 网络攻击的低成本与高回报陷阱 暗网中&#xff0c;一次完整的网络钓鱼攻击仅需30美元/月起步&#xff0c;而勒索软件攻击成本平均1000美元&#xff0c;却能导致企业损失高达445万美元&#xff08;IBM 2023年数据&#xff09;。例如&#xff0c;信用卡信息每条仅售10美元&am…

大语言模型 20 - MCP 在客户端中使用 Cursor Cline 中配置 MCP 服务

MCP 基本介绍 官方地址&#xff1a; https://modelcontextprotocol.io/introduction “MCP 是一种开放协议&#xff0c;旨在标准化应用程序向大型语言模型&#xff08;LLM&#xff09;提供上下文的方式。可以把 MCP 想象成 AI 应用程序的 USB-C 接口。就像 USB-C 提供了一种…

MySQL 在 CentOS 7 环境下的安装教程

&#x1f31f; 各位看官好&#xff0c;我是maomi_9526&#xff01; &#x1f30d; 种一棵树最好是十年前&#xff0c;其次是现在&#xff01; &#x1f680; 今天来学习Mysql的相关知识。 &#x1f44d; 如果觉得这篇文章有帮助&#xff0c;欢迎您一键三连&#xff0c;分享给更…

WPF的基础设施:XAML基础语法

XAML基础语法 1 控件声明与属性设置1.1 特性语法&#xff08;Attribute Syntax&#xff09;1.2 属性元素语法&#xff08;Property Element Syntax&#xff09;1.3 特殊值标记扩展 2 x:Name与Name的区别3 注释与代码折叠4 实用技巧集合5 常见错误排查 XAML( Extensible Applic…

机器学习笔记【Week3】

一、逻辑回归&#xff08;Logistic Regression&#xff09; 与线性回归的区别&#xff1a; 问题类型输出类型举例回归问题连续实数房价预测、气温预测分类问题离散类别&#xff08;0 或 1&#xff09;是否患病、是否点击广告、是否合格 我们希望构建一个模型&#xff0c;根据…

6.4.2_3最短路径问题_Floyd算法

Floyd弗洛伊德 膜拜大佬&#xff0c;给大佬鞠躬鞠躬鞠躬。。。。。。。。。 Floyd算法 ----解决顶点间的最短路径&#xff1a; 过程&#xff1a; 如下&#xff1a; 初始化(没有中转点)&#xff1a;2个邻接矩阵A和path&#xff0c;第一个是没有中转点的2个顶点之间的最短路径…

uniapp|实现多端图片上传、拍照上传自定义插入水印内容及拖拽自定义水印位置,实现水印相机、图片下载保存等功能

本文以基础视角,详细讲解如何在uni-app中实现图片上传→水印动态编辑→图片下载的全流程功能。 目录 引言应用场景分析(社交媒体、内容保护、企业素材管理等)uniapp跨平台开发优势核心功能实现​图片上传模块多来源支持:相册选择(`uni.chooseImage`)与拍照(`sourceType:…

2021年认证杯SPSSPRO杯数学建模B题(第二阶段)依巴谷星表中的毕星团求解全过程文档及程序

2021年认证杯SPSSPRO杯数学建模 B题 依巴谷星表中的毕星团 原题再现&#xff1a; 依巴谷卫星&#xff08;High Precision Parallax Collecting Satellite&#xff0c;缩写为 Hip-parcos&#xff09;&#xff0c;全称为“依巴谷高精度视差测量卫星”&#xff0c;是欧洲空间局发…

行为型:解释器模式

目录 1、核心思想 2、实现方式 2.1 模式结构 2.2 实现案例 3、优缺点分析 4、适用场景 5、注意事项 1、核心思想 目的&#xff1a;针对某种语言并基于其语法特征创建一系列的表达式类&#xff08;包括终极表达式与非终极表达式&#xff09;​&#xff0c;利用树结构模式…

Redis分布式缓存核心架构全解析:持久化、高可用与分片实战

一、持久化机制&#xff1a;数据安全双引擎 1.1 RDB与AOF的架构设计 Redis通过RDB&#xff08;快照持久化&#xff09;和AOF&#xff08;日志持久化&#xff09;两大机制实现数据持久化。 • RDB架构&#xff1a;采用COW&#xff08;写时复制&#xff09;技术&#xff0c;主进程…