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,一经查实,立即删除!

相关文章

线阵相机如何选型

线阵相机特点 1.线阵相机使用的线扫描传感器通常只有一行感光单元&#xff08;少数彩色线阵使用三行感光单元的传感器&#xff09; 2.线阵相机每次只采集一行图像&#xff1b; 3.线阵相机每次只输出一行图像&#xff1b; 4.与传统的面阵相机相比&#xff0c;面阵扫描每次采集若…

JavaScript引用数据类型

JS总共分为两种数据类型&#xff1a; 1.基本数据类型 2.引用数据类型 基本数据类型在之前的文章中待大家了解过了 今天我们就来了解一下引用数据类型&#xff1a; 首先呢&#xff0c;我们要知道引用数据类型是存储在哪里的&#xff1a;引用数据类型是存放在堆内存中的对象…

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;我的手机剪贴板需要同步到另外的两台设…

gitconfig区分工作和个人账号

用git&#xff0c;一般会有个人账号比如github的账号&#xff0c;也会有工作上的gitlab等的账号。如果不做一下区分&#xff0c;容易出现工作项目一些commit记录的是自己github的账号&#xff0c;显得很奇怪。 一般~/.gitconfig 会有git账号和邮箱配置&#xff0c;这个一般就是…

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

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

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

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

什么时候使用静态方法,面向对象编程,设计模式,php编程

静态方法在编程中有多种适用情况&#xff0c;以下是一些常见的使用场景&#xff1a; 1. **工具类或辅助函数**&#xff1a;当你需要创建一个类来提供一些与类本身的实例无关的工具方法或辅助函数时&#xff0c;静态方法是很合适的。这些方法可以直接通过类名来调用&#xff0c…

centos7安装单机模式kafka 2.12-3.5.2

一、目标 在centos7上安装kafka的单机模式,而且仅仅使用kafka一个包来安装(不单独安装zookeeper,而采用kafka自带的zookeeper)。 二、准备工作 1、下载kafka、openjdk-17 貌似新版本的最好使用OpenJDK 17 及以上版本效果才好。 Oracle的JDK官方下载页面:Java Downloa…

华为鲲鹏云认证考试内容有哪些?华为鲲鹏云认证考试报名条件

华为鲲鹏云认证考试是华为公司为了验证IT专业人士在鲲鹏计算及云计算领域的专业能力而设立的一项认证考试。以下是关于华为鲲鹏云认证考试的一些详细信息&#xff1a; 考试内容&#xff1a;华为鲲鹏云认证考试的内容主要包括理论考核和实践考核两大部分。理论考核涉及云计算、…

商业开源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 是一款高…

JS的入口函数,并讲解入口函数的作用

windowonload function window.addEventListener (“load”&#xff0c;function ()) document.addEventListener (“DOMContentLoaded”&#xff0c; function ()) 作用&#xff1a; DOMContentLoaded DOM加载完毕&#xff0c;不包含图片fash css等就可以执行&#xff0c;加…

pytorch之model.eval()、model.fuse()及model.fuse.eval()介绍

在 PyTorch 中&#xff0c;model.eval() 是用于将模型设置为评估模式的方法&#xff0c;而 model.fuse() 是用于量化模型中的融合操作的方法。下面是它们的详细介绍&#xff1a; 1. model.eval()方法介绍 当涉及到 PyTorch 中的模型评估时&#xff0c;model.eval() 是一个非常…

2.灌水..

2.灌水 - 蓝桥云课 (lanqiao.cn) 问题描述 小蓝有一个01矩阵。他打算将第一行第一列的0变为2。变化过程有传染性&#xff0c;每次2的上下左右四个相邻的位置中的0都会变成2。直到最后每个2的周围都是1或2结束 请问&#xff0c;最终矩阵中有多少个 2? 以下是小蓝的矩阵&#xf…

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

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

C语言程序10题

第81题 &#xff08;10.0分&#xff09; 难度:易 第2章 /*------------------------------------------------------- 【程序填空】 --------------------------------------------------------- 功能&#xff1a;打印出如下图案&#xff08;菱形&#xff0…

MySQL 之 数据库操作 及 表操作

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

Qt QWebSocket讲解

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