【C++】多态的认识和理解

news2024/11/14 17:48:20

在这里插入图片描述
个人主页

在这里插入图片描述

文章目录

  • ⭐一、多态的概念
  • 🎄二、多态的定义及实现
    • 1.多态的构成
    • 2.实现多态的条件
    • 3.虚函数的概念
    • 4.虚函数的重写和覆盖
    • 5.析构函数的重写
    • 6.协变
    • 7.override和 final关键字
    • 8.重载、重写/覆盖、隐藏这三者的区别
  • 🏠三、纯虚函数和抽象类的关系
  • 🏝️四、多态的原理
    • 1.虚函数表指针
    • 2.多态是如何实现的
    • 3.动态绑定和静态绑定
  • 🚀五、虚函数表
    • 1.概念
    • 2.虚函数和虚函数表两者的存储位置

⭐一、多态的概念

多态(polymorphism),简单的来说就是多种形态。但多态又可以分为两种:一种是编译是的多态,称为静态多态;还有一种是运行时的多态,称为动态多态。下面我们就更进一步去了解一下什么是静态多态和什么是动态多态。

1.静态多态
静态多态主要就是函数重载以及函数模板,它们传不同的类型参数就可以调用不同的函数,通过参数不同就能够达到多种形态。而至于为什么会被称为是编译时的多态,是因为它们的实参传递给形参的参数匹配是在编译时完成的。

2.动态多态
具体的来说,就是去完成某个行为(函数),可以通过传不同的对象去完成不同的行为,因此达到多种形态。 例如:当我们要去买票时,我们会发现普通人去买票时,是全价票;学生去买票时,是优惠票;而军人去买票时是优先买票。又或者同样是动物叫这一行为,传猫对象过去,就是”(>ω<)喵“,传狗对象过去,就是"汪汪"。

🎄二、多态的定义及实现

1.多态的构成

多态是⼀种继承关系的下的类对象,去调用同一函数,从而产生了不同的行为。
例如:Student继承了Person,Person对象买票为全价票,而Student对象买票则为优惠票。

2.实现多态的条件

实现多态有两个重要的条件:
• 必须是指针或者引用调用的函数。
• 被调用的函数必须是虚函数。

如果想要实现多态的效果,首先必须是基类的指针或引用,因为只有基类的指针或引用才能指向派生类对象。其次派生类必须对基类的虚函数进行重新或者覆盖,派生类才能有不同的函数,从而达到多态的效果。

3.虚函数的概念

虚函数,就是在类成员函数前面加上virtual修饰,那么这个成员函数就被称为虚函数。注意:非成员函数不能加上virtual进行修饰。

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

4.虚函数的重写和覆盖

概念:在派生类中有一个跟基类完全相同的虚函数(既派生类的虚函数与基类的虚函数的返回值类型、函数名以及参数列表完全相同),称派生类的虚函数重写了基类的虚函数。
注意:在重写基类的虚函数时,派生类的虚函数在不加上virtual的情况下,也能构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但这种写法不是很规范,不建议这样使用。

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

class Student : public Person {
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票-打折" << endl; 
	}
};

void Func(Person* ptr)
{
	// 这⾥可以看到虽然都是Person指针Ptr在调⽤BuyTicket
	// 但是跟ptr没关系,⽽是由ptr指向的对象决定的。
	ptr->BuyTicket();
}

int main()
{
	Person ps;
	Student st;

	Func(&ps);
	Func(&st);

	return 0;
}

在这里插入图片描述

通过上述代码以及运行结果,我们可以发现子类Student中的BuyTicket重写了基类Person的BuyTicket。

5.析构函数的重写

我们首先要了解虽然析构函数的名字看起来不一样,但实际上编译器对析构函数的名称做了特殊的处理,编译后的析构函数名称统一处理为destructor,因此析构函数的名称实际上都为destructor。

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

class B : public A 
{
public:
	~B()
	{
		cout << "~B()->delete:" << _p << endl;
		delete _p;
	}
protected:
	int* _p = new int[10];
};

int main()
{
	A* p1 = new A;
	A* p2 = new B;
	delete p1;
	delete p2;
	return 0;
}

通过阅读上述代码,如果我们在~A之前不加virtual是否会发生报错呢?结果是肯定的,因为如果 ~A()前面不加上virtual,那么deletep2时只调用了A的析构函数,而没用调用B的析构函数,从而导致内存泄漏的问题,这就是为什么在基类中的析构函数设计为虚函数。

6.协变

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

class A {};
class B : public A {};

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

class Student : public Person {
public:
	virtual B* BuyTicket()
	{
		cout << "买票-打折" << endl;
		return nullptr;
	}
};

void Func(Person* ptr)
{
	ptr->BuyTicket();
}

int main()
{
	Person ps;
	Student st;
	Func(&ps);
	Func(&st);
	return 0;
}

在这里插入图片描述

7.override和 final关键字

C++对函数的重写要求比较严格,如函数名字写错了或参数写错等都无法构成重载,而这种错误在编译期间是不会报出的,只有在运行时才会出现错误。因此在C++11中提供了override,帮助用户检测是否完成重写。如果我们不想让派生类重写这个虚函数,那么就可以用final进行修饰。

1.override

class Car {
public:
	virtual void Dirve()
	{}
};
class Benz :public Car {
public:
	virtual void Drive() override { cout << "Benz-舒适" << endl; }
};
int main()
{
	return 0;
}

在这里插入图片描述
2.final

class Car
{
public:
	virtual void Drive() final {}
};
class Benz :public Car
{
public:
	virtual void Drive() { cout << "Benz-舒适" << endl; }
};
int main()
{
	return 0;
}

在这里插入图片描述

8.重载、重写/覆盖、隐藏这三者的区别

在这里插入图片描述

🏠三、纯虚函数和抽象类的关系

在虚函数的后面写上 =0 ,则这个函数就被称为纯虚函数。纯虚函数不需要定义实现(因为要被派生类进行重写),只需要声明即可。而包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象。如果派生类继承后不重写纯虚函数,那么派生类也是抽象类。 因此纯虚函数在某种程度上强制了派生类重写虚函数,因为不重写实例化不出对象。

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

class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};

🏝️四、多态的原理

1.虚函数表指针

我们先来看一道题:
下⾯编译为32位程序的运行结果是什么()
A. 编译报错 B. 运行报错 C. 8 D. 12

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
protected:
	int _b = 1;
	char _ch = 'x';
};

int main()
{
	Base b;
	cout << sizeof(b) << endl;
	return 0;
}

根据我们之前学习的知识,我们会觉得b的大小为8个字节。但实际上程序运行结果是12字节,这是为什么呢?这就是我们所要说的虚函数表指针。
在这里插入图片描述

Base类除了_b和_ch成员,还多⼀个_vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。⼀个含有虚函数的类中都至少都有⼀个虚函数表指针,因为⼀个类所有虚函数的地址要被放到这个类对象的虚函数表中,虚函数表也简称虚表

2.多态是如何实现的

我们以买票为例来探讨多态的实现。
在这里插入图片描述

通过下图我们可以看到,当满足多态条件后,底层不再是编译时通过调用对象来确定函数的地址,而是运行时到通过指向的对象的虚表中去确定对应的虚函数的地址,这样就实现了指针或引用指向基类去调用基类的虚函数,指向派生类去调用派生类对应的虚函数。
第⼀张图,ptr指向的Person对象,调用的是Person的虚函数;第⼆张图,ptr指向的Student对象,调用的是Student的虚函数。

在这里插入图片描述
在这里插入图片描述

3.动态绑定和静态绑定

不满足多态条件(指针或者引用+调用虚函数)的函数调用是在编译时绑定,也就是编译时确定调用函数的地址,叫做静态绑定。
在这里插入图片描述

满足多态条件的函数调用是在运行时绑定,也就是在运行时到指向对象的虚函数表中找到调用函数的地址,也就做动态绑定。
在这里插入图片描述

🚀五、虚函数表

1.概念

虚函数表本质是⼀个存虚函数指针的指针数组,⼀般情况这个数组最后面放了⼀个0x00000000标记。(这个C++并没有进行规定,各个编译器自行定义的,vs系列编译器会再后面放个0x00000000标记,g++系列编译不会放)

基类对象的虚函数表中存放基类所有虚函数的地址。

派生类的虚函数表中包含基类的虚函数地址,派生类重写的虚函数地址以及派生类自己的虚函数地址三个部分。

派生类由两部分构成,分别是继承下来的基类和自己的成员,⼀般情况下,继承下来的基类中有虚函数表指针,自己就不会再生成虚函数表指针。但是要注意的是这里继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同⼀个,就像基类对象的成员和派生类对象中的基类对象成员也独立的。

2.虚函数和虚函数表两者的存储位置

虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函数的地址又被存放到了虚表中。

虚函数表的存储位置在C++标准中并没有规定,取决于不同的编译器,在vs中,虚函数表是存放在代码段(常量区)的。

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

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

相关文章

智能边缘网关深入剖析-天拓四方

随着物联网、大数据和云计算等技术的飞速发展&#xff0c;智能边缘网关作为一种新兴的技术与应用逐渐走入人们的视野。本文将从全新视角对智能边缘网关进行深入剖析&#xff0c;阐述其定义、功能、重要性及其在工业领域的应用。 一、定义 智能边缘网关是集成了数据采集、处理…

低代码开发平台系统架构概述

概述 织信低代码开发平台&#xff08;产品全称&#xff1a;织信Informat&#xff09;是一款集成了应用设计、运行与管理的综合性平台。它提供了丰富的功能模块&#xff0c;帮助用户快速构建、部署和维护应用程序。织信低代码平台通过集成丰富的功能模块&#xff0c;为用户提供…

Redhat 7,8系(复刻系列) 一键部署Oracle21c-xe rpm

Oracle21c-xe前言 无论您是开发人员、DBA、数据科学家、教育工作者,还是仅仅对数据库感兴趣,Oracle Database Express Edition (XE) 都是理想的入门方式。它是全球企业可依赖的强大的 Oracle Database,提供简单的下载、易于使用和功能齐全的体验。您可以在任何环境中使用该…

物业的帮手,中央空调分户计费系统

随着现代科技的飞速发展&#xff0c;建筑管理和能源消耗的智能化已成为不可逆转的趋势。传统按面积收费的中央空调计费模式存在诸多弊端&#xff0c;例如能源浪费、费用不透明、物业纠纷频发等问题。为了解决这些问题&#xff0c;一种能够测量和记录中央空调所消耗的能源&#…

【已解决】IDEA鼠标光标与黑块切换问题,亲测有效

前言 前两天我妹妹说她室友的idea光标变成黑块状了&#xff0c;解决不了跑来问我&#xff0c;这是刚入门开发者经常遇到的问题&#xff0c;这篇文章介绍一下这两种方式&#xff0c;方便刚入门的小伙伴儿们更清楚地了解idea&#xff0c;使用idea。 希望这篇文章能够帮助到遇到…

硬件工程师笔试面试——变压器

目录 9、变压器 9.1 基础 变压器原理图 变压器实物图 9.1.1 概念 9.1.2 变压器组成结构 9.1.3 变压器原理 9.1.4 变压器的类型 9.1.5 应用领域 9.2 相关问题 9.2.1 变压器的工作原理是什么? 9.2.2 如何选择合适的变压器类型? 9.2.3 变压器在实际应用中,如何进行…

百川智能在 AI Agent 领域的思考与探索 —— 2024 稀土开发者大会总结

引言 在 2024 年稀土开发者大会上&#xff0c;百川智能的马宝昌先生分享了百川在 AI Agent 领域的最新探索与思考&#xff0c;展示了百川如何通过大模型技术的创新推动 Agent 应用的发展。这次演讲涵盖了从 AI 基础技术、强化学习、多模态模型&#xff0c;到具体的 Agent 应用…

从数据仓库到数据中台再到数据飞轮:社交媒体的数据技术进化史

前言 大家好&#xff0c;我是在大数据方面具有一定理解的博主。今天我想分享下从数据仓库到数据中台再到数据飞轮:社交媒体的数据技术进化史&#xff0c;也是这篇文章主题。我亲眼目睹了社交媒体的快速发展&#xff0c;以及随之而来的海量数据的生成与积累。如何有效地管理和利…

安泰功率放大器在超声行业中的应用有哪些

超声技术是一种在医疗、工业、科学等领域广泛应用的非侵入性、高分辨率的检测和成像技术。功率放大器在超声领域中扮演着至关重要的角色&#xff0c;它们不仅仅是信号的增强器&#xff0c;更是推动超声技术发展的关键组件。下面西安安泰电子官网将深入介绍功率放大器在超声行业…

【Linux】基础IO认识(2)

基础IO认识&#xff08;2&#xff09; 1、补充系统调用1、1、read调用1、2、stat 2、重定向2、1、文件描述符的分配规则2、2、实现重定向(dup2) 3、缓冲区的理解3、1、缓冲区典型实例3、2、缓冲区代码形式展示 4、深化和实践利用4、1、在shell中加入重定向4、2、简单实现库的封…

Axios基本语法和前后端交互

Axios是一个js框架&#xff0c;用于发送ajax请求。 一、导入 // node中&#xff0c;使用npm安装 npm install axios // HTML中&#xff0c;使用cdn安装 <script src"https://unpkg.com/axios/dist/axios.min.js"></script> 二、基本使用 // 使用axios…

MTK平台--蓝牙驱动数据加载的过程

前言: 先看这张图可以知道架构 LinuxKernel层: bluez协议栈、uart驱动, h4协议, hci,l2cap, sco, rfcomm Library层: libbluedroid.so 等 Framework层: 实现了Headset /Handsfree 和 A2DP/AVRCP profile,但其实现方式不同Handset/Handfree是直接 在bluez的RFCOMM So…

imagen: 具有深度语言理解的逼真的文本到图像扩散模型

1. 项目主页 Imagen: Text-to-Image Diffusion Models 我们推出了 Imagen&#xff0c;这是一种文本到图像的扩散模型&#xff0c;具有前所未有的照片级真实感和深层次的语言理解能力。Imagen 建立在大型 Transformer 语言模型在文本理解方面的强大功能之上&#xff0c;并依赖于…

JVM 调优篇7 调优案例2-元空间的优化解决

一 元空间 1.1 功能概述 方法区&#xff08;Method Area&#xff09;与 Java 堆一样&#xff0c;是各个线程共享的内存区域&#xff0c;它用于存储已被虚拟机加载的类信息、常量、即时编译器编译后的代码等数据。虽然Java 虚拟机规范把方法区描述为堆的一个逻辑部分&#xf…

数据结构与算法-18算法专向(hash)

话题引入&#xff1a; 给你N&#xff08;1<N<10&#xff09;个自然数,每个数的范围为&#xff08;1~10000000000&#xff09;。现在让你以最快的速度判断某一个数是否在这N个数内&#xff0c;不得使用已经封装好的类&#xff0c;该如何实现。 A[] new int[N1]&#xff…

快来尝尝,超赞的食家巷一窝丝

一窝丝&#xff0c;这个名字听起来就充满了诗意和神秘。当你第一次见到它时&#xff0c;定会被它那精致的外形所吸引。纤细如丝&#xff0c;盘绕在一起&#xff0c;宛如一个精美的艺术品。那丝丝缕缕&#xff0c;散发着淡淡的麦香味&#xff0c;仿佛在诉说着古老的故事。 制作食…

解读 Java 经典巨著《Effective Java》90条编程法则,第5条:优先考虑依赖注入来引用资源

【前言】欢迎订阅【解读《Effective Java》】系列专栏 《Effective Java》是 Java 开发领域的经典著作&#xff0c;作者 Joshua Bloch 以丰富的经验和深入的知识&#xff0c;全面探讨了 Java 编程中的最佳实践。这本书被公认为 Java 开发者的必读经典&#xff0c;对提升编码技…

Java 中常用的排序算法

Java 中常用的排序算法有很多&#xff0c;每种算法的时间复杂度和适用场景都不同。以下是几种常见的排序算法及其 Java 实现和讲解&#xff1a; 1. 冒泡排序 (Bubble Sort) 算法思路&#xff1a; 重复地遍历数组&#xff0c;每次比较相邻两个元素。如果前一个比后一个大&…

Web接入Sonic平台之安装

问题及解决方案 1.安装python的airtest-bdd依赖时报错&#xff0c;显示无法编译psutil note: This error originates from a subprocess, and is likely not a problem with pip. ERROR: Failed building wheel for psutil Failed to build psutil ERROR: ERROR: Failed to b…

【2025】基于 SpringBoot 的电影购票系统、电影购票系统、智能电影购票系统、电影购票平台、电影购票管理、微服务电影购票系统(源码+文档+讲解)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…