《21天学通C++》(第十一章)多态

为什么需要多态?
为了最大限度地减少代码,提高可读性

1.虚函数

虚函数是C++中的一种特殊成员函数,它允许在派生类(也称为子类)中重写(覆盖)基类的实现,使用virtual进行声明

在C++中,如果基类中的成员函数不是虚函数,派生类中的同名函数并不会覆盖或重写基类中的函数,而是产生函数隐藏,意味着如果你通过基类类型的指针或引用调用该函数,实际上调用的是基类中的版本,而不是派生类中的版本。

不使用虚函数:

#include <iostream>
using namespace std;class Base {
public:// 普通函数,不是虚函数void func() {cout << "Base func" << endl;}
};class Derived : public Base {
public:// 看起来像是重写,实际上是函数隐藏void func() {cout << "Derived func" << endl;}
};int main() {Base* basePtr = new Derived();basePtr->func(); // 调用 Base::func,而不是 Derived::func//输出结果为Base funcdelete basePtr;system("pause");return 0;
}

使用虚函数

#include <iostream>
using namespace std;class Base {
public:// 声明为虚函数virtual void func() {cout << "Base func" << endl;}
};class Derived : public Base {
public://真正地重写void func() {cout << "Derived func" << endl;}
};int main() {Base* basePtr = new Derived();basePtr->func(); // 正确调用 Derived::func//输出结果为Derived funcdelete basePtr;system("pause");return 0;
}

2.使用虚函数实现多态行为

通过函数引用实现

#include <iostream>
using namespace std;// 基类 Fish,定义了鱼类的通用行为
class Fish {
public:// 虚函数 swim,允许派生类重写,实现多态virtual void swim() const {cout << "Fish is swimming" << endl;}
};// 派生类 Tuna,继承自 Fish
class Tuna : public Fish {
public:// 重写 Fish 类的 swim 函数,实现 Tuna 类特有的游泳行为void swim() const override {cout << "Tuna is swimming fast" << endl;}
};// 派生类 Carp,继承自 Fish
class Carp : public Fish {
public:// 重写 Fish 类的 swim 函数,实现 Carp 类特有的游泳行为void swim() const override {cout << "Carp is swimming slowly" << endl;}
};// 函数,使用 Fish 类的引用参数来实现多态
void makeFishSwim(const Fish& fish) {fish.swim(); // 根据传入对象的实际类型调用相应的 swim 方法
}int main() {Tuna tuna;Carp carp;// 通过引用传递给函数,实现多态makeFishSwim(tuna); // 输出 "Tuna is swimming fast"makeFishSwim(carp); // 输出 "Carp is swimming slowly"system("pause");return 0;
}

通过指针实现:

#include <iostream>
using namespace std;// 基类 Fish,定义了鱼类的通用行为
class Fish {
public:// 虚函数 swim,允许派生类重写,实现多态virtual void swim() {cout << "Fish is swimming" << endl;}// 虚析构函数virtual ~Fish() {cout << "Fish is deconstructed" << endl;}
};// 派生类 Tuna,继承自 Fish
class Tuna : public Fish {
public:// 重写 Fish 类的 swim 函数,实现 Tuna 类特有的游泳行为void swim() override {cout << "Tuna is swimming fast" << endl;}// Tuna 类的析构函数~Tuna() {cout << "Tuna is deconstructed" << endl;}
};// 派生类 Carp,继承自 Fish
class Carp : public Fish {
public:// 重写 Fish 类的 swim 函数,实现 Carp 类特有的游泳行为void swim() override {cout << "Carp is swimming slowly" << endl;}// Carp 类的析构函数~Carp() {cout << "Carp is deconstructed" << endl;}
};int main() {// 创建派生类对象Fish* fish = new Tuna();fish->swim(); // 调用 Tuna::swim,输出 "Tuna is swimming fast"Fish* carp = new Carp();carp->swim(); // 调用 Carp::swim,输出 "Carp is swimming slowly"// 删除对象,调用相应的析构函数delete fish;delete carp;system("pause");return 0;
}

3.虚函数的工作原理——虚函数表

虚函数表(通常称为vtable)是C++中实现运行时多态的一种机制。当一个类包含至少一个虚函数时,编译器会为这个类创建一个虚函数表,这张表包含了类中所有虚函数的地址。

工作流程如下:

1.虚函数表的创建: 当一个类中包含至少一个虚函数时,编译器会为这个类创建一个虚函数表。这个表包含了该类所有虚函数的地址。

2.虚函数表指针: 编译器为每个对象添加一个指针,指向其类的虚函数表。这个指针通常存储在对象的内存布局的最前面。

3.调用虚函数: 当你通过一个基类指针或引用调用一个虚函数时,编译器生成的代码首先会访问对象的虚函数表指针,然后查找并调用表中对应的函数。

4.动态绑定: 由于虚函数表的存在,函数调用的解析是在运行时进行的,这称为动态绑定或晚期绑定。这意味着即使基类指针指向的是派生类对象,调用的也是派生类中重写的函数版本。

class Base {
public:virtual void show() {std::cout << "Base show" << std::endl;}virtual ~Base() {}  // 虚析构函数
};class Derived : public Base {
public:void show() override {  // 重写基类中的虚函数std::cout << "Derived show" << std::endl;}
};int main() {Base* basePtr = new Derived();  // 创建Derived对象的指针,但声明为Base类型basePtr->show();  // 调用show(),虽然basePtr是Base类型,但实际调用的是Derived的show()delete basePtr;return 0;
}

4.抽象基类和纯虚函数

抽象基类: 至少包含一个纯虚函数,而且无法被实例化,只能用于派生其他类,简称为ABC

纯虚函数: 它在基类中声明但故意不提供实现,其声明的函数体部分使用 = 0 来标识

virtual ReturnType FunctionName() = 0;

抽象基类使用方法如下:

#include <iostream>
using namespace std;// 抽象基类
class Shape {
public:// 纯虚函数,用于定义绘制形状的接口virtual void draw() const = 0;// 虚析构函数,确保派生类的析构函数被正确调用virtual ~Shape() {}
};// 派生类 Circle,表示圆形
class Circle : public Shape {
public:// 实现 Circle 的 draw 方法void draw() const override {std::cout << "Drawing a circle." << std::endl;}
};// 派生类 Rectangle,表示矩形
class Rectangle : public Shape {
public:// 实现 Rectangle 的 draw 方法void draw() const override {std::cout << "Drawing a rectangle." << std::endl;}
};int main() {// 创建一个指向 Shape 的指针数组,用于存储不同形状的指针Shape* shapes[] = { new Circle(), new Rectangle() };// 使用基类指针调用 draw 方法,实现多态for (Shape* shape : shapes) {shape->draw(); // 根据对象的实际类型调用相应的派生类的 draw 方法}// 释放动态分配的内存for (Shape* shape : shapes) {delete shape;}system("pause");return 0;
}

5.使用虚继承解决菱形问题

菱形问题: 即一个派生类继承自两个中间基类,而这两个中间基类又都继承自同一个基类时。这种继承结构在类图上看起来像一个菱形,因此得名。
在这里插入图片描述
田园犬类同时继承狗类和哺乳类,而哺乳类和狗类又同时继承动物类,呈现一个菱形结构。

在这个例子中田园犬类会分别从狗类和哺乳类中各自继承一个动物类,导致内存浪费和潜在的一致性问题,所以为了解决这个问题,可以使用虚函数继承来解决

#include <iostream>
using namespace std;// 定义基类 Animal
class Animal {
public:// 动物的呼吸方法virtual void breathe() { cout << "Animal breathes" << endl; }// 虚析构函数,确保派生类可以正确释放资源virtual ~Animal() {}
};// 定义中间基类 Mammal,使用虚继承自 Animal
class Mammal : virtual public Animal {
public:// 哺乳动物特有的哺育行为void nurse() { cout << "Mammal nurses its young" << endl; }// 虚析构函数virtual ~Mammal() {}
};// 定义中间基类 Dog,使用虚继承自 Animal
class Dog : virtual public Animal {
public:// 狗的吠叫行为void bark() { cout << "Dog barks" << endl; }// 虚析构函数virtual ~Dog() {}
};// 定义派生类 Poodle,同时继承自 Dog 和 Mammal
class Poodle : public Dog, public Mammal {
public:// 贵宾犬特有的行为void prance() { cout << "Poodle prances" << endl; }// 虚析构函数virtual ~Poodle() {}
};// 主函数
int main() {// 创建 Poodle 对象Poodle myPoodle;// 调用从各个基类继承来的方法myPoodle.bark();    // Dog 类的 bark 函数myPoodle.nurse();   // Mammal 类的 nurse 函数myPoodle.breathe();  // Animal 类的 breathe 函数myPoodle.prance();   // Poodle 类的 prance 函数system("pause"); // 用于在控制台程序结束前暂停,以便查看输出return 0;
}

6.表明覆盖意图的限定符override

使用override关键字有助于编译器检查函数签名是否与基类中的虚函数相匹配,从而提高代码的可读性和安全性。

使用方法如下:

class Base {
public:virtual void function() {// 基类}
};class Derived : public Base {
public:void function() override { // 使用 override 明确指出重写// 派生类}
};

7.使用final禁止覆盖函数

final关键字用于阻止派生类进一步重写(覆盖)基类中的虚函数。当你希望某个虚函数在派生类中保持最终实现,不允许任何进一步的重写时,可以使用final关键字。

class Base {
public:virtual void function() final {//使用final禁止覆盖// 基类实现}
};class Derived : public Base {
public:void function() override { // 这里会编译错误,因为 Base::function() 被声明为 final// 派生类实现}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/320043.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java EE】多线程(二)Thread 类与常用方法

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

【Qt QML】Frame组件

Frame&#xff08;框架&#xff09;包含在&#xff1a; import QtQuick.Controls继承自Pane控件。用于在可视框架内布局一组逻辑控件。简单来说就是用来包裹和突出显示其他可视元素。Frame不提供自己的布局&#xff0c;但需要自己对元素位置进行设置和定位&#xff0c;例如通过…

【JavaEE 初阶(二)】线程安全问题

❣博主主页: 33的博客❣ ▶️文章专栏分类:JavaEE◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多线程知识 目录 1.前言2.synchronized2.1例子2.2synchronized修饰代码块2.3 synchronized修饰方法2.4sy…

python中怎么清屏

一、“Windows命令行窗口”下清屏&#xff0c;可用下面两种方法&#xff1a; 第一种方法&#xff0c;在命令行窗口输入&#xff1a; import os ios.system("cls") 第二种方法&#xff0c;在命令行窗口输入&#xff1a; import subprocess isubprocess.call("cl…

数据结构--链表进阶面试题

在链表题目开始之前我们来复习一道数组元素的逆序问题&#xff1a; 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 提示&#xff1a; 1 < nums.length < 10^5-2^31 < nums[i] < 2^31 - 10 < k < 10^5 思…

微信小程序之搜索框样式(带源码)

一、效果图&#xff1a; 点击搜索框&#xff0c;“请输入搜索内容消失”&#xff0c;可输入关键字 二、代码&#xff1a; 2.1、WXML代码&#xff1a; <!--搜索框部分--><view class"search"><view class"search-btn">&#x1f50d;&l…

QT5之事件——包含提升控件

事件概述 信号就是事件的一种&#xff0c;事件由用户触发&#xff1b; 鼠标点击窗口&#xff0c;也可以检测到事件&#xff1b;产生事件后&#xff0c;传给事件处理&#xff0c;判断事件类型&#xff0c;后执行事件相应函数&#xff1b; 类似单片机的中断&#xff08;中断向量…

Docker 入门与实践:从零开始构建容器化应用环境

Docker 一、docker常用命令docker ps 格式化输出Linux设置命令别名 二、数据卷相关命令挂载到默认目录&#xff08;/var/lib/docker&#xff09;挂载到本地目录 三、自定义镜像Dockerfile构建镜像的命令 四、网络自定义网络 五、DockerCompose相关命令 一、docker常用命令 dock…

Superset二次开发之Legend功能优化

背景 Legend数据太长,影响整体图表体验,为改善用户体验,需要实现:1.数据省略展示,‘...’表示,鼠标悬停时,展示完整信息 2:文本内容从左向右滚动展示 柱状图优化 柱状图来自第三方Echarts插件,效果展示 功能核心在于红框的内容 option = {tooltip: {trigger: item,ax…

软件杯 深度学习的水果识别 opencv python

文章目录 0 前言2 开发简介3 识别原理3.1 传统图像识别原理3.2 深度学习水果识别 4 数据集5 部分关键代码5.1 处理训练集的数据结构5.2 模型网络结构5.3 训练模型 6 识别效果7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习…

Vue3+Nuxt3 从0到1搭建官网项目(SEO搜索、中英文切换、图片懒加载)

Vue2Nuxt2 从 0 到1 搭建官网~ Vue3Nuxt3 从0到1搭建官网项目 安装 Nuxt3&#xff0c;创建项目初始化的 package.json项目结构初始化项目pages 文件下创建index.vue引入sass修改 app.vue 文件查看效果 配置公共的css、metaassets下的cssreset.scss 重置文件common.scss 配置nux…

CTF-WEB(MISC)

安全攻防知识——CTF之MISC - 知乎 CTF之MISC杂项从入门到放弃_ctf杂项 你的名字-CSDN博客 CTF MICS笔记总结_archpr 掩码攻击-CSDN博客 一、图片隐写 CTF杂项---文件类型识别、分离、合并、隐写_ctf图片分离-CSDN博客 EXIF&#xff08;Exchangeable Image File&#xff09;是…

考虑极端天气线路脆弱性的配电网分布式电源和储能优化配置模型

1 主要内容 程序主要参考《考虑极端天气线路脆弱性的配电网分布式电源配置优化模型-马宇帆》&#xff0c;针对极端天气严重威胁配电网安全稳定运行的问题。基于微气象、微地形对配电网的线路脆弱性进行分析&#xff0c;然后进行分布式电源接入位置与极端天气的关联性分析&…

​【收录 Hello 算法】第 3 章 数据结构

第 3 章 数据结构 Abstract 数据结构如同一副稳固而多样的框架。 它为数据的有序组织提供了蓝图&#xff0c;算法得以在此基础上生动起来。 本章内容 3.1 数据结构分类3.2 基本数据类型3.3 数字编码 *3.4 字符编码 *3.5 小结

Java | Leetcode Java题解之第59题螺旋矩阵II

题目&#xff1a; 题解&#xff1a; class Solution {public int[][] generateMatrix(int n) {int num 1;int[][] matrix new int[n][n];int left 0, right n - 1, top 0, bottom n - 1;while (left < right && top < bottom) {for (int column left; co…

uniapp:K线图,支持H5,APP

使用KLineChart完成K线图制作,完成效果: 1、安装KLineChart npm install klinecharts2、页面中使用 <template><view class="index"><!-- 上方选项卡 --><view class="kline-tabs"><view :style="{color: current==ite…

《动手学深度学习(Pytorch版)》Task03:线性神经网络——4.29打卡

《动手学深度学习&#xff08;Pytorch版&#xff09;》Task03&#xff1a;线性神经网络 线性回归基本元素线性模型损失函数随机梯度下降 正态分布与平方损失 线性回归的从零开始实现读取数据集初始化模型参数定义模型定义损失函数定义优化算法训练 线性回归的简洁实现读取数据集…

【c++】Resharper 去掉中文注释拼写

参考大神&#xff1a; Resharper 去掉注释拼写 reshaper的中文注释一堆下划线&#xff0c;看的很累、很乱&#xff1a; options 里 在code inspetion里 搜索 去掉 Typo in comment 就可以不在中文注释提示 重启vs reshaperd 中文注释下划线没了。小番茄的还在。

jsPDF + html2canvas + Vue3 + ts项目内,分页导出当前页面为PDF、A 页面内导出 B 页面的内容为PDF,隐藏导出按钮等多余元素

jsPDF html2canvas Vue3 ts Arco Design项目&#xff0c;分页导出当前页面为PDF、A 页面内导出 B 页面的内容为PDF&#xff0c;隐藏导出按钮等多余元素… 1.下载所需依赖 pnpm install --save html2canvaspnpm install --save jspdf引入依赖 <script setup lang"…

2010NOIP普及组真题 3. 导弹拦截

线上OJ&#xff1a; 一本通&#xff1a;http://ybt.ssoier.cn:8088/problem_show.php?pid1951 核心思想&#xff1a; 1、我们把导弹分为区间1和区间2来看。1#拦截区间1&#xff0c;2#拦截区间2。 2、则&#xff1a;1#的拦截半径为区间1 中 最远的导弹&#xff0c;而2#的拦截半…