C++---多态

2023-09-18 12:34:04

前言

在买火车票的时候,如果你是学生,是买半价票;普通人是全家买票,军人买票是优先买票。

多态的概念

多态:多种形式,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的形态。

多态的定义及实现

多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。Person对象买票全家,Student对象买票半价。

必须通过基类的指针或者引用调用虚函数
被调用的函数必须是虚函数,切派生类必须对基类的虚函数进行重写

虚函数

继承中提到过虚继承,关键字是 virtual,这里的虚函数,是指virtual修饰的类成员函数称为虚函数。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "买票---全价" << endl;
	}
};

虚函数的重写

虚函数的重写(覆盖) ,派生类中有一个根据类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型,函数名字,参数列表完全相同),称子类的虚函数重写了基类的虚函数

class Person
{
public:
	virtual void BuyTicket() const
	{
		cout << "买票---全价" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket() const
	{
		cout << "买票---半价" << endl;
	}
};
/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用*/

void func(const Person& p)
{
	p.BuyTicket();
}

int main()
{
	func(Student());

	return 0;
}

当虚函数通过指针或者引用调用时,我们并不知道该函数真正作用的对象是什么类型,因为他可能是一个基类的对象也可能是一个派生类的对象,直到运行时才会决定到底执行哪个版本,判断依据是引用或指针所绑定的对象的真是类型。

当且仅当对通过指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才有可能与静态类型不同。

所以上面的结果是 半价

在这里插入图片描述

虚函数重写的两个例外

协变(基类与派生类虚函数返回值类型不同)

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

在这里插入图片描述

我将上面的代码Student中的函数返回值改为int,就会产生协变。

如果我将基类的函数和派生类的函数都改成指针类型。
在这里插入图片描述

在这里插入图片描述

代码就能正常运行。

析构函数的重写

如果基类的析构函数为选好念书,此时派生类析构函数只要定义,无论是否加 virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不是的,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称同一处理称destructor

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

};

class Student : public Person
{
public:
	 ~Student()
	{
		cout << "~Student" << endl;

	}
	
};

int main()
{
	Person* p = new Person;
	delete p;

	p = new Student;
	delete p;
	return 0;
}

在这里插入图片描述

执行了两次基类的析构函数,因为p的类型是基类的指针类型,所以在不重写的情况下,只能调用基类的析构函数,如果这个时候派生类里面涉及到动态内存等申请资源的函数,且最后并没有释放资源,就会造成内存泄漏。

override和final

如果把某个函数指定为 final,则之后任何尝试覆盖该函数的操作都将引发错误。

class Person
{
public:
	virtual void BuyTicket() const final
	{
		cout << "买票---全价" << endl;
	}
};

使用 override标记了某个函数,但该函数并没有覆盖已存在的虚函数,此时编译器将报错。

class B
{
public:
	virtual void f1(int) const;
	virtual void f2();
	void f3();
}

class D : B
{
public:
	void f1(int) const override; 正确,f1跟基类中的f1匹配
    void f2(int) override; 错误,没有匹配参数
    void f3() override; 错误,f3不是虚函数
    void f4() override; 错误,B中没有名为f4的函数
}

在这里插入图片描述

虚函数的默认参数

和其他函数一样,虚函数也可以拥有默认参数。如果我们通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的派生类中的函数版本也是如此。此时,传入派生类函数的将是基类函数定义的默认参数。如果派生类函数依赖不同的实参,则程序结果将于我们的预期不同。

如果虚函数使用默认参数,则基类和派生类中定义的默认实参最好一致。

class A
{
public:
	virtual void f(int a = 1)
	{
		cout << "A::f()" << a << endl;
	}
};

class B : public A
{
public:
	virtual void f(int a = 2)
	{
		cout << "B::f()" << a << endl;
	}
};

int main()
{
	B b;
	A* a = &b;
	a->f();

	return 0;
}

输出结果B::f()1

抽象基类

在虚函数的后面写上 =0则这个函数为纯虚函数。这样做可以告诉用户当前这个纯虚函数没有实际意思。一个纯虚函数无需定义,在函数体的位置写上 =0即可。这个 =0只能出现在类内部虚函数声明处。

class A
{
public:
	virtual void f() = 0;
};

含有纯虚函数的类是抽象基类。抽象基类负责定义接口,而后续的其他类可以覆盖该接口。抽象基类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写虚函数,派生类才能实例化出对象。

更多推荐

【Node.js】认识express并创建基本web服务器:

文章目录一、初识Express【1】Express简介【2】Express的基本使用【3】托管静态资源【4】nodemon二、Express路由【1】路由的概念【2】路由的使用三、Express中间件【1】中间件的概念【2】Express中间件的初体验【3】中间件的分类【4】自定义中间件四、使用Express写接口【1

Excel相关操作

文章目录4.Excel4.1周报业务逻辑讲解4.2基础概念4.3练习数据熟悉4.4数据透视表+图4.5常用函数sumsumifsumifssum和subtotal的区别if函数&嵌套vlookupmatchindex总结4.6周报搭建4.Excel4.1周报业务逻辑讲解在这种周报,可以根据平台和日期筛选所有数据,联动的

《动手学深度学习 Pytorch版》 5.6 GPU

5.6.1计算设备importtorchfromtorchimportnntorch.device('cpu'),torch.device('cuda:0')#cuda等价于cuda:0(只有一块显卡没法试别的块号)(device(type='cpu'),device(type='cuda',index=0))torc

ElasticSearch 因为索引字段改变,平滑迁移索引

问题:某个索引创建时,没有按照想要的mapping,进行创建。有个字段是text,不是想要的keyWord此时需要重新按照mapping创建新索引,并迁移数据,一、不使用别名的方式迁移1.创建新索引:使用Elasticsearch的PUT请求创建一个新的索引,例如PUT/new_index。在创建新索引时,确保按照想要

算法通关村第14关【青铜】| 什么是堆

1.堆的概念堆(Heap):堆是一种特殊的树状数据结构,通常用于实现优先队列和相关算法。堆分为最大堆(MaxHeap)和最小堆(MinHeap)两种类型,具体取决于根节点的值与子节点的关系。在最大堆中,根节点的值最大,而在最小堆中,根节点的值最小。堆具有以下特性:它是一个完全二叉树,通常使用数组来表示。在最大堆中,每个

数据库数据恢复-ORACLE常见故障有哪些?恢复数据的可能性高吗?

ORACLE数据库常见故障:1、ORACLE数据库无法启动或无法正常工作。2、ORACLE数据库ASM存储破坏。3、ORACLE数据库数据文件丢失。4、ORACLE数据库数据文件部分损坏。5、ORACLE数据库DUMP文件损坏。ORACLE数据库数据恢复可能性分析:1、ORACLE数据库无法启动或无法正常工作:突然出现

SpringCloud——微服务

微服务技术栈在之前的开发过程中,我们将所有的服务都部署在一台服务器中,当我们的服务开始越来越多,业务越来越复杂,当一台服务器不能承担我们的业务的时候,就需要将不同的业务分开部署在不同的服务器上,这每一个单独分离的服务,就是微服务,这些搭载了单个业务的服务器,就是【服务集群】。这些服务集群是用来做具体的业务逻辑的,当我们

PCIE研究-2

PCIe是一种高速串行总线,用于连接计算机内部的各种设备。在PCIe中,有四种不同的设备类型:Switch、Bridge、RootComplex和EndPoint。本篇文章将介绍这四种设备类型的基础知识。1.SwitchSwitch是PCIe中最常见的设备类型之一,它可以将一个PCIe总线分成多个子总线。Switch可

加密资产托管:迈向安全与合规的未来!

在当今数字化时代,加密货币正逐渐走进人们的视野,并成为越来越多投资者和机构的关注焦点。然而,加密领域仍存在一个主要问题:如果丢失了密钥,就会导致无法访问资产。为了解决这一问题,加密货币行业正在向资产托管的方向发展。传统金融体系中,资产托管是非常重要的环节,它涉及到金融机构或第三方专业机构为投资者保管和管理其资产。在加密

网安周报|CISA发布增强开源安全性的计划

1、CISA发布增强开源安全性的计划美国一家领先的安全机构发布了一项期待已久的计划,详细说明了它将如何增强联邦政府和整个生态系统的开源安全性。美国网络安全和基础设施安全局(CISA)开源软件安全路线图在安全开源峰会上发布。据估计,解决开源软件中的网络风险是拜登政府的一个关键优先事项,因为96%的代码库包含开源代码。CI

干净优雅的做iOS应用内全局交互屏蔽

本文字数:4930字预计阅读时间:28分钟01交互屏蔽的需求很多应用开发者都会遇到这样一个需求,当程序需要处理某个敏感的核心任务,或者执行某些动画时,需要杜绝一切外部干扰,优先保证任务的完成,之后再去处理其它任务。否则如果在处理过程中受到外部事件的干扰,可能会引入严重的问题,而规避这些问题需要额外编写过多的逻辑。例如,

热文推荐