从0开始C++(八):多态的实现

 相关文章:

从0开始C++(一):从C到C++

从0开始C++(二):类、对象、封装

从0开始C++(三):构造函数与析构函数详解

从0开始C++(四):作用域限定符、this指针、static与const关键字

从0开始C++(五):友元函数&运算符重载

从0开始C++(六):模板与容器的使用

从0开始C++(七):继承

目录

什么是多态

函数覆盖

 虚函数

多态的实现

多态的原理 

 虚析构函数


什么是多态

多态是面向对象编程中的一个重要概念,它指的是同一种行为或方法可以根据不同的对象来表现出不同的形态或结果。简单来说,多态就是同一个方法在不同的对象上产生不同的效果

在面向对象编程中,多态是通过继承和方法重写来实现的。当一个父类有多个子类时,可以使用父类的引用来指向任意一个子类的对象,然后通过调用相同的方法来实现不同的行为。这样可以极大地提高代码的灵活性和可扩展性

多态的使用可以提高代码的可读性和可维护性,简化代码的逻辑结构,使代码更加灵活和易于扩展。它是面向对象编程的重要特性之一,也是面向对象编程的核心思想之一。

在面向对象编程中,我们通常将多态分为两种类型:静态多态(也被称为编译时多态)、动态多态(也被称为运行时多态)。

静态多态:

● 静态多态是指在编译时就能确定要调用的方法,通过函数重载和运算符重载、模板来实现。

动态多态:

● 动态多态是指在运行时根据对象的实际类型来确定要调用的函数,通过继承和函数覆盖来实现。

注意:本文中后续说的多态均为动态多态。

 多态的使用具有三个前提条件下面会进行详细介绍:

● 公有继承(继承权限为 public  )

● 函数覆盖

● 基类的指针/引用指向派生类的对象

函数覆盖

 函数覆盖、函数隐藏。这两个比较相似,但是函数隐藏不支持多态。而函数覆盖是多态的必要条件。函数覆盖和函数隐藏的区别有以下几点:

● 函数隐藏是派生类中存在与基类同名同参的函数,编译器会将基类的同名同参数的函数进行隐藏。

● 函数覆盖是基类中定义了一个虚函数,派生类编译写一个同名同参数的函数将基类中的虚函数进行重写并覆盖。注意:覆盖的函数必须是虚函数

 虚函数

虚函数(Virtual Function)是一种特殊类型的成员函数,用于实现多态性。虚函数可以在基类中声明,并在派生类中重新定义和实现。通过使用虚函数,可以实现基类指针或引用调用派生类对象的特定成员函数,从而实现运行时的动态绑定。

要将函数声明为虚函数,需要在函数声明前加上关键字  virtual 。基类中的虚函数的定义和实现是通用的,而派生类可以根据自己的需求对这些虚函数进行重写。

需要注意

虚函数具有传递性:基类中被覆盖的函数是虚函数,派生类中新覆盖的函数也是虚函数。

只有普通成员函数与析构函数可以被声明为虚函数

使用虚函数的步骤如下:

1、在基类中声明虚函数:在基类的函数声明前加上关键字 virtual ,如下所示:

class Base {
public:
    virtual void foo() {
        // 函数实现
    }
};

2、在派生类中重写虚函数:在派生类中实现与基类中虚函数具有相同名称和参数列表的函数,同时加上 override 关键字,明确表明这是对基类中虚函数的重写,如下所示:

class Derived : public Base {
public:
    void foo() override {
        // 函数实现
    }
};

多态的实现

我们在开篇时提到过,要实现多态,需要有三个前提条件:

● 公有继承(已经实现)

● 函数覆盖(已经实现)

● 基类的指针/引用指向派生类的对象(待实现)

为什么要基类的指针/引用指向派生类的对象?

● 实现运行时多态:当使用基类的指针或引用指向派生类的对象时,程序在运行时会根据对象的实际类型来调用相应的函数,而不是根据指针或者引用类型。

● 统一接口:基类的指针可以作为一个通用的接口,用于操作不同类型的派生类对象,这样可以使代码更灵活,减少重复的代码。并且的支持和拓展更好进行维护。

 使用方法如下:

#include <iostream>

using namespace std;

class Animal
{
public:

    virtual void eat()
    {
        cout << "动物爱吃饭" << endl;
    }
};

class Dog:public Animal
{
public:

    void eat()override
    {
        cout << "狗爱吃骨头" << endl;
    }
};

class Cat:public Animal
{
public:

    void eat()override
    {
        cout << "猫爱吃鱼" << endl;
    }
};

int main()
{
    // 基类指针指向派生类对象
    Animal *a1 = new Dog;
    // 调用派生类覆盖的虚函数
    a1->eat();  // 狗爱吃骨头

    Animal *a3 = new Cat;
    a3->eat();  // 猫爱吃鱼

    Dog d1;
    Animal &a2 = d1;
    a2.eat();   // 狗爱吃骨头

    return 0;
}

多态的原理 

实现多态性的机制是通过虚函数表 vtable 来实现的。每个包含虚函数的类都有一个虚函数表,其中存储了指向各个虚函数的指针。每个类的对象内部会有一个隐藏的虚函数表指针成员变量,指向当前类的虚函数表。当调用虚函数时,会根据对象的虚函数表中相应位置的指针找到正确的函数进行调用。

 虚析构函数

如果不使用虚析构函数,且基类的指针指向派生类的对象,使用delete销毁对象时,只能触发基类的析构函数,如果在派生类中申请内存等资源,则会导致内存无法释放,出现内存泄漏的问题。

解决方案是给基类的析构函数使用virtual修饰为虚析构函数,通过传递性可以把各个派生类的析构函数都变为虚析构函数,因此建议给一个可能为基类的类中的析构函数设置成虚析构函数。

示例如下:

#include <iostream>

using namespace std;

class Animal
{
public:

    virtual void eat()
    {
        cout << "动物爱吃饭" << endl;
    }
    // 虚析构函数
    virtual ~Animal()
    {
        cout << "Animal析构函数被调用了" << endl;
    }
};

class Dog:public Animal
{
public:

    void eat()override
    {
        cout << "狗爱吃骨头" << endl;
    }
    ~Dog()
    {
        cout << "Dog 析构函数被调用了" << endl;
    }
};

int main()
{
    // 基类指针指向派生类对象
    Animal *a1 = new Dog;
    // 调用派生类覆盖的虚函数
    a1->eat();  // 狗爱吃骨头

    delete a1;

    return 0;
}

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

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

相关文章

React+TS前台项目实战(十九)-- 全局Input组件封装:加载状态和清除功能的实现

文章目录 前言Input组件1. 功能分析2. 代码详细注释3. 使用方式4. 效果展示 总结 前言 今天我们来封装一个input输入框组件&#xff0c;并提供一些常用的功能&#xff0c;你可以选择不同的 尺寸、添加前缀、显示加载状态、触发回调函数、自定义样式 等等。这些功能在这个项目中…

vite+vue3+ts项目搭建流程 (pnpm, eslint, prettier, stylint, husky,commitlint )

vitevue3ts项目搭建 项目搭建项目目录结构 项目配置自动打开项目eslint①vue3环境代码校验插件②修改.eslintrc.cjs配置文件③.eslintignore忽略文件④运行脚本 prettier①安装依赖包②.prettierrc添加规则③.prettierignore忽略文件④运行脚本 stylint①.stylelintrc.cjs配置文…

【云原生】Kubernetes网络知识

Kubernetes网络管理 文章目录 Kubernetes网络管理一、案例概述二、案例前置知识点2.1、Kubernetes网络模型2.2、Docker网络基础2.3、Kubernetes网络通信2.3.1、Pod内容器与内容之间的通信2.3.2、Pod与Pod之间的通信 2.4、Flannel网络插件2.5、Calico网络插件2.5.1、Calico网络模…

免费下载电子书的网站

在如今的数字化时代&#xff0c;电子书已成为许多人书籍阅读的首选。下面小编就和大家分享一些提供免费查找下载电子书服务的网站&#xff0c;这些网站不仅资源丰富&#xff0c;而且操作简便。 免费下载电子书的网站&#xff1a;https://www.bgrdh.com/favorites/1355.html 1…

数据可视化期末考试(编程)

1.KNN 1.新增数据的分类 import pandas as pd # 您的原始数据字典 data { 电影名称: [电影1, 电影2, 电影3, 电影4, 电影5], 打斗镜头: [10, 5, 108, 115, 20], 接吻镜头: [110, 89, 5, 8, 200], 电影类型: [爱情片, 爱情片, 动作片, 动作片, 爱情片] } …

昇思25天学习打卡营第8天 | 模型的保存与加载

内容介绍&#xff1a;在训练网络模型的过程中&#xff0c;实际上我们希望保存中间和最后的结果&#xff0c;用于微调&#xff08;fine-tune&#xff09;和后续的模型推理与部署&#xff0c;本章节我们将介绍如何保存与加载模型。 具体内容&#xff1a; 1. 导包 import numpy…

1.1 MySQL用户管理

1.1.1 用户的定义 用户名主机域 mysql> select user,host,password from mysql.user; --------------------------------------------------------------- | user | host | password | -----------------------------------------…

el-form重置后input无法输入问题

新增用户遇到的问题&#xff1a; 如果你没有为 formData 设置默认值&#xff0c;而只是将其初始化为空对象 {}&#xff0c;则在打开dialog时&#xff0c;正常输入&#xff0c; formdata会变成如下 但是&#xff0c;打开后&#xff0c;直接使用 resetFields 或直接清空表单&…

LLDB 详解

LLDB 详解 LLDB 详解编译器集成优势LLDB 的主要功能命令格式原始&#xff08;raw&#xff09;命令选项终止符: -- LLDB 中的变量唯一匹配原则helpexpressionprint、call、po控制流程&#xff1a;continue、next、step、finishregister read / writethread backtracethread retu…

基于weixin小程序新生报到系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;学生管理&#xff0c;班级信息管理&#xff0c;师资力量管理&#xff0c;宿舍信息管理&#xff0c;宿舍安排管理&#xff0c;签到信息管理&#xff0c;论坛管理 小程序功能包括&#xff1a;系统首页&am…

考研数学一有多难?130+背后的残酷真相

考研数学一很难 大家平时在网上上看到很多人说自己考了130&#xff0c;其实这些人只占参加考研数学人数的极少部分&#xff0c;有个数据可以展示出来考研数学到底有多难&#xff1a; 在几百万考研大军中&#xff0c;能考到120分以上的考生只有2%。绝大多数人的分数集中在30到…

【MySQL进阶之路 | 高级篇】MySQL8.0索引新特性->降序索引与隐藏索引

1. 支持降序索引 降序索引以降序存储键值.虽然在语法上&#xff0c;从MySQL4版本已经支持降序索引的语法了&#xff0c;但实际上该DESC定义是被忽略的.知道MySQL8.x版本才开始真正支持降序索引.(仅限于InnoDB存储引擎). MySQL在8.0版本前创建的仍然是升序索引&#xff0c;使用…

【C++11(二)】lambda表达式和可变参数模板

一、可变参数模板 C11的新特性可变参数模板 能够让您创建可以接受 可变参数的函数模板和类模板 // Args是一个模板参数包&#xff0c;args是一个函数形参参数包 // 声明一个参数包Args...args&#xff0c;这个参数包中可以包含0到任意个模板参数。 template <class ...Arg…

vue3 使用JsMind的方法,以及引入提示报错,无法找到模块“jsmind”的声明文件

最终结果&#xff1a; 一、使用&#xff1a;使用yarn或者npm 安装 yarn add jsmind npm install vue-jsmind 二、引入 两种方法&#xff1a;&#xff08;如果这样引入没问题按照这样引入&#xff09; import "jsmind/style/jsmind.css"; import JsMind from &quo…

【SSM】医疗健康平台-用户端-体检预约

知识目标 了解FreeMarker&#xff0c;能够简述FreeMarker的作用和生成文件的原理 熟悉FreeMarker的常用指令&#xff0c;能够在FTL标签中正确使用assign指令、include指令、if指令和list指令 掌握显示套餐列表功能的实现 掌握显示套餐详情功能的实现 掌握体检预约功能的实现…

nodejs——ejs模版遇到原型链污染产生rce

[GYCTF2020]Ez_Express 打开是一个登陆框 在源代码中找到 在代码里找到敏感关键字 找到merge 想到原型链污染 这里登陆只能用ADMIN才能登陆成功 但是这里index.php又设置了一个waf ban了admin的大小写 这里需要绕过这个waf 看注册这段代码 用的是这个toUpperCase()函数 之前…

【深度强化学习】如何使用多进程(multiprocessing、pipe)来加速训练

文章目录 实验结果实现思路思路1思路2 进程与线程介绍如何实现multiprocessing、Pipe的范例关于时间对比上的问题代码修改收敛为何不稳定 技巧进程资源抢占问题线程问题cpu和gpu问题 进阶&#xff08;还没看懂/还没实验&#xff09;附代码raw代码mul代码 实验结果 实验平台&am…

natsort 自然排序

1、安装 pip install natsort 2、为什么使用natsort 而不是sorted 在python中只需要调用sorted函数就可以了&#xff0c;但是这个函数有一个缺点&#xff0c;就是它是按照从第一位开始的顺序排列的。意思是&#xff1a; wav_file [1.wav, 13.wav, 9.wav, 2.wav,"23.wav…

Golang | Leetcode Golang题解之第198题打家劫舍

题目&#xff1a; 题解&#xff1a; func rob(nums []int) int {if len(nums) 0 {return 0}if len(nums) 1 {return nums[0]}first : nums[0]second : max(nums[0], nums[1])for i : 2; i < len(nums); i {first, second second, max(first nums[i], second)}return se…

图形编辑器基于Paper.js教程04: Paper.js中的基础知识

背景 了解paper.js的基础知识&#xff0c;在往后的开发过程中会让你如履平地。 基础知识 paper.js 提供了两种编写方式&#xff0c;一种是纯粹的JavaScript编写&#xff0c;还有一种是使用官方提供的PaperScript。 区别就是在于&#xff0c;调用paper下的字对象是否需要加pa…