C++ 多态性

在这里插入图片描述

一 多态性的分类

编译时的多态
函数重载
运算符重载
运行时的多态
虚函数

1 运算符重载的引入

使用C++编写程序时,我们不仅要使用基本数据类型,还要设计新的数据类型-------类类型。

一般情况下,基本数据类型的运算都是运算符来表达,这很直观,语义也简单。

例如:

int a,b,c;a=b+c;

对于基本数据类型,就隐含着运算符重载的概念。

在这里插入图片描述
在这里插入图片描述
如果直接将运算符作用在类类型之上,情况又如何呢?
例如:

Complex ret,c1,c2;ret=c1+c2;

编译器将不能识别运算符的语义。
需要一种机制来重新定义运算符作用在类类型上的含义。
这种机制就是运算符重载。

二 两种重载函数的比较

多数情况下,运算符可以重载为类的成员函数,也可以重载为友元函数。但两种重载也有各自特点:
一般情况下,单目运算符重载为类的成员函数;双目元素重载为类的友元函数。
有些双目运算符不能重载为类的友元函数:=,(),[],->
类型转换函数只能定义为类的成员函数,而不能定义为友元函数。
若一个运算符的操作需要修改对象的状态,则重载为成员函数比较好;
若运算符所需要的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选择友元函数;
若运算符是成员函数,最左边的操作数必须是运算符类的对象(或者类对象的引用)。如果左边操作数必须是一个不同类的对象,或者是基本数据类型,则必须重载为友元函数;
当需要重载运算符的元素具有交换性时,重载为友元函数;

1 重载运算符的几点注意事项

大多数预定义的运算符可以被重载,重载后的优先级、结合级及所需的操作数都不变。
但少数的C++运算符不能重载:
例如:::、#、?:、.、
不能重载非运算符的符号,例如:;
C++ 不运行重载不存在的运算符,如"?"、“**”等。

当运算符被重载时,它是被绑定在一个特定的类类型之上的。当此运算符不作用在特定类类型上时,它将保持原有的含义。

当重载运算符时,不能创造新的运算符符号,例如不能用"**"来表示求幕运算符。

应当尽可能保持重载运算符原有的语义。试想,如果在某个程序中用"+“表示减,”*"表示除,那么这个程序读起来将会非常别扭。

三 多态性的引入

1 虚函数和多态性

重载普通的成员函数的两种方式:
在同一个类中重载:重载函数是以参数特征区分的。
派生类重载基类的成员函数:
由于重载函数处在不同的类中,因此它们的原型可以完全相同。调用时使用“类名::函数名”的方式加以区分。
以上两种重载的匹配都是在编译的时候静态完成的。

重载是一种简单形式的多态。
C++提供另一种更加灵活的多态机制:虚函数。虚函数运行函数调用与函数体的匹配在运行时才确定。
虚函数提供的是一种动态绑定的机制。

2 赋值兼容规则

在公有派生方式下,派生类对象可以作为基类对象来使用,具体方式如下:
在这里插入图片描述

派生类拥有从基类继承过来的成员;
基类对象和派生类对象的内存布局方式;
在这里插入图片描述
当一个派生类对象直接赋值给基类对象时,不是所有的数据都赋给了基类对象,赋予的只是派生类对象的一部分。这部分叫做派生类对象的“切片(sliced)”。

注意
回忆一下不同的继承方式,子类对基类中成员的访问权限:
在这里插入图片描述
只有在公有派生的情况下,才有可能出现“基类的公有成员变成派生类的公有成员”的情况。
在这里插入图片描述
通过基类引用或指针所能看到的是一个基类对象,派生类中的成员对于基类引用或指针来说是“不可见的”。

我们能不能“通过基类引用或指针来访问派生类的成员”呢?
为了达到上述目的,我们可以利用C++的虚函数机制,将基类的Print说明为虚函数形式。这样就可以通过基类引用或指针来访问派生类中的Print。

3 虚函数

在基类中用virtual关键字声明的成员函数即为虚函数。

虚函数可以在一个或多个派生类中被重写定义,但要求重定义时虚函数的原型(包括返回值类型、函数名、参数列表)必须完全相同。

3 基类中的函数具有虚特性的条件

在基类中用virtual将函数说明为虚函数。
在公有派生类中原型一致地重载该虚函数。
定义基类引用或指针,使其引用或指向派生类对象。当通过该引用或指针调用需要函数时,该函数将体现出虚特性来。

C++中,基类必须指出希望派生类重定义哪些函数。定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。

注意:
在派生类中重载虚函数时必须与基类中的函数原型相同,否则该函数将丢失虚特性。

仅返回类型不同,其他相同。C++编译器认为这种情况是不允许的。

函数原型不同,,仅函数名相同。C++编译器认为这是一般的函数重载,此时虚特性丢失。

四 虚函数与多态性

1 提供虚函数的意义

提升软件的重用性
基类使用虚函数提供一个接口,但派生类可以定义自己的实现版本。
虚函数调用的解释依赖于它的对象类型,这就实现了“一个接口,多种语义”的概念。
提供软件架构的合理性。

2 虚函数和虚指针

在编译时,为每个有虚函数的类建立一张虚函数表VTABLE,表中存放的时每一个虚函数的指针;同时用一个虚指针VPTR指向这张表的入口。
访问某个虚函数时,不是直接找到那个函数的地址,而是通过VPTR间接查到它的地址。

在这里插入图片描述
在这里插入图片描述
对象的内存空间除了保存数据成员外,还保存VPTR。VPTR由构造函数来初始化。

3 对虚函数的要求

虚函数必须是类的非静态成员函数。
不能将虚函数说明为全局函数。
不能将虚函数说明为静态成员函数。
不能将虚函数说明为友元函数。

本质的原因就是非静态成员函数隐含传递this指针,而通过this指针能够找到VPTR。

4 在成员函数中调用虚函数

在一个基类或派生类的成员函数中,可以直接调用类等级中的虚函数。此时需要根据成员函数中this指针所指向的对象来判断调用的时哪一个函数。

5 析构函数可以定义为虚函数

构造函数不能定义为虚函数。
而析构函数可以定义为虚函数。

若析构函数为虚函数,那么当使用delete释放基类指针所指向的派生类对象时,先调用派生类的析构函数,再调用基类的析构函数。

五 纯虚函数与抽象类

在这里插入图片描述

基类中的这些公共接口只需要有售卖而不需要有实现,即纯虚函数。纯虚函数刻画了派生类应该遵循的协议,这些协议的具体实现由派生类来决定。

在这里插入图片描述
将一个函数说明为纯虚hasn’t,就要求任何派生类都定义自己的实现。
拥有纯虚函数的类被称为抽象类。抽象类不能被实例化,只能作为基类被使用。
抽象类的派生类需要实现纯虚函数,否则该派生类也是一个抽象类。
当抽象类的所有函数成员都是纯虚函数时,这个类被称为接口类。

小结:
继承和动态绑定在两个方面简化了我们的程序:
能够容易地定义与其他类相似但又不相同的新类,能更容易地编写忽略这些相似类型之间区别的程序。

许多应用程序的特性可以用一些相关但略有不同的概率描述。面向对象编程与这种应用非常匹配。通过继承可以定义一些类型,可以模型不同冲类;通过动态绑定可以编写程序,使用这些类而又忽略与具体类型相关的差异。

继承和动态绑定的思想在概念上非常简单,但对于如何创建应用程序以及对于程序设计语言必须支持得特性,含义深远。

面向对象编程的关键思想是多态性。因为在需要情况下可以互换地使用派生类型或基类型的“许多形态”,所以称通过继承而相关联的类型为多态类型。C++中,多态型仅用于通过继承而相关性的类型的引用或指针。

我们称因继承而相关的类构成一个继承层次。其中一个类称为根,所有其他类直接或间接继承根类。

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

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

相关文章

弥合孤岛:克服构建 DevOps 文化的挑战

持续变革正在发生软件开发行业。DevOps 因其对自动化、协作和持续改进的关注而成为优化软件交付并弥合开发和运营团队之间鸿沟的重要方法。然而,过渡到真正的 DevOps 文化并非没有挑战。本文探讨了您在追求 DevOps 时可能面临的障碍并提供了解决方案。 01 了解 Dev…

听说SOLIDWORKS科研版可以节约研发成本?

近几年来,政府越来越重视科研带动产业,绩效优良的产业技术研究院对于国家和地区的学术成果转化、技术创新、产业发展等具有不可忽视的促进和带动作用。研究院会承担众多新产业的基础研究工作,而常规的基础研究需要长期的积累,每个…

探讨 cs2019 c++ 的STL 库中的模板 conjunction 与 disjunction

(1)在 STL 库源码中这俩模板经常出现,用来给源码编译中的条件选择,模板的版本选择等提供依据。先给出其定义: 以及: 可以得出结论: conj 是为了查找逻辑布尔型模板参数中的第一个 false &#x…

RK3566(泰山派):GP7101背光驱动

RK3566(泰山派):GP7101背光驱动 文章目录 RK3566(泰山派):GP7101背光驱动GP7101背光驱动电路配置i2c1设备树创建驱动编写Makefilegp7101_bl.c驱动触摸I2C驱动框架。驱动中的结构体probe函数devm_backlight_…

DHCP原理

什么是DHCP DHCP (Dynamic Host Configuration Protocol,动态主机配置协议)是由Internet工作任务小组设计开发的,专门用于为TCP/IP网络中的计算机自动分配TCP/IP参数的协议,是一个应用层协议,使用UDP的67和68端口。 DHCP的前身是B…

CUDA backend requires cuDNN. Please resolve dependency or disable的解决方法

先把 C:\Program Files\NVIDIA\CUDNN\v9.0里面的bin,include,lib文件夹中最里面的文件 复制到 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4中的bin,include,lib文件夹 你的路径或许有点不一样,但大概就是这样 注意,复制完后,文…

C语言学习【常量和C预处理器】

C语言学习【常量和C预处理器】 符号常量(symbolic constant) C预处理器可以用来定义常量 就像这样 #define TAXRATE 0.015/* 通用格式 末尾不加分号 */ /* 大写表示符号常量是 C 语言一贯的传统 */ #define NAME value编译程序时,程序中所有TAXRATE都会被替换成0.…

Arduino-ILI9341驱动-SPI接口TFTLCD实现触摸功能系列之触控开关二

Arduino-ILI9341驱动-SPI接口TFTLCD实现触摸功能系列之触控开关二 1.概述 这篇文章在触摸屏上绘制一个开关,通过点击开关实现控制灯的开关功能。 2.硬件 硬件连接参考第一篇文章介绍 Arduino-ILI9341驱动-SPI接口TFTLCD实现触摸功能系列之获取触控坐标一 3.实现…

智能EDM邮件群发工具哪个好?

企业之间的竞争日益激烈,如何高效、精准地触达目标客户,成为每个市场战略家必须面对的挑战。在此背景下,云衔科技凭借其前沿的AI技术和深厚的行业洞察,匠心推出了全方位一站式智能EDM邮件营销服务平台,重新定义了邮件营…

Git使用(3):版本管理

一、查看历史 编写一个java类进行测试 选择Git -> Show Git Log查看日志。 第一次修改推送到远程仓库了,所以有origin(远程仓库地址),第二次修改只提交到本地仓库所以没有。 二、版本回退 1、本地回退 在要回退的版本上右键&a…

Nginx的正向代理与反向代理

你好呀,我是赵兴晨,文科程序员。 今天,我们将一起了解什么是Nginx的正向代理?什么是Nginx的反向代理?并实际动手实践。 以下内容都是满满的干货,绝对不容错过。我建议先收藏这篇文章,然后找一…

吴恩达深度学习笔记:优化算法 (Optimization algorithms)2.1-2.2

目录 第二门课: 改善深层神经网络:超参数调试、正 则 化 以 及 优 化 (Improving Deep Neural Networks:Hyperparameter tuning, Regularization and Optimization)第二周:优化算法 (Optimization algorithms)2.1 Mini-batch 梯度下降(Mini-b…