C++进阶 多态讲解

news2024/11/19 0:35:06

作者:@小萌新
专栏:@C++进阶
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:简单介绍C++中多态的概念

多态

  • 多态的概念
  • 多态的定义及实现
    • 多态的构成条件
    • 虚函数
    • 虚函数的重写
    • 虚函数重写的两个例外
      • 协变
      • 析构函数的重写
    • C++11 override和final
      • final
      • override
    • 重载、覆盖(重写)、隐藏(重定义)的对比
  • 抽象类
    • 抽象类的概念
    • 接口继承和实现继承
  • 总结

多态的概念

多态就是函数调用的多种形态,使用多态能够使得不同的对象去完成同一件事时,产生不同的动作和结果

例如 我们去吃海底捞的时候 普通人去就是原价 学生去就会有学生优惠 这就叫做多态

多态的定义及实现

多态的构成条件

多态是指不同继承关系的类对象,去调用同一函数,产生了不同的行为。语法上 我们这里要满足两个条件

  1. 必须通过基类的指针或者引用调用虚函数。

我们会在文章的后面解释 为什么只能用指针或者是引用 不能使用对象

  1. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。

还是一样 我们下面会解释 为什么是虚函数 为什么必须要重写

虚函数

被virtual修饰的类成员函数被称为虚函数。

例如下面的这段代码

class Person
{
	// 虚函数
	virtual void Print();
};

int main()
{
	return 0;
}

我们的Print就是虚函数

这里有两点需要注意的:

  1. 只有类的非静态成员函数前可以加virtual,普通函数前不能加virtual。

关于这个问题 因为静态成员和普通成员函数是没有this指针的

  1. 虚函数这里的virtual和虚继承中的virtual是同一个关键字,但是它们之间没有任何关系。虚函数这里的virtual是为了实现多态,而虚继承的virtual是为了解决菱形继承的数据冗余和二义性。

这个是关于virtual的用法 就不用过多解释了

虚函数的重写

虚函数的重写在语法层面上叫做重写

在原理层面上叫做覆盖 后面的例子会让大家明白这一点

它有两个必要条件

  1. 必须是虚函数

  2. 三同 即 函数名相同 参数相同 返回值相同

还是一样 我们来看代码

class Person
{
public:
	virtual void buy_ticket()
	{
		cout << "买票 - 原价" << endl;
	}
private:

};

class child : public Person
{
public:
	// 这里的virtual也可以不写 因为语法规定 只要三同 实际上这里的函数就继承了父类的虚函数属性
	// 但是不管我们平时敲代码 或者写项目的时候都要加上去 保证代码的可读性
	virtual void buy_ticket()
	{
		cout << "买票 - 半价" << endl;
	}
private:
};

class soldier : public Person
{
public:
	// 为了证明上面说可以省略 virtual 的正确性 这里省略之 
	void buy_ticket()
	{
		cout << "买票 - 优先" << endl;
	}
private:

};

现在我们通过父类的对象指针还有引用调用看看能不能完成多态

void func1(Person& p)
{
	p.buy_ticket(); 
}

void func2(Person* p)
{
	p->buy_ticket();
}

void test_vritual()
{
	Person p;
	child c;
	soldier s;

	func1(p);
	func1(c);
	func1(s);

	cout << "test ------ ptr" << endl;

	func2(&p);
	func2(&c);
	func2(&s);
}



int main()
{
	test_vritual();
	return 0;
}

显示效果如下

在这里插入图片描述

虚函数重写的两个例外

协变

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

比如说我们改写下之前写的代码

在这里插入图片描述
我们可以看到这里它们的返回值并不相同 但是依然满足多态 可以运行 这就是协变机制

要记住的一点是 协变的返回值必须是基类或者派生类的指针或引用 不然会报错 类似这样

在这里插入图片描述

析构函数的重写

如果父类的析构函数为虚函数 那么只要子类的析构函数定义了 那么它就与父类中的析构函数构成重写

比如说我们看下面的代码

class a
{
public:
	virtual ~a()
	{
		cout << "~a" << endl;
	}
};



class b : public a
{
public:
	virtual ~b()
	{
		cout << "~b" << endl;
	}
};

其中 a和b的析构函数就构成多态

怎么证明呢? 我们再来看下面的一段代码

void func(a& p)
{
	p.~a();
}

int main()
{
	a a1;
	b b1;


	cout << "start test" << endl;
	func(a1);
	func(b1);
	cout << "test end" << endl;

	return 0;
}

运行结果如下

在这里插入图片描述
我们可以发现 我们输入不同的对象引用确实触发了不同的析构函数

至于为什么出现了三次析构函数 可以参考下我上一篇继承的博客

派生类对象在析构时,会先调用派生类的析构函数再调用基类的析构函数。

至于后面的三次析构则是 a1 和 b1的生命周期结束了 自动调用的

那么这里的问题就来了

父类和子类的析构函数构成重写的意义何在呢?

我们试想下面的场景

我们创建一个父类对象和一个子类对象 并且使用父类的指针指向它们

然后全部delete掉

a* a1 = new a;
a* b1 = new b;

delete a1;
delete b1;

此时如果没有重写析构函数的话 两次析构其实都是析构的父类的

这样子就会造成一个内存泄漏的情况

而我们期望的是 delete a1 就是析构父类

delete b1 就是析构父类加子类

本质上是一种多态 所以我们要重写

记不记得我们上面继承提过一个知识点

析构函数的名字会被统一处理成destructor();

现在应该能充分理解为什么这么做的原因了吧 为了多态开路

C++11 override和final

我们从上面的博文中就可以看出 C++对于函数重写比较严格 但是我们有可能由于自身的疏忽 导致字符写反 或者返回值写错等原因无法构成重写

而这种错误要在程序运行之后才能被编译器发现 我们觉得有点太慢了

为了解决这个问题 C++中给出了两个关键字 这里我们来一个个学习下它们

final

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

我们来看下面的代码

class Person
{
public:
	virtual void print() final;
private:

};


class child : public Person
{
public:
	void print()
	{
		;
	}
private:

};

运行下我们可以发现

在这里插入图片描述

编译的时候会报错 不能够重写

override

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

我们来看下面的两组对比

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述
具体的内容看上面这张图就好

抽象类

抽象类的概念

在虚函数的后面写上=0,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类)

抽象类不能实例化出对象

为了证明这个概念 我们写出下面的代码

class person
{
public:
	virtual void print() = 0;
private:
};


int main()
{
	person p;
	return 0;
}

在这里插入图片描述
我们可以发现 符合我们上面的结论

派生类继承抽象类后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象

比如说这样子

class child : public person
{
public:
private:
};

在这里插入图片描述

接着我们重写下虚函数试试

class child : public person
{
public:
	virtual void print() 
	{
		cout << "child" << endl;
	}
private:
};

在这里插入图片描述
我们发现 这样子就可以运行了

抽象类既然不能实例化出对象,那抽象类存在的意义是什么?

我们说 意义有二

  1. 抽象类可以更好的去表示现实世界中,没有实例对象对应的抽象类型,比如:植物、人、动物等。

  2. 抽象类很好的体现了虚函数的继承是一种接口继承,强制子类去重写纯虚函数,因为子类若是不重写从父类继承下来的纯虚函数,那么子类也是抽象类也不能实例化出对象。

接口继承和实现继承

实现继承: 普通函数的继承是一种实现继承,派生类继承了基类函数的实现,可以使用该函数。

接口继承: 虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态

总结

在这里插入图片描述
本文主要讲解了C++中多态的一些使用

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

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

相关文章

【ML】numpy meshgrid函数使用说明(全网最简单版)

【ML】numpy meshgrid函数使用说明meshgrid的作用&#xff1f;怎么使用&#xff08;举例说明&#xff09;手工描点&#xff08;帮助理解&#xff09;怎么画三维&#xff1f;附画图代码meshgrid的作用&#xff1f; 首先要明白numpy.meshgrid()函数是为了画网格&#xff0c;&…

Systemverilog实现参数化的Round-Robin Arbiter Tree

本篇内容涉及的rtl代码为开源组织PLUP的common cell仓库中的源代码&#xff0c;本文只是对其进行些许解读。源码链接如下&#xff1a;[https://github.com/pulp-platform/common_cells/blob/dc555643226419b7a602f0aa39d449545ea4c1f2/src/rr_arb_tree.sv] “想要快速提升编程能…

基于springboot的公司人事管理系统

1 简介 今天向大家介绍一个帮助往届学生完成的毕业设计项目&#xff0c;公司人事管理系统。 计算机毕业生设计,课程设计需要帮助的可以找我 源码获取------》 链接&#xff1a;https://pan.baidu.com/s/1CdxrlV7GeRRmsT9UWEMtJg 提取码&#xff1a;cygy 2 设计概要 21世纪…

测试人不得不知的 HTTP 状态码知识

HTTP协议是当前使用最广泛的一种通信协议&#xff0c;在性能测试中&#xff0c;也使用的非常广泛。但是&#xff0c;确有很多人在调试性能测试脚本的时候&#xff0c;弄不明白HTTP状态码&#xff0c;不能通过HTTP状态码做些基本判断&#xff0c;今天&#xff0c;就来给大家好好…

客户终身价值(CLTV)计算和回归预测模型(Python)

内容介绍 本文整理了客户终身价值&#xff08;CLV或者CLTV&#xff09;的相关概念&#xff0c;并对一家英国线上零售公司的一年交易数据进行分析&#xff0c;计算该公司所有客户的CLV并且建立回归预测模型。 一、客户生命周期价值 用户生命周期价值Customer Lifetime value(…

常见实用的锁策略详解

&#x1f388;专栏链接:多线程相关知识详解 目录 1.乐观锁VS悲观锁 2.读写锁VS普通互斥锁 3.轻量级锁VS重量级锁 4.自旋锁VS挂起等待锁 5. 公平锁VS非公平锁 6.可重入锁VS不可重入锁 7.关于synchronized的锁策略以及自适应 1.乐观锁VS悲观锁 乐观锁:预测锁…

Windows中安装配置RabbitMQ

本次安装环境win10&#xff0c;采用版本 OTP 25.0.3https://github.com/erlang/otp/releases/tag/OTP-25.0.3RabbitMQ 3.10.13 Release RabbitMQ 3.10.13 rabbitmq/rabbitmq-server GitHubOpen source RabbitMQ: core server and tier 1 (built-in) plugins - Release Rabbi…

[N1CTF 2018]eating_cms parse_url绕过

index.php <?php require_once "function.php"; if(isset($_SESSION[login] )){Header("Location: user.php?pageinfo"); } else{include "templates/index.html"; } ?> function.php <?php session_start(); require_once &q…

Kafka Producer 自定义 拦截器 序列化

Kafka Producer 拦截器 & 序列化 前言 文章中的版本信息、maven依赖如下 JDK17 kafka_2.13-3.3.1 pom文件 <dependencies><dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId><version>…

NR HARQ (四)Type-2 HARQ-ACK codebook

微信同步更新欢迎关注同名modem协议笔记 上篇提到type-1 HARQ-ACK codebook&#xff0c;即semi-static codebook&#xff0c;UE要为每个PDSCH候选位置生成反馈&#xff0c;也会包含实际没有下行传输的PDSCH&#xff0c;再加上配置CBG的场景&#xff0c;HARQ-ACK 码本中包含的无…

【Linux 内核 内存管理】物理内存组织结构

一、 UMA和NUMA两种模型 共享存储型多处理机有两种模型 一致内存访问&#xff08;Uniform-Memory-Access&#xff0c;简称UMA&#xff09;模型 非一致内存访问&#xff08;Nonuniform-Memory-Access&#xff0c;简称NUMA&#xff09;模型 UMA模型 物理存储器被所有处理器件均…

超标量处理器设计——第八章_发射

超标量处理器设计——第八章_发射 参考《超标量处理器》姚永斌著 文章目录超标量处理器设计——第八章_发射8.1 简述8.1.1 集中式 VS. 分布式8.1.2 数据捕捉 VS. 非数据捕捉8.1.3 压缩 VS. 非压缩8.2 发射过程的流水线8.2.1 非数据捕捉结构的流水线8.2.2 数据捕捉结构的流水线8…

随手写系列——写一个凯撒密码转换页面

文章目录先展示效果H5编写C3编写JS编写——方法一&#xff1a;过程版JS编写——方法二&#xff1a;对象版代码获取先展示效果 &#xff08;因为主要是实现功能&#xff0c;所以CSS写的很粗糙&#xff09; H5编写 基础结构如下&#xff1a; 先构成最外面的大盒子.box&#…

【Flutter】之便于提高开发效率的周边库和轮子

GetX 状态管理 GetX包含很多功能&#xff0c;各种弹出widget、路由管理、国际化、Utils、状态管理等。 基于路由管理 1. 添加到项目中 1.1. 将此添加到pubspec.yaml文件中。 get: 4.1.4 1.2. 在命令行中运行 flutter packages get 1.3. 在MaterialApp前面加上 “Get”&…

centos7 yum安装postgreSQL

安装环境centos7.6 安装步骤&#xff1a; 1、安装postgresql&#xff1a; yum install postgresql-server 2、安装postgresql 扩展包&#xff1a; yum install postgresql-contrib 3、初始化&#xff1a; postgresql-setup initdb 4、启动开机自启动&#xff1a; systemc…

说话人识别神经网络推理方式

概述 说话人识别是一个序列总结&#xff08;Sequence Summarization&#xff09;任务&#xff0c;输入是音频&#xff08;或者说&#xff0c;声学特征的序列&#xff09;&#xff0c;输出是说话人的嵌入码&#xff0c;有的神经网络可以输入一对音频&#xff0c;直接输出这对音…

java微信支付v3系列——9.微信支付之商家转账API

这个功能就比较复杂了&#xff0c;首先是得有90天的资金流水才能开通&#xff0c;其次开通后还需要在官网进行配置&#xff0c;不能直接调用&#xff0c;并且限制了IP地址。 如下图所示&#xff0c;首先需要进行产品设置&#xff0c;将里面都设置好后才能进行开发&#xff0c;…

feign 调用常见问题避坑指南!

摘要&#xff1a;主要是总结了一下这段时间在使用 feign 的过程中的遇到的一些坑点。一、Get请求自动转化成POST的问题1、client 请求参数没有加上 RequestParam 注解问题代码&#xff1a;GetMapping("/showName") String showName(String name);错误提示&#xff1a…

让 APISpace 告诉你什么场景使用什么API

Q1&#xff1a;某商家打算搞年底促销活动&#xff0c;需要将活动信息通过短信的形式通知给用户&#xff0c;这个场景可以用什么接口&#xff1f; 发送通知类的短信&#xff0c;可以使用 通知短信 API~ 通知短信&#xff0c;支持三大运营商&#xff0c;虚拟运营商短信发送&…

第14章 并发控制与恢复

第14章 并发控制与恢复 考试范围&#xff1a; 14.1-14.3, 14.8-14.11 考试题型&#xff1a; 事务操作 考试内容&#xff1a; 1、锁/共享锁/排它锁的概念 2、多粒度锁 Multiple Granularity 3、两阶段封锁协议 The Two-Phase Locking Protocol 两段锁协议是指同一事务对任何…