多态的使用和原理(c++详解)

article/2025/8/15 4:12:40

一、多态的概念

        多态顾名思义就是多种形态,它分为编译时的多态(静态多态)运行时的多态(动态多态),编译时多态(静态多态)就是函数重载,模板等,通过不同的参数来完成对不同的函数的调用(即生成多种形态)并且这个过程在编译阶段就已经完成

        动态多态是在运行时根据对象的实际类型来确定调用函数的哪个版本,完成不同的⾏为。

二、多态构成条件

1.虚函数

        在类成员函数的返回类型前面添加virtual关键字即为虚函数,注意:虚函数只能定义于普通成员函数,构造函数以及类外函数不能定义虚函数。

2.虚函数的重写

        虚函数重写指的是子类(派生类)对父类(基类)的重写。重写的要求是子类虚函数的返回值,函数名,参数类型必须和父类一模一样。但函数的实现逻辑不用相同。

        这里如果虚函数的重写没有加virtual,但是父类加了virtual那么子类依旧保持virtual的性质,也可构成重写。

        注意:对虚函数重写并没有要求缺省参数要相同,但在这里强烈建议把缺省参数设为相同值,要不然会给你带来很大的弊端和误导性。接下来我会讲到。

3.调用方式

        要实现多态效果,第⼀必须是基类的指针或引⽤,因为只有基类的指针或引⽤才能既指向派⽣类对象又能指向基类;第⼆派⽣类必须对基类的虚函数重写/覆盖,重写或者覆盖了,派⽣类才能有不同的函数,多态的不同形态效果才能达到。

⽐如火车买票这个操作,当普通⼈买票时,是全价买票;学⽣买票时,是优惠买票;军⼈买票时是优先买票,我们就可以用多态来实现,如下:

#include<iostream>
using namespace std;
class ticket
{
public:virtual void func(){cout << "普通票" << endl;}
private:
};
class student:public ticket
{
public:virtual void func(){cout << "学生票" << endl;}
private:
};
void fm(ticket& pu)
{pu.func();
}
int main()
{ticket tk;student stu;fm(tk);fm(stu);return 0;
}

4.override和final的修饰

        override关键字:因为多态的实现细节要求太多了特别是对虚函数的重写,因此C++11提供了override,可以帮助⽤⼾来检查虚函数的重写是否正确,需要放在重写的函数参数列表后面。

        final关键字:如果不想子类对该虚函数进行重写的话就可以使用final关键字,放在函数名后面。

5.协变 

        刚才我们说了虚函数的重写一定要满足子类虚函数的返回值,函数名,参数类型必须和父类相同。协变是个例外情况。当子类重写父类虚函数时,若与父类虚函数返回值类型不同,即父类虚函数返回父类对象的指针或引用,子类虚函数返回子类对象的指针或引用,此时称为协变。协变的实际意义并不⼤,所以我们了解⼀下即可。

代码示例:

class A{};
class B :public A{};
class ticket
{
public:virtual ticket* func()//ticket也可以是A{cout << "普通票" << endl;return this;}
private:
};
class student:public ticket
{
public:virtual student* func() override//student也可以是B{cout << "学生票" << endl;return this;}
private:
};

析构函数的重写

        父类的析构函数为虚函数,此时派⽣类析构函数只要定义,⽆论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成destructor所以基类的析构函数加了vialtual修饰,派⽣类的析构函数就构成重写

A* p1 = new A;
A* p2 = new B;
delete p1;
delete p2;

        假设B是A的子类上⾯的代码如果~A(),不加virtual,那么delete p2时只调⽤A的析构函数,没有调⽤B的析构函数,就会导致内存泄漏问题。

三、纯虚函数和抽象类

        在虚函数的参数列表后⾯写上=0,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现没啥意义因为要被子类重写,但是语法上可以实现),只要声明即可。包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象如果子类继承后不重写纯虚函数,那么子类也是抽象类。纯虚函数某种程度上强制了子类重写虚函数,因为不重写实例化不出对象。

四、多态原理

        在分析对象的储存空间时,我们讲过对于同一个类实例化出的不同对象,这些对象使用的函数都是相同的,不同的是它们成员变量。所以每个对象只需要对成员变量进行储存,不用对成员函数进行储存,每一个对象使用的都是这个类的公共成员函数。

        c++为虚函数单独设立了一块区域来储存虚函数的地址,叫做虚表,而这块区域其实 就是一个函数指针数组。即用来储存函数指针的一个数组。那么父类和子类就各自有一个虚表,在对象实例化的时候就会隐含(隐含:类似于成员函数里面看不见的this指针一样)着一个指针——虚表指针,来指向虚表。

#include<iostream>
using namespace std;
class A
{
public:virtual void func(){}
};
class B
{
public:void func(){}
};
int main()
{A a;B b;cout << "a:" << sizeof(a) << endl;cout << "b:" << sizeof(b) << endl;return 0;
}

而虚表指针也是需要占用空间的大家可以自行地去运行一下以上代码,输出结果为:

a:4(或8,即32位与64位机器的区别)

b:1

        所以在调用对象的虚函数时就跟以什么类型的形式调用无关,而是跟这个对象实例化时具体类型有关。

#include<iostream>
using namespace std;
class ticket
{
public:virtual void func(){cout << "普通票" << endl;}
};
class student :public ticket
{
public:virtual void func() {cout << "学生票" << endl;}
};
int main()
{ticket* tk = new student;tk->func();return 0;
}

以上的输出结果是“学生票”。

注意:根据切片原理,子类可强制类型转化为父类,父类不能强制类型转化为子类。

五、练习

以下程序输出结果是什么()

  • A:A->0
  • B:B->1
  • C:A->1
  • D:B->0
  • E:编译出错
  • F:以上都不正确

        这里虽然B类的func成员没有写virtual关键字,但它是由A继承下来的依旧保留virtual的性质,然后因为重写并为要求参数的缺省值相同,所以这里构成函数的重写。再来看主函数main,p调用了test,而test是A的成员函数隐含了一个const   A* (this指针)的参数类型,p传到test函数满足多态,所以这里调用的是B的func。但是这里有个坑,该题的输出结果并不是“B->0”,而是“B->1”。

        要注意重写只是重写了函数的实现,也就是说实现多态的时候相当于调用的是父类的接口声明和子类的函数实现,而并不关心子类的函数接口声明。

        所以在我们自己写虚函数的时候,最好把缺省参数设为相同值,要不然会给你带来很大的误导性。


http://www.hkcw.cn/article/ojjhHaBJoF.shtml

相关文章

C++ 底层实现细节隐藏全攻略:从简单到复杂的五种模式

目录标题 1 引言&#xff1a;为什么要“隐藏实现”1.1 头文件暴露带来的三大痛点1.2 ABI 稳定 vs API 兼容&#xff1a;先分清概念1.3 选型三问法——评估你到底要不要隐藏 2 模式一&#xff1a;直接按值成员 —— “裸奔”也能跑2.1 典型写法与最小示例2.2 何时按值最合适&…

使用国内镜像网站在线下载安装Qt(解决官网慢的问题)——Qt

国内镜像网站 中国科学技术大学&#xff1a;http://mirrors.ustc.edu.cn/qtproject/清华大学&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/qt/北京理工大学&#xff1a;http://mirror.bit.edu.cn/qtproject/ 南京大学&#xff1a;https://mirror.nju.edu.cn/qt腾讯镜像&…

超全超详细!JDK 安装及环境配置(Java SE 8 保姆级教程)

一、JDK 简介 JDK&#xff08;Java Development Kit&#xff09;是用于开发 Java 程序的工具包&#xff0c;包括编译器 javac、Java 运行环境&#xff08;JRE&#xff09;以及各种开发工具。安装和配置 JDK 是学习和使用 Java 编程的第一步&#xff0c;以下是 Java 和 JDK 的具…

Java 大视界 -- 基于 Java 的大数据分布式数据库在社交网络数据存储与查询中的架构设计与性能优化(225)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

C++协程从入门到精通

文章目录 一、C协程入门知识&#xff08;一&#xff09;基本概念&#xff08;二&#xff09;特点&#xff08;三&#xff09;应用场景 二、C协程精通知识&#xff08;一&#xff09;高级特性&#xff08;二&#xff09;优化技巧&#xff08;三&#xff09;错误处理机制&#xf…

蓝桥杯第十六届c组c++题目及个人理解

本篇文章只是部分题目的理解&#xff0c;代码和思路仅供参考&#xff0c;切勿当成正确答案&#xff0c;欢迎各位小伙伴在评论区与博主交流&#xff01; 目录 题目&#xff1a;2025 题目解析 核心提取 代码展示 题目&#xff1a;数位倍数 题目解析 核心提取 代码展示 …

C++日新月异的未来代码:C++11(上)

文章目录 1.统一的列表初始化1.1 普通{ }初始化1.2 initializer_list 2.声明2.1 auto、nullptr2.2 decltype 3.左值右值3.1 概念3.2 左值引用与右值引用比较3.3 左值引用与右值引用的应用3.4 完美转发 希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xf…

C++从入门到实战(十二)详细讲解C++如何实现内存管理

C从入门到实战&#xff08;十二&#xff09;详细讲解C如何实现内存管理 前言一、C内存管理方式1. new/delete操作内置类型2. 异常与内存管理的联系&#xff08;简单了解&#xff09;3. new和delete操作自定义类型 二、 operator new与operator delete函数&#xff08;重点&…

【2025年最新版】Java JDK安装、环境配置教程 (图文非常详细)

文章目录 【2025年最新版】Java JDK安装、环境配置教程 &#xff08;图文非常详细&#xff09;1. JDK介绍2. 下载 JDK3. 安装 JDK4. 配置环境变量5. 验证安装6. 创建并测试简单的 Java 程序6.1 创建 Java 程序&#xff1a;6.2 编译和运行程序&#xff1a;6.3 在显示或更改文件的…

【Linux系统】从 C 语言文件操作到系统调用的核心原理

文章目录 前言lesson 15_基础IO一、共识原理二、回顾C语言接口2.1 文件的打开操作2.2 文件的读取与写入操作2.3 三个标准输入输出流 三、过渡到系统&#xff0c;认识文件系统调用3.1 open 系统调用1. 比特位标志位示例 3.2 write 系统调用1. 模拟实现 w 选项2. 模拟实现 a 选项…

JavaSwing之--JTextField

JavaSwing之–JTextField JTextField 是一个允许编辑单行文本的轻量级组件&#xff0c;它提供了一系列的构造方法和常用方法用来编写可以存储文本的文本框满足程序功能的需求。 以下在简要介绍常用构造方法、普通方法后详解各种方法的应用及举例。 一、构造方法 方法名称功…

Windows系统之VHD安装

环境准备 工具说明Dism部署系统、提取和转换系统镜像等等&#xff0c;还有很多功能大家可以自行探索。这里只用到Dism的部署系统功能。 Releases Chuyu-Team/Dism-Multi-language GitHubbcdedit.exe自带工具 C:\Windows\System32\bcdedit.exe 创建虚拟磁盘 首先右键点击我…

解决Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field ‘com.sun.tools.javac.tre

问题描述 在更新自建基础项目过程中&#xff0c;compile、install报错。 Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualid 解决方案 问题原因是Lombok &#xff0c;与 JDK 21 兼容的最低 Lombok 版本是…

【C++】二叉搜索树 - 从基础概念到代码实现

&#x1f4cc; 个人主页&#xff1a; 孙同学_ &#x1f527; 文章专栏&#xff1a;C &#x1f4a1; 关注我&#xff0c;分享经验&#xff0c;助你少走弯路 文章目录 1. 二叉搜索树的概念2. 二叉搜索树的性能分析3. 二叉搜索树的插入4. 二叉搜素树的查找5. 二叉搜索树的删除6.二…

C++之类和对象基础

⾯向对象三⼤特性&#xff1a;封装、继承、多态 类和对象 一.类的定义1. 类的定义格式2.类域 二.实例化1.对象2.对象的大小 三.this指针 在 C 的世界里&#xff0c;类和对象构成了面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;的核心框架&…

报错java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not ...解决方法

在运行项目时出现java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualidzz这样的报错 解决方法 1.第一步&#xff1a;在pom文件中将lombok的版本改成最新的 此时1.18.34是新…

2025-03-12 Python深度学习1——安装Anaconda与PyTorch库

文章目录 1 配置 Anaconda1.1 下载1.2 安装1.3 配置环境变量1.4 检查安装 2 安装 PyTorch 库2.1 创建 DL 环境2.2 安装/升级 CUDA2.3 配置环境变量2.4 安装 Pytorch 库方法一&#xff08;不稳定&#xff09;方法二&#xff08;推荐&#xff09; 2.5 检查安装 3 Pycharm Communi…

C++ 关联式容器:map,multimap,set,multiset

目录 引言 一、关联式容器概述 1.1 与序列式容器的区别 1.2 底层结构 二、set容器详解set介绍 2.1 set的特性 2.2 set的模板参数 2.3 set的常用接口 2.4 set使用示例 三、map容器详解map介绍 3.1 map的特性 3.2 map的模板参数 3.3 map的常用接口 3.4 map使用示例 …

从零开始配置Qt+VsCode环境

从零开始配置QtVsCode环境 文章目录 从零开始配置QtVsCode环境写在前面扩展安装及配置Qt Configure配置 VsCode创建Qt工程VsCodeQMakeMinGwVsCodeQMakeMsvcVsCodeCMakeMinGwVsCodeCMakeMsvcQtCreatorQMakeMinGw->VsCodeQtCreatorQMakeMsvc->VsCodeQtCreatorCMakeMinGw-&g…

Matlab/Simulink - BLDC直流无刷电机仿真基础教程(一) - 三相逆变器的搭建

Matlab/Simulink - BLDC直流无刷电机仿真基础教程&#xff08;一&#xff09; - 三相逆变器的搭建 前言一、BLDC电机六步换相简明控制原理二、Simulink中BLDC电机模块的机械连接三、三相逆变电路的搭建四、仿真参数设置与仿真结果验证五、补充内容参考链接 前言 本系列文章分享…