c++进阶(c++里的多态)

news2024/11/25 11:30:09

文章目录

  • 1.多态的概念
    • 1.1多态的概念
  • 2.动态的定义及实现
    • 2.1多态的构成条件
    • 2.2虚函数
    • 2.3虚函数的重写
      • 虚函数重写的两个例外
    • 2.4 C++11 override和final
    • 2.5重载、覆盖(重写)、隐藏(重定义)的对比
  • 3.抽象类
    • 3.1概念
    • 3.2接口继承和实现继承
  • 4.多态的原理
    • 4.1虚函数表
    • 4.2多态的原理

1.多态的概念

1.1多态的概念

多态的概念:一般来说,就是多种状态。具体来讲就是去完成某个行为,当不同对象去完成时会产生不同的状态。
举个例子:比如买票这个行为,当普通人买票时,为全价买票;当学生买票时,为半价买票;军人买票时,为优先买票。

2.动态的定义及实现

2.1多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生不同的行为。比如student基础了person。person对象买票全价,student对象买票半价。
那么在继承中要构成多态还有两个条件:

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

#include <iostream>
using namespace std;
//多态
class person
{
public:
	virtual void BuyTicket()
	{
		cout << "全价买票" << endl;
	}
private:
};
class student :public person
{
public:
	virtual void BuyTicket()
	{
		cout << "半价买票" << endl;
	}
private:

};
void test(person& p)
{
	p.BuyTicket();
}
int main()
{
	person p;
	student s;
	test(p);
	test(s);
	return 0;
}

运行结果

2.2虚函数

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

class person
{
   virtual void BuyTicket()
   {
       cout<<"全价买票"<<endl;
   }
}

2.3虚函数的重写

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

#include <iostream>
using namespace std;
//多态
class person
{
public:
	virtual void BuyTicket()
	{
		cout << "全价买票" << endl;
	}
private:
};
class student :public person
{
public:
/*在重写基类虚函数时,派生类的虚函数前可以不加virtual关键字,
虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性)但是这种写法不是很规范,不建议这样写。*/
	virtual void BuyTicket()
	{
		cout << "半价买票" << endl;
	}
private:

};
void test(person& p)
{
	p.BuyTicket();
}
int main()
{
	person p;
	student s;
	test(p);
	test(s);
	return 0;
}

虚函数重写的两个例外

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

class A{};
class B:pubilc A{};
class person
{
     virtual A* f()
     {
        return new A;
     }
}
class student: pubilc person
{
     virtual B* f()
     { 
        return new B;
     }
}

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

class person
{
public:
	virtual ~person()
	{
		cout << "~person" << endl;
	}
	
private:
};
class student :public person
{
public:
	virtual ~student()
	{
		cout << "~student" << endl;
	}
private:

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

运行结果

2.4 C++11 override和final

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

class person
{
public:
	virtual void BuyTicket() final
	{
		cout << "全价买票" << endl;
	}
};
class student :public person
{
public:
	virtual void BuyTicket()
	{
		cout << "半价买票" << endl;
	}
};

编译报错

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

class person
{
public:
	virtual void BuyTicket() final
	{
		cout << "全价买票" << endl;
	}
};
class student :public person
{
public:
	virtual void BuyTicket() override
	{
		cout << "半价买票" << endl;
	}
};

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

三个概念的对比:
重载:1.两个函数再同一作用域
2.函数名/参数相同
重写(覆盖):1.两个函数分别再基类和派生类的作用域
2.函数名/参数/返回值都必须相同(协变除外)
3.两个函数必须是虚函数
重定义:1.两个函数分别再基类和派生类的作用域
2.函数名相同
3.两个基类和派生类的同名函数不构成重写就是重定义

3.抽象类

3.1概念

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

class car
{
public:
	virtual void Drive() = 0;
};

class Tesla :public car
{
public:
	virtual void Drive()
	{
		cout << "Tesla" << endl;
	}
};
class Benz :public car
{
	virtual void Drive()
	{
		cout << "Benz" << endl;
	}
};

int main()
{
	car* pBenz = new Benz;
	pBenz->Drive();
	car* pTesla = new Tesla;
	pTesla->Drive();
	return 0;
}

运行结果

3.2接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现,虚函数的继承是一种借口继承,派生类继承的基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

4.多态的原理

4.1虚函数表

计算sizeof(a)的大小,注意:程序在x86环境下运行

class A
{
public:
	virtual void f()
	{
		cout << "f()" << endl;
	}
private:
	int _a = 0;

};
int main()
{
    A a;
	cout << sizeof(a) << endl;
	return 0;
}

通过运行我们发现了结果为8
为什么呢?函数不是存在公共区域的吗?
我们调试一下
调试结果
可以看到,除了_a成员,还多出了 _ vfptr放在了对象的前面(不同平台可能不同,博主使用的为vs2022),对象中的这个指针我叫做虚函数指针,一个含有虚函数的类都至少有一个虚函数指针,因为虚函数的递增要被放到虚函数表中,虚函数也称虚表,那么派生类中这个表会放写什么呢?

class A
{
public:
	virtual void f()
	{
		cout << "f()-A" << endl;
	}
	virtual void f2()
	{

	}
	void f3()
	{

	}
private:
	int _a = 0;

};
class B :public A
{
public:
	virtual void f()
	{
		cout << "f()-B" << endl;
	}
private:
	int _b = 1;
};
int main()
{
	A a;
	cout << sizeof(a) << endl;
	B b;
	return 0;
}

调试结果
通过观察和测试,我们可以发现
1.派生类对象b中也存在一个虚表指针,b对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
2.基类a对象和派生类b对象虚表是不一样的,这里我们发现f1()完成了重写,所以b的虚表中存的是重写的A::f1(),所以虚函数的重写也叫做覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
3.另外f2()继承下来后是虚函数,所以放进了虚表,**f3()**也继承下来了,但不是虚函数,所以不会放进虚表。
4.虚函数表的本质是一个存放虚函数指针的指针数组,一般情况下析构数组最后放一个nullptr。
5.总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份放到派生类虚表中。b.如果派生类重写基类中某个函数,用派生类总结的虚函数覆盖虚表中基类的虚函数 c.派生类总结新增的虚函数按其在派生类中的声明次序增加到派生类虚表的最后
6.这里还有一个容易混淆的问题:虚函数存在哪?虚表存在哪?
**虚函数存在虚表,虚表存在对象中。注意这个回答是错误的。**很多同学都是这样深信不疑。注意虚表,存的是虚函数指针,不是虚函数,虚函数和普通函数一样,都是存在代码段的,只是他的这种又存到了虚表。另外对象中存的也不是虚表,而是虚表指针。那么虚表存在哪呢?实际是存在代码段的。

4.2多态的原理

多态

  1. 观察下图的红色箭头我们看到,p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚
    函数是Person::BuyTicket。
  2. 观察下图的蓝色箭头我们看到,p是指向johnson对象时,p->BuyTicket在johson的虚表中
    找到虚函数是Student::BuyTicket。
  3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。
  4. 反过来思考我们要达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调
    用虚函数。反思一下为什么?
    在这里插入图片描述

    在这里插入图片描述

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

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

相关文章

记录微信小程序云开发的增删改查

目录 一、准备工作 1、创建集合添加数据 2、设置数据权限 3、小程序连接数据库 二、增删改查 1.查 1、查询单集合所有数据 2、条件查询 1、直接:相当于等于 2、调用指令 3、查询单条&#xff08;根据id查询&#xff09; 2.增 3.改 4.删 一、准备工作 1、创建集合添…

Linux--任务管理与守护进程

目录 任务管理 进程组概念 作业概念 会话概念 补充 守护进程 基本概念 守护进程的查看 守护进程的创建 自己手写守护进程 使用系统调用函数创建守护进程 任务管理 进程组概念 每一个进程除了有一个进程ID之外&#xff0c;还有一个进程组ID&#xff0c;进程组是一个或…

由浅到深认识Java语言(29):集合

该文章Github地址&#xff1a;https://github.com/AntonyCheng/java-notes 在此介绍一下作者开源的SpringBoot项目初始化模板&#xff08;Github仓库地址&#xff1a;https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址&#xff1a;https://blog.c…

光致发光荧光量子产率测试光纤光谱仪

光致发光荧光量子产率测试系统是一种用于测量材料发光效率的高精度设备&#xff0c;它通过光致发光方法来确定样品的发射效率。光致发光荧光量子产率测试系统不仅提供了一种高效、可靠的测量手段&#xff0c;而且对于提升科学研究和工业应用中的发光材料性能具有重要作用。通过…

Python:基础语法

一、import与from.....import 有时候我们需要使用一些第三方库或包时&#xff0c;我们就需要通过import或from.....import导入模块。 # 导入库 import sys print("hello,world") 当我们自己写了些函数&#xff0c;在其他py文件&#xff0c;我们也可以通过from.....im…

【Java程序设计】【C00361】基于Springboot的考勤管理系统(有论文)

基于Springboot的考勤管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 项目获取 &#x1f345;文末点击卡片获取源码&#x1f345; 开发环境 运行环境&#xff1a;推荐jdk1.8&#xff1b; 开发工具&#xff1a;eclipse以及idea&…

「10」文本(GDI+):添加文字,可设置背景添加移动效果

「10」文本&#xff08;GDI&#xff09;添加文字&#xff0c;可设置背景添加移动效果 在OBS软件里&#xff0c;通过来源组件「文本&#xff08;GDI&#xff09;」&#xff0c;您可以添加任意您想要呈现的文字&#xff0c;在直播窗口中显示&#xff0c;它可以是提示语、广告词、…

SQLServer SEQUENCE用法

SEQUENCE&#xff1a;数据库中的序列生成器 在数据库管理中&#xff0c;经常需要生成唯一且递增的数值序列&#xff0c;用于作为主键或其他需要唯一标识的列的值。为了实现这一功能&#xff0c;SQL Server 引入了 SEQUENCE 对象。SEQUENCE 是一个独立的数据库对象&#xff0c;用…

python.类

1.类用class定义 name等是属性 是成员变量 定义完类了要定义对象 class Student: #定义了一个Student的类nameNoneageNonegenderNonesdu1Student() #创建了一个Student类的对象 sdu1.nameleo sdu1.age20 sdu1.gendermaleprint(sdu1.name) print(sdu1.age) print(sdu1.…

Day21|二叉树part07:530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先

530. *二叉搜索树的最小绝对差&#xff08;双指针题型&#xff09; 众所周知二叉搜索树的中序遍历序列是一个有序数组&#xff0c;因此最基本的方法就是遍历得到中序序列再进行计算&#xff0c;实际上可以用双指针法&#xff0c;记录中序遍历前一个指针和当前指针的差值&#…

一个bitter组织下载器样本分析

BITTER 该组织最早在2016由美国安全公司Forcepoint进行了披露&#xff0c;并且命名为“BITTER”&#xff0c;同年国内友商360也跟进发布了分析报告&#xff0c;命名为“蔓灵花” 样本分析 MD5&#xff1a;806626d6e7a283efffb53b3831d53346 vt:看文件名判断是伪装成pdf的自解…

小学生古诗文大会往届真题测一测(来自主办方)和非常详细的解析

新学期开学一眨眼已经过了一个多月了&#xff0c;有家长朋友开始关心2024年上海市小学生古诗文大会什么时候开始&#xff1f;如何准备小学生古诗文大会&#xff1f;如何激发孩子学习古诗词的兴趣&#xff1f;如何提高小学古诗词和古诗文大会的学习成绩&#xff1f;... 最近&…

YT8531调试记录

总结 还是从设备树&#xff0c;mac驱动&#xff0c;mac驱动对mdio总线的注册&#xff0c;phy驱动 &#xff0c;phy的datasheet&#xff0c;cpu的datasheet 几个方面来看来看 0.确认供电&#xff0c;以及phy的地址(一般会有多个地址&#xff0c;根据相关引脚电平可配置) 1.确…

Linux离线安装mysql,node,forever

PS:本文是基于centos7实现的,要求系统能够查看ifconfig和unzip解压命令, 实现无网络可安装运行 首先现在百度网盘的离线文件包****安装Xftp 和 Xshell 把机房压缩包传到 home目录下****解压unzip 包名.zip 获取IP先获取到 linux 主机的ip ifconfig Xftp 连接输入IP,然后按照…

CentOS使用Docker部署Halo并结合内网穿透实现公网访问本地博客

文章目录 1. Docker部署Halo1.1 检查Docker版本如果未安装Docker可参考已安装Docker步骤&#xff1a;1.2 在Docker中部署Halo 2. Linux安装Cpolar2.1 打开服务器防火墙2.2 安装cpolar内网穿透 3. 配置Halo个人博客公网地址4. 固定Halo公网地址 本文主要介绍如何在CentOS 7系统使…

搭建本地局域网域名并配置本地的mqtt服务器

1. 第一步&#xff1a; 首先准备一台windows电脑&#xff0c;安装 Technitium DNS Server 链接如下&#xff1a; Technitium DNS Server | An Open Source DNS Server For Privacy & Security 启动 start 然后进入 http://localhost:5380/ 下载完成之后&#xff0c;需要…

内网端口如何映射到外网?

内网端口映射到外网是一项重要的网络技术&#xff0c;它可以实现在任何网络环境下远程访问和管理内网设备。在复杂的网络环境中&#xff0c;内网设备通常无法直接被外网访问&#xff0c;而内网端口映射技术可以解决这个问题。本文将介绍一种名为【天联】的组网产品&#xff0c;…

最小割问题合集,最大权闭合图,最大密度子图,最小权点覆盖,最大权独立子图,OJ练习,代码详解

文章目录 零、回顾1、流网络的割2、最小割问题 一、最小割的应用1.1POJ1966 -- Cable TV Network1.1.1原题链接1.1.2思路分析1.1.3AC代码 1.2ZOJ 2676 Network Wars1.2.1原题链接1.2.2思路分析1.2.3AC代码 1.3OPTM - Optimal Marks1.3.1原题链接1.3.2思路分析1.3.3AC代码 二、最…

VS2022 nuget 无法解析 PackageSourceMapping 已启用,未考虑以下源: nuget.org。

前言&#xff1a; VS中的项目包的指向是 nuget.org&#xff0c;不是本地的下载后包。 解决方法&#xff1a; 把“包源映射”里的全部移除。

tcp/ip是什么意思,tcp/ip协议包含哪几层

TCP/IP是一种网络通信协议&#xff0c;它是互联网所采用的基本协议。TCP/IP协议是由美国国防部高级研究计划局&#xff08;ARPA&#xff09;在上世纪70年代设计开发的&#xff0c;经过多年发展和完善&#xff0c;已成为全球范围内最重要的网络通信协议之一。 首先&#xff0c;让…