【C++深度探索】全面解析多态性机制(一)

hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹
在这里插入图片描述

💥个人主页:大耳朵土土垚的博客
💥 所属专栏:C++入门至进阶
这里将会不定期更新有关C++的内容,欢迎大家多多点赞关注收藏💖💖

目录

  • 1.什么是多态
  • 2.虚函数的定义与重写
  • 3.多态的实现
  • 4.虚函数重写的两个例外
  • 5.C++11 override 和 final
  • 6.重载、覆盖(重写)、隐藏(重定义)的对比
  • 7.抽象类
  • 8.结语

1.什么是多态

在C++中,多态(Polymorphism)是指通过基类指针引用来访问派生类对象的一种机制。简单来说,它允许我们在基类类型的指针或引用上调用派生类对象的成员函数。

通俗来说,就是多种形态,或者说完成某个行为时,当不同的对象去完成会产生出不同的状态。

例如:买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。不同的对象去完成同一个行为——买票,会产生不同的状态。

在代码中的具体体现则依赖于虚函数(Virtual Function)。在基类中,可以将某个成员函数声明为虚函数,而在派生类中重写该函数。通过使用基类指针或引用指向派生类对象,并调用该虚函数,实际上在运行时会根据对象的实际类型调用合适的函数

2.虚函数的定义与重写

虚函数:即被virtual修饰的类成员函数称为虚函数

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

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

class Person {
public:
//虚函数virtual void BuyTicket() { cout << "买票-全价" << endl; }
};class Student : public Person {
public:
//虚函数重写virtual void BuyTicket() { cout << "买票-半价" << endl; }};

在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,所以不建议这样使用

注意这里与继承中的隐藏区分一下,隐藏是只要在派生类中有与基类函数名相同的函数就构成,而重写则需要返回值、函数名和参数列表完全相同才构成

3.多态的实现

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如上述代码Student类继承了Person。而Person对象买票全价,Student对象买票半价。

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

  • 必须通过基类的指针或者引用调用虚函数
  • 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

例如:

class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
//条件二:调用的是虚函数并且派生类对虚函数进行重写virtual void BuyTicket() { cout << "买票-半价" << endl; }
};void Func(Person& p)
{
//条件一:基类的引用或指针调用虚函数p.BuyTicket();
}int main()
{Person ps;Student st;Func(ps);Func(st);return 0;
}

多态的实现结果如下:

在这里插入图片描述

通过使用基类引用p指向派生类对象,并调用虚函数BuyTicket(),实际上在运行时会根据对象的实际类型调用合适的函数来实现多态,也就是不同的对象完成同一个行为会有不同的状态——普通人买票全价,学生买票半价

这种通过基类指针或引用调用派生类对象的成员函数的行为称为多态。它使得我们可以在不同类型的对象上使用相同的接口,提供了更高的灵活性、可扩展性和代码复用性。

4.虚函数重写的两个例外

  • 协变(基类与派生类虚函数返回值类型不同)
    基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

前面我们学习过虚函数重写必须要求基类与派生类除了函数体以外其它完全相同,但是对于协变,基类与派生类的返回值类型可以不同,但基类与派生类的函数的返回类型必须是继承关系

//协变
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; }//派生类虚函数返回值类型为派生类指针或引用
};

当然基类虚函数返回值类型可以是别的基类也可以是自己,派生类虚函数返回值类型可以是别的派生类也可以是自己,与基类相呼应:

class Person {
public:virtual Person* f() { return new Person; }//基类虚函数返回值类型为基类指针或引用
};
class Student : public Person {
public:virtual Student* f() { return new Student; }//派生类虚函数返回值类型为派生类指针或引用
};
  • 析构函数的重写(基类与派生类析构函数的名字不同)
    如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。

这是因为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor

例如:

//析构函数重写
class Person {
public:virtual ~Person() { cout << "~Person()" << endl; }
};class Student : public Person {
public:
//构成多态条件二:基类虚函数并且派生类虚函数重写virtual ~Student() { cout << "~Student()" << endl; }
};int main()
{
//构成多态条件一:使用基类指针或引用调用虚函数(这里是析构函数)Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}

只有派生类Student的析构函数重写了Person的析构函数,delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。

结果如下:
在这里插入图片描述

这里要注意派生类的析构调用完之后会自动调用基类对象的析构函数,所以这里基类的析构函数调用了两次

我们可以对比一下,当没有实现多态时,对于delete对象调用析构函数是不会根据所指向的对象调用相应的析构函数,而是直接调用Person类的析构函数:

//没有实现多态
class Person {
public:~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:~Student() { cout << "~Student()" << endl; }
};int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}

结果如下:

在这里插入图片描述

如果是这样,那么如果子类Student类中动态开辟了一块空间而没有调用合适的析构函数就会造成内存泄漏:

//内存泄漏
class Person {
public:~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:int* ptr = new int[10];//动态开辟空间~Student() { delete[] ptr;//释放空间cout << "~Student()" << endl;}
};int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}

在这里插入图片描述

在类的继承关系中,派生类的析构函数会自动调用基类的析构函数。因此,可以重写(覆盖)基类的析构函数,以处理派生类特有的资源清理需求

尤其是父类的析构函数强力建议设置为虚函数,这样动态释放父类指针所指的子类对象时,能够达到析构的多态。

5.C++11 override 和 final

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。

  • final:修饰虚函数,表示该虚函数不能再被重写
    例如:
class Person {
public:virtual void BuyTicket() final { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};

结果如下:

在这里插入图片描述

此外final也可以修饰类表明该类不可被继承。

  • override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Person {
public:virtual void BuyTicket(){ cout << "买票-全价" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() override{ cout << "买票-半价" << endl; }
};

重写了BuyTicket(),编译不报错

如下图所示,屏蔽了基类的虚函数,派生类的函数没有重写,编译报错:

在这里插入图片描述

6.重载、覆盖(重写)、隐藏(重定义)的对比

在这里插入图片描述

重载与重定义主要区别在于作用域,而重定义与重写主要区别在于函数返回值与函数参数列表是否相同。

重写是重定义的一种特殊形式,重定义中包括重写

7.抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。

纯虚函数是在基类中声明的虚函数,但没有给出具体的实现,也就是没有函数体。抽象类只能用作其他类的基类,不能被直接实例化。

派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。

派生类必须实现基类中的所有纯虚函数,否则派生类也会成为抽象类。

例如:

//抽象类
class Person {
public:virtual void BuyTicket() = 0;
};class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};

在上面的示例中,Person是一个抽象类,定义了一个纯虚函数:BuyTicket()。Student是Person的派生类,必须实现基类中的纯虚函数

在这里插入图片描述

注意:普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

8.结语

对于多态,我们可以将其理解为同一件事,不同的对象去做会有不同的状态,这就构成了多态,这与我们的现实生活息息相关,比如上文中举例的买票行为,再比如价格歧视等等。而我们在编程中要实现多态就必须要满足两个条件:一个是基类的指针或引用来调用虚函数,另一个则是基类中定义虚函数并且在派生类中对该虚函数进行重写;这两个条件缺一不可,这与多态实现的底层原理有关,我们后续再了解。对于虚函数重写的两个例外中析构函数的重写要掌握清楚,此外对于重载、重写与重定义的区别我们也要弄明白。以上就是今天的所有内容啦~ 完结撒花~ 🥳🎉🎉

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

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

相关文章

演唱会售票系统(Springboot+MySQL+Mybatis+BootStrap)

本演唱会售票系统结合了多个流行的技术栈&#xff0c;提供了全面的功能模块&#xff0c;包括用户和管理员两个角色。前端采用Bootstrap框架设计响应式界面&#xff0c;后端采用Spring Boot和MyBatis Plus实现业务逻辑和数据库操作&#xff0c;Sa-Token确保系统的安全性。通过这…

深入分析与解决4.3问题:iOS应用版本更新审核被拒原因解析

深入分析与解决4.3问题&#xff1a;iOS应用版本更新审核被拒原因解析 在iOS应用开发和发布过程中&#xff0c;遇到4.3问题&#xff08;设计 - 垃圾邮件&#xff09;是一个常见且令人头疼的情况。即使您的应用已成功发布其第一个版本&#xff0c;但在进行版本更新时&#xff0c…

【机器学习】初学者经典案例(随记)

&#x1f388;边走、边悟&#x1f388;迟早会好 一、概念 机器学习是一种利用数据来改进模型性能的计算方法&#xff0c;属于人工智能的一个分支。它旨在让计算机系统通过经验自动改进&#xff0c;而不需要明确编程。 类型 监督学习&#xff1a;使用带标签的数据进行训练&…

队列+二叉树广度优先

题目出自力扣-n叉树的层序遍历 我是原始人&#xff0c;递归写出一道题就只有递归思路&#xff0c;开始的想法是写深搜函数&#xff0c;传一个随着层数递增的int参数q&#xff0c;节点空就return&#xff0c;否则遍历所有节点&#xff0c;每个子节点又以q1为层数递归&#xff…

C++ | Leetcode C++题解之第226题翻转二叉树

题目&#xff1a; 题解&#xff1a; class Solution { public:TreeNode* invertTree(TreeNode* root) {if (root nullptr) {return nullptr;}TreeNode* left invertTree(root->left);TreeNode* right invertTree(root->right);root->left right;root->right …

js字符串文字添加不同颜色,replace的妙用$1...$9

更改字符串第一个数字为红色显示&#xff0c;第二个数字为黄色显示 $1匹配的是正则第一个括号选中的字符串&#xff0c;可以使用正则不断用括号匹配然后更改样式 const testStr "剩余12个名额&#xff0c;截止时间12月25日" testStr this.testStr.replace(/(\d)(\D…

简单状压dp(以力扣464为例)

目录 1.状态压缩dp是啥&#xff1f; 2.题目分析 3.解题思路 4.算法分析 5.代码分析 6.代码一览 7.结语 1.状态压缩dp是啥&#xff1f; 顾名思义&#xff0c;状态压缩dp就是将原本会超出内存限制的存储改用更加有效的存储方式。简而言之&#xff0c;就是压缩dp的空间。 …

设计模式探索:建造者模式

1. 什么是建造者模式 建造者模式 (Builder Pattern)&#xff0c;也被称为生成器模式&#xff0c;是一种创建型设计模式。 定义&#xff1a;将一个复杂对象的构建与表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 建造者模式要解决的问题&#xff1a; 建造者模…

谷粒商城学习-10-docker安装mysql

文章目录 一&#xff0c;拉取MySQL镜像1&#xff0c;搜索MySQL的Docker镜像2&#xff0c;拉取MySQL镜像3&#xff0c;查看已经拉取的镜像 二&#xff0c;创建、启动MySQL容器1&#xff0c;使用docker run创建启动容器2&#xff0c;使用docker ps查看运行状态的容器3&#xff0c…

力扣-dfs

何为深度优先搜索算法&#xff1f; 深度优先搜索算法&#xff0c;即DFS。就是找一个点&#xff0c;往下搜索&#xff0c;搜索到尽头再折回&#xff0c;走下一个路口。 695.岛屿的最大面积 695. 岛屿的最大面积 题目 给你一个大小为 m x n 的二进制矩阵 grid 。 岛屿 是由一些相…

Qt:12.输入类控件(QSpinBox-整数值输入的小部件、QDateEdit、QTimeEdit、QDateTimeEdit- 日期和时间输入的控件)

目录 一、QSpinBox-整数值输入的小部件&#xff1a; 1.1QSpinBox介绍&#xff1a; 1.2属性介绍&#xff1a; 1.3通用属性介绍&#xff1a; 1.4信号介绍&#xff1a; 二、QDateEdit、QTimeEdit、QDateTimeEdit- 日期和时间输入的控件&#xff1a; 2.1QDateEdit、QTimeEdit…

测试面试宝典(一)——你觉得测试和开发需要怎么结合才能使软件的质量得到更好的保障?

“在我看来&#xff0c;测试和开发的有效结合对于保障软件质量至关重要。 首先&#xff0c;在需求分析阶段&#xff0c;测试人员就应该参与进来&#xff0c;与开发人员一起理解软件的需求和功能。这样测试人员可以提前制定测试计划和策略&#xff0c;明确测试的重点和范围。 在…

springboot零食盒子-计算机毕业设计源码50658

目 录 1 绪论 1.1 研究背景 1.2研究意义 1.3论文结构与章节安排 2 微信小程序的零食盒子系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据流程 3.3.2 业务流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 2.5本章小结 3 微信…

人工智能算法工程师(中级)课程3-sklearn机器学习之数据处理与代码详解

大家好&#xff0c;我是微学AI,今天给大家分享一下人工智能算法工程师(中级)课程3-sklearn机器学习之数据处理与代码详解。 Sklearn&#xff08;Scikit-learn&#xff09;是一个基于Python的开源机器学习库&#xff0c;它提供了简单有效的数据挖掘和数据分析工具。Sklearn包含了…

【初阶数据结构】树与二叉树:从零开始的奇幻之旅

初阶数据结构相关知识点可以通过点击以下链接进行学习一起加油&#xff01;时间与空间复杂度的深度剖析深入解析顺序表:探索底层逻辑深入解析单链表:探索底层逻辑深入解析带头双向循环链表:探索底层逻辑深入解析栈:探索底层逻辑深入解析队列:探索底层逻辑深入解析循环队列:探索…

后VMware时代,一体化技术平台建设思路

在数字化转型的浪潮中&#xff0c;企业对IT基础设施的需求正在发生根本性的变化。VMware时代的结束&#xff0c;为企业带来了重新构建技术平台的机遇与挑战。6月28日&#xff0c;在主题为【聚力生态&#xff0c;VMware全链替代】的线上研讨会上&#xff0c;灵雀云首席解决方案专…

基于Java+Vue的场馆预约系统源码体育馆羽毛球馆篮球馆预约

市场前景 市场需求持续增长&#xff1a;近年来&#xff0c;随着人们生活水平的提高和休闲娱乐需求的多样化&#xff0c;各类场馆&#xff08;如体育馆、图书馆、博物馆、剧院等&#xff09;的访问量不断增加。然而&#xff0c;传统的预约方式往往存在效率低下、信息不透明等问…

专注于国产FPGA芯片研发的异格技术Pre-A+轮融资,博将控股再次投资

近日&#xff0c;苏州异格技术有限公司&#xff08;以下简称“异格技术”&#xff09;宣布成功完成数亿元的Pre-A轮融资&#xff0c;由博将控股在参与Pre-A轮投资后&#xff0c;持续投资。这标志着继2022年获得经纬中国、红点中国、红杉中国等机构数亿元天使轮融资后&#xff0…

[数仓]四、离线数仓(Hive数仓系统-续)

第8章 数仓搭建-DWT层 8.1 访客主题 1)建表语句 DROP TABLE IF EXISTS dwt_visitor_topic; CREATE EXTERNAL TABLE dwt_visitor_topic (`mid_id` STRING COMMENT 设备id,`brand` STRING COMMENT 手机品牌,`model` STRING COMMENT 手机型号,`channel` ARRAY<STRING> C…

九.核心动画 - 显式动画

引言 本篇博客紧接着上一篇的隐式动画开始介绍显式动画。隐式动画是创建动态页面的一种简单的直接的方式&#xff0c;也是UIKit的动画机制基础。但是它并不能涵盖所有的动画类型。 显式动画 接下来我们就来研究另外一种动画显式动画&#xff0c;它能够对一些属性做指定的动画…