当前位置: 首页 > 编程日记 > 正文

C++继承后的多态 | 抽象类

目录

多态的概念

多态的定义和实现

多态的定义条件

虚函数

虚函数的重写

特殊情况

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

析构函数的重写

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;
}


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

相关文章:

C++会搜索的二叉树(BSTree)

本片文章主要介绍了二叉搜索树,并模拟实现!!!

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

本文主要讲解单链表模板,文中附有图文讲解,希望对你的算法学习有一定的帮助。

Java运算符及运算符的优先级【超详细】

int a = 10;int b = 20;a + b;a < b;对操作数进行操作时的符号,不同运算符操作的含义不同。作为一门计算机语言,Java也提供了一套丰富的运算符来操纵变量。Java中运算符可分为以下:算术运算符(+ - */)、关系运算符(< > ==)、逻辑运算符、位运算符、移位运算符以及条件运算符等,接下来我们来一个一个介绍。

Java中Jar包部署nohup后台启动定时按日期分割日志

在JAVA开发中很多没有用docker部署,而是选择传统的Jar包部署方式,这样就涉及日志的产生,如果直接部署日志文件会越来越大,程序怎么可能不出现问题,这样打开日志文件就会很慢,不方便后续问题的定位和解决。这样就要采取优化方案,对日志进行分割,这样便于查看。

Java Comparator多属性排序

自然排序通常情况跟equals保持一致,e1,e2是类C的元素,如果e1.compareTo(e2) == 0 那么e1和e2 equals的结果也是true。因为有的时候它俩结果一致,比如刚好就只有一个属性排序的时候,但多数情况下,二者是不一致的。比如有多个排序属性的时候。有的时候获取的数据需要在内存排序,需要根据List中T对象的属性p1,p2,p3等进行排序,该怎么写呢?这种规则掌握了,管它几个属性排序,管它什么升序降序,通通解决。多个属性,按不同的升降序规则,就变的非常简单了,只需要在下面的。

Linux 查看磁盘空间

Linux 查看磁盘空间可以使用 df 和 du 命令。

Linux多种方法安装MySQL

源码安装:优点是安装包比较小,只有十多M,缺点是安装依赖的库多,安装编译时间长,安装步骤复杂容易出错。使用官方编译好的二进制文件安装:优点是安装速度快,安装步骤简单,缺点是安装包很大,300M左右。yum安装。rpm安装。

Linux中mysql 默认安装位置&Linux 安装 MySQL

MySQL在Linux系统上的默认安装位置是目录。这是MySQL服务器的数据目录,包含所有数据库文件。通过检查MySQL二进制文件的路径,我们可以确认MySQL是否正确安装。在目录中,MySQL使用一系列文件和子目录来组织和存储数据。确保理解MySQL数据目录的结构对于管理和维护MySQL数据库至关重要。按照顺序安装即可解决。

Linux使用systemd服务和crontab实现Shell脚本开机自动运行&编辑当前用户或者指定用户的crontab内容crontab -e

systemd是Linux系统中的一个初始化系统和服务管理器。它可以用于在系统启动时自动运行Shell脚本。crontab是一个用于定时执行任务的工具。我们可以通过编辑crontab文件来设置开机自启动。

Linux定时任务详解&crontab -e 编辑之后如何保存并退出(Ubuntu)

Linux定时任务是一种可执行的命令或者脚本,在特定的时间或者时间间隔下自动执行。通过在系统中预设一些需要执行的任务,可以让Linux定时任务自动执行并完成这些任务。定时任务可以用于自动备份、系统清理、监控、自动化维护等任务。在Linux中,常用的定时任务程序有系统自带的cron和at命令。其中,cron是一个强大的定时任务工具,可以按照设定的实际时间执行命令,非常常用。anacron最小检测周期是天,使用anacron管理的定时任务应该最小是每隔一天执行。

nginx常用操作命令

都启动了同一个Nginx进程。不一样的是:1、Systemd 是一系列工具的集合,其作用也远远不仅是启动操作系统,它还接管了后台服务、结束、状态查询。2、Systemd更方便作服务管理,启动管理Nginx更佳方便。3、Systemd可以作开机服务管理,让服务跟随系统启动。

linux配置nginx开机自启&nginx部署与systemctl控制启动&/etc/systemd/system 和 /lib/systemd/system 的区别

目录/lib/systemd/system 以及/usr/lib/systemd/system 其实指向的是同一目录,在/目录下执行命令lltotal 28该目录中包含的是软件包安装的单元,也就是说通过 yum、dnf、rpm 等软件包管理命令管理的 systemd 单元文件,都放置在该目录下。/etc/systemd/system/(系统管理员安装的单元, 优先级更高)在一般的使用场景下,每一个 Unit(服务等) 都有一个配置文件,告诉 Systemd 怎么启动这个 Unit。

spring 笔记七 Spring JdbcTemplate

它是spring框架中提供的一个对象,是对原始繁琐的JdbcAPI对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。

spring 笔记八 SpringMVC异常处理和SpringMVC拦截器

① 创建异常处理器类实现HandlerExceptionResolver② 配置异常处理器③ 编写异常页面④ 测试异常跳转①创建异常处理器类实现HandlerExceptionResolver@Override//处理异常的代码实现//创建ModelAndView对象②配置异常处理器③编写异常页面。

【redis】Redis 架构演化之路

里面大致说了redis的架构演进。开始学redis架构的时候,总是感觉架构好多,迷迷糊糊的,但是你知道Redis 架构演化之路之后,就明白了前因后果,这样学习更加深入以及搞笑。这篇文章我想和你聊一聊 Redis 的架构演化之路。现如今 Redis 变得越来越流行,几乎在很多项目中都要被用到,不知道你在使用 Redis 时,有没有思考过,Redis 到底是如何稳定、高性能地提供服务的?如果你对 Redis 已经有些了解,肯定也听说过。

【redis】Redis哨兵机制和集群有什么区别?

Redis哨兵机制和集群有什么区别?redis集群有几种实现方式,一个是主从集群,一个是redis cluster.

Elasticsearch分布式搜索分析引擎本地部署与远程访问

本文主要讲解如何使用Elasticsearch分布式搜索分析引擎本地部署与远程访问

讲解K-Means聚类算法进行压缩图片

在本文中,我们讲解了如何使用K-Means聚类算法来压缩图像。通过K-Means算法,我们能够找到图像中的主要颜色,并用这些颜色替换原始图像中的像素颜色,从而实现图像的压缩。这个简单的技术可以在一定程度上减小图像文件的大小,同时保持图像的可视化效果。希望这篇文章能够帮助你理解如何使用K-Means聚类算法进行图像压缩。如果你想进一步学习图像处理和压缩的知识,推荐你深入研究相关的算法和工具。

讲解pytorch可视化 resnet50特征图

然后,我们加载了查询图像,并提取了查询图像的特征。接下来,我们以类似的方式对图像数据库中的每个图像提取特征。然后,我们计算查询图像特征与数据库中每个图像特征的相似度,并根据相似度对数据库图像进行排序。通过这种方法,我们可以使用ResNet50的特征图来构建一个简单的图像检索系统。该系统可以在图像数据库中找到与查询图像相似的图像,从而在实际应用中具有广泛的用途,如图像搜索引擎、商品推荐等。这对于理解模型在图像中学到的特征非常有帮助,并帮助我们进行图像分析和理解计算机视觉模型的工作原理。最后,我们使用模型的。

讲解Unsupported gpu architecture ‘compute_*‘2017解决方法

在使用2017年以前的NVIDIA GPU进行深度学习训练时,经常会遇到"Unsupported GPU Architecture 'compute_*'"的错误。本篇文章将介绍该错误的原因并提供解决方法。

讲解c1xx: fatal error C1356: 无法找到 mspdbcore.dll

本文介绍了这个错误的原因,并提供了一些解决方案来解决这个问题。如果你遇到这个错误,请尝试上述解决方案,希望能帮助你解决这个问题并顺利进行 C++ 编程。这个错误通常出现在编译过程中,而且很可能是由于缺少或损坏了 mspdbcore.dll 文件引起的。在本文中,我们将讨论这个错误的原因,并提供一些解决方案来解决这个问题。是 Visual Studio 内部使用的一个关键文件,它提供了用于编译、链接和调试的重要功能。这有时可以清除一些隐藏的配置问题,并解决。错误时,下面的示例代码可以帮助你解决这个问题。

讲解TypeError: Class advice impossible in Python3. Use the @Implementer class decorator instead

在Python3中,当我们尝试在类上使用旧的类修饰符(class decorator)时,可能会遇到的错误。为了解决这个问题,我们可以使用类修饰符来替代旧的类修饰符。类修饰符是模块提供的一个装饰器,用于实现接口定义。通过使用类修饰符,我们可以在Python3中实现类方法和静态方法的装饰,同时保持代码的兼容性和可读性。希望本文能够帮助你理解如何解决错误,并正确使用类修饰符来装饰类方法和静态方法。

docker搭建maven私库Nexus3

阿里代理地址:http://maven.aliyun.com/nexus/content/groups/public/由于nexus的默认端口为8081,我们在启动的时候改为18091后需要修改nexus的配置文件。这样就可以在本地浏览器进入nexus页面了,地址为 服务器ip:18091。右上角登录用户名为admin,密码为之前查看的密码。配置maven-central的代理地址。删除nuget开头的仓库。同时查看admin密码。

Gitlab基础篇: Gitlab docker 安装部署、Gitlab 设置账号密码

安装docker gitlab前确保docker环境,如果没有搭建docker请查阅“Linux docker 安装文档”可以看到在docker ps -a 打印中看到 容器ID ps 展示的容器ID只时原来的一部分。修改docker镜像的gitlab容器端口前需要把gitlab容器以及docker镜像关闭。通过容器ID就能找到containers下具体哪一个是gitlab容器的配置。修改config.v2.json、hostconfig.json文件。docker 下载 gitlab容器。

spring 笔记五 SpringMVC的数据响应

上述方式手动拼接json格式字符串的方式很麻烦,开发中往往要将复杂的java对象转换成json格式的字符串,我们可以使用web阶段学习过的json转换工具jackson进行转换,导入jackson坐标。在方法上添加@ResponseBody就可以返回json格式的字符串,但是这样配置比较麻烦,配置的代码比较多,因此,我们可以使用mvc的注解驱动代替上述配置。② 将需要回写的字符串直接返回,但此时需要通过@ResponseBody注解告知SpringMVC框架,方法。

spring 笔记六 SpringMVC 获得请求数据

• SpringMVC默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。• 但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。自定义类型转换器的开发步骤:① 定义转换器类实现Converter接口② 在配置文件中声明转换器③ 在中引用转换器①定义转换器类实现Converter接口@Overridetry {②在配置文件中声明转换器

static final、static、@PostConstruct、构造方法、@AutoWired执行的顺序

Java提供的注解,被用来修饰方法,被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。调用的顺序为: 构造函数 > @Autowired > @PostConstruct(2)作用:@PostConstruct注解的方法在项目启动的时候执行这个方法,也可以理解为在spring容器启动的时候执行,可作为一些数据的常规化加载,比如读取数据字典之类、目录树缓存。

【HarmonyOS开发】拖拽动画的实现

在开发拖拽动画时,发现png的图片在拖拽结束后,会出现图片闪动的不流畅问题,改为svg图片解决。因此通过大量的对比验证,确认为鸿蒙底层窜然问题。

YOLOv8-Pose训练自己的数据集

至此,整个YOLOv8-Pose模型训练预测阶段完成。此过程同样可以在linux系统上进行,在数据准备过程中需要仔细,保证最后得到的数据准确,最好是用显卡进行训练。有问题评论区见!

【Redis】Redis中的大key怎么处理?

在Redis中,大key是指存储了大量数据的键。处理大key可能会对Redis服务器的性能和资源消耗产生负面影响。询问这个问题,首先要知道redis的大key会有什么影响。大key占用的内存空间较大,当大量的大key存在时,会消耗大量的内存资源。这可能导致Redis服务器的内存不足,并触发内存溢出,从而影响系统的稳定性和性能。当读取或写入大key时,需要将大量的数据通过网络传输。对于大量的网络传输,特别是在高并发的情况下,会增加整体的延迟,并占用带宽资源。