C++相关概念和易错语法(22)(final、纯虚函数、继承多态难点)

1.final

final在继承和多态中都可以使用,在继承中是指不想将自己被继承,在多态中是指不想该函数被重写,比较简单,下面是一些使用例子。

2.纯虚函数

当我们需要抽象一个类的时候,我们就需要用到纯虚函数。所谓抽象的类是指高度概括的,需要针对不同事物有不同处理的。如植物是一种抽象的类,而像苹果、香蕉就是具象的,单独讨论植物太过庞大,没有太大意义,因此我们的重心放在由植物具象出来的苹果,我们可以具体讨论它的成分、营养价值等。理解了这个例子,就能理解为什么有抽象类,纯虚函数的存在了

这就是一个纯虚函数,就是在虚函数后面加上 = 0,它对应的类就叫抽象类。注意,只要有一个纯虚函数,这个类就叫抽象类,抽象类不能被实例化,就算你不打算用这个纯虚函数。唯一能做的就是调用这个类里面的static成员,因为它们不需要实例化就能调用

这么做的意义就在于纯虚函数对应的类本身就高度抽象,实例化它没有意义。但我们可以讨论将它具象化的事物,这就要用到虚函数的重写功能。我们可以理解,纯虚函数存在的意义是依赖于虚函数的性质存在的,这里需要我们深刻思考。

3.继承、多态难点

继承、多态的用法、意义几乎讲的差不多了,绝大多数情况下已经够用了,只不过在极少数情况下仍有一些坑。

(1)多态调用重写的函数

先看下面的代码,想想结果是什么


#include <iostream>
using namespace std;class A
{
public:virtual void test(int a = 0){}
};class B final : public A
{
public:void test(int a){cout << a << endl;}
};int main()
{A* a = new B;a->test();return 0;
}

不少人会想,这难道不报错吗?但结果是

我们需要知道,当构成多态和重写时,调用函数是以父类声明+子类定义进行的,对于三个类及以上都是如此,这个父类指的是构成多态的父类

我们也可以进一步理解为什么只需要父类写virtual,子类可以不写,因为子类的函数声明根本没有意义(在多态中),写不写都是以父类的声明为标准。但是在多态语法以外就不会出现这种反直觉处理情况了。


(2)继承调用父类函数时this的类型变化

先看看下面的代码,想想test2的隐含的this指针是B*还是A*


#include <iostream>
using namespace std;class A
{
public:virtual void test(int a = 0){}	void test2(){test();}
};class B final : public A
{
public:void test(int a = 1){cout << a << endl;}
};int main()
{B* a = new B;a->test2();return 0;
}

既然是B*调用函数,那理所应当应该是B*为形参来接受啊,但实际不是这么理解的。

当子类去调用父类的成员函数时,隐含的指针类型始终是父类的。要理解这里,我们假设这个指针的类型是子类的,那如果子类又写了一个一模一样的函数构成隐藏,那么就会因为参数和假设的函数完全相同而报错,所以是行不通的。

当子类调用父类时,this指针会发生一次赋值兼容转换,这里是从B*赋值兼容转换为A*,赋值兼容转换为指针只会影响访问的方式,指针的值,指向的内容都不会改变。但学了多态之后,我们是否可以将这种特性和多态的形成条件结合起来呢?上面这段代码就是如此。

结合上一个易错点,这段代码的最终结果是

(3)多态访问限制的特殊处理

先看看下面的代码,看看是否能够正常访问


#include <iostream>
using namespace std;class A
{
public:virtual void Test(){cout << "A" << endl;}
};class B : public A
{
private:void Test(){cout << "B" << endl;}
};int main()
{A* p = new B;p->Test();return 0;
}

很多人以为p的类型是A*,A访问不了B,但其实程序运行没有问题

我们要理解访问限定符限制的是什么,是防止其它类调用private的函数,这里p是一个指针,本身就指向B对象的空间,只不过访问方式按A进行。由于符合多态的条件,就按虚函数表进行访问。那么问题在于:B会不会阻止呢?

我们先看看什么情况是会阻止的

我们发现无论在A还是在main函数中,都没有办法调用B中的private成员,这也符合我们之前的预期。但是为什么A* p = new B;  p->Test();这种操作就可以呢?

事实上,这是多态中的特殊处理,当我们用父类的指针或引用来访问子类的虚函数时,是会以父类的访问限定符为标准的。子类的限制不会起到作用。同理,就算子时public,父是private,那么就无法访问

一般建议都设为public

4.动静态绑定

动静态绑定都是为了定位一个函数,从反汇编的角度上讲就是确定call的对应的地址是什么,只不过两者的方式有一定的区别。

(1)动态绑定:多态调用函数的核心时动态绑定,也叫运行时绑定。也就是借助虚函数表,在这个函数指针数组中确定函数的地址。
(2)静态绑定:我们平时写的函数都可以认为是静态绑定(包括函数重载、普通函数、模板函数),函数如果声明定义在一起就在编译后进符号表,如果声明定义分离在两个文件则在链接时进符号表,运行时是根据符号表来查找函数。

在多态中,在满足动态绑定的情况下我们指定类域调用函数那就自动转为静态绑定,就失去了多态的特性。


#include <iostream>
using namespace std;class A
{
public:virtual void Test(){cout << "A" << endl;}
};class B final : public A
{
public:void Test(){cout << "B" << endl;}
};int main()
{A* p = new B;p->Test();p->A::Test();return 0;
}

运行结果

5.继承、多态的一些知识点和处理技巧

(1)多用const修饰函数,保证匿名对象传参可以调用函数

(2)函数第一句的指令理解为函数的地址,成员函数要打印它们的地址函数名前要加&,其余函数函数名就是它的地址(&可加可不加,但成员函数一定要加)

(3)cout打印地址很麻烦,char*不会打印地址,会按字符串去打印,这跟流插入的重载有关。有几个关于函数指针的重载会导致出现bug,打印地址很受阻,最好使用printf

(4)关联性强的类型之间支持隐式类型转换,如整型家族+double(内置类型)、指针之间,有的支持强转,如int和int*。

关联性弱的自定义类型,想取头地址,可以使用*((int*)&Base),虚函数表的地址就在类的开头

(5)只有virtual修饰的成员函数才能叫作虚函数,而像static修饰的成员函数、全局函数都不能定义为虚函数,全局定义的虚函数没有意义,static修饰的成员函数不属于对象,就算加了virtual,也进不了虚函数表,没有意义。

(6)virtual修饰的成员函数声明定义分离时定义处不写virtual

(7)友元不是成员函数,所以不能用virtual修饰

(8)多继承可能有多张虚函数表,按继承顺序排序,但单继承对应的就只有一张虚函数表,如果多继承后自己又写了虚函数,则默认放在第一张虚函数表后面

(9)如果不重写虚函数,那共用同一个函数,如果所有的函数都不重写,两个类存的函数的地址都相同,但是这对应两张虚函数表,开辟的是不同的空间

(10)虚函数表是在编译期间就形成了。而多态是动态绑定(运行时绑定/晚期绑定),是因为编译时编译器只负责检查语法错误,而不负责读取内容,只有运行起来才知道函数调用的地址。

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

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

相关文章

如何设计统计量及相关假设检验

一、如何设置H0和H1假设 谁做H0&#xff0c;谁做H1&#xff0c;在统计学的假设检验里是有约定俗成的规定的。即&#xff1a;status quo&#xff08;默认/现状&#xff09;是H0&#xff0c;而新观点或试图challenge现状的是H1。H1也叫research hypothesis&#xff0c;所以我们做…

【多个Python版本存在,使用pip+不同版本安装库时,windows弹出打开方式窗口的解决方法】

问题描述 电脑上存在python3.9&#xff0c;3.10&#xff0c;3.11&#xff0c;安装顺序也是先安装3.9&#xff0c;然后3.10&#xff0c;最后3.11&#xff0c;那么直接使用pip安装&#xff0c;会装在3.11的位置&#xff0c;经过搜索可以通过pip版本&#xff0c;比如pip3.9 insta…

如何在勒索软件攻击中幸存下来:最佳备份实践、勒索拦截方案

无论身处什么业务或行业&#xff0c;数据都是您业务的关键资产。没有针对数据进行安全可靠的备份保护&#xff0c;您将会受到许多“可能性”的威胁&#xff0c;无论数据丢失是由于在键盘上洒了饮料还是遭受到了勒索软件的攻击。 为了确保业务不被中断&#xff0c;企业数据不会…

Python: 初识Python

文章目录 1. Python的背景知识1.1 Python是咋来的?1.2 Python的特点1.3 Python能干啥?1.4 Python的缺点 2. 搭建Python环境2.1 安装Python2.2 安装PyCharm2.3 用pycharm编写python程序 1. Python的背景知识 1.1 Python是咋来的? 由Guido van Rossum于1989年圣诞节为打发无…

一个用于管理多个 Node.js 版本的安装和切换开源工具

大家好&#xff0c;今天给大家分享一个用于管理多个Node.js版本的工具 NVM&#xff08;Node Version Manager&#xff09;&#xff0c;它允许开发者在同一台机器上安装和使用不同版本的Node.js&#xff0c;解决了版本兼容性问题&#xff0c;为开发者提供了极大的便利。 在开发环…

路网双线合并单线——ArcGISpro 解决方法

路网双线合并成单线是一个在地图制作、交通规划以及GIS分析中常见的需求。双线路网定义&#xff1a;具有不同流向、不同平面结构的道路。此外&#xff0c;车道数较多的道路&#xff08;例如&#xff0c;双黄实线车道数大于4的道路&#xff09;也可以视为双线路网&#xff0c;本…

iPhone 如何修改锁屏密码?修改密码的具体步骤总结

修改 iPhone 锁屏密码 当你还记得当前设置的锁屏密码时&#xff0c;想要修改密码就非常的简单了&#xff0c;只需要简单的点几下就可以重新设置新密码&#xff0c;下面是具体的操作步骤&#xff1a; 首先我们进入设置应用程序&#xff0c;然后找到“面容 ID 与密码”。 然后需…

(01)Unity使用在线AI大模型(使用百度千帆服务)

目录 一、概要 二、环境说明 三、申请百度千帆Key 四、使用千帆大模型 四、给大模型套壳 一、概要 在Unity中使用在线大模型分为两篇发布&#xff0c;此篇文档为在Python中使用千帆大模型&#xff0c;整体实现逻辑是&#xff1a;在Python中接入大模型—>发布为可传参的…

护眼台灯的功能作用有哪些?深挖台灯护眼是真的吗

随着现代生活方式的改变&#xff0c;孩子们面临着越来越多的视力挑战。在近视学生中&#xff0c;近10%为高度近视&#xff0c;且占比随年级升高而增长。幼儿园6岁儿童中有1.5%为高度近视&#xff0c;而高中阶段则达到了17.6%。为了守护孩子们的视力健康&#xff0c;在科技飞速发…

无符号数和有符号数的转换

1、有符号数转换成无符号数 1.1 例一 首先&#xff0c;我们需要清楚 C语言中负数是以补码的形式进行存储的。 示例&#xff1a;负数-1&#xff0c; &#xff08;此处&#xff0c;假设是8位二进制表示&#xff09; 对应正数的原码&#xff1a;0000 0001&#xff1b;取反&…

通俗易懂多图透彻讲解二叉树的遍历--前序, 中序和后序

二叉树的遍历是一个数据结构中经常会遇到的知识点, 具体又分为前序, 中序和后序三种. 什么是树? 先来理解一下什么是树, 从一个我们相对熟悉的家谱树(Family Tree)说起吧. 家族的根是爷爷, 然后生了两个娃, 大伯和你爸爸. 继续往下, 有堂哥堂姐, 还有你以及你妹, 等等. 一个…

简化流程,强化协作——揭秘可道云TeamOS文档审批的实用魅力

在团队协作的过程中&#xff0c;文档审批是确保信息安全和流程规范的重要环节。然而&#xff0c;传统的文档审批流程往往繁琐且僵化&#xff0c;难以满足团队快速响应和灵活协作的需求。 可道云teamOS的文档审批功能&#xff0c;以其独特的灵活性和便捷性&#xff0c;为团队带…

java——Junit单元测试

测试分类 黑盒测试&#xff1a;不输入代码&#xff0c;给输入值&#xff0c;看程序能够给出期望的值。 白盒测试&#xff1a;写代码&#xff0c;关注程序具体执行流程。 JUnit单元测试 一个测试框架&#xff0c;供java开发人员编写单元测试。 是程序员测试&#xff0c;即白…

PBT激光穿透率测量仪

在现代材料科学与工业制造领域&#xff0c;激光技术以其高精度、高效率和非接触性等特点&#xff0c;成为了不可或缺的测量与加工手段。其中&#xff0c;PBT&#xff08;聚对苯二甲酸丁二醇酯&#xff09;作为一种重要的热塑性工程塑料&#xff0c;因其优异的机械性能、耐热性和…

嵌入式全栈设计思路:STM32G4+ChibiOS+FreeRTOS+PID控制+PFC算法构建高效智能电源管理系统(附代码示例)

智能电源管理系统是一个基于STM32G4微控制器的高性能数字电源控制解决方案。本项目旨在设计一个功能全面、高效稳定的电源管理系统,可广泛应用于工业控制、新能源、通信设备等领域。 1.1 系统主要特点 高精度数字电源控制&#xff1a;利用STM32G4的高性能ADC和定时器,实现精确…

HTML5+CSS3小实例:纯CSS实现奥运五环

实例:纯CSS实现奥运五环 技术栈:HTML+CSS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-sca…

网页数据抓取:融合BeautifulSoup和Scrapy的高级爬虫技术

网页数据抓取&#xff1a;融合BeautifulSoup和Scrapy的高级爬虫技术 在当今的大数据时代&#xff0c;网络爬虫技术已经成为获取信息的重要手段之一。Python凭借其强大的库支持&#xff0c;成为了进行网页数据抓取的首选语言。在众多的爬虫库中&#xff0c;BeautifulSoup和Scrap…

在Android Jetpack Compose中实现夜间模式

在Android Jetpack Compose中实现夜间模式 随着用户对夜间模式需求的增加,Android开发者需要掌握如何在应用中实现这一功能。Jetpack Compose作为现代Android UI工具包,提供了简便且灵活的方式来实现夜间模式。本文将详细介绍如何在Jetpack Compose中实现夜间模式,包括配置…

Linux系统之玩转fortune命令

Linux系统之好玩的fortune命令 一、fortune命令介绍1.1 fortune简介1.2 fortune中英文 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本 四、fortune英文版的使用4.1 安装fortune英文版4.2 命令帮助4.3 fortu…