C++系列-多态

🌈个人主页:羽晨同学 

💫个人格言:“成为自己未来的主人~”  

多态

多态就是不同类型的对象,去做同一个行为,但是产生的结果是不同的。

比如说:

都是动物叫声,猫是喵喵,狗是汪汪,它们的叫声是不相同的。

多态的定义和实现

多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。

在继承中,构成多态需要两个条件:

  1. 必须通过基类的指针或者引用调用虚函数。
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。
class Person 
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
class Student:public Person
{//重写/覆盖virtual void BuyTicket(){cout << "买票-半价" << endl;}
};
class Soldier:public Person
{
public://重写/覆盖virtual void BuyTicket(){cout << "买票-优先" << endl;}
};
//多态条件
//1.虚函数重写
//2.父类指针或者引用调用虚函数
void func(Person& p)
{p.BuyTicket();
}
int main()
{Person p;Student st;Soldier so;func(st);func(so);return 0;
}

你看,这样子就实现了多态。

虚函数

被virtual修饰的函数就叫做虚函数

class Student:public Person
{//重写/覆盖virtual void BuyTicket(){cout << "买票-半价" << endl;}
};

虚函数的重写

虚函数的重写(覆盖):派生类和基类中的某个函数函数名相同,参数相同,返回值相同,就称子类的虚函数重写了基类的虚函数。

class Person 
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
class Student:public Person
{//重写/覆盖virtual void BuyTicket(){cout << "买票-半价" << endl;}
};
class Soldier:public Person
{
public://重写/覆盖virtual void BuyTicket(){cout << "买票-优先" << endl;}
};

你看,这样其实就是标准的构成了函数重写的代码。

但是,由于继承的存在,所以,其实派生类不写virtual也会构成函数重写。

class Person 
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
class Student:public Person
{//重写/覆盖virtual void BuyTicket(){cout << "买票-半价" << endl;}
};
class Soldier:public Person
{
public://重写/覆盖void BuyTicket(){cout << "买票-优先" << endl;}
};

比如上面的这样,但是这样子是不规范的,所以我们最好加上virtual。

虚函数重写的两个例外:

协变(基类和派生类的返回值类型不同)

派生类重写基类虚函数时,与基类虚函数返回值类型不同,即基类虚函数返回基类对象的指针或者引用,派生类对象返回派生类对象的指针或者引用,这个就叫做协变。

class A{};
class B :public A {};class Person 
{
public:virtual A* BuyTicket(){cout << "买票-全价" << endl;return 0;}
};
class Student:public Person
{//重写/覆盖virtual B* BuyTicket(){cout << "买票-半价" << endl;return 0;}
};
class Soldier:public Person
{
public://重写/覆盖virtual B* BuyTicket(){cout << "买票-优先" << endl;return 0;}
};
//多态条件
//1.虚函数重写
//2.父类指针或者引用调用虚函数
void func(Person& p)
{p.BuyTicket();
}
int main()
{Person p;Student st;Soldier so;func(st);func(so);return 0;
}

这样子,我们就实现了协变。

析构函数的重写

我们先来看一下下面的这段代码:

class Person
{
public:virtual ~Person(){cout << "virtual ~Person()" << endl;}
};
class Student:public Person
{
public:protected:int* _ptr = new int[10];
};
int main()
{Person* p1 = new Student;delete p1;return 0;
}

在这段代码当中,我们删除了p1,但是结果调用的是基类的析构函数。

那为什么会这样呢,是因为编译器对析构函数的名字做了特殊的处理,编译后的析构的名字同一处理为了destructor;这样子的话,基类和父类的析构函数就构成了隐藏。

这样子调用的话,那么会造成内存泄漏。

但是,只要我们构成了多态,那么就解决了这个问题。

class Person
{
public:virtual ~Person(){cout << "virtual ~Person()" << endl;}
};
class Student:public Person
{
public:virtual ~Student(){cout << "virtual ~Student()" << endl;}
protected:int* _ptr = new int[10];
};
int main()
{//Student st;Person* p1 = new Student;delete p1;Person* p2 = new Person;delete p2;return 0;
}

override 和final

上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数字母次序写反而无法构成重写,这种错误在编译期间是无法报出的,只有在程序运行时没有到预期结果才会debug,这样子得不偿失。 

final:修饰虚函数,表示该虚函数不能再被重写

class A final
{
public:static A CreatObj(){return A();}
private:A(){};
};
class V:public A
{};
int main()
{A::CreatObj();return 0;
}

所以,在这个代码中,会出现报错的问题。

override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

class Car {
public:virtual void Drive() {}
};
class Benz :public Car {
public:virtual void Drive() override { cout << "Benz-舒适" << endl; }
};
int main()
{Car a;return 0;
}

重载,覆盖(重写),隐藏(重定义)的对比

重载:

两个函数在同一个作用域。

函数名相同,参数不同。

重写(覆盖)

两个函数分别在基类和派生类的作用域。

函数名,参数,返回值都必须相同(协变除外)

两个函数必须是虚函数

重定义

两个函数分别在基类和派生类的作用域

函数名相同

来年各个基类和派生类的同名函数不构成重写就是重定义

抽象类

在虚函数后面写上=0,则这个函数为纯虚函数,包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象,纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

class Car
{
public:virtual void Drive() = 0;
};
class Benz :public Car
{
public:virtual void Drive(){cout << "BWM-舒适" << endl;}
};
class BWM :public Car
{
public:virtual void Drive(){cout << "BWM-操控" << endl;}
};
int main()
{Car a;Benz bz;return 0;
}

在这个代码中,因为基类中有纯虚函数,所以不能实例化出对象,而派生类中对纯虚函数进行了重写,所以可以进行实例化。

多态的原理

虚函数表

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};
int main()
{cout<<sizeof(Base);return 0;
}

大家可以想一想这个结果是什么。

答案是8,那么,这是为什么呢?

我们来仔细看一下Base里面有什么。

我们可以看到的是在这里面还存在了一个虚函数表的指针,这个就是存放虚函数的地址的地方。

 

class Base
{
public:virtual void func1(){cout << "Base::func1()" << endl;}virtual void func2(){cout << "Base::func2()" << endl;}void func3(){cout << "Base::func3()" << endl;}
private:int _b = 1;
};
class Derive : public Base
{
public:virtual void func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};
void func1(Base* p)
{p->func1();p->func3();
}
int main()
{Base b;Derive d;func1(&b);func1(&d);return 0;
}

  • 通过这个结果,我们可以得到的是,派生类中也有一个虚表的指针,里面存放的有两部分,一个是自己的成员,一个是从基类继承下来的成员。
  • 基类b对象和派生类d对象虚表是不一样的,我们这里发现func1完成了重写,所以d中存放的是Derieve::Func1();,所以虚函数的重写也叫覆盖。
  • 不是虚函数,函数的指针不会被放进虚表
  • 虚表存放在对象里面,虚表里面存放的是虚函数的指针,虚函数存放在代码段那里。

满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象中的找的,不满足多态的函数调用是编译时就确认好的。

动态绑定和静态绑定

在程序编译期间确定了程序的行为,叫做静态绑定。

在程序运行期间,在对象中找的行为,叫做动态绑定

好了,本次的文章就到这里了,我们下次再见。 

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

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

相关文章

LC记录二:丑数专题,一文秒解丑数3题

文章目录 263.丑数1264.丑数21201.丑数3 263.丑数1 https://leetcode.cn/problems/ugly-number/description/ 简单题&#xff0c;丑数只包含质因子2、3、5。所以直接使用 n 循环 除 2 3 5最后判断结果是否等于1即可。 代码&#xff1a; class Solution {public boolean isUg…

Java中的顺序控制、分支控制、嵌套分支if-else

if-else 顺序控制分支控制if-else单分支1.基本语法2.说明&#xff1a;3.案例说明4.流程图 双分支1.基本语法2.说明&#xff1a;3.案例说明4.流程图5.练习 多分支1.基本语法2.说明&#xff1a;3.流程图4.练习 嵌套分支1.基本介绍2.基本语法3.练习 顺序控制 1.介绍&#xff1a;程…

AntFlow-Vue3 :一个仿钉钉流程审批,且满足99.8%以上审批流程需求的企业级工作流平台,开源且免费!

在现代企业管理中&#xff0c;流程审批的高效性直接影响到工作的流畅度与生产力。最近&#xff0c;我发现了一个非常有趣的项目—— AntFlow-Vue3 。这个项目不仅提供了一个灵活且可定制的工作流平台&#xff0c;还能让用户以可视化的方式创建和管理审批流程。 如果你是一名前…

【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-1

忽然有一天&#xff0c;我想要做一件事&#xff1a;去代码中去验证那些曾经被“灌输”的理论。                                                                                  – 服装…

【Java 集合】List接口 —— ArrayList 与 LinkedList 详解

List接口继承自Collection接口&#xff0c;是单列集合的一个重要分支。 在List集合中允许出现重复的元素&#xff0c;所有的元素是以一种线性方式进行存储的&#xff0c;在程序中可以通过索引&#xff08;类似于数组中的元素角标&#xff09;来访问集合中的指定元素。另外&…

ESP32 Bluedroid 篇(1)—— ibeacon 广播

前言 前面我们已经了解了 ESP32 的 BLE 整体架构&#xff0c;现在我们开始实际学习一下Bluedroid 从机篇的广播和扫描。本文将会以 ble_ibeacon demo 为例子进行讲解&#xff0c;需要注意的一点是。ibeacon 分为两个部分&#xff0c;一个是作为广播者&#xff0c;一个是作为观…

时序数据库 TDengine 的入门体验和操作记录

时序数据库 TDengine 的学习和使用经验 什么是 TDengine &#xff1f;什么是时序数据 &#xff1f;使用RPM安装包部署默认的网络端口 TDengine 使用TDengine 命令行&#xff08;CLI&#xff09;taosBenchmark服务器内存需求删库跑路测试 使用体验文档纠错 什么是 TDengine &…

【框架篇】过滤器和拦截器的区别以及使用场景

在项目开发中&#xff0c;常常会同时配置拦截器&#xff08;Interceptor&#xff09;和过滤器&#xff08;Filter&#xff09;&#xff0c;以下就是它们两个主要的区别&#xff1a; 过滤器&#xff08;Filter&#xff09; 配置和实现 Filter的实现还是很简单的&#xff0c;可…

【Python语言初识(六)】

一、网络编程入门 1.1、TCP/IP模型 实现网络通信的基础是网络通信协议&#xff0c;这些协议通常是由互联网工程任务组 &#xff08;IETF&#xff09;制定的。所谓“协议”就是通信计算机双方必须共同遵从的一组约定&#xff0c;例如怎样建立连接、怎样互相识别等&#xff0c;…

k8s搭建一主三从的mysql8集群---无坑

一&#xff0c;环境准备 1.1 k8s集群服务器 ip角色系统主机名cpumem192.168.40.129mastercentos7.9k8smaster48192.168.40.130node1centos7.9k8snode148192.168.40.131node2centos7.9k8snode248192.168.40.132node3centos7.9k8snode348 k8s集群操作请参考《K8s安装部署&…

力扣(leetcode)每日一题 1845 座位预约管理系统| treeSet和priority Queue的区别|线段树

之前发过一篇&#xff0c;感觉还有深挖的地方&#xff0c;于是又补充一些信息 这题目虽然是middle难度题目&#xff0c;要解答出来是只要easy的时间&#xff0c;但是深挖可以有hard的难度 题解1 可以帮助复习线段树的使用&#xff0c;题解2 可以复习一下java基础知识 题解1 线…

Springboot使用redis,以及解决redis缓存穿透,击穿,雪崩等问题

1.Redis面试题-缓存穿透,缓存击穿,缓存雪崩 1 穿透: 两边都不存在&#xff08;皇帝的新装&#xff09; &#xff08;返回空值&#xff09;&#xff08;互斥锁&#xff09;&#xff08;黑名单&#xff09; &#xff08;布隆过滤器&#xff09; 2 击穿&#xff1a;一个或多个热…

Kotlin:1.8.0 的新特性

一、概述 Kotlin 1.8.0版本英语官方文档 Kotlin 1.8.0 中文官方文档 The Kotlin 1.8.0 release is out and here are some of its biggest highlights: Kotlin 1.8.0发布了&#xff0c;下面是它的一些亮点: JVM 平台新增实验性函数&#xff1a;递归复制或删除目录内容改进了 …

9--苍穹外卖-SpringBoot项目中Redis的介绍及其使用实例 详解

目录 Redis入门 Redis简介 Redis服务启动与停止 服务启动命令 Redis数据类型 5种常用数据类型介绍 各种数据类型的特点 Redis常用命令 字符串操作命令 哈希操作命令 列表操作命令 集合操作命令 有序集合操作命令 通用命令 在java中操作Redis Redis的Java客户端 …

uni-app在线预览pdf

这里推荐下载pdf.js 插件 PDF.js - Browse Files at SourceForge.net 特此注意 如果报 Promise.withResolvers is not a function 请去查看版本兼容问题 降低pdf.js版本提高node版本 下载完成后 在 static 文件夹下新建 pdf 文件夹&#xff0c;将解压文件放进 pdf 文件…

生信初学者教程(十一):数据校正

文章目录 介绍加载R包导入数据准备数据ComBatremoveBatchEffectVoom SNM批次效应校正结果比较校正后的结果输出校正后的结果总结介绍 批次效应在生物学数据分析中是一个普遍存在的问题,它指的是由于实验过程中非生物学因素(如样本处理时间、实验条件、测序平台等)的差异,导…

集师专属知识付费小程序搭建 心理咨询小程序搭建

一、产品简介 集师SaaS知识付费软件&#xff0c;为知识创业者或商家提供一站式内容交付解决方案&#xff0c;助力商家搭建集品牌传播、商业变现和用户运营于一体的线上知识服务系统&#xff0c;覆盖全渠道经营场景&#xff0c;占据每个流量入口&#xff0c;使流量变现快速高效…

Python笔记 - 利用装饰器设计注解体系

认识注解 注解&#xff08;Annotation&#xff09;是一种用于为代码添加元数据的机制。这些元数据可以在运行时被访问&#xff0c;用于为代码元素&#xff08;如类、方法、字段等&#xff09;提供额外的信息或指示。 由于Python中装饰器只能装饰类和方法&#xff0c;因此也只…

828华为云征文|华为云弹性云服务器FlexusX实例下的Nginx性能测试

本文写的是华为云弹性云服务器FlexusX实例下的Nginx性能测试 目录 一、华为云弹性云服务器FlexusX实例简介二、测试环境三、测试工具四、测试方法五、测试结果 下面是华为云弹性云服务器FlexusX实例下的Nginx性能测试。 一、华为云弹性云服务器FlexusX实例简介 华为云弹性云服…