当前位置: 首页 > news >正文

C++多态

2. 多态的概念

同一操作作用于不同对象,可以产生不同的行为。 我们以 “乐器演奏会”为例,指挥家在台上挥动指挥棒,此时:

  • 钢琴演奏者听到后,开始弹奏钢琴。
  • 小提琴演奏者听到后,开始拉小提琴。
  • 鼓手听到后,开始击鼓。

在这个场景中,“指挥棒挥动” 就是统一的接口,而不同乐器的演奏者则是不同的对象,他们对这一统一指令做出了不同的响应,这就解释了什么是多态。

在这里插入图片描述

多态的核心价值:

  1. 接口与实现分离: 基类定义接口,派生类提供实现
  2. **可扩展性:**新增派生类不影响现有代码
  3. **代码复用:**通用逻辑放在基类,派生类专注差异
  4. **设计灵活性:**支持运行时对象替换(工厂模式、策略模式等)

2.1 多态的定义和实现

在C++中实现多态主要通过方法重写继承实现。在继承中要构成多态还有两个条件(缺一不可):

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且子类必须对基类的虚函数进行重写

注: 使用父类指针指向子类对象是实现多态性的核心机制

在这里插入图片描述


2.2 虚函数

虚函数:即被virtual关键字修饰的类成员函数称为虚函数。

class Person {
public:// 虚函数(用于多态)virtual void introduce() {cout << "我是普通人。" << endl;}

2.3 虚函数重写

子类中定义与父类虚函数 同名、同参数列表、同返回类型 的函数,重写(覆盖)父类的实现。

注意: 在重写父类虚函数时,子类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用。

class Person {
public:virtual void introduce() {cout << "我是普通人。" << endl; }
};
class Student : public Person {
public:void introduce() override {// 重写父类的虚函数,override检查是否重写cout << "我是一个学生。" << endl; }
};

虚函数的重写有两个例外:协变析构函数的重写。


  • 协变(父类与子类虚函数返回值类型不同)

子类重写父类虚函数时,与父类虚函数返回值类型不同。即父类虚函数返回基类对象的指针或者引用,子类虚函数返回子类对象的指针或者引用时,称为协变。

class A{};
class B : public A {};
class Person {
public:virtual A* f() {return new A;}};
class Student : public Person {public:virtual B* f() {return new B;}};

  • 虚析构函数(父类与子类析构函数的名字不同)

当通过父类指针删除子类对象,而父类的析构函数不是虚函数时,只会调用父类的析构函数,而不会调用子类的析构函数。这会导致子类对象的资源无法被正确释放,造成内存泄漏。父类析构函数为虚函数,子类只需定义即可构成重写。

在这里插入图片描述

此处因为Person的析构函数不是虚函数,delete p2只会根据指针类型(Person*)调用父类析构函数,导致子类Student析构函数未被调用。


在这里插入图片描述

将父类的析构函数声明为虚函数,此时通过父类指针删除子类对象时,会先调用子类的析构函数,再调用父类的析构函数,确保资源正确释放。


2.4 final和override

final:修饰虚函数,表示该虚函数不能再被重写
override: 检查派子类虚函数是否重写了父类某个虚函数,如果没有重写编译报错(重写检查)。


2.5 重载、重写、重定义

  • 重载:在同一个类中定义多个同名方法,但这些方法的参数列表不同(参数类型、数量或顺序)。编译器根据调用时的实参类型和数量来决定执行哪个方法。
  • 重写: 也叫做覆盖,一般发生在子类和父类继承关系之间。子类重新定义父类中有相同名称和参数的虚函数。
  • 隐藏: 也叫做重定义,子类重新定义父类中有相同名称的非虚函数(参数列表可以不同),指派生类的函数屏蔽了与其同名的基类函数。可以理解成发生在继承中的重载

在这里插入图片描述

重定义:

class Base {
public:void show() { cout << "Base show" << endl; }    // 非虚函数void print(int x) { cout << "Base: " << x << endl; }
};class Derived : public Base {
public:// 重定义show(隐藏基类show)void show() { cout << "Derived show" << endl; }// 重定义print(参数不同)void print(double x) { cout << "Derived: " << x << endl; }
};

2.6 抽象类

本质是强制子类完成基类虚函数的重写来实现特定方法。通过父类指针引用操作子类类对象。override只在语法上检查是否完成重写。

定义:

  • 抽象类是包含至少一个纯虚函数的类,无法被实例化,只能作为父类被继承。
  • 纯虚函数的声明方式为:virtual 返回类型 函数名(参数列表) = 0;,体现出了接口的继承。

2.7 接口继承与实现继承

2.7.1 接口继承

指子类仅继承父类的方法签名(接口),不继承具体实现。目的是为了重写,实现多态。

2.7.2 实现继承

实现继承是指派生类继承基类的方法实现,可直接使用或重写。通过普通虚函数非虚函数实现。


2.8 多态的原理

在这里插入图片描述

我们定义一个没有成员的空类,只有一个虚函数。但是它的大小为4,可以推测其中暗含一个指向虚函数表的指针,依靠虚函数表指针+虚表实现多态?下面进行验证


虚函数表:

  • 每个包含虚函数的类都会有一个虚函数表,它是一个函数指针数组。
  • 表中存储虚函数地址,表中存放着虚函数的地址。当通过基类指针或引用调用虚函数时,程序会使用对象的虚函数表来查找正确的函数地址并调用。

虚函数指针:

  • vfptr是一个存储在对象中的指针,指向该对象类的vtable。每个包虚函数表指针(vfptr):含虚函数的类的对象都会有一个vfptr
  • 当创建对象时,对象的vfptr会被初始化为指向其类的虚函数表。当通过基类指针或引用调用虚函数时,会通过vfptr访问虚函数表,从而实现多态

在这里插入图片描述

通过观察和测试,我们发现了以下几点问题:

http://www.lqws.cn/news/473671.html

相关文章:

  • 【Docker基础】Docker镜像管理:docker pull详解
  • flutter环境变量记录
  • Spring Cloud OpenFeign:微服务调用的终极利器
  • 面试题-定义一个函数入参数是any类型,返回值是string类型,如何写出这个函数,代码示例
  • 从C++编程入手设计模式——观察者模式
  • Matplotlib绘制矩阵图,plt.matshow/imshow 与 ax.pcolor(pcolormesh)方法的使用
  • Python的6万张图像数据集CIFAR-10和CIFAR-100说明
  • PowerBI HtmlContent生成表格
  • 【软考高级系统架构论文】论企业应用系统的数据持久层架构设计
  • MinIO入门教程:从零开始搭建方便快捷的分布式对象存储服务
  • 一文详解归并分治算法
  • Linux -- 文件描述符和重定向
  • Java 面试指南:深度解析 Spring Boot 与微服务架构
  • OpenBMC构建之旅:从命令到镜像的内存执行全解析
  • Java 包装类详解
  • 机器学习15-XGBoost
  • 吴恩达:从斯坦福到 Coursera,他的深度学习布道之路
  • 一套基于粒子群优化(PSO)算法的天线波束扫描MATLAB实现方案
  • Vibe Coding - 进阶 Cursor Rules
  • 深度学习的可解释性——SketchXAI:人类草图可解释性初探
  • LangChain-5-agent
  • 【51单片机2位数码管100毫秒的9.9秒表】2022-5-16
  • CNN工作原理和架构
  • 15.1 LangChain多轮对话训练实战:打造高自然度语言学习Agent的三大核心技术
  • 编程基础:调用访问
  • MagicTryOn: 变革性的AI视频虚拟试衣体验
  • 磁性传感器在电机控制闭环系统中的反馈作用
  • α射线检测DIY 空霸KB6011云母窗口魔改版 5250饼管 新固件下载
  • VMWare-Centos7.x 忘记 Linux 密码
  • QT学习教程(三十五)