《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 func
    delete 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 func
    delete 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
        // 派生类实现
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【GameFi】链游 | Seraph | 区块链上的动作角色扮演 NFT 装备收集和掠夺游戏

官网下载 新赛季公告&#xff1a;https://www.seraph.game/#/news/357 开始时间&#xff1a;2024年4月19日 11:00 (UTC8&#xff09; discard会有人发送一些激活码&#xff0c;或者有一些活动&#xff0c;只需要填表格关注账号&#xff0c;参与了就会将激活码发到你的邮箱 …

Remix框架实现 SSR

SSR SSR是一种网页渲染方式&#xff0c;它与传统的客户端渲染&#xff08;CSR&#xff09;相对&#xff0c;在日常的项目中我们更多是使用 CSR 的方式进行前端分离开发&#xff0c;渲染会在浏览器端进行。然而在SSR中&#xff0c;当用户请求一个网页时&#xff0c;服务器将生成…

U盘提示“被写保护”无法操作处理怎么办?

今天在使用U盘复制拷贝文件时&#xff0c;U盘出现“U盘被写保护”提示&#xff0c;导致U盘明明有空闲内存却无法复制的情况。这种情况很常见&#xff0c;很多人在插入U盘到电脑后&#xff0c;会出现"U盘被写保护"的提示&#xff0c;导致无法进行删除、保存、复制等操…

一、Redis五种常用数据类型

Redis优势&#xff1a; 1、性能高—基于内存实现数据的存储 2、丰富的数据类型 5种常用&#xff0c;3种高级 3、原子—redis的所有单个操作都是原子性&#xff0c;即要么成功&#xff0c;要么失败。其多个操作也支持采用事务的方式实现原子性。 Redis特点&#xff1a; 1、支持…

vscode连接服务器的docker步骤

进入容器之后&#xff0c;操作方式与本地windows系统操作逻辑一样&#xff1b;容器内部结构都能任意查看和使用&#xff0c;创建文件及编写python脚本都可以直接使用vs code编辑器进行编辑和调试&#xff0c;从而避免使用命令行及vim编辑文件&#xff0c;非常直观且方便~

【精品毕设推荐】基于Javaee的影视创作论坛的设计与实现

点击下载原文及代码 摘 要 随着时代的发展&#xff0c;互联网的出现&#xff0c;给传统影视行业带来的最大便利就是&#xff0c;方便了影视从业人员以及爱好者的交流和互动&#xff0c;而为用户提供一个书写影评&#xff0c;阅读影评以及回复影评的平台&#xff0c;以影评为…

动态规划——斐波那契数列模型:91.解码方法

文章目录 题目描述算法原理1.状态表示2.状态转移方程3.初始化⽅法⼀&#xff08;直接初始化&#xff09;⽅法⼆&#xff08;添加辅助位置初始化&#xff09; 4.填表顺序5.返回值 代码实现C优化Java优化 题目描述 题目链接&#xff1a;91.解码方法 算法原理 类似于斐波那契…

制作外贸脚本的流程和代码分享!

在全球化的今天&#xff0c;外贸业务成为了许多企业拓展市场、增加收入的重要途径&#xff0c;而在外贸业务中&#xff0c;一个优秀的脚本往往能够起到事半功倍的效果。 那么&#xff0c;如何制作一个高效、专业的外贸脚本呢?本文将为您详细解析制作外贸脚本的流程&#xff0…

苹果11手机开不了机怎么办?四大原因及解决方法总结!

苹果手机以其流畅的操作系统和出色的性能出名&#xff0c;但终究只是一部手机&#xff0c;黑屏、死机等问题还是有可能会出现的。 那么&#xff0c;苹果手机为什么莫名其妙黑屏开不了机呢&#xff1f;苹果11手机开不了机怎么办&#xff1f;小编为大家总结了4个可能原因&#x…

国产最强多模态大模型Step Fun-1V,究竟有多好用?

前言&#xff1a; 2023年是大模型的元年&#xff0c;随着ChatGPT的爆火&#xff0c;将大模型带入了公众的视野 &#xff0c;国内也随之掀起了百模大战的浪潮。在这股浪潮的推动下&#xff0c;诸多科技公司&#xff0c;乃至大厂都争相推出自己的大模型产品&#xff0c;希望能够…

【打赏收款收银台多合一支付收款HTML源码】

打赏收款收银台多合一支付收款HTML源码 效果图部分源码领取源码下期更新预报 效果图 部分源码 <!DOCTYPE HTML> <html> <head> <title>打赏台</title> <meta name"keywords" content"收银台,个人收款二维码,支付宝在线收款,微…

【C语言】高质量选择题

目录 题目一&#xff1a; 题目二&#xff1a; 题目三&#xff1a; 题目四&#xff1a; 题目五&#xff1a; 题目六&#xff1a; 题目七&#xff1a; 题目八&#xff1a; 题目九&#xff1a; 题目十&#xff1a; 题目十一&#xff1a; 题目十二&#xff1a; 题目十…

方法的入栈和出栈

一.作用域问题 1.全局作用域 在全局都能进行访问的变量 var a 10;function fn() {var b 20;return a b;}console.log(fn()); 2.局部的作用域 只能在限定的范围内进行访问 function fn() {var b 20;}console.log(b); b is not defined 打印的结果是b这个变量没用定义 3…

[JUCE库]关于JUCE如何生成动态链接库 juce-7.0.1-windows

前言 当我们在使用JUCE库的时候&#xff0c;可能会需要使用到静态链接的方式&#xff0c;还好的一点是JUCE本身提供了CMake编译&#xff0c;也提供了单独的sln编译。 本文章仅针对juce-7.0.1-windows&#xff0c;由于不同版本之间差异较大&#xff0c;可能不能通用&#xff0…

Unity数据持久化之XML

目录 数据持久化XML概述XML文件格式XML基本语法XML属性 C#读取存储XMLXML文件存放位置C#读取XML文件C#存储XML文件 实践小项目必备知识点XML序列化&#xff08;不支持字典&#xff09;XML反序列化IXmlSerializable接口让Dictionary支持序列化反序列化 数据持久化XML概述 什么是…

redis进阶--IDEA环境

目录 一、解决redis服务器端口问题 二、java环境下使用redis 三、javaSpringt环境下使用redis 四、redis持久化 1、持久化概念 2、redis持久化策略 3、RDB策略 4、AOF策略 5、混合持久化策略 五、redis事务 1、数据库事务 2、redis事务特点 3、redis事务的作用 4…

jquery项目 html使用export import方式调用模块

jquery的老项目&#xff0c;引入vue3, 需要方便使用export, import方式引用一些常用的方法与常量 导出模块 export js/numberUtil.js /*** Description:* Author Lani* date 2024/1/10*//* * 【金额】 保留2位小数&#xff0c;不四舍五入 * 5.992550 >5.99 , 2 > 2.…

Linux字符设备驱动(二) - 与设备驱动模型的关系

一&#xff0c;从/dev目录说起 从事Linux嵌入式驱动开发的人&#xff0c;都很熟悉下面的一些基础知识&#xff0c; 比如&#xff0c;对于一个char类型的设备&#xff0c;我想对其进行read wirte 和ioctl操作&#xff0c;那么&#xff1a; 1&#xff09;我们通常会在内核驱动中…

2024-05-06 问AI: 介绍一下深度学习中的LSTM网络

文心一言 当谈到深度学习中的LSTM&#xff08;Long Short-Term Memory&#xff09;网络时&#xff0c;它是一种特殊的循环神经网络&#xff08;RNN&#xff09;架构&#xff0c;旨在解决传统RNN在处理长序列时遇到的梯度消失和梯度爆炸问题。LSTM网络因其能够捕捉序列数据中的…

VTK —— 三、简单操作 - 示例1 - 3D点之间的平方距离和欧几里得距离(附完整源码)

代码效果 本代码编译运行均在如下链接文章生成的库执行成功&#xff0c;若无VTK库则请先参考如下链接编译vtk源码&#xff1a; VTK —— 一、Windows10下编译VTK源码&#xff0c;并用Vs2017代码测试&#xff08;附编译流程、附编译好的库、vtk测试源码&#xff09; 教程描述 本…