C++进阶:多态(笔记)

目录

  • 1. 多态相关概念
    • 1.1 简述:多态
    • 1.2 概念汇总与补充
  • 2. 多态重写的底层原理
    • 2.1 虚函数存储的结构与位置
    • 2.2 重写覆盖

1. 多态相关概念

1.1 简述:多态

1. 什么是多态

  1. 不同的对象去做同一个行为时,得到的结果不同。反应到编程语言中,即为不同类型的对象调用同一个函数,得到的返回值不同。
class Person
{
public:virtual void BuyTicket(){cout << "全票" << endl;}
};class Children : public Person
{
public:virtual void BuyTicket(){cout << "半票" << endl;}
};void BuyTicket(Person& p)
{p.BuyTicket();
}int main()
{Person p1;Children p2;BuyTicket(p1);BuyTicket(p2);return 0;
}

2. 虚函数

  1. 虚函数:被关键字virtual修饰的函数
virtual void func1()
{//...
}

3. 构成多态的条件

  1. 父类与子类的虚函数必须构成重写关系(三同:函数名,函数参数,函数返回值相同)
  2. 必须用父类的指针或者引用去调用虚函数。

4. 多态的特殊情况

  1. 协变:父类与子类的虚函数返回值不同,也可以构成重写,只是返回值类型必须为父类/子类(不是同一父类/子类也可以)的指针或者引用。
  2. 析构函数即使函数名不同也构成重写,这时因为编译在编译时会将析构函数的名字统一处理为destructor
  3. 特殊的,父类的虚函数加virtual,子类的虚函数不加virtual也构成重写,子类的虚函数被视作实现重写(建议不要省略)。
class A
{
public:virtual void func(int a = 1){cout << "A->" << a << endl;}virtual void test(){func();}
};class B : public A
{
public:virtual void func(int a = 0){cout << "B->" << a << endl;}
};int main()
{B().test();return 0;
}

在这里插入图片描述

  1. <1> B类因为本身没有进行test函数的重写,所以调用test时会调用从A类继承而来的test。
    <2> 调用继承而来的test的时需要使用父类指针进行调用,会发生赋值兼容转换从B类中截断出A类的部分。
    <3> 调用的func函数为B类重写A类后的func函数,又因为虚函数的重写是实现重现,会直接继承父类的函数框架,只重写内部的实现。

5. 多态调用与普通调用

  1. 普通调用:根据指针/引用的类型,调用指针/引用类型的函数
  2. 多态调用:使用父类指针指向子类对象,根据指针/引用指向对象的类型,调用指向对象的函数。
  3. 即使父类与子类构成多态,但不采用多态调用的方式,也不会达到多态的效果。
  4. 普通函数会在编译时就将函数的地址写入符号表中,而重写的虚函数其地址存储在对象的虚表中,当我们使用多态调用对其进行调用时,则是在运行时从对象的虚表中获得对应虚函数的地址。

6. 补充语法

  1. 如何定义实现一个不能被继承的父类:
    <1> 使用private私有化构造函数
    <2> 使用关键字final修饰父类
class A final
{//...
};
  1. 关键字override,检查子类是否重写了父类的虚函数,如果没有,会发生报错
class Person()
{
public:virtual void func1(){//...}
};class Student : public Person
{
public:virtual void func1() override{//...}
}

7. 含有虚函数的类的大小

class Base
{
public:virtual void func1(){cout << "hello world" << endl;}
private:int _b = 1;char _c = 'a';
};
  1. 内存对齐:(VS最大对齐数为8)
    <1> int(对齐数4),大小 + 4,地址相对位置起始处:0(小于8,取自己;大于8,取8)
    <2> char(对齐数1),大小 + 1,地址相对位置起始处:4
    <3> 最后空间大小取最大对齐数的整数倍处(取8),8字节
  2. 虚函数表指针(虚表指针):指向虚函数表的指针,虚函数表内存储序函数的地址,虚函数表可以视作一个函数指针数组
  3. 含有虚函数的类内部,除开本身包含的成员变量外,还有额外包含一个虚表指针,虚表内存储着这个类所有虚函数的地址。
int main()
{Base a;return 0;
}

在这里插入图片描述

8. 抽象类

  1. 包含纯虚函数的类,这种类无法实例化出对象。
  2. 继承抽象类的子类必须要对纯虚函数进行重写,重写后子类才能够实例化出对象。
class Car
{
public:virtual void Drive() = 0{}
};class Benz : public Car
{
public:virtual void Drive(){cout << "舒适" << endl;}
};class BMW : public Car
{
public:virtual void Drive(){cout << "操控" << endl;}
};

1.2 概念汇总与补充

1. 重载,隐藏与重写

  1. 重载:会函数的重载,构成重载的函数必须在一个作用域中,且函数名相同,参数类型不同。
  2. 隐藏(覆盖):在父类与子类中的同名函数,在不同的作用域中,参数类型可以不同。
  3. 重写(重定义):在父类与子类中函数名相同,参数类型相同,返回值相同的虚函数(协变例外)。
  4. 注:分别在父类作用域与子类作用域的同名函数,不是重写就是隐藏。

2. 内联函数可以作为虚函数

  1. 想要能够作为一个虚函数的前提为必须是一个函数,拥有函数地址,可是内联函数在一般情况下不会创建栈帧,会直接在原地展开没有函数地址。
  2. 但,特殊的,内联函数在多态调用是不会展开,其也就不再具备内联属性,这里体现了内联函数的双向属性。

3. 静态成员函数与构造函数

  1. 静态成员函数没有this指针,而虚函数的调用需要通过父类指针来实现多态调用,运行时也就无法通过虚表指针进行调用,所以静态成员函数不能时虚函数。
  2. 构造函数只有在创建对象时的初始化列表中初始化对象,而虚表指针需要在编译阶段就完成初始化,所以构造函数不能是虚函数。

4. 虚函数的调用速度与抽象类

  1. 虚函数在进行普通调用时与普通函数的调用方式相同,只有在进行多态调用时,因为在运行时要通过虚表指针去搜索虚函数地址,所以会比普通函数的调用慢。
  2. 抽象类强制其子类进行虚函数重写,是一种接口继承的体现。

2. 多态重写的底层原理

2.1 虚函数存储的结构与位置

1. 虚函数的存储结构
在这里插入图片描述

class Base
{
public:virtual void func1(){cout << "func1" << endl;}virtual void func2(){cout << "func2" << endl;}virtual void func3(){cout << "func3" << endl;}private:int _b = 1;char _c = 'a';
};typedef void(*PTR)();void Print(PTR* p)
{for (int i = 0; p[i]; i++){printf("p[%d] = %p\n", i, p[i]);}p[0]();p[1]();p[2]();
}int main()
{Base* p = new Base;//相近类型才可以发生类型转换Print((PTR*)(*((int*)p)));return 0;
}

2. 虚函数与虚表指针在内存中的位置

class A
{
public:virtual func(){}
}int main()
{//栈int a = 10;//堆int* b = new int;//静态区static int c = 0;//常量区const char* str = "hello world";printf("栈:%p\n", &a);printf("堆:%p\n", b);printf("静态区:%p\n", &c);printf("常量区:%p\n", str);//函数指针typedef void (*PTR)();A d;PTR* pd = (PTR*)(*((int*)(&d)));printf("虚表指针:%p\n", pd);printf("虚函数地址:%p\n", pd[0]);return 0;
}

在这里插入图片描述
3. 补充

  1. 当子类拥有独属于自己的虚函数时,也会将此虚函数的函数指针记录至虚表中,但,监视窗口无法查看。

在这里插入图片描述

2.2 重写覆盖

class A
{
public:virtual void func1(){}
};class B : public A
{
public://重写/未重写//virtual void func1()//{}
};int main()
{A* p1 = new A;B* p2 = new B;return 0;
}

在这里插入图片描述

  1. 语法上的重写,反映到底层实现上就是函数指针的覆盖。

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

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

相关文章

Go语言HTTP服务实现GET和POST请求的同时支持

引言 在现代的Web开发中&#xff0c;HTTP服务是构建网络应用程序的基础。而支持GET和POST请求是其中最基本、最常见的功能之一。GET请求用于从服务器获取数据&#xff0c;而POST请求则用于向服务器提交数据。在Go语言中&#xff0c;通过标准库中的net/http包&#xff0c;我们可…

实景三维技术:开启自然资源管理的新篇章

随着科技的不断进步&#xff0c;实景三维技术已经在多个领域得到了广泛的应用。而在自然资源管理领域&#xff0c;实景三维技术更是发挥着越来越重要的作用。本文将介绍实景三维在自然资源管理领域的应用&#xff0c;探讨其带来的优势和变革。一、什么是实景三维技术&#xff1…

开源一款剪贴板跨设备共享工具

shigen坚持更新文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 个人IP&#xff1a;shigen 昨天遇到了一个很棘手的问题&#xff1a;我的手机剪贴板需要同步到另外的两台设…

HarmonyOS实战开发-一次开发,多端部署-音乐专辑

简介 基于自适应和响应式布局&#xff0c;实现一次开发、多端部署音乐专辑页面。 相关概念 一次开发&#xff0c;多端部署&#xff1a;一套代码工程&#xff0c;一次开发上架&#xff0c;多端按需部署。支撑开发者快速高效的开发支持多种终端设备形态的应用&#xff0c;实现对…

Chatgpt掘金之旅—有爱AI商业实战篇(二)

演示站点&#xff1a; https://ai.uaai.cn 对话模块 官方论坛&#xff1a; www.jingyuai.com 京娱AI 一、前言&#xff1a; 成为一名商业作者是一个蕴含着无限可能的职业选择。在当下数字化的时代&#xff0c;作家们有着众多的平台可以展示和推广自己的作品。无论您是对写书、文…

商业开源MES+源码+可拖拽式数据大屏

商业开源的一套超有价值的JAVA制造执行MES系统源码 带本地部署搭建教程 教你如何在本地运行运行起来。 开发环境&#xff1a;jdk11tomcatmysql8springbootmaven 需要源码&#xff0c;私信我付费获取。 一、系统概述&#xff1a; 万界星空科技免费试用MES、开源MES、商业开…

PP-YOLOE: An evolved version of YOLO

摘要 我们在之前 PP-YOLOv2 的基础上进行了优化&#xff0c;使用 无锚 范式&#xff0c;更强大的主干和颈部配备了 CSPRepResStage 。 ET-head 和动态标签分配算法 TAL 。 1 、介绍 受 YOLOX 的启发&#xff0c;我们进一步优化了之前的工作 PP-YOLOv2 。 PP-YOLOv2 是一款高…

PHP在线客服系统源码修复版

源码简介 在线客服系统网站源码https://www.888host.cn/330.html 新增消息预知&#xff0c;消息撤回&#xff0c;消息已读未读&#xff0c; 修复需要刷新才能收到消息 修复客户来源地址 修复消息提示音 修复桌面推送提醒 搭建环境 宝塔面板 &#xff0c;Nginx1.16-1.18 …

MySQL 之 数据库操作 及 表操作

&#x1f389;欢迎大家观看AUGENSTERN_dc的文章(o゜▽゜)o☆✨✨ &#x1f389;感谢各位读者在百忙之中抽出时间来垂阅我的文章&#xff0c;我会尽我所能向的大家分享我的知识和经验&#x1f4d6; &#x1f389;希望我们在一篇篇的文章中能够共同进步&#xff01;&#xff01;&…

Qt QWebSocket讲解

QWebSocket 是 Qt 框架中用于处理 WebSocket 通信的类。WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。这使得客户端和服务器之间可以进行实时交互&#xff0c;而无需频繁地建立和关闭连接。 QWebSocket 的基本使用 创建 QWebSocket 对象&#xff1a; 你可以创建一个…

【跟着CHATGPT学习硬件外设 | 01】SPI

文章目录 &#x1f680; 概念揭秘关键精华&#x1f31f; 秒懂案例生活类比实战演练 &#x1f50d; 原理与工作流程探秘步骤1&#xff1a;初始化SPI接口步骤2&#xff1a;主设备启动通信步骤3&#xff1a;主设备发送数据步骤4&#xff1a;从设备接收数据步骤5&#xff1a;从设备…

一文彻底搞懂 TSL 流程

文章目录 1. 什么是 TSL2. TSL 流程3. CA 签发流程 1. 什么是 TSL HTTPS&#xff08;Hyper Text Transfer Protocol Secure&#xff09;是基于 HTTP 协议之上的安全通信协议&#xff0c;它使用 TLS 或 SSL 加密协议来保护网络通信的安全性和隐私性。 TLS&#xff08;Transpor…

模拟游戏《幸福工厂》好玩吗?《幸福工厂》怎么在mac电脑上打开?

关于《幸福工厂》这款游戏是否好玩&#xff0c;普遍的玩家反馈和评价表明&#xff0c;《幸福工厂》&#xff08;Satisfactory&#xff09;因其深度的工厂建造模拟、自由度极高的探索以及精美的图形表现而受到许多玩家的喜爱。它允许玩家在一个开放的世界中规划并建立复杂的生产…

DeepL Pro3.1 下载地址及安装教程

DeepL Pro是DeepL公司推出的专业翻译服务。DeepL是一家专注于机器翻译和自然语言处理技术的公司&#xff0c;其翻译引擎被认为在质量和准确性方面表现优秀.DeepL Pro提供了一系列高级功能和服务&#xff0c;以满足专业用户的翻译需求。其中包括&#xff1a; 高质量翻译&#xf…

Vue3+Vite Nginx部署 跨域

打包项目 webstorm打开项目之后&#xff0c;在Terminal执行打包命令 pnpm run build:prod 复制到Nginx 打包完成之后,生成的包在根目录dist&#xff0c;把dist目录拷贝到Nginx放网站目录下&#xff1a;\nginx-1.25.2\html\divided &#xff0c;dist改名了divided 修改配置…

基于Java+SpringBoot+vue仓库管理系统设计与实现

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

归并排序---分治法

1、算法的概念 归并排序&#xff1a;是创建在归并操作上的一种有效的排序算法。算法是采用分治法的一个非常典型的应用&#xff0c;且各层分治递归可以同时进行。归并排序的思路简单&#xff0c;速度仅次于快速排序&#xff0c;为稳定排序算法&#xff0c;一般用于对总体无序&…

鸿蒙OS开发实战:【网络管理HTTP数据请求】

一、场景介绍 应用通过HTTP发起一个数据请求&#xff0c;支持常见的GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT方法。 二、 接口说明 HTTP数据请求功能主要由http模块提供。 使用该功能需要申请ohos.permission.INTERNET权限。 涉及的接口如下表&#xff0c;…

Nest安装及使用~

前提条件 请确保您的操作系统上安装了 Node.js&#xff08;版本 > 16&#xff09; &#x1f4da;要查看指南&#xff0c;请访问 https://docs.nestjs.com/ &#x1f4da;要查看中文 指南&#xff0c; 请访问 https://docs.nestjs.cn/ $ node -v v16.18.1 $ npm -v 7.x.x安…

基于YOLOV8+Pyqt5光伏太阳能电池板目标检测系统

1、YOLOV8算法 YOLOv8 是当前效果较好的目标检测 算法&#xff0c;它的核心网络来源于 DarkNet-53&#xff0c;该网络初次在 YOLOv3[11] 中被引入&#xff0c;并深受 ResNet[12] 的影响。DarkNet-53 使用了残差机制&#xff0c;并连续添加了卷积模块来加强其功能性。 这 53 层…