【C++干货铺】继承后的多态 | 抽象类

news2024/11/14 19:54:41

=========================================================================

个人主页点击直达:小白不是程序媛

C++系列专栏:C++干货铺

代码仓库:Gitee

=========================================================================

目录

多态的概念

多态的定义和实现

多态的定义条件

虚函数

虚函数的重写

特殊情况

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

析构函数的重写

C++11 override 和 final关键字

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

抽象类


多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。 

举个栗子:很多朋友在外地上大学,回家或者去学校可能都会买火车票或者其他交通工具的票。但是作为一个大学生我们是有学生认证的大学生身份,买票有打折优惠;但是对于其他不是学生的公民来说就是普通人的身份是享受不到这个优惠的,必须全价买票;又或者对于军人来说,他们有军人的身份,军人优先买票。

因此对于买票这个行为,当普通人买票时,是全价买票学生买票时,是半价买票军人
买票
时,是优先买票


多态的定义和实现

多态的定义条件

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

那么在继承中要构成多态还有两个条件

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

虚函数

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

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

虚函数的重写

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

注意:重写只是对函数体的内容重写而不是对整个函数重写,使用的还是基类的接口,只不过是函数体的内容变了。

class Person
{
public :
	virtual void Buy()
	{
		cout << "普通人——全价买票" << endl;
	}
};
class Student :public Person
{
public:
	virtual void Buy()
	{
		cout << "学生——半价买票" << endl;
	}
};
void Func(Person& p)
{
	p.Buy();
}
int main()
{
	Person p1;
	Student s1;
	Func(p1);
	Func(s1);
	return 0;
}

特殊情况

在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用。

class Person
{
public :
	virtual void Buy()
	{
		cout << "普通人——全价买票" << endl;
	}
};
class Student :public Person
{
public:
	virtual void Buy()
	{
		cout << "学生——半价票" << endl;
	}
	//派生类的虚函数前面可以不加virtual 但是这样并不规范,不建议这样使用
	//void Buy()
	//{
	//	cout << "学生——半价票" << endl;
	//}
};
void Func(Person& p)
{
	p.Buy();
}
int main()
{
	Person p1;
	Student s1;
	Func(p1);
	Func(s1);
	return 0;
}

虚函数的两个例外:

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

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

class Person
{
public:
	virtual Person* Buy()
	{
		cout << "普通人——全价买票" << endl;
		return nullptr;
	}
};
class Student :public Person
{
public:
	virtual Student* Buy()
	{
		cout << "学生——半价票" << endl;
		return nullptr;
	}
};
void Func(Person& p)
{
	p.Buy();
}
int main()
{
	Person p1;
	Student s1;
	Func(p1);
	Func(s1);
	return 0;
}

返回值可以为其他类的指针

class A
{
public:
};
class B : public A
{
public:
};
class Person
{
public:
	virtual A* Buy()
	{
		cout << "普通人——全价买票" << endl;
		return nullptr;
	}
};
class Student :public Person
{
public:
	virtual B* Buy()
	{
		cout << "学生——半价票" << endl;
		return nullptr;
	}
};
void Func(Person& p)
{
	p.Buy();
}
int main()
{
	Person p1;
	Student s1;
	Func(p1);
	Func(s1);
	return 0;
}

析构函数的重写

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

class Person
{
public :
	virtual ~Person()
	{
		cout << "~Person()" << endl;
	}
};
class Student
{
public:
	virtual ~Student()
	{
		cout << "~Student()" << endl;
	}
};
//只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函
数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;
	delete p1;
	delete p2;
}

C++11 override 和 final关键字

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数
名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有
得到预期结果才来debug会得不偿失,因此:C++11提供了overridefinal两个关键字,可以帮
助用户检测是否重写

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

class Person
{
public :
	virtual void Func()final
	{
		cout << "被final修饰" << endl;
	}
};
class Student : public Person
{
public:
	virtual void Func()
	{

	}
};

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

//override
// 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Person
{
public:
	virtual void Func() {};
};
class Student : public Person
{
public:
	void Func ()override {};
};

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


抽象类

抽象类的概念

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

class car
{
public :
	virtual void Drive() = 0;
};
class Benz :public car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};
class BMW :public car
{
public:
	virtual void Drive()
	{
		cout << "BMW-操控" << endl;
	}
};
int main()
{
	Benz b;
	b.Drive();
	BMW m;
	m.Drive();
	return 0;
}


今天对继承之后的多态的分享到这就结束了,希望大家读完后有很大的收获,也可以在评论区点评文章中的内容和分享自己的看法。您三连的支持就是我前进的动力,感谢大家的支持!! !

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

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

相关文章

如果你找不到东西,请先确保你在正确的地方寻找

之前我们在几篇文章中描述了如何进行”思想”调试&#xff0c;今天的文章我将不会这样做。 因为下面的编程错误大部分人都会遇到&#xff0c;如果你看一眼下面的代码&#xff0c;你不会发现有什么问题&#xff0c;这仅仅是因为你的的大脑只给你希望看到的&#xff0c;而不是那…

分数约分-第11届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第20讲。 分数约分&#xf…

算法模板之单链表图文讲解

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;算法模板、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️使用数组模拟单链表讲解1.1 &#x1f514;为什么我们要使用数组去模拟单链表…

appium2.0.1安装完整教程+uiautomator2安装教程

第一步&#xff1a;根据官网命令安装appium&#xff08;Install Appium - Appium Documentation&#xff09; 注意npm前提是设置淘宝镜像&#xff1a; npm config set registry https://registry.npmmirror.com/ 会魔法的除外。。。 npm i --locationglobal appium或者 npm…

多线程 (上) - 学习笔记

前置知识 什么是线程和进程? 进程: 是程序的一次执行,一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间&#xff0c;一个进程可以有多个线程&#xff0c;比如在Windows系统中&#xff0c;一个运行的xx.exe就是一个进程。 线程: 进程中的一个执行流&#xff0…

Element-Ui定制Dropdown组件

1.效果 说明&#xff1a;移入后新增图标&#xff0c;然后移入后图标变色。当然大家可以想到用mouseover移入事件来实现移入颜色的变化&#xff0c;但是在使用Dropdown组件的时候&#xff0c;不支持这种写法。因此采用了原生的遍历对象的形式&#xff0c;为每一个item对象绑定鼠…

通过WinCC基本功能实现批次查询及批次报表

谈到WinCC中的批次数据处理和批次报表&#xff0c;也许有人会想到PM-Quality这款专业的批次报表软件。但如果你的银子有限&#xff0c;批次报表要求又比较简单&#xff0c;不妨看看此文。 —《通过 WinCC 基本功能实现批次数据过滤查询以及打印批次数据报表》 实现的功能描述 …

一维数组的定义

什么是数组&#xff1f; &#xff08;1&#xff09;数组是具有一定顺序关系的若干变量的集合&#xff0c;组成数组的各个变量统称为数组的元素 &#xff08;2&#xff09;数组中的各元素的数据类型要求相同&#xff0c;用数组名和下标确定&#xff0c;数组可以是一维的&#…

无经验小白开发一个 JavaWeb项目,需要注意哪些要点?

大家好我是咕噜铁蛋 &#xff0c;我收集了许多来自互联网的宝贵资源&#xff0c;这些资源帮助我学习和理解如何从零开始开发JavaWeb项目。今天&#xff0c;我将与大家分享一些关键的要点&#xff0c;包括项目规划、技术选型、数据库设计、代码编写和测试部署等。如果你有任何问…

大数据存储技术(3)—— HBase分布式数据库

目录 一、HBase简介 &#xff08;一&#xff09;概念 &#xff08;二&#xff09;特点 &#xff08;三&#xff09;HBase架构 二、HBase原理 &#xff08;一&#xff09;读流程 &#xff08;二&#xff09;写流程 &#xff08;三&#xff09;数据 flush 过程 &#xf…

Mysql数据库 19.Mysql 锁

MySQL锁 锁&#xff1a;锁是计算机用以协调多个进程间并发访问同一共享资源的一种机制&#xff0c;在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源&#xff0c;如何保证数据并发访问的一…

【MySQL备份】MySQL备份工具-MyDumper

目录 什么是MyDumper MyDumper优势有哪些 如何安装MyDumper 参数解释 1 mydumper参数解释 备份流程 一致性快照如何工作&#xff1f; 如何排除&#xff08;或包含&#xff09;数据库&#xff1f; 输出文件 Metadata文件 ​编辑 表数据 文件 表结构 文件 建库文件…

关于uview-ui的u-tabs标签滑块不居中的问题

在uniapp中&#xff0c;打开文件 uni_modules/uview-ui/components/u-tabs/u-tabs.vue 然后在style中添加以下代码即可 /deep/ .u-tabs__wrapper__nav__line {left: 18rpx; } 之前效果图&#xff1a; 之后效果图&#xff1a; 注意&#xff0c;代码中的18rpx需要自行调整

半导体:Gem/Secs基本协议库的开发(5)

此篇是1-4 《半导体》的会和处啦&#xff0c;我们有了协议库&#xff0c;也有了通讯库&#xff0c;这不得快乐的玩一把~ 一、先创建一个从站&#xff0c;也就是我们的Equipment端 QT - guiCONFIG c11 console CONFIG - app_bundle CONFIG no_debug_release # 不会生…

深入理解JVM设计的精髓与独特之处

这是Java代码的执行过程 从软件工程的视角去深入拆解&#xff0c;无疑极具吸引力&#xff1a;首个阶段仅依赖于源高级语言的细微之处&#xff0c;而第二阶段则仅仅专注于目标机器语言的特质。 不可否认&#xff0c;在这两个编译阶段之间的衔接&#xff08;具体指明中间处理步…

C语言----文件操作(二)

在上一篇文章中我们简单介绍了在C语言中文件是什么以及文件的打开和关闭操作&#xff0c;在实际工作中&#xff0c;我们不仅仅是要打开和关闭文件&#xff0c;二是需要对文件进行增删改写。本文将详细介绍如果对文件进行安全读写。 一&#xff0c;以字符形式读写文件&#xff…

一文搞懂OSI参考模型与TCP/IP

OSI参考模型与TCP/IP 1. OSI参考模型1.1 概念1.2 数据传输过程 2. TCP/IP2.1 概念2.2 数据传输过程 3. 对应关系4. 例子4.1 发送数据包4.2 传输数据包4.3 接收数据包 1. OSI参考模型 1.1 概念 OSI模型&#xff08;Open System Interconnection Reference Model&#xff09;&a…

MLX:苹果 专为统一内存架构(UMA) 设计的机器学习框架

“晨兴理荒秽&#xff0c;带月荷锄归” 夜深闻讯&#xff0c;有点兴奋&#xff5e; 苹果为 UMA 设计的深度学习框架真的来了 统一内存架构 得益于 CPU 与 GPU 内存的共享&#xff0c;同时与 MacOS 和 M 芯片 交相辉映&#xff0c;在效率上&#xff0c;实现对其他框架的降维打…

【后端卷前端3】

侦听器 监听的数据是 data()中的动态数据~响应式数据 <template><div><p>{{showHello}}</p><button click"updateHello">修改数据</button></div> </template><script>export default {name: "goodsTe…

cesium 自定义贴图,shadertoy移植教程。

1.前言 cesium中提供了一些高级的api&#xff0c;可以自己写一些shader来制作炫酷的效果。 ShaderToy 是一个可以在线编写、测试和分享图形渲染着色器的网站。它提供了一个图形化的编辑器&#xff0c;可以让用户编写基于 WebGL 的 GLSL 着色器代码&#xff0c;并实时预览渲染结…