面向对象之_多态_1

news2024/11/24 16:37:00

目录

一. 多态

多态是什么

二. 多态的构成条件

1. 虚函数

2. 虚函数重写(隐藏)

3. 父类型的引用或者指针调用

 4. 多态的特殊情况

1) 子类可以不加 virtual 关键字

2) 协变

三. 关键字

1.  virtual 

2. final

3. override

四. 多态的原理

1. 虚函数表指针

2. 调用原理

多态是怎么样被调用的?

多态为什么不能是父类的对象调用?

为什么需要是虚函数呢?

五. 总结


一. 多态

多态是什么

多态:简单的说就是不同类型的对象调用相同的函数产生的结果是不同的。

二. 多态的构成条件

  1. 虚函数重写

  2. 父类型的指针或者引用调用虚函数

1. 虚函数

虚函数就是一个函数前面加一个关键字 virtual 关键字,这样就是虚函数了,但是虚函数只能是成员函数才能是虚函数,而全局函数不可以成为虚函数。

virtual void fun()
{
	cout << "fun()" << endl;
}


int main()
{

	return 0;
}

只有成员函数才可以是虚函数。

class A
{
public:
	virtual void fun()
	{

	}
};

 这样就是没有问题的。

2. 虚函数重写(隐藏)

我们的父类和子类只有函数三同才可以构成虚函数重写。

三同:函数名相同,参数相同,返回值相同,然后还需要是虚函数。

只有满足三同并且还都是虚函数才能构成重写。

class person
{
public:
	virtual void buyTicket()
	{
		cout << "void person::buyTicket()" << endl;
	}
};

class student : public person
{
public:
	virtual void buyTicket()
	{
		cout << "void student::buyTicket()" << endl;
	}
};

上面代码就是一个重写。

3. 父类型的引用或者指针调用

void fun(person& p)
{
	p.buyTicket();
}


int main()
{
	person p;
	student s;
	fun(p);
	fun(s);

	return 0;
}

上面就是多态了。

如果我们破坏了里面的任意一个条件,那么就是不构成多态的,我们下面破坏掉虚函数的条件,让父类不是虚函数。

class person
{
public:
	void buyTicket()
	{
		cout << "void person::buyTicket()" << endl;
	}
};

class student : public person
{
public:
	virtual void buyTicket()
	{
		cout << "void student::buyTicket()" << endl;
	}
};

void fun(person& p)
{
	p.buyTicket();
}


int main()
{
	person p;
	student s;
	fun(p);
	fun(s);

	return 0;
}

 

此时我们的调用就是普通的 person对象 调用 buyTicket 函数。

我们在破坏一个其他的条件,返回值。

class student : public person
{
public:
	virtual void buyTicket()
	{
		cout << "void student::buyTicket()" << endl;
	}
};

void fun(person& p)
{
	p.buyTicket();
}

 此时编译都不通过。

我们在破坏一个参数的条件。

class person
{
public:
	virtual void buyTicket(int a)
	{
		cout << "void person::buyTicket()" << endl;
	}
};

class student : public person
{
public:
	virtual void buyTicket()
	{
		cout << "void student::buyTicket()" << endl;
	}
};

 此时编译也是不通过的,如果改变的是子类的参数,那么编译通过但是调用时不成功的。

 4. 多态的特殊情况

在多态里面,是由一些特殊情况的

1) 子类可以不加 virtual 关键字

class person
{
public:
	virtual void buyTicket()
	{
		cout << "void person::buyTicket()" << endl;
	}
};

class student : public person
{
public:
	void buyTicket()
	{
		cout << "void student::buyTicket()" << endl;
	}
};

我们的多态也是成功的,所以在多态这里我们的子类时可以不加 virtual 关键字的,但是基类时需要加的。

2) 协变

 协变就是参数可以不同,但是我们是有条件的,我们的协变只能是返回父类和子类的指针或者是引用。

class person
{
public:
	virtual person& buyTicket()
	{
		cout << "void person::buyTicket()" << endl;
	}
};

class student : public person
{
public:
	virtual student& buyTicket()
	{
		cout << "void student::buyTicket()" << endl;
	}
};

而且我们必选是同时是指针或者是引用,我们也不仅是可以是自己的父类和子类,我们也可以是其他的父类和子类。

class A
{};

class B : public A
{};

class person
{
public:
	virtual A& buyTicket()
	{
		cout << "void person::buyTicket()" << endl;
	}
};

class student : public person
{
public:
	virtual B& buyTicket()
	{
		cout << "void student::buyTicket()" << endl;
	}
};

而我们的返回值必须是父类返回父类,子类返回子类。

class person
{
public:
	virtual B& buyTicket()
	{
		cout << "void person::buyTicket()" << endl;
	}
};

class student : public person
{
public:
	virtual A& buyTicket()
	{
		cout << "void student::buyTicket()" << endl;
	}
};

三. 关键字

1.  virtual 

virtual 关键字:

  • 虚继承:在继承的时候使用该关键字
  • 虚函数:在函数前面加 virtual 关键字

2. final

final 关键字:

  • 最终类:在类的后面加 final 表示该类无法被继承
class A final
{};


class B : public A
{};

这里就是我们不能对 A 进行继承

3. override

override 关键字:

  • 检查是否完成重写:在派生类中使用,加在想要被重写的函数后面
  • 静态检查:在编译时静态检查,确保派生类中重写了基类的虚函数
class A 
{
public:
	void fun()
	{}
};


class B : public A
{
public:
	void fun() override
	{}
};

 

 许哦一这里只需要完成重写就可以了。

class A 
{
public:
	virtual void fun()
	{}
};


class B : public A
{
public:
	void fun() override
	{}
};

四. 多态的原理

1. 虚函数表指针

在前面的菱形虚拟继承继承中,如果有重定义的函数或者变量,那么里面是有一个虚基表的,这个是虚函数表指针,不要弄混了,下面看一下虚函数表指针。

在有虚函数的类中,该类都会有一个虚函数表指针,该指针就是指向该类中的虚函数的。

class A
{
public:
	virtual void fun()
	{}

	int _a;
};

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

这里的 sizeof 是8,所以这里的 A 肯定不只有一个 _a 的成员变量,还有一个虚函数表指针,这个是一个指针,所以在 32 位和 64 位下的环境是不同的。

下面1看一下该类的内部是怎么样的。

 

看一下这个指针里面的内容。

 

现在 a 这个对象里面只有一个虚函数,所以该表里面只存储一个,而虚函数表指针的本质,就可以理解为一个函数指针数组,该函数指针数组里面存储的就是一个个虚函数,如果还有其他的虚函数,那么还是会放在该类的这个虚函数表里面。

虚函数表:每个类共同使用一个虚函数表。

下面看一下相同类型的对象使用的虚函数表是否相同。

 

 

2. 调用原理

多态是怎么样被调用的?

多态是需要父类的对象或者是指针,然后调用重写的虚函数。

在父类的指针或者引用看来,它自己指向的对象模型就是一个父类对象。

class A
{
public:
	virtual void fun()
	{
		cout << "A::fun()" << endl;
	}

	int _a;
};

class B : public A
{
public:
	virtual void fun()
	{
		cout << "B::fun()" << endl;
	}
	int _b;
};

int main()
{
	B b;
	return 0;
}

这里看一下B 类型对象的对象模型

 所以在父类的引用或者指针看来就是一个父类的对象,但是调用为什么会形成多态?

看一下这个虚表指针里面的内容。

B 类型的对象里面的虚表指针里面是重写后的自己的虚函数,所以在调用的时候可以调用到自己的函数。

那么为什么B 类型的对象里面的虚表指针里面是自己的函数地址呢?下面分为3步。

  1. 在多态中,派生类首先会把基类的虚函数表拷贝下来。
  2. 如果派生类中对基类的虚函数进行了重写,那么就会将虚函数表中的基类的被重写的虚函数替换成自己的虚函数的地址。
  3. 如果自己也有自己的虚函数,就将自己的虚函数添加到虚函数表中。

 

多态为什么不能是父类的对象调用?

多态为什么不能是父类的对象呢?

int main()
{
	B b;
	A a = b;
}

现在将b 赋值给a,那么现在的a里面的虚表是父类的虚表还是子类的虚表?

当然是自己的,因为在父子类在进行赋值的时候,虚表是不会进行赋值的,如果虚表进行赋值的话,那么现在的 a 里面的虚表就是B 类型的虚表,那么a 对象调用虚函数的时候是调用的就是派生类的函数了,所以在进行赋值的时候虚表不能赋值,由于虚表在赋值的时候不能不拷贝,所以多态调用的时候不能是父类的对象。

int main()
{
	B b;
	A a;
	b._a = 10;

	a = b;
}

通过这段代码测试一下,这里将里面的 _a 变量赋值时为例更好的查看。

 

这里看到虚表时没有变化的。

为什么需要是虚函数呢?

前面说过了,虚函数会放到虚表里面,那么如果不是虚函数的话,那么当然是不会放在虚表里面的,所以如果函数都不在虚表里面即使是父类的指针或者是引用在虚表里面也找不到应该重写后的虚函数,所以也就没法实现多态。

五. 总结

多态

  • 多态的调用需要父类的引用或者是指针调用
  • 调用的函数必须是虚函数进行重写
  • 虚函数重写的条件是三同(函数名相同,返回值,参数)
  • 在多态这里子类是可以不加 virtual 的,只要父类加了,那么子类与父类的函数又是三同,就构成多态
  • 协变,协变就是返回值可以不同,但是返回值必须是父类返回父类的指针或者是引用,子类必须返回子类的指针或者是引用,而且返回的父子类只要是父子类既可以,不一定是自己的父子类 

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

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

相关文章

【MST+离线】CF1851 G

Problem - G - Codeforces 题意&#xff1a; Code&#xff1a; #include <bits/stdc.h>#define int long longusing namespace std;const int N2e510; const int mod1e97;int a[N]; struct node {int x, y, val;bool operator < (const node &k) const {return …

疾风计划-程序设计基础-期末考试-05

擀面皮 有一块1x1的方形面团&#xff08;不考虑面团的厚度&#xff09;&#xff0c;其口感值为0。擀面师傅要将其擀成一个N x M&#xff08;纵向长N&#xff0c;横向宽M&#xff09;的面皮。师傅的擀面手法娴熟&#xff0c;每次下手&#xff0c;要么横向擀一下&#xff08;使得…

Ubuntu 20.04下的录屏与视频剪辑软件

ubuntu20.04下的录屏与视频剪辑 一、录屏软件SimpleScreenRecorder安装与使用 1、安装 2、设置录制窗口参数 3、开始录制 二、视频剪辑软件kdenlive的安装 1、安装 2、启动 一、录屏软件SimpleScreenRecorder安装与使用 1、安装 &#xff08;1&#xff09;直接在终端输入以下命…

嵌入式系统中的GPIO控制:从理论到实践与高级应用

本文将探讨嵌入式系统中的GPIO(通用输入输出)控制,着重介绍GPIO的原理和基本用法。我们将使用一个实际的示例项目来演示如何通过编程配置和控制GPIO引脚。将基于ARM Cortex-M微控制器,并使用C语言进行编写。 GPIO是嵌入式系统中最常见且功能最强大的接口之一。它允许硬件工…

【JVM】详解对象的创建过程

文章目录 1、创建对像的几种方式1、new关键字2、反射3、clone4、反序列化 2、创建过程步骤 1、检查类是否已经被加载步骤 2、 为对象分配内存空间1、指针碰撞针对指针碰撞线程不安全&#xff0c;有两种方案&#xff1a; 2、空闲列表选择哪种分配方式 步骤3、将内存空间初始化为…

C语言中的数组(详解)

C语言中的数组&#xff08;详解&#xff09; 一、一维数组1.一维数组的创建2.数组的初始化3.一维数组的使用4.一维数组在内存中的存储二、二维数组1.二维数组的创建2.二维数组的初始化3.二维数组的使用4.二维数组在内存中的存储三、数组越界四、数组作为函数参数1.冒泡排序2.数…

MySQL基础扎实——MySQL中有那些不同的表格

表格类型 在MySQL中&#xff0c;常见的表格类型有以下几种&#xff1a; MyISAM&#xff1a;是MySQL默认的表格类型&#xff0c;具有较高的性能和较小的存储空间占用。但是&#xff0c;MyISAM不支持事务、崩溃恢复和数据行级锁定。 InnoDB&#xff1a;是MySQL提供的一个更强大…

理解基本的Android编程 (1/2)

1、Android概述 Android 是一个开源的&#xff0c;基于 Linux 的移动设备操作系统。 Android开发优势 开放源代码 众多开发者及强大的社区 不断增长的市场 国际化的App集成 低廉的开发成本 更高的成功几率 丰富的开发环境 Android应用程序 Android 应用程序一般使用…

EtherNet/IP转Modbus网关以连接AB PLC

本案例为西门子S7-1200 PLC通过捷米特Modbus转EtherNet/IP网关捷米特JM-EIP-RTU连接AB PLC的配置案例。 网关分别从ETHERNET/IP一侧和MODBUS一侧读写数据&#xff0c;存入各自的缓冲区&#xff0c;网关内部将缓冲区的数据进行交换&#xff0c;从而实现两边数据的传输。 网关做为…

VMware NSX Advanced Load Balancer (NSX ALB) 22.1.4 - 负载均衡平台

VMware NSX Advanced Load Balancer (NSX ALB) 22.1.4 - 负载均衡平台 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-nsx-alb-22/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 负载均衡平台 NSX Advanced Load…

SpringBoot-6

Spring Boot 中的 MVC 支持 Spring Boot 的 MVC 支持主要来最常用的几个注解&#xff0c;包括RestController 用于声明控制器、RequestMapping用于实现方法映射地址、PathVariable 用于接受路径中的参数、RequestParam 用于接受 request 请求中的参数以及RequestBody 用于接受…

echarts 饼图中间添加文字

需求&#xff1a;饼图中间展示总量数据 方法一、设置series对应饼图的label属性 series: [{type: "pie",radius: [55%, 62%],center: ["67%", "50%"],itemStyle: {borderRadius: 10,borderColor: #fff,borderWidth: 2},// 主要代码在这里label: …

docker 的compose安装

1. Docker Compose 环境安装 Docker Compose 是 Docker 的独立产品&#xff0c;因此需要安装 Docker 之后在单独安装 Docker Compose 下载 curl -L https://github.com/docker/compose/releases/download/1.21.1/docker-compose-uname -s-uname -m -o /usr/local/bin/docker…

HBase有写入数据,页面端显示无数据量

写了一个测试类&#xff0c;插入几条数据&#xff0c;测试HBase的数据量。很简单的功能&#xff0c;这就出现问题了。。网页端可以看到&#xff0c;能够看到读写请求&#xff0c;但是不管是内存、还是磁盘&#xff0c;都没有数据。 于是就想到去HDFS查看&#xff0c;也是有数据…

VU3-02

1.一些小点 1.1 npm i -D less (安装less) -D 安装依赖到开发环境中 只在开发中生效 正式打包的时候没有它&#xff0c;只在开发时有效 1.2 父子组件传参 &#xff08;1&#xff09;子组件中定义自己的参数和事件 父传子&#xff1a;const props defineProps(["item&quo…

利用读时建模等数据分析能力,实现网络安全态势感知的落地

摘要&#xff1a;本文提出一种基于鸿鹄数据平台的网络安全态势感知系统&#xff0c;系统借助鸿鹄数据平台读时建模、时序处理、数据搜索等高效灵活的超大数据存储和分析处理能力&#xff0c;支持海量大数据存储、分类、统计到数据分析、关联、预测、判断的网络安全态势感知能力…

MySQL索引优化分析和锁详解

MySQL 1. 索引 1.1 索引的概述 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构&#xff08;有序&#xff09; 1.2 索引的优缺点 优点 提高数据检索效率,降低磁盘IO的成本通过索引列对数据进行排序, 降低数据排序的成本,降低CPU的消耗 缺点 索引是需要…

【使用回溯法求解八皇后问题(92个解)】

在每一个横列、竖列、斜列都只有一个皇后 解决的冲突 包括行、列和两条对角线规定每一行放置一个皇后&#xff0c;不会造成行上的冲突 当第col列被某个皇后占领之后&#xff0c;则同一列上的所有空格都不能再放置皇后&#xff0c;并且要把flag[col]置为被占领状态 对角线有两个…

【Docker】Docker的通信安全

Docker的通信安全 前言一、Docker 容器与虚拟机的区别1. 隔离与共享2. 性能与损耗 二、Docker 存在的安全问题1. Docker 自身漏洞2. Docker 源码问题 三、Docker 架构缺陷与安全机制1. 容器之间的局域网攻击2. DDoS 攻击耗尽资源3. 有漏洞的系统调用4. 共享 root 用户权限 四、…

OBS 迁移--华为云

一、创建迁移i任务 1. 登录管理控制台。 2. 单击管理控制台左上角的 在下拉框中选择区域。 3. 单击“ 服务列表 ”&#xff0c;选择“ 迁移 > 对象存储迁移服务 OMS ”&#xff0c;进入“ 对象存储迁移服务 ”页面。 4. 单击页面右上角“ 创建迁移任务 ”。 5. 仔细阅读…