C++技能进阶指南——多态语法剖析
前言:多态是面向对象的三大特性之一。顾名思义, 多态就是多种状态。 那么是什么的多种状态呢? 这里的可能有很多。比如我们去买火车票, 有普通票, 学生票; 又比如我们去旅游, 有儿童票, 有成人票等等。 这些都是多态的例子。 具体转化为我们的编程思想就是:让不同类型的对象去完成相同的事, 这就是多态。
本篇内容主要讲述多态, 多为语法方面的知识点。 适合已经学完继承的友友们观看。
目录
一、多态的相关概念
1.1虚函数
1.2虚函数的重写
1.3虚函数重写的两个例外
1.4override 和 final 的使用
二、重载、重写、隐藏(重定义)的区别
三、如何构成多态
四、抽象类
五、普通继承和接口继承
六、静态绑定和动态绑定
具体什么是多态在前言中已经提到, 正文部分不做赘述。
一、多态的相关概念
1.1虚函数
被virtual关键字修饰的成员函数叫做虚函数。 例如:
//A位基类
class A
{
public:virtual void func() //定义一个虚函数{cout << "Afunc()" << endl;}
};
需要注意的是, 对于构造函数和析构函数来说。 析构函数可以是虚函数, 但是构造函数不可以是虚函数。
具体原因如下:(建议看完整篇文章和总结虚函数表机制——c++多态底层原理-CSDN博客 之后再来看下面这段解释):
首先:通过之前的学习, 我们知道了, 虚函数的地址是存在虚函数表里面的。 想要调用对应的虚函数, 我们需要先去虚函数表中寻找对应虚函数的地址。 但是虚函数表是在构造函数的初始化列表初始化的。如果构造函数是虚函数, 那么调用构造函数的时候就找不到。 所以构造函数没办法是虚函数。
1.2虚函数的重写
虚函数的重写就是: 在派生类当中, 有一个和基类中某一个虚函数函数头的虚函数(函数头就是:函数的返回值, 函数名, 函数的参数列表)。 这个时候就会构成虚函数的重写, 即 子类重写了基类的虚函数。
//A位基类
class A
{
public:virtual void func() {cout << "Afunc()" << endl;}
};//B类继承A类
class B : public A
{
public://重写A类的func函数virtual void func() //注意, 这里的virtual可以不写, 因为编译器默认这里是加了virtual的{cout << "Bfunc()" << endl;}
};
需要注意的是, 上图中派生类的func可以不加virtual, 因为基类的func是虚函数, 编译器会默认派生类中和他函数头相同的函数也是虚函数。
1.3虚函数重写的两个例外
协变:派生类在重写基类的虚函数的时候, 与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用, 派生类虚函数返回派生类对象的指针或者引用的时候, 成为协变。
//A位基类
class A
{
public:virtual A* func(){cout << "Afunc()" << endl;}
};//B类继承A类
class B : public A
{
public://重写A类的func函数virtual B* func(){cout << "Bfunc()" << endl;}
};//C类继承A类
class C : public A
{virtual C* func(){cout << "Cfunc" << endl;}
};
析构函数的重写: 基类析构函数如果加了virtual, 那么说明基类的析构函数为虚函数。 这个时候如果派生类的析构函数也就变成了虚函数。 那么成不成为虚函数对于析构函数来说有什么不同呢?
首先我们需要知道的是, 在一个普通的类之中, 编译器其实将析构函数统一处理成为了destructor。
然后, 对于一个派生类来说, 如果它的析构函数不是虚函数。 当我们使用父类的指针构成多态时, 只会析构派生类的一部分:
//A位基类
class A
{
public:virtual A* func(){cout << "Afunc()" << endl;}//其他动态内存分配的空间//int* ...//double* ...
};//B类继承A类
class B : public A
{
public://重写A类的func函数virtual B* func(){cout << "Bfunc()" << endl;}//其他动态内存分配的空间//int* ...//double* ...
};void test_func(A* p)
{p->func();
}int main()
{C c;A* p = nullptr;p = &c;delete p;return 0;
}
如上图, 假如delete p, 那么就只能释放属于C类自己的那一部分。那么属于A类的那一部分将得不到释放。
但是, 如果我们对A类的析构函数使用虚函数。 那么派生类的析构函数也变成了虚函数, 这个时候如果再形成多态。delete p就能将A类和C类都释放掉。
//A位基类
class A
{
public:virtual A* func(){cout << "Afunc()" << endl;}virtual ~A() {}
};//B类继承A类
class B : public A
{
public://重写A类的func函数virtual B* func(){cout << "Bfunc()" << endl;}virtual ~B(){}
};
1.4override 和 final 的使用
先谈override, override是用来检验某个虚函数是否构成了重写。如果没有构成重写, 那么编译器就会报错。
如下为构成重写:
//A位基类
class A
{
public:virtual void func(){cout << "Afunc()" << endl;}};
//B类继承A类
class B : public A
{
public://重写A类的func函数virtual void func() override{cout << "Bfunc()" << endl;}};
如下为没有构成重写:
//A位基类
class A
{
public:void func(){cout << "Afunc()" << endl;}
};
//B类继承A类
class B : public A
{
public://重写A类的func函数virtual void func() override{cout << "Bfunc()" << endl;}};
二、重载、重写、隐藏(重定义)的区别
- 重载: 函数处于相同作用域内, 并且函数的函数名相同, 参数不同。
- 重写: 函数分别处于基类和派生类中,并且都是虚函数, 并且有相同的函数头
- 隐藏: 继承体系中函数分别处在基类和派生类的作用与之中, 不是虚函数,并且都具有相同的函数头
三、如何构成多态
要形成多态有两个条件:
- 一、虚函数的重写。
- 二、父类的指针指向子类,或者父类的引用引用子类对象。
如下为一个多态的实例:
//A位基类 class A { public:virtual void func() {cout << "Afunc()" << endl;} };//B类继承A类 class B : public A { public:virtual void func() {cout << "Bfunc()" << endl;} };//C类继承A类 class C : public A {virtual void func() {cout << "Cfunc" << endl;} };int main() {C c;B b;A* p = nullptr;p = &c;p->func();p = &b;p->func();return 0; }
在这串代码中, B类和C类都是A类的派生类。 他们都有对A类中的虚函数func进行重写, 满足条件一。
然后基类的指针p先是指向了C类的对象。 又指向了B类的对象。 构成了父类的指针指向子类, 满足条件二。
所以, 这就是一个多态。
其实, 多态的应用场景多为这样:
//A位基类
class A
{
public:virtual void func() {cout << "Afunc()" << endl;}
};//B类继承A类
class B : public A
{
public:virtual void func() {cout << "Bfunc()" << endl;}
};//C类继承A类
class C : public A
{virtual void func() {cout << "Cfunc" << endl;}
};void test_func(A* p)
{p->func();
}int main()
{C c;B b;test_func(&b);test_func(&c);return 0;
}
这样, 通过传送不同类型的对象给test_func函数, 就能构成多态。
四、抽象类
如果一个虚函数后面加上 =0, 那么这个虚函数就是纯虚函数, 并且包含这个纯虚函数的类叫做抽象类。
抽象类不能实例化对象。
//A位基类
class A
{
public:virtual void func() = 0;
};int main()
{A a;return 0;
}
但是A的派生类如果重写了纯虚函数, 那么就可以这个派生类就可以实例化处对象。
但是如果A的派生类没有重写纯虚函数, 那么这个派生类同样不能实例化处对象。
//A位基类
class A
{
public:virtual void func() = 0;
};//B类继承A类
class B : public A
{
public://重写A类的func函数
};int main()
{B b;return 0;
}
五、普通继承和接口继承
普通继承:在继承体系中, 派生类继承了基类的函数, 能够直接使用的是普通继承, 这类继承继承的是基类函数的实现。
接口继承:如果继承了基类的虚函数, 并且重写实现了多态。 那么就是一种接口继承, 多态的体系是一种接口的继承, 具体的函数实现是由派生类自己实现的。
六、静态绑定和动态绑定
静态绑定: 静态绑定又被成为前期绑定, 当程序在编译的时候确定的要调用的函数, 确定了程序要执行的行为, 这个过程成为静态多态。 比如我们使用的函数重载就是静态的多态。
动态绑定: 动态绑定又被成为后期绑定, 当程序在编译之后也就是运行期间根据不同的对象调用不同的函数。 这个过程叫做动态多态, 也就是多态。
------------------------------------------------------
ps: 本篇内容没有讲解多态的原理, 因为多态的原理其实就是虚函数表。 而虚函数表的详细讲解博主之前已经写过一篇: 总结虚函数表机制——c++多态底层原理-CSDN博客 。
在这篇文章中, 博主用自己的理解讲解的虚函数表的机制与实现。 写的不甚严谨, 但是里面的结论却是博主通过调试一步一步验证的来的。感兴趣的友友们可以看一下。
后续补带有虚函数的类的内存大小的计算(暂时有点模糊, 先不写, 而且最近考试比较多。可能要等暑假才能补上这一板块)。
相关文章:

C++技能进阶指南——多态语法剖析
前言:多态是面向对象的三大特性之一。顾名思义, 多态就是多种状态。 那么是什么的多种状态呢? 这里的可能有很多。比如我们去买火车票, 有普通票, 学生票; 又比如我们去旅游, 有儿童票ÿ…...

Linux内存管理--系列文章肆
一、引子 上篇文章介绍了目标文件,也就是讲到编译过程中的汇编这个阶段。本篇要讲目标文件怎么变成一个可执行文件的,介绍编译过程中的链接。 链接主要分为两种,静态链接和动态链接。它们本质上的区别,是在程序的编译和运行过程中…...

kali下载zsteg和stegpy
1.kali下载zsteg 从 GitHub 上克隆zsteg到kali git clone https://github.com/zed-0xff/zsteg 切换目录 cd zsteg 用于安装名为 zsteg 的 Ruby Gem 包 gem install zsteg 2.kali下载stegpy 下载网站内的stegpy-master压缩包GitCode - 开发者的代码家园 并拉到kali中 切换到s…...

前端面试题日常练-day34 【面试题】
题目 希望这些选择题能够帮助您进行前端面试的准备,答案在文末。 1. jQuery中,以下哪个选项用于筛选出第一个匹配的元素? a) first() b) get(0) c) eq(0) d) find(":first") 2. 在jQuery中,以下哪个选项用于在元素上…...

网站笔记:huggingface model memory calculator
Model Memory Utility - a Hugging Face Space by hf-accelerate 这个工具可以计算在 Hugging Face Hub上托管的大型模型训练和执行推理时所需的vRAM内存量。模型所需的最低推荐vRAM内存量表示为“最大层”的大小,模型的训练大约是其大小的4倍(针对Adam…...

SpringBoot2.0.x旧版集成Swagger UI报错Unable to infer base url...解决办法
一、问题描述 1.1项目背景 SpringBoot2.0.9的旧版项目维护开发,集成Swagger-ui2.9.2无法访问的问题。不用想啊,这种老项目是各种过滤器拦截器的配置,访问不到,肯定是它们在作妖。懂得都懂啊,这里交给大家一个排错的办…...

软件项目详细设计说明书实际项目参考(word原件下载及全套软件资料包)
系统详细设计说明书案例(直接套用) 1.系统总体设计 2.性能设计 3.系统功能模块详细设计 4.数据库设计 5.接口设计 6.系统出错处理设计 7.系统处理规定 软件开发全文档下载(下面链接或者本文末个人名片直接获取):软件开发全套资料-…...

电脑文件qt5core.dll如何修复?如何快速的解决qt5core.dll丢失问题
软件应用程序依赖于各种复杂的文件系统以保证其顺畅运行。这些文件中,动态链接库(Dynamic Link Library,简称DLL)是Windows操作系统中实现多种功能的关键组件之一。然而,DLL文件出现问题是Windows用户可能面临的常见挑…...

USART串口通信(stm32)
一、串口通信 通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统 通信协议:制定通信的规则,通信双方按照协议规则进行数据收发 STM32F103C8T6 USART资源: USART1、 USART2、 USART3 自带波特率发生器&…...

快速分析变量间关系(Boruta+SHAP+RCS)的 APP(streamlit)
快速分析变量间关系(BorutaSHAPRCS)的 APP(streamlit) 以下情况下,你需要这个快速分析的APP: 正式分析之前的预分析,有助于确定分析的方向和重点变量;收集变量过程中,监测收集的变量…...

解决docker中container运行闪退终止的问题
在运行bindmount-test时,点击完运行按钮后闪退结束运行。 第一步查看log日志: 2024-05-18 23:46:18 Error: Cannot find module /app/nodemon 2024-05-18 23:46:18 at Function.Module._resolveFilename (internal/modules/cjs/loader.js:668:15) …...

Redis 性能管理
一、Redis 性能管理 #查看Redis内存使用 172.168.1.11:6379> info memory 1. 内存碎片率 操作系统分配的内存值 used_memory_rss 除以 Redis 使用的内存总量值 used_memory 计算得出。内存值 used_memory_rss 表示该进程所占物理内存的大小,即为操作系统分配给…...

节水“云”科普丨北京昌平VR节水云展馆精彩上线
2024年5月15日上午,由北京昌平区水务局主办的“推进城市节水,建设美丽昌平——2024年全国城市节约用水宣传周暨‘坚持节水优先 树立节水标杆’昌平节水在行动主题实践活动”隆重举办,活动期间,昌平区水务局应用VR虚拟现实技术创新…...

linux的系统调用open, read函数(文件编程)使用demo
1.引言 为了学习linux系统下的app开发,记载了学习文件编程的笔记 2.open函数 功能 打开一个文件 头文件 #include<sys/stat.h> #include<fcntl.h> 函数形式 int open(const char* pathname, int flags, mode_t mode); 返回值 如果调用成功,…...

C语言基础——循环(2)+关机程序
欢迎点赞支持 个人主页:励志不掉头发的内向程序员; 专栏主页:C语言基础; 文章目录 目录 前言 一、for循环的补充 二、循环的嵌套 1、嵌套的介绍 1.1 练习: 题目解析: 优化: 三、goto语句 1、go…...

cnVcXsrv 21.1.13.1—VcXsrv 21.1.13中文版本简单说明~~
对于VcXsrv的使用目的和用途相信大家都很了解。前不久VcXsrv做了更新,并且将项目托管到github上了。链接如下: VcXsrv: Windows X-server based on the xorg git sourceshttps://github.com/marchaesen/vcxsrv也可以简单查看如下链接: VcXs…...

心链2---前端开发(整合路由,搜索页面,用户信息页开发)
心链——伙伴匹配系统 接口调试 说书人📖:上回书说到用了两种方法查询标签1.SQL查询,2.内存查询;两种查询效率是部分上下,打的是难解难分,是时大地皴裂,天色聚变,老祖斟酌再三最后决…...

wordpress主题模板兔Modown 9.1开心版附送erphpdown v17.1插件
Modown 9.1开心版是一款模板兔开发的wordpress主题可,持续更新多年,优秀的资源下载类主题该模板基于Erphpdown,可以销售软件、视频教程、文章等等,通过主题和插件结合可以实现付费下载、付费阅读等功能,配合模板兔的一…...

openai api的初次尝试
不懂已经不去百度了,现在直接问chatgpt就解决绝大多数问题了。 OpenAI API目前还没有官方支持的npm库,但是您可以使用现有的第三方npm库进行OpenAI API的访问和使用。这里提供一个npm库 openai-node 的安装和使用方法: 在命令行或终端中使用…...

Distributed Transactions Mit 6.824
Topic1:distributed transactions concurrency control atomic commit 传统计划:事务 程序员标记代码序列的开始/结束作为事务。 事务示例 x 和 y 是银行余额——数据库表中的记录。x 和 y 位于不同的服务器上(可能在不同的银行&#x…...

Redis可视化工具:Another Redis Desktop Manager下载安装使用
1.Github下载 github下载地址: Releases qishibo/AnotherRedisDesktopManager GitHub 2. 安装 直接双击exe文件进行安装 3. 连接Redis服务 先启动Redis服务,具体启动过程可参考: Windows安装并启动Redis服务端(zip包)…...

Parquet文件格式详解(含行、列式存储区别)
Parquet文件格式详解 Parquet 是一种列式存储格式,旨在高效地存储和处理大规模数据集。它被设计用于在大数据生态系统中进行数据存储和分析,如 Apache Hadoop 和 Apache Spark。 行式存储 vs 列式存储 在了解 Parquet 文件格式之前,先来对…...

一文了解https为什么是安全的
目录 前言一、https和http二、http为什么不安全?2.1 http的工作原理2.2 http的明文传输 三、https3.1 加密3.2 身份验证 四、总结 前言 目前绝大多数网站都已经切换到了https,切换的原因很简单,因为它更安全,https未来会完全取代…...

[‘column‘]和[:,‘column‘]的区别
之前,关于numpy和pandas的操作一直不熟悉,对于获取数据中的行,列一直混淆。 df[column] df[column]是 Pandas DataFrame 切片的常用语法,用于选择名为 column 的单个列。它返回一个 Pandas Series 对象。 df.loc[:,column] df[:,…...

icloud如何高效利用
iCloud是Apple提供的一项云存储和云计算服务,能够帮助用户在不同的Apple设备之间同步和共享数据。要高效利用iCloud,可以参考以下几个方面: 自动备份:确保所有重要的Apple设备都开启了iCloud备份功能,这样可以自动将设…...

k8s二进制安装与部署
目录 一、实验目的 二、实验环境 三、实验步骤 3.1 操作系统初始化配置 3.2 部署 docker引擎 3.3 部署 etcd 集群 3.3.1 在 master01 节点上操作 3.3.2 在 node01 节点上操作 3.3.3 在 node02 节点上操作 3.4 部署 Master 组件 3.4.1 在 mast…...

驱动编译报error: negative width in bit-field ‘<anonymous>’错误
错误如下图所示: 代码如下: 问题点:module_param的其他用户的权限参数上。 在Linux中,文件权限由读(r)、写(w)、执行(x)权限组成,分别对应数值4、2、1。 第一位0是占位符,在这里没有意义,因为…...

Go语言的命名规范是怎样的?
文章目录 Go语言的命名规范详解一、标识符命名规范示例代码 二、包名命名规范示例代码 三、变量命名规范示例代码 四、常量命名规范示例代码 五、函数命名规范示例代码 总结 Go语言的命名规范详解 在Go语言中,代码的命名规范对于项目的可读性、可维护性和可扩展性至…...

Vue3骨架屏(Skeleton)
效果如下图:在线预览 APIs 参数说明类型默认值必传animated是否展示动画效果booleantruefalsebutton是否使用按钮占位图boolean | SkeletonButtonPropsfalsefalseavatar是否显示头像占位图boolean | SkeletonAvatarPropsfalsefalseinput是否使用输入框占位图boolea…...

【文末附gpt升级方案】亚马逊与Hugging Face合作:定制芯片低成本运行AI模型的创新探索
亚马逊与Hugging Face合作:定制芯片低成本运行AI模型的创新探索 摘要 本文探讨了亚马逊云部门与人工智能初创公司Hugging Face的合作,旨在通过定制计算芯片Inferentia2在亚马逊网络服务(AWS)上更低成本地运行数千个AI模型。文章首…...