| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> 【C++】多态 -> 正文阅读 |
|
[C++知识库]【C++】多态 |
文章目录声明:本文中的代码及运行结果均是在32位平台下、VS2022中的测试结果。 概念通俗来说就是多种形态,具体一些就是对于某个行为,当不同的对象去完成时会产生出不同的状态。 再进一步到C++中,就是函数调用的多种形态,这一特性可以让我们在调用函数时更加灵活。 多态又分为静态多态和动态多态:
静态多条在前面讲过,本篇主要讲动态多态,从其定义中就可看出,这里和继承关系非常密切,所以如果对继承不太熟的读者,可以到【C++】继承 了解一下。 一、多态的定义及实现1.多态的构成条件多态是指不同继承关系的类对象,调用同一函数时产生了不同的行为。比如Student(学生)继承了Person(承认),Person对象在买票这一行为上需要全价,而Student对象买票半价。 在继承中要构成多态还有两个条件:
2.virtual关键字和虚函数在成员函数前加virtual关键字后,该函数就变为虚函数(注意一般的函数前不可以加virtual)。 示例代码如下:
编译结果如下: 注意:
3.虚函数重写派生类中有一个跟基类完全相同的虚函数,即派生类虚函数与基类虚函数的返回值类型、函数名、参数列表完全相同,这时称子类的虚函数重写了基类的虚函数。 示例代码如下:
运行结果如下: 下面的代码中,func1以父类的引用为参数,func2以父类的指针为参数,func3以父类的对象为参数。 示例代码如下:
很显然以父类的对象为参数无法实现多态,这也印证了最开始提到的,只有父类的引用或指针才可以实现多态。 运行结果如下: 而如果父类的函数都不是virtual修饰的虚函数,那么就更无法实现多态了,很容易理解,这里就不做演示了。 4.虚函数重写的三个例外(1)协变子类重写父类虚函数时,与父类虚函数仅有返回值类型不同,且父类虚函数返回父类对象的指针或者引用,子类虚函数返回子类对象的指针或者引用时,称为协变。 示例代码如下:
运行结果如下,可以看到虽然pointer()函数的返回值不同,但依然实现了多态。 (2)析构函数重写由于析构函数的函数名是有要求的,所以从代码角度看,子类和父类析构函数的函数名不同,破坏了多态的要求。但实际上,在【C++】继承 提到过,编译器对所有析构函数的函数名都做了特殊处理,编译后析构函数的名称统一处理成destructor。 但是析构函数重写是必要的吗? 下面列举两种情况: ①不需要重写虚构函数示例代码如下:
运行结果如下: 这时两个类中都没有什么需要在析构函数中处理的东西,所以不重写析构函数没有什么影响。 这里再解释一下打印的结果:临时变量储存在栈中,符合后进先出的规则,所以先析构s,后析构p。而析构s时根据继承的规则,先调用子类的析构函数、再调用父类的析构函数,这时s的析构结束了。之后再析构p。 ②需要重写析构函数示例代码如下:
运行结果如下: 从打印结果来看,s指针在delete时仅调用了父类的析构函数,而他在new时申请的是Student的空间,这样就可能造成内存泄漏。这种场景下就需要对Student类的析构函数进行重写。 下面代码将两个类的析构函数定义为虚函数,这样就可以达到析构的目的。 示例代码如下:
运行结果如下,通过重写析构函数,指针在delete时就可以释放对应的资源,不会出现内存泄漏的问题。 (3)子类的虚函数可以不用virtual修饰如果父类的某一个函数已经定义为虚函数,那么子类与之相同函数不需要用virtual修饰,也认为是完成了重写。但实际上这也是C++的一个小坑,为了方便代码的维护,建议还是只要需要用到虚函数的位置全部都用virtual修饰。 5.final修饰父类的虚函数,使该虚函数无法被重写。 示例代码如下:
可以看到,在编译阶段就报错,子类中无法对父类的BuyTickets()进行重写。 编译结果如下: 6.override修饰子类的虚函数,检查该虚函数是否重写,若没有重写则编译报错。 示例代码如下:
代码中子类的BuyTickets函数与父类的参数列表不同,没有构成重写,override直接在编译阶段就报错。 编译结果如下: 二、抽象类1.概念在虚函数的后面写上 “= 0” ,则这个函数为纯虚函数,包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。 下图中用抽象类创建对象失败,证明抽象类无法实例化出对象。 2.接口继承和实现继承普通函数的继承是一种实现继承,子类继承了父类函数,继承的是函数的实现。而虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态。尤其是抽象类的产生,更是强制子类重写父类(否则子类也无法实例化出对象,类的功能大打折扣)。 三、多态的原理1.引出虚函数表指针定义一个如下的类,类成员有一个int类型的变量和一个char类型的变量,成员函数有一个虚函数,计算它的大小。
根据自定义类型(一):结构体 中内存对齐的规则可得该类定义出的对象的大小为8字节,但下面的运行结果是12字节(运行环境是32位平台、VS2022,后文中不再解释)。 只要类中有虚函数,就一定会存在虚函数表指针,它就是用来实现多态的。 2.虚函数表一个含有虚函数的类中都至少有一个虚函数表指针,因为虚函数的地址要被放到虚函数表(本质是一个数组)中,虚表指针指向的就是这个数组的起始地址。 虚函数表也简称虚表,虚函数表指针简称为虚表指针。 如下代码的类中定义了两个虚函数,一个普通函数,观察通过它定义的对象中虚表指针的情况。
可以看到,类中定义了两个虚函数,则对象中含有虚表指针指向的数组中有两个元素,而普通函数则没有对应的虚表指针。 3.多态的原理再回到前面的代码,通过解释其逻辑来说明多态的原理:
通过调试可以看到,p和s中的虚表指针指向的地址不同,也就是说两个类中定义的BuyTickets()不是同一个,所以在调用时调用各自定义的函数,实现了多态。 但如果不满足多态条件,那么调用哪个函数是在编译是就可以确定的,调用函数的对象是什么类型,就调用哪个类型定义的虚函数,与传什么类型的参数无关。 四、静态绑定和动态绑定
感谢阅读,如有错误请批评指正 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/11 5:53:12- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |