多态的概念及实现

2024-09-05 19:28
文章标签 实现 概念 多态

本文主要是介绍多态的概念及实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

前言

2. 多态的定义及实现

2.1多态的构成条件

2.2 虚函数

2.3虚函数的重写

重写、重载、重定义的辨析

虚函数重写的两个例外:

2.4 C++11 override 和 final

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

这样就会报错。

2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

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

易错

3. 抽象类

3.1 概念

3.2 接口继承和实现继承


前言

继承与多态常常伴随出现,前面已经进行了继承的学习,本文讲借助以下要点,进行多态的学习。

1. 多态的概念
2. 多态的定义及实现
3. 抽象类
1. 多态的概念
多态的概念:
在C++中,多态是面向对象编程(OOP)的一个核心概念。多态允许以统一的接口处理不同的数据类型,这意味着可以通过一个接口来访问一个方法,而这些 方法可以具有不同的实现
通俗来说,就是多种形态, 具体点就是去完成某个行为,当不同的对象去完成时会 产生出不同的状态
举个栗子:比如 买票这个行为,当 普通人买票时,是全价买票; 学生买票时,是半价买票; 军人
买票时是优先买票。针对买票的对象不同,得到的处理方式不同,结果就不同。
以此为例,进入多态的学习

2. 多态的定义及实现

2.1多态的构成条件

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

在继承中要 构成多态还有两个条件
1. 必须通过 基类的指针或者引用调用 虚函数
2. 被调用的函数必须是虚函数,且派生类必须对 基类的虚函数进行重写
咱们就先简单看一个多态的例子
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;
}

在上述调用中,采用了基类的引用传参,调用了被重写之后的虚函数。结果如下

采用不同的对象取调用函数之后,产生了不同的结果。

但是当我们采用对象传参调用之后,结果又是一样的,这是为什么呢?

下面就对原理进行详细的解释。

2.2 虚函数

虚函数:即被virtual修饰的类成员函数称为虚函数。virtual可以修饰类,变成虚基类,也可修饰函数,变成虚函数。(只能修饰成员函数)
class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl;}
};

2.3虚函数的重写

虚函数的重写(覆盖): 派生类中有一个 跟基类完全相同的虚函数 (即派生类虚函数与基类虚函数的
返回值类型、函数名字、参数列表 完全相同),称子类的虚函数重写了基类的虚函数。
可以说重写就是条件更加严格的隐藏(重定义)
注意:1.虚函数的重写是对函数体部分进行的重写,而不是参数部分。
2.重写时,父类的虚函数有virtual,子类可以省略这个符号

重写、重载、重定义的辨析

重写(覆盖)::继承关系中,父子类两个虚函数三同(函数名相同 参数相同 返回值相同)

重载:在同一作用域,函数名相同,参数不同

重定义(隐藏):父子类的函数中,函数名相同,子类隐藏父类的现象。

虚函数重写的两个例外:

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;}
};
2. 析构函数的重写(基类与派生类析构函数的名字不同)
如果 基类的析构函数为虚函数,此时派生类析构函数只要定义, 无论是否加virtual关键字,
都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
理,编译后 析构函数的名称统一处理成destructor
这种目的是为了防止在调用析构是出现内存泄漏。
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;
}

2.4 C++11 override final

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

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

final放在虚函数的函数名之后(final修饰类时,也是放在类名之后)(只能修饰虚函数)

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

这样就会报错。

2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

override也是放在函数名之后,检测派生类的虚函数有没有完成重写,没有完成重写就会报错。
class Car{
public:virtual void Drive(){}
};
class Benz :public Car {
public:virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

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

易错

答案是B。

原因:

1.我们用p取调用test()函数是允许调用的,因为存在继承

2.调用的test()函数的this指针是A*类型,因此内部调用的func()函数也是A*类型的调用。

3.func()函数完成了重写,同时调用时采用的是父类的指针,因此符合多态调用的概念(父类类型的指针、引用,去调用重写过的虚函数)

4.因此我们用子类的指针调用func()函数时,实际上实现了多态调用。调用的函数体部分是子类重写过的函数体:“B->”

但是内部打印的val值是基类val的值,即 1

那如果采用这种调用呢?

答案是D

这并不构成多态调用,因为this指针的类型是B*。这只是一个简单的隐藏(重定义)。

3. 抽象类

3.1 概念

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。 包含纯虚函数的类叫做抽象类(也叫接口
类), 抽象类不能实例化出对象。派生类继承后也不能实例化出对象, 只有重写纯虚函数,派生
类才能实例化出对象。纯虚函数 规范了派生类必须重写,另外纯虚函数更 体现出了接口继承
(可以定义指针,但是不能实例化对象)
如何理解抽象类呢?
其实可以把抽象类看成一个集合。比如说“动物”  “水果”  ,我们可以通过动物得到兔子、小猫、、、通过水果得到苹果、香蕉、、、、
class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};
class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};
void Test()
{
Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}

也就意味着这个地方的多态调用是在多个子类之间实现的,而不是父-子之间实现的

3.2 接口继承和实现继承

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

这篇关于多态的概念及实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1139849

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机