news 2026/9/7 21:45:22

面向对象的三大特性之一——多态

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
面向对象的三大特性之一——多态

概念

通俗来说,就是多种形态。多态分为编译时多态(静态多态)和运行时多态(动态多态),我们重点介绍运行时多态。编译时多态(静态多态)主要就是我们前面讲的函数重载和函数模板,他们传不同类型的参数就可以调用不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的,我们一般把编译时归为静态,运行时归为动态

运行时多态,具体点就是去完成某个行为(函数)(情况不同行为就会随之不同),传不同的对象就会完成不同的行为,就达到多种形态。比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是优惠买票;军人买票时是优先买票

具体来说,多态是一个继承关系的下的类对象去调用同一函数,产生了不同的行为

多态的实现条件:1、必须由基类的指针或引用来调用 ,因为只有基类的指针或引用才能既指向基类对象又指向派生类对象2、被调用的函数是虚函数(类成员函数前加关键字virtual即成虚函数,非成员函数不能加virtual),派生类必须对基类的虚函数完成重写/覆盖

派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同,但参数的默认值可以不同)就构成虚函数的重写/覆盖

在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了,在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用。不过在考试选择题中,经常会故意买这个坑,让你判断是否构成多态

这段代码的输出结果是什么

默认参数是在编译时静态绑定的,当编译到“func();”,此时处在类 A 的作用域,编译器看到的是 A 声明的“func(int val = 1)”,编译时期直接改写代码为“func(1)”。p是指向类B的一个对象的指针,test()函数被继承到派生类B,p->test()指调用test()函数,从前面类和对象的知识我们知道成员函数的参数列表隐含有当前类类型(基类A)的指针this,p传给this,"func(1);"其实是“this->func(1);”,构成多态,执行结果是B->1。p->func()不构成多态,是派生类调用自己的成员函数,执行结果是B->0。

协变(了解)

派生类重写基类虚函数时,可以与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。协变的实际意义并不大,所以我们了解一下即可

析构函数的重写

分析此段代码有什么问题?

只能保证派生类B的基类成员部分释放掉,但自己的成员没有释放。那怎么解决呢?多态!

将基类和派生类的析构函数前加virtual,可还不满足多态的条件(函数名不同),为此C++的创造者设立了一个规则,编译后析构函数的函数名均被改为destructor,这也回答了上一部分中为什么基类、派生类的析构函数构成隐藏

关键字:override和final

C++对多态虚函数重写的要求比较严格,如果考虑不周,没有实现出多态,编译时不会报错,等到程序结果不符合预期,调试才能发现问题,C++11为了帮助用户检测是否重写,新增了override关键字

如果不想派生类重写某虚函数,可以在虚函数后面加final,格式同override。至此,我们已经知道了关键字final的两个用法,另一个是让类无法继承

总结

重载/隐藏/重写的对比
重载

两个函数在同一作用域

函数名相同,参数列表不同(个数不同/类型不同),返回值可以相同,也可以不同

隐藏

两个函数分别在继承体系的父类或子类中

函数名相同(由此不构成重写,就会是隐藏)

父类子类的成员变量名称相同也会构成隐藏(类型不同也会隐藏)

重写

两个函数分别在继承体系的父类或子类中

函数名、参数列表、返回值相同,协变有点例外

两个函数都是虚函数

纯虚函数与抽象类

virtual 返回类型 函数名(参数) = 0;

在虚函数的后面写上=0,则这个函数为纯虚函数,不需要写函数体(也可以有函数体,但极少用)。包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象,如果派生类继承后不重写纯虚函数,那么派生类也是抽象类。纯虚函数某种程度上强制了派生类重写虚函数,因为不重写实例化不出对象

多态的原理

虚函数表指针

从我们前面类和对象的知识来看,这段代码的结果是8,但这里不同,类中有虚函数,底层与普通类的对象不同,还多了一个_vfptr的指针(32位下4字节,64位下8字节)放在对象内存空间的开头,那么_vfptr是什么呢,它是虚函数表指针(v是virtual,f是function),它指向一个函数指针数组(节省空间,不用将所有虚函数指针存放在对象内存中),里面存放的是当前对象的所有虚函数指针。虚函数表简称为虚表

多态的机理

那是系统是如何实现基类的指针/引用来调用所指向对象的虚函数呢?C/C++调用函数有两种方式,1、函数在当前文件声明定义一起,编译器在编译时就可以直接确定函数地址;2、函数声明与定义分离,编译器在链接时确定函数地址。满足多态条件后,虚函数调用并不是这两种情况,而是运行时到指向对象的虚表中确定所调用虚函数的地址

动态绑定与静态绑定

对不满足多态条件(指针或者引用+调用虚函数)的函数调用是在编译时绑定,也就是编译时确定调用函数的地址,叫做静态绑定
满足多态条件的函数调用是在运行时绑定,也就是在运行时到指向对象的虚函数表中找到调用函数
的地址,也就做动态绑定

虚函数表

基类对象的虚函数表中存放基类所有虚函数的地址。同类型的对象共用同一张虚表,不同类型的对
象各自有独立的虚表,所以基类和派生类有各自独立的虚表

派生类由两部分构成,继承下来的基类和自己的成员,一般情况下,继承下来的基类中有虚函数表
指针,自己就不会再生成虚函数表指针。但是要注意的这里继承下来的基类部分虚函数表指针和基
类对象的虚函数表指针不是同一个,就像基类对象的成员和派生类对象中的基类对象成员也独立

派生类中重写的基类的虚函数,派生类的虚函数表中对应的虚函数就会被覆盖成派生类重写的虚函
数地址

派生类的虚函数表中包含,(1)基类的虚函数地址,(2)派生类重写的虚函数地址完成覆盖,(3)派生类自己的虚函数地址三个部分

虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个0x00000000标
记。(这个C++并没有进行规定,各个编译器自行定义的,VS系列编译器会再后面放个0x00000000标记,g++系列编译不会放)

虚函数存在哪的?虚函数和普通函数一样的,编译好后是一段指令,都是存在代码段的,只是虚函
数的地址又存到了虚表中

虚函数表存在哪的?这个问题严格说并没有标准答案C++标准并没有规定,VS下是存在代码段(常量区)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 21:44:52

电梯安全双重保障:自校准与第三方验证技术解析

1. 项目背景与核心价值电梯作为现代建筑中不可或缺的垂直交通工具,其安全性直接关系到千家万户的生命财产安全。这次参与特检院的"向内校准、向外验证"项目,让我对电梯安全治理有了全新的认识。不同于常规的定期检验,这套体系通过设…

作者头像 李华
网站建设 2026/9/7 21:44:41

企业微信AI客服机器人实战:从消息回调到大模型接入

1. 先想清楚边界:你做的到底是“群客服”还是“消息转发AI大脑”上个月我接了一个小需求。朋友经营一个本地服务群,六百多人,每天都被相似的问题包围:几点开门、怎么预约、收费多少、售后找谁。他想把这些高频问题直接丢给AI&…

作者头像 李华
网站建设 2026/9/7 21:44:30

超级碗快闪店:沉浸式体验与零售技术创新

1. 项目背景与行业趋势这个快闪项目出现在超级碗前夕绝非偶然。作为美国收视率最高的体育赛事,超级碗早已超越单纯的体育竞技范畴,成为品牌营销的"黄金战场"。根据Nielsen数据,2023年超级碗吸引了超过1.13亿观众,30秒广…

作者头像 李华
网站建设 2026/9/7 21:42:57

奥拉星阴间渡平民通关攻略:残烬同焚MVP打法详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 21:40:44

ansible 实例 -- 用于 Linux 文件系统的操作 -- 创建空文件

如何创建空的 ~/example.txt 文件,或更新已存在文件的访问时间。 Ansible 创建空文件 我们来谈谈 Ansible 模块 file。 全名是 ansible.builtin.file,这意味着它是 Ansible 自带的 “builtin” 集合的一部分。 这是一个非常稳定且已发布多年的模块。 它适用于多种操作系…

作者头像 李华