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

news2024/12/27 13:56:16

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.coloradmin.cn/o/1928908.html

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

相关文章

深入理解I/O模型

目录 一、I/O 模型简介 二、I/O 模型 2.1 同步阻塞 I/O 2.2 同步非阻塞I/O 2.3 I/O多路复用 2.4 异步I/O 2.5 信号驱动 I/O 三、总结 一、I/O 模型简介 所谓的 I/O 就是计算机内存与外部设备之间拷贝数据数据的过程。有 5 中 I/O 模型&#xff0c;分别是同步阻塞 I/O、同步…

单端、差分信号处理抗干扰能力解析

采用仪表运放对信号源进行处理&#xff0c; 信号源地上有共模干扰&#xff0c;经过差分信号处理后Vout上不会有干扰&#xff0c;差分信号可以非常好的抗共模干扰。 经过差分信号处理后&#xff0c;以单端信号输出进入ADC还是会有干扰&#xff0c;所以信号链采用差分 处理后&…

Java二十三种设计模式-适配器模式(6/23)

适配器模式&#xff1a;使不兼容的接口协同工作的桥梁 引言 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许不兼容的接口之间可以一起工作&#xff0c;通过将一个类的接口转换成客户端期望的另一个接口。 在计算机编程中&#x…

AI论文精读笔记-MAE

1. 论文基本信息 论文标题&#xff1a;Masked Autoencoders Are Scalable Vision Learners 作者&#xff1a;Kaiming He∗,† Xinlei Chen∗ Saining Xie Yanghao Li Piotr Doll ́ ar Ross Girshick 发表时间和期刊&#xff1a;19 Dec 2021; arxiv 论文链接&#xff1a;Mas…

苹果预计因Apple Intelligence引发设备升级潮

&#x1f989; AI新闻 &#x1f680; 苹果预计因Apple Intelligence引发设备升级潮 摘要&#xff1a;据彭博社报道&#xff0c;摩根士丹利将苹果列为首选股票&#xff0c;预期Apple Intelligence发布将引发创纪录的设备升级。分析师Erik Woodring 将苹果目标股价上调至273美元…

前端开发(基础)

目录 一、Web前端项目初始化 环境准备 创建项目 前端工程化配置 引入组件库 开发规范 全局通用布局 基础布局结构 全局底部栏 动态替换内容 全局顶部栏 通用路由菜单 支持多套布局 请求 请求工具库 全局自定义请求 自动生成请求代码 全局状态管理 全局权限管…

电力调度台如何助力电力指挥中心更智慧

在现代电力系统的复杂运行环境中&#xff0c;电力调度台正逐渐成为电力指挥中心实现智慧化管理的关键力量。 电力调度台具备强大的信息集成与处理能力。它能够将来自不同监测系统、传感器和数据源的海量数据汇聚一处&#xff0c;包括电力设备的实时运行状态、电力负荷的动态变化…

应急靶场(4):Windows Server 2019 - Web3

目录 一、攻击者的两个IP地址 二、隐藏用户名称 三、黑客遗留下的flag【3个】 下载好靶场&#xff08;前来挑战&#xff01;应急响应靶机训练-Web3&#xff09;并搭建好环境&#xff0c;使用帐号密码&#xff08;administrator / xj123456&#xff09;登录靶机。 一、攻击者的两…

张幼玲:心中有火,眼里有光照医路

在我们的传统社会中&#xff0c;男科医生这一职业往往被人们带着异样的眼光看待。然而&#xff0c;张幼玲却选择了这一领域&#xff0c;成为了一名专业男科医生。他以其丰富的临床经验、高超的医术和对患者的关爱&#xff0c;赢得了患者和社会的广泛赞誉。 张幼玲出生于一个中医…

ASP.NET Core----基础学习06----将所有数据在页面中显示 布局页面的使用

文章目录 1. 将数据以list的形式展示在页面中2. 布局页面的使用3. 自定义设置视图文件是否需要加载的JS 1. 将数据以list的形式展示在页面中 step1:在接口文件中添加新的方法GetAllStudents&#xff08;&#xff09; step2:在mock的数据中添加方法GetAllStudents&#xff08;&a…

7/13 - 7/15

vo.setId(rs.getLong("id"))什么意思&#xff1f; vo.setId(rs.getLong("id")); 这行代码是在Java中使用ResultSet对象&#xff08;通常用于从数据库中检索数据&#xff09;获取一个名为"id"的列&#xff0c;并将其作为long类型设置为一个对象…

Billu_b0x靶机

信息收集 使用arp-scan 生成网络接口地址来查看ip 输入命令&#xff1a; arp-scan -l 可以查看到我们的目标ip为192.168.187.153 nmap扫描端口开放 输入命令&#xff1a; nmap -min-rate 10000 -p- 192.168.187.153 可以看到开放2个端口 nmap扫描端口信息 输入命令&…

工作中项目git如何管理,冲突,push不上去如何解决

主要涉及的知识点 现在公司中一般的git仓库的管理方式是怎么样的代码为什么push不上线上仓库如何解决代码冲突 分支管理方式 git checkout -b 分支名字 是创建并切换到分支 git push origin 分支名字 推到远程仓库分支上 主流的git管理方式 共用一个仓库&#xff0c;不同…

Golang | Leetcode Golang题解之第237题删除链表中的节点

题目&#xff1a; 题解&#xff1a; func deleteNode(node *ListNode) {node.Val node.Next.Valnode.Next node.Next.Next }

解决宝塔Spring Boot项目获取不到环境变量的问题

问题描述 在使用宝塔面板管理Spring Boot项目时&#xff0c;可能会遇到代码无法获取 /etc/profile 文件中设置的Linux环境变量的问题。虽然在SSH终端中可以正常获取&#xff0c;但在通过宝塔面板启动的Spring Boot项目中&#xff0c;环境变量却无法被读取。 解决方案&#xf…

TS 入门(三):Typescript函数与对象类型

目录 前言回顾1. 函数类型a. 基本函数类型b. 可选参数和默认参数c. 剩余参数 2. 对象类型a. 基本对象类型b. 可选属性和只读属性 3. 类型别名和接口a. 类型别名b. 接口扩展 4. 类型推断和上下文类型a. 类型推断b. 上下文类型 扩展知识点&#xff1a;函数重载结语 前言 在前两章…

实验06 持续集成测试

知识点 集成测试定义 集成测试是将多个单元组合起来形成更大的单元&#xff0c;并测试它们是否能协同工作形成子系统。一种旨在暴露单元接口之间、组件/系统间交互或协同工作时所存在的缺陷的测试。 集成测试关注的问题 模块间数据传递是否正确。一个模块的功能是否影响另一…

[iOS]内存分区

[iOS]内存分区 文章目录 [iOS]内存分区五大分区栈区堆区全局区常量区代码区验证内存使用注意事项总结 函数栈堆栈溢出栈的作用 参考博客 在iOS中&#xff0c;内存主要分为栈区、堆区、全局区、常量区、代码区五大区域 还记得OC是C的超类 所以C的内存分区也是一样的 iOS系统中&a…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑频率不同响应阶段的惯量评估优化策略》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

嵌入式人工智能(6-树莓派4B按键输入控制LED)

1、按键 按键的原理都是一样&#xff0c;通过按键开关的按下导通&#xff0c;抬起断开的情况&#xff0c;GPIO引脚来检测其是否有电流流入。GPIO有input()方法&#xff0c;对于GPIO引脚检测电流&#xff0c;不能让其引脚悬空&#xff0c;否则引脚会受周边环境电磁干扰产生微弱…