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

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

在这里插入图片描述

文章目录

  • ⭐一、多态的概念
  • 🎄二、多态的定义及实现
    • 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.rhkb.cn/news/426388.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

纯血鸿蒙NEXT常用的几个官方网站

一、官方文档 https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/Readme-CN.md刚入门查看最多的就是UI开发模块&#xff0c;首先要熟悉组件使用 二、官方API参考 https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/development-i…

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

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

【操作系统】二、进程管理:4.死锁(银行家算法、系统安全状态、静态分配策略、资源有序分配法)

五、死锁 文章目录 五、死锁1.产生1.1产生情况1.2产生的4个必要条件 2.处理方式2.1预防死锁2.1.1破坏互斥条件2.1.2破坏请求和保持条件2.1.3破坏不可抢占条件2.1.4破坏循环等待条件 2.2避免死锁2.2.1系统安全状态❗2.2.2银行家算法 2.3检测死锁2.4解除死锁 死锁&#xff1a;资源…

ubuntu安装wordpress(基于LNMP环境)

参考链接 Ubuntu安装LNMP 安装步骤 环境需要LNMP环境&#xff0c;如果没有安装可以参考ZATA—LNMP简单安装 在mysql中设置wordpress所用的用户名和密码 #1. 登录mysql mysql -uroot -p #2. 创建wordpress数据库 create database wordpress; #3. 创建新用户user&#xff0c;…

使用Java实现一个简单的B树

1.B树简介 B树是一个搜索树&#xff0c;数据结构可以抽象成如二叉树一样的树&#xff0c;不过它有平衡、有序、多路的特点。 平衡&#xff1a;所有叶子节点都在同一层。有序&#xff1a;任一元素的左子树都小于它&#xff0c;右子树都大于它。多路&#xff1a;B树的每个节点最多…

【Linux】文件权限与类型全解:你的文件安全指南

欢迎来到 CILMY23 的博客 &#x1f3c6;本篇主题为&#xff1a;文件权限与类型全解&#xff1a;你的文件安全指南 &#x1f3c6;个人主页&#xff1a;CILMY23-CSDN博客 &#x1f3c6;系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 | 贪心算法 | Linux | 算法专题…

激光雷达点云处理—学习随记

一、激光雷达基本概念 激光雷达&#xff08;Light Detection and Ranging&#xff0c;LiDAR&#xff09;&#xff0c;是一种发射激光&#xff08;可见光-近红外&#xff09;于被瞄准物体表面并记录反射光被信号接收器接收到的时间以测定距离的方法。激光雷达通过以下公式确定物…

基于springboot酒店管理系统

酒店管理系统 552酒店管理系统 摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;…

【MySQL】查询语句之inner、left、right、full join 的区别

前言&#xff1a; INNER JOIN 和 OUTER JOIN 是SQL中常用的两种连接方式&#xff0c;用于从两表活多表中提取相关的数据。两者区别主要在于返回的 结果集 如何处理 匹配 与 不匹配 的行。 目录 1、INNER JOIN 2、OUTER JOIN 3、总结 1、INNER JOIN 称为内连接&#xff0c;只…

【Python机器学习】NLP信息提取——命名实体与关系

我们希望计算机能够从文本中提取信息和事实&#xff0c;从而略微理解用户所说的内容。例如&#xff0c;当用户说“提醒我星期一浏览***.org网站”&#xff0c;我们希望这句话触发当天后下一个周一的日程或者提醒的操作。 要触发上述操作&#xff0c;需要知道“我”代表一种特定…

5.内容创作的未来:ChatGPT如何辅助写作(5/10)

引言 在信息爆炸的时代&#xff0c;内容创作已成为连接品牌与受众、传递信息与知识、以及塑造文化与观念的重要手段。随着数字媒体的兴起&#xff0c;内容创作的需求日益增长&#xff0c;对创作者的写作速度和质量提出了更高的要求。人工智能&#xff08;AI&#xff09;技术的…

感谢问界M9一打二十,让我们买到这么便宜的BBA

文 | AUTO芯球 作者 | 雷慢 国产豪华车&#xff0c;终于扬眉吐气了&#xff0c; 你敢信吗&#xff1f;在50万以上豪华车中&#xff0c; 现在问界M9一款车的月销量&#xff0c; 是其他前20名销量的总和&#xff01; 要知道&#xff0c;它的对手是各种宝马、奔驰、雷克萨斯的…

私有化通讯工具:安全、高效、个性化,重塑企业沟通生态

在当今数字化时代&#xff0c;即时通讯已成为企业日常运营中不可或缺的一部分。随着数据安全和隐私保护意识的日益增强&#xff0c;越来越多的企业开始寻求更加安全、可控的沟通方式。私有化聊天工具应运而生&#xff0c;以其独特的核心优势&#xff0c;为企业构建了一个安全、…

VMware vSphere 8.0 Update 3b 发布下载,新增功能概览

VMware vSphere 8.0 Update 3b 发布下载&#xff0c;新增功能概览 vSphere 8.0U3 | ESXi 8.0U3 & vCenter Server 8.0U3 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-vsphere-8-u3/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页…

【Leetcode】70. 爬楼梯

题目来源 70. 爬楼梯 题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1 阶…

webpack5 构建优化方案看这篇就够了!【Node.js进阶】

无论在面试还是内部晋升&#xff0c;webpack 构建优化方案 一直都是非常重要的部分。 webpack5构建加持 一、项目完成目标二、搭建项目1. 安装koa、koa/router &#xff08;如果已经配置可路过&#xff09;2. 创建入口文件3. 安装构建依赖4. 在项目根目录添加 .babelrc 文件5. …

一般在写SQL时需要注意哪些问题,可以提高查询的效率?

很多人写SQL按照自己喜好&#xff0c;没有规则意识&#xff0c;这对于自主查询影响不大&#xff0c;你爱怎么搞就怎么搞&#xff0c;一旦涉及到提交任务或团队共享&#xff0c;就不能乱写了&#xff0c;会浪费资源影响到开发效率&#xff0c;严重的甚至会服务器瘫痪。 提几个关…

进程的重要函数

进程的重要函数: fork函数 了解fork函数 通过调用fork()函数&#xff0c;则会产生一个新的进程。调用fork()函数的进程叫做 父进程&#xff0c;产生的新进程则为子进程。 其编码过程: 1.函数功能: 函数头文件 #include <sys/types.h> #include <unistd.h> 函数…

运用Java实现倒计时功能

这个功能其实是比较好实现的&#xff0c;一般来说java中实现倒计时有两种方法&#xff1a; 1、使用 scheduledexecutorservice创建一个可重复执行的任务&#xff0c;直到时间到&#xff1a; ScheduledExecutorService 是 Java 中一种用于安排延迟或定期任务的工具。我们可以使…

云计算第四阶段------CLOUD Day4---Day6

Cloud DAY4 项目架构图&#xff1a; 环境准备&#xff1a; 主机名称IP地址配置logstash192.168.1.27最低配置4核8G #书接上文&#xff0c;我们在华为云平台租了几台云服务器&#xff0c;这次买一台性能好的服务器&#xff0c;作为logstash软件部署的载体。 今天给小伙伴们带来…