多态及其原理

news2024/12/29 9:33:33

文章目录

    • 构成多态的条件
    • 虚函数
      • 作用:完成重写
    • 重写
    • 重载 重写 隐藏
    • 为什么析构函数要搞成符合多态?
    • 原理
      • 预热
      • 对于基类指针或引用指向父类或者子类的成员函数是如何调用不同的函数呢?

一个类如果是基类,它的析构函数最好加上virtual

构成多态的条件

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

多态,不同对象传递过去,调用不同函数
多态调用看的指向的对象
普通对象(普通调用(不构成多态)),看当前者类型

虚函数

只有成员函数才可以加virtual
加上virtual它就叫做虚函数
在这里插入图片描述

作用:完成重写

重写

虚函数重写的一些细节:
// 重写的条件本来是虚函数+三同(即派生类虚函数与基类虚函数的
返回值类型、函数名字、参数列表完全相同),但是有一些例外
// 1、派生类的重写虚函数可以不加virtual – (建议大家都加上)
// 2、协变,返回的值可以不同,但是要求返回值必须是父子关系指针和引用

重载 重写 隐藏

函数重载发生在同一作用域

重写和隐藏 发生在基类和派生类
隐藏只要函数名一样就符合条件

为什么析构函数要搞成符合多态?

场景一

class Person
{
public:
	~Person()
	{
		cout << "~Person()" << endl;
	}
};
class Student:public Person
{
public:	
   ~Student()
	{
		cout << "~Student()" << endl;
	}
};
int main()
{
	Person ps;
	Student st;
	return 0;	
}

这种情况下程序走的好着呢
在这里插入图片描述
也没上多态,不是也可以吗?为什么非得把父类和子类的析构函数搞成虚函数重写呢?
因为下面的场景下,不构成重写会造成内存泄漏

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

		delete[] _a;
	}
private:
	int* _a = new int[10];
};
int main()
{
	/*Person ps;
	Student st;*/

	Person* p = new Person;//基类指针既可以指向基类,也可以指向派生类
	delete p;

	p = new Student;  
	delete p;	//p->destructor() + operator delete(p)(free) 	
				//p是基类指针,指向Student,多态条件一满足
				//这里我们期望p指向谁,调用谁的析构
			//如果析构函数不构成多态,那么p->destructor() 是普通调用,只看当前者p的类型,永远只调用~person
	return 0;
}

运行结果
在这里插入图片描述
delete p 做了2件事

p->destructor() + operator delete§(free)

如果析构函数不构成多态,那么p->destructor() 是普通调用,只看当前者p的类型,永远只调用~person

这不是我们期望的,这里我们期望p指向谁,调用谁的析构

基类指针既可以指向基类,也可以指向派生类

那么我们就需要满足多态的条件
p是基类指针,指向Student,调用析构函数。多态条件一满足
编译器帮助我们把类析构函数都被处理成destructor这个统一的名字
形参类型一样,函数名一样,析构又没有返回值,那么就需加上virtual即可

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

		delete[] _a;
	}
private:
	int* _a = new int[10];
};
int main()
{
	/*Person ps;
	Student st;*/

	Person* p = new Person;//基类指针既可以指向基类,也可以指向派生类
	delete p;

	p = new Student;  
	delete p;	//p->destructor() + operator delete(p)(free) 	
				//p是基类指针,指向Student,多态条件一满足
				//这里我们期望p指向谁,调用谁的析构
			//如果析构函数不构成多态,那么p->destructor() 是普通调用,只看当前者p的类型,永远只调用~person
	return 0;
}

在这里插入图片描述

原理

预热

Base的大小是多少呢?
在这里插入图片描述
那么虚函数存在哪里呢?
存在了代码段(常量区)
回顾我们类和对象大小时的知识,成员函数也不保存在对象中,而是存放在公共的代码段
这里的vfptr是虚函数表指针,存的只是虚函数的地址!
成员函数加了virtual就会放到虚函数表指针里面

对于基类指针或引用指向父类或者子类的成员函数是如何调用不同的函数呢?

父类指针或者引用指向子类,发生切片,拿到的仍然是一个父类
指向父类还是父类
那么他们是如何调用不同的成员函数的呢?

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

	int _a = 1;
};

class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
	int _b = 1;
};

void Func(Person& p)
{
	// 符合多态,运行时到指向对象的虚函数表中找调用函数的地址
	p.BuyTicket();
}

int main()
{
	Person Mike;
	Func(Mike);

	Student Johnson;
	Func(Johnson);

	return 0;
}

Mike和Johnson的监视中看到他们的虚函数表里面存的地址不同,因为子类对父类的虚表进行了覆盖(子类拷贝了父类的虚表再进行重写)

P指针指向基类和指向子类时调用的函数是不同的地址
在这里插入图片描述
符合多态的话,P指向父类就是父类,P指向子类,完成切片后看到的还是一个子类,那么P到底是指向父类还是子类就不得而知了。
但是我不管,运行时,符合多态,运行时到指向对象的虚函数表中找调用函数的地址

在这里插入图片描述

在编译时就确定成员函数的地址
如果不符合多态,那么就看调用者P的类型,去Person里去找这个函数,在用函数名修饰规则就可以找到函数的地址
在这里插入图片描述在这里插入图片描述

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

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

相关文章

【【51单片机的LCD1602 最简单的一集】】

最简单的一集&#xff0c;明白运算显示就没问题 这一节相对简单 其实只要明白显示行列就可以了 剩下来的取什么基本上就是遍历 然后读取到\0停止 下面是基础的LCD1602的功能 #include <REGX52.H> sbit LCD_RSP2^6; sbit LCD_RWP2^5; sbit LCD_EP2^7; #define LCD_Data…

ES6基础知识七:你是怎么理解ES6中 Generator的?使用场景?

一、介绍 Generator 函数是 ES6 提供的一种异步编程解决方案&#xff0c;语法行为与传统函数完全不同 回顾下上文提到的解决异步的手段&#xff1a; 回调函数promise 那么&#xff0c;上文我们提到promsie已经是一种比较流行的解决异步方案&#xff0c;那么为什么还出现Gen…

归并排序法解释

什么是归并排序法 归并排序是一种常见的排序算法&#xff0c;它基于分治策略&#xff0c;将一个大问题分解为小问题来解决。归并排序的主要思想是将待排序的数组分成两个子数组&#xff0c;分别对这两个子数组进行排序&#xff0c;最后将两个有序的子数组合并成一个有序的数组…

Openlayers入门,Openlayers调整中心点坐标、Openlayers调整缩放级别、Openlayers调整地图可视角度和地图复位

专栏目录: OpenLayers入门教程汇总目录 前言 本章介绍一下Openlayers最基础的调整中心点坐标方式、调整缩放级别、调整地图可视角度和地图复位的小功能示例,非常简单,可直接上手。 二、依赖和使用 "ol": "^6.15.1"使用npm安装依赖npm install ol@6…

重学C++系列之继承

一、什么是继承 继承是面向对象三大特性之一&#xff0c;C中&#xff0c;被继承的类称为基类&#xff08;父类&#xff09;&#xff0c;继承别的类的类成为派生类&#xff08;子类&#xff09;&#xff0c;继承除了基类的构造函数和析构函数不继承外&#xff0c;其余成员全部继…

【安卓】视频播放器实现过程,超详细注释,自定义视频进度条,打开本地文件播放视频等功能。

一、实现效果 废话不多说&#xff0c;直接上代码&#xff0c;里面有详细注释&#xff0c;不清楚的评论区留言。 二、布局代码 <?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res…

Android安卓实战项目(3)—一个炫酷的健身APP界面

Android安卓实战项目&#xff08;3&#xff09;—一个炫酷的健身APP界面 一.项目运行介绍 1.大致浏览 2.功能介绍 &#xff08;1&#xff09;功能一 上导航条 &#xff08;2&#xff09;功能二 下导航条 二.具体实现 MainActivity.java package com.rckdeveloper.fitene…

python 面向对象编程的特点 - 封装 - 继承(经典类、新式类) - 多态 - 静态方法、类方法 - 下划线的使用 - 回合制攻击游戏实验

目录 面向对象编程的特点&#xff1a; 封装&#xff1a;封装是将数据和操作&#xff08;方法&#xff09;封装在一个对象中的能力 继承&#xff1a;继承是指一个类&#xff08;子类&#xff09;可以继承另一个类&#xff08;父类&#xff09;的属性和方法。 我们为什么需要继…

Python采集法外狂徒张三所有视频【含jS逆向解密】

传说中&#xff0c;有人因为只是远远的看了一眼法外狂徒张三就进去了&#x1f602; 我现在是获取他视频&#xff0c;岂不是直接终生了&#x1f929; 网友&#xff1a;赶紧跑路吧 &#x1f60f; 好了话不多说&#xff0c;我们直接开始今天的内容吧&#xff01; 你需要准备 …

详解STM32的GPIO八种输入输出模式,GPIO各种输入输出的区别、初始化的步骤详解,看这文章就行了(超详细)

在STM32微控制器中&#xff0c;常见的输入输出(GPIO)模式有八种&#xff0c;分别是推挽输出、开漏输出、复用推挽输出、复用开漏输出、浮空输入、上拉输入、下拉输入和模拟输入。下面我将为你解释每种模式的特点和区别&#xff0c;并提供相应的示例代码。 文章目录 介绍区别初…

组合模式-树形结构的处理

A公司需要筛选出年龄35岁及以上(如果是领导&#xff0c;年龄为45岁及以上)的人。其组织架构图如下。 图 A公司部分组织架构图 图 传统解决方案 public class Development {private String name;public Development(String name) {this.name name;}List<Employee> emplo…

uni-app优雅的实现时间戳转换日期格式

现在显示的格式如下图&#xff1a; 我期望统一格式&#xff0c;所以不妨前端处理一下&#xff0c;核心代码如下 filters: {// 时间戳处理formatDate: function(value, spe /) {value value * 1000let data new Date(value);let year data.getFullYear();let month data.…

【设计模式——学习笔记】23种设计模式——适配器模式Adapter(原理讲解+应用场景介绍+案例介绍+Java代码实现)

介绍 生活中的案例 不同国家的插座不同&#xff0c;出国旅游充电器不能直接使用&#xff0c;可以通过使用多功能转换插头来辅助使用 基础介绍 适配器模式将某个类的接口转换成客户端期望的另一个接口表示&#xff0c;主的目的是兼容性&#xff0c;让原本因接口不匹配不能一起…

github gitlab 多用户多平台切换

一、背景 我需要用账号1 来登录并管理github 账号 我需要用账号2 来登录并管理gitlab 账号 二、设置账号 邮箱 设置账号1用户名与邮箱 git config --global user.name "miaojiang" git config --global user.email "187133163.com" 三、生成本地密钥…

LT6911C 是一款HDMI 1.4到双端口MIPIDSI/CSI或者LVDS加音频的一款高性能芯片

LT6911C 1.描述&#xff1a; LT6911C是一款高性能的HDMI1.4到MIPIDSI/CSI/LVDS芯片&#xff0c;用于VR/智能手机/显示器应用程序。对于MIPIDSI/CSI输出&#xff0c;LT6911C具有可配置的单端口或双端口MIPIDSI/CSI&#xff0c;具有1个高速时钟通道和1个~4个高速数据通道&#…

ChatGLM-RM(Reward Model)实现代码逐行讲解

这里我们尝试通过RM训练让模型学会从给定上下文中提取信息&#xff0c;来进行RM模型的实践。你可以从下面链接获取代码 GitHub - Pillars-Creation/ChatGLM-RLHF-LoRA-RM: ChatGLM-6B添加了RLHF的实现&#xff0c;以及部分核心代码的逐行讲解 ,实例部分是做了个新闻短标题的生成…

入行软件测试7年,才知道原来字节跳动这么容易进

当前就业环境&#xff0c;裁员、失业消息满天飞&#xff0c;好像有一份工作就不错了&#xff0c;更别说高薪了。其实这只是一方面&#xff0c;而另一方面&#xff0c;各大企业依然求贤若渴&#xff0c;高技术人才依然紧缺&#xff0c;只要你技术过硬&#xff0c;拿个年薪50w不是…

FUNBOX_1靶场详解

FUNBOX_1靶场复盘 这个系列的靶场给出的干扰因素都挺多的&#xff0c;必须从中找到有用的线索才可以。 这个靶场你扫描到ip地址后打开网页会发现&#xff0c;ip自动转换成域名了&#xff0c;所以我们需要添加一条hosts解析才可以。 192.168.102.190 funbox.fritz.box从目录…

4EVERLAND 托管让 Permaweb 变得更容易!

在互联网托管领域&#xff0c;我们通常将其与存储和管理网站的服务联系起来。传统的 Web2 托管服务在集中式服务器模型上运行&#xff0c;其中网站文件和数据库存储在集中管理的服务器上。用户通过互联网访问网站。这种托管模式应用广泛&#xff0c;相对简单&#xff0c;适合很…

计算机存储结构、执行速度及对应用的影响

万丈高楼&#xff0c;平地起。 计算机世界的信息化软件工程&#xff0c;是构筑于计算机硬件之上的。 由于信息的流转依托于计算机不同的部件&#xff0c;所以计算机系统的内部设计、各类应用架构无不受部件之间速度差异的影响。 本文&#xff0c;主要先介绍存储体系&#xff0c…