C++ 多态

何为多态

多态分为编译时多态(静态)运行时多态(动态)。

静态的多态 主要就是函数重载和函数模板,它们是通过传不同参数以调用不同函数,达到多种形态的效果

动态的多态 比较复杂,也是本文的主要讲解目标。如果学过继承,那应该知道一个父类是可以拥有多个子类的,这些子类都会包含有父类的 "特征"。 比如说:一个叫做 "动物" 的父类,它里面有一个成员函数名为 "叫",功能是发出叫声,对于它的不同子类,如 "狗" 或 "猫" 的对象,在调用这个成员函数时,应该要执行出不同的效果,"狗" 调用 "叫" 应该输出 "汪汪","猫" 应该输出 "喵喵",这是通过对一个行为传不同的对象,达到多种形态

(接下来的内容中,如果没有特别说明,在提及多态时,指的都是动态的多态。)

多态的必要条件

要实现多态,必须满足以下条件:

1.被调用的函数一定是虚函数。

2.必须是父类的指针或引用去调用

虚函数 是实现多态最关键的部分之一,多态的逻辑全靠它来完善,为了实现多态,子类会对父类中的函数进行重写 (后面讲),重写的子类函数和父类函数的返回值函数名,形参都会和父类相同,为了防止混乱。就要用到虚函数。

虚函数 和之前讲过的虚继承在表面上十分相似,都是用 virtual 进行修饰。

(要实现多态的父类函数必须设置为虚函数,子类可以设置也可以不设置。)

在有 virtual 修饰函数的父类中会生成一张虚函数表 ,里面存放着父类的虚函数的地址 。子类的同名函数无论有没有用 virtual 修饰,都会继承父类的的虚函数表 ,里面也包含着子类自己的虚函数地址。 子类会对父类的虚函数进行重写(构成重写要具备 函数名,返回值,参数 相同切父类函数为虚函数),并用重写后的函数的地址覆盖原本的函数地址。当使用父类指针或引用去调用时,调用到的就是经过重写后的函数,由此达成多态效果。

(图中展示的方法比较 low ,既然有多态其实可以用更优的方法去实现,但这样的例子比较适合理解。)

重写对于析构函数是特别:只要父类的析构函数是虚函数,甚至我们知道父类的析构和子类的析构的函数名是不同的(正常重写要求函数为虚函数且函数名相同),只要在子类中定义了析构函数,那么就构成重写,这是C++的规定,但没有规定如何实现,所以各大编译器各显神通,但一般都是通过名字修饰的方式来实现。

没有 virtual 修饰的析构只会调用父类的析构,会有内存泄漏:

有 virtual 修饰的析构,调用子类和父类的析构,没有内存泄漏:

还有一个值得注意的点是:重写后的函数使用的是自己的实现,但声明仍然是父类的声明。也就是说,如果父类和子类的函数参数中都设置缺省值,进行调用时仍是根据父类的缺省值来实现函数功能的。

重载,重写和隐藏的区分

在我的 C++ 继承 博客中讲到了隐藏 这个概念,隐藏其实不难理解,就是子类中和父类同名函数会将父类中的函数暂时"屏蔽",导致无法调用,但可以通过作用域解析符::来显式调用

那为什么不在继承部分对它讲解呢?如果观察地比较仔细的话,应该已经发现刚才提到的重写 和隐藏在 "外形" 上好像长得一模一样,同时又和类和对象时学到的重载,为了更好地讲解和区分,所以把隐藏放到多态部分和另外两个一起讲解,先来一张表格清晰地展示它们的异同:

可以看到它们仨在 "函数名" 这个地方都是要求相同的,但重载的作用域和另外两个不同,所以很好区分,真正要区分的是重写和隐藏。

可能你想通过是否有 virtual 来区分重写和隐藏,但仔细看,有无 virtual 对隐藏是无关的,所以不能区分。那怎么办?可以这么记忆:重写 的条件非常严苛,必须要求函数名,返回值,参数 都相同且父类有 virtual ,才构成重写,协变另说,一旦有一个条件不符合,就是隐藏

协变 是重写中的异类,协变的父类返回父类的指针/引用,子类则返回子类的指针/引用 。它的特殊好像把我刚才所述都推翻了,好像无法判断重写和隐藏了。但实际上只要把协变当作重写的一个变种,记作:除了普通重写和协变这种特殊重写之外,就是隐藏,就可以了。

不过从上面可以看出区分重写和隐藏还是比较麻烦的,而且如果你因为疏忽导致本来想写成重写的函数写成了隐藏,编译器可不会帮你报错,于是 C++11 引入 override 关键字来显示表示:我要重写。如果不构成重写会报错。

final 关键字在继承时就有提到,被它修饰的类会变成 "最终类" ,无法继承。在这里 final 也会发挥它的 "最终作用" :被它修饰的函数不能重写。

抽象类和纯虚函数

在现实世界中,有些东西是不能形成具体对象的,比如:冷热,高低。但一些东西可以有这些特性,我们把不能形成具体对象的东西认为是抽象的,C++中也有一种类叫做抽象类,它们是无法被实例化的,但是可以被继承。

抽象类的核心是纯虚函数,纯虚函数就是在虚函数后面加上=0。

抽象类的子类通常需要对抽象类的纯虚函数进行重写,但也可以不重写,这时子类也是抽象类。

相关推荐
福楠2 小时前
现代C++ | 智能指针
c语言·开发语言·c++
汉克老师2 小时前
GESP5级C++考试语法知识(十二、递归算法(二))
c++·算法·记忆化搜索·时间复杂度·递归算法·gesp5级·gesp五级
旺仔.2912 小时前
顺序容器:Array 数组 详解
c++
qq_392807952 小时前
Qt 注册 C++ 给 QML 调用的几种方式
数据库·c++·qt
宵时待雨3 小时前
C++笔记归纳15:红黑树
开发语言·数据结构·c++·笔记
具身小佬3 小时前
两轴机械臂,ros2上位机控制,直接输入坐标或者键盘控制,can通信控制
c++·ubuntu
cccyi73 小时前
【C++ 脚手架】Jsoncpp 库的介绍与使用
c++·optional·jsoncpp
Yupureki3 小时前
《Linux系统编程》16.进程间通信-共享内存
linux·运维·服务器·c语言·数据结构·c++
看山是山_Lau3 小时前
如何封装和定义一个函数
c语言·开发语言·c++·笔记