《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.mzph.cn/news/830415.shtml

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

相关文章

docker学习笔记1:什么是docker

目录 什么是dockerDocker 的主要组件学习 Docker 的步骤资源和教程什么是docker docker官网:https://www.docker.com Docker 是一个开源的应用容器引擎,它允许开发者打包应用以及应用的运行环境到一个可移植的容器中,然后发布到任何支持 Docker 的平台上运行。这种打包方式…

elementUI之el-select选择器赋值为空后无法选中回显

elementUI之el-select选择器赋值为空后无法选中回显 问题原因&#xff1a; 焦点问题 解决办法&#xff1a; this.$set(this.form, monthlyRent, );

vue+element-ui实现横向长箭头,横向线上下可自定义文字(使用after伪元素实现箭头)

项目场景&#xff1a; 需要实现一个长箭头&#xff0c;横向线上下可自定义文字 代码描述 <div><span class"data-model">{{ //上方文字}}</span><el-divider class"q"> </el-divider>//分隔线<span class"data-mod…

数据的定义及其分类

1&#xff09;、数据&#xff1a;任何以电子或者其他方式对信息的记录 2&#xff09;、重要数据&#xff1a;特定领域、特定群体、特定区域或者达到一定精度和规模的、一旦被篡改或者泄露、损毁&#xff0c;可能直接危害国家安全、经济运行、社会稳定、公共健康和安全的数据&a…

贝叶斯统计实战:Python引领的现代数据分析之旅

贝叶斯统计这个名字取自长老会牧师兼业余数学家托马斯贝叶斯(Thomas Bayes&#xff0c;1702—1761)&#xff0c;他最先推导出了贝叶斯定理&#xff0c;该定理于其逝世后的1763年发表。但真正开发贝叶斯方法的第一人是Pierre-Simon Laplace(1749—1827)&#xff0c;因此将其称为…

智慧农业设备——虫情监测系统

随着科技的不断进步和农业生产的日益现代化&#xff0c;智慧农业成为了新时代农业发展的重要方向。其中&#xff0c;虫情监测系统作为智慧农业的重要组成部分&#xff0c;正逐渐受到广大农户和农业专家的关注。 虫情监测系统是一种基于现代传感技术、图像识别技术和大数据分析技…

使用pydub库操作与二进制音频数据互转

一、二进制数据转AudioSegment对象 binary_data b... # 二进制音频数据 combined_audio AudioSegment.from_file(io.BytesIO(binary_data)) 二、AudioSegment对象转二进制数据 binary_data b... # 二进制音频数据combined_audio AudioSegment.from_file(io.BytesIO(bin…

深入理解Java消息中间件-云原生和容器化对消息中间件的影响

在经历了从物理服务器到虚拟化技术的演进后&#xff0c;当前IT架构的发展势头正在向云原生和容器化迈进。这一趋势对于整个技术领域&#xff0c;尤其是消息中间件领域产生了深远的影响。本文将探讨云原生和容器化如何改变了消息中间件的设计、部署和运维方式。 设计哲学的变革…

【Kafka】Kafka与flume整合(四)

Kafka和Flume整合 Kafka与flume整合流程 Kafka整合flume流程图 flume主要是做日志数据(离线或实时)地采集。 图-1 数据处理 图-1显示的是flume采集完毕数据之后&#xff0c;进行的离线处理和实时处理两条业务线&#xff0c;现在再来学习flume和kafka的整合处理。 配置flume…

【C++算法竞赛 · 图论】树

目录 前言 树 树的定义 树的相关概念 树的遍历 1 先序遍历 2 中序遍历 3 后序遍历 前言 前两篇文章&#xff08;【C算法竞赛 图论】图论基础、【C算法竞赛 图论】图的存储&#xff09;中&#xff0c;介绍了图的相关概念与存储&#xff0c;还不了解的可以去补补课。 …

【ETAS CP AUTOSAR工具链】RTE层基本概念与开发流程

本篇文章续接上篇文章【ETAS CP AUTOSAR工具链】基本概念与开发流程&#xff0c;继续按上篇文章描述的ETAS CP工具链进行开发的基本框架&#xff0c;讲述了“RTE集成与配置”这部分的基本概念与开发流程。 RTE&#xff08;Runtime Environment&#xff09;处于应用层与基础软件…

标贝语音识别技术在金融领域中的应用实例

随着语音识别技术与文本挖掘、自然语言处理等技术的不断融合&#xff0c;智能语音交互技术在金融领域中爆发了出巨大的应用潜力。标贝科技根据自身与金融领域合作的经验为大家梳理出以下几点智能语音识别技术在金融领域中的应用实例。 一、智能柜台服务 语音识别的主要功能就…

微PE制作系统重装以及w11详细安装教程windows11安装pe系统制作

1.在电脑上插入一个U盘&#xff08;U盘的内存不小于8G&#xff09;&#xff0c;鼠标右击插入的【U盘】选择【格式化】。 2.文件系统选择【NTFS】&#xff0c;点击【开始】。 3.点击【确定】。 4.格式化完成&#xff0c;点击【确定】。 5.鼠标右击【PE工具箱V2.3】压缩包选…

unity项目《样板间展示》开发:菜单界面

unity项目《样板间展示》开发&#xff1a;菜单界面 前言UI菜单创建逻辑实现结语 前言 这是这个项目demo教程的最后一节&#xff0c;这节是菜单界面部分的创建 UI菜单创建 创建一个新的场景&#xff0c;在Scene文件中右键选择Create->Scene&#xff0c;创建新的场景 在场景…

【深耕 Python】Data Science with Python 数据科学(16)Scikit-learn机器学习(一)

写在前面 关于数据科学环境的建立&#xff0c;可以参考我的博客&#xff1a; 【深耕 Python】Data Science with Python 数据科学&#xff08;1&#xff09;环境搭建 往期数据科学博文&#xff1a; 【深耕 Python】Data Science with Python 数据科学&#xff08;2&#xf…

unity3d使用3D WebView播放网页中的视频

Unity2021.3.35f1&#xff0c;硬件ESP32-Cam&#xff0c;3D WebView插件 1.新建工程&#xff0c;导入3D WebView for Winfows和3D WebView for Android 2.打开场景Assets\Vuplex\WebView\Demos\Scenes\2_CanvasWebViewDemo 3.修改Canvas的Render Mode为Screen Space-Camera&am…

【C++】学习笔记——string_1

文章目录 四、模板初阶2. 类模板 五、STL简介1. 什么是STL2. STL的六大组件3. 如何学习STL 六、string类1. string类对象的容量操作 未完待续 四、模板初阶 2. 类模板 函数模板就是&#xff1a;模板 函数&#xff1b;类模板就是&#xff1a;模板 类。和函数模板用法基本相同…

Rust HashMap

一、HashMap是什么&#xff0c;怎么用 1、HashMap是什么 HashMap 也是 Rust 标准库中提供的集合类型&#xff0c;但是又与动态数组不同&#xff0c;HashMap 中存储的是一一映射的 KV 键值对&#xff0c;并提供了平均时间复杂度为 O(1) 的查询方法。 2、HashMap怎么用 &…

elementui的el-select+el-tree+el-input实现可搜索的下拉树组件

部分实现代码如下 <template> <div><el-selectv-model"item.TableName"placeholder"请选择":disabled"!item.disabled"visible-change"handleVisible"ref"TableName"><el-input placeholder"请输…

RustGUI学习(iced)之小部件(三):如何使用下拉列表pick_list?

前言 本专栏是学习Rust的GUI库iced的合集&#xff0c;将介绍iced涉及的各个小部件分别介绍&#xff0c;最后会汇总为一个总的程序。 iced是RustGUI中比较强大的一个&#xff0c;目前处于发展中&#xff08;即版本可能会改变&#xff09;&#xff0c;本专栏基于版本0.12.1. 概述…