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.mfbz.cn/a/800733.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【微服务】Spring Cloud Config解决的问题和案例

文章目录 强烈推荐引言解决问题1. 配置管理的集中化2. 配置的版本控制3. 环境特定配置4. 配置的动态刷新5. 安全管理敏感数据6. 配置的一致性 组件1. **配置服务器&#xff08;Config Server&#xff09;**2. **配置客户端&#xff08;Config Client&#xff09;** 配置示例配置…

电脑数据恢复软件哪个好?这六款软件轻松恢复数据

随着电脑使用的日益频繁&#xff0c;数据的丢失也成为了一个不可避免的问题。在生活中&#xff0c;我们常因误删除、误格式化、分区失败、中病毒等而丢失数据。在这种情况下&#xff0c;一个好的数据恢复软件就显得尤为重要。 电脑数据恢复软件哪个好&#xff1f;本文将为大家…

Midjourney 商业实战案例(附AI学习工具教程资料)

前言 Midjourney 商业实战案例 &#xff08;附AI学习工具教程资料&#xff09; 如何把 AI 绘画应用到设计工作中&#xff1f; AI 绘画技术可以应用于设计工作中&#xff0c;帮助设计师更快速、更高效地完成设计工作&#xff0c;以下是一些常见的应用&#xff1a; **1. 快速…

对接企业微信API自建应用配置企业可信IP

前言 为了实现系统调用团队会议功能&#xff0c;组织发起企业微信会议&#xff0c;于是需要和企业微信做API对接。对接过程很难受&#xff0c;文档不清晰、没有SDK、没有技术支持甚至文档报文和实际接口报文都不匹配&#xff0c;只能说企业微信的API是从业以来见过的最难用的AP…

【数据结构与算法 经典例题】判断二叉树是否对称

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法 经典例题》C语言 期待您的关注 目录 一、问题描述 二、解题思路 三、C语言实现代码 一、问题描述 给你一个二…

ENSP中NAT的相关实验(两个私网,一个公网)

题目 实验需求 1.按照图示配置IP地址&#xff0c;公网地址100.1.1.1/24 2.私网A通过NAPT&#xff0c;使R1接入到互联网&#xff0c;私网B通过EASY IP&#xff0c;使R3接入到互联网 3.私网A配置NAT SERVER把Telnet的Telnet服务发布到公网&#xff0c;使PC2可以访问 三、实验…

Vue中使用mind-map实现在线思维导图

概述 在前面的文章Vue中实现在线画流程图实现中介绍了流程图的在线绘制&#xff0c;在本文&#xff0c;给大家分享一下基于mind-map实现在线的思维导图&#xff0c;并实现&#xff1a;1. 导图导出为图片&#xff1b;2. 打开xmind文件。 实现效果 实现 1. mind-map简介 simp…

随笔(三):CSS

一、CSS .&#xff08;类选择器&#xff09;和 #&#xff08;ID选择器&#xff09;在CSS中的主要区别在于它们的选择范围和用途&#xff1a; 1. 选择范围 类选择器&#xff08;. 开头&#xff09;&#xff1a; 类选择器用于选择具有指定类名的所有HTML元素。由于一个HTML元素…

spring boot基础知识

spring boot是整合spring 一系列的包的坐标集合 对依赖进行整合 总体介绍 spring boot是用来方便构建项目的工具 spring cloud是用来方便spring boot项目之间进行数据交互通讯和配置的 spring cloud data Flow 是用来进行数据的连接的 Spring 缺点 配置繁琐 虽然Spring的组件代…

超市管理系统 需求分析与设计 UML 方向

一、项目介绍 1.1项目背景 随着经济一体化和电子商务的迅速发展&#xff0c;网络传播信息的速度打破了传统信息传递的模式&#xff0c;互联网的高速发展和计算机应用在各个高校进展迅速&#xff0c;更多信息化产品的突飞猛进&#xff0c;让现代的管理模式也发生了巨大的变化&…

vue数据缓存

data 对象未定义或未正确传递&#xff1a;确保 data 对象在你调用 onMounted 钩子时已经存在且包含 base.columns 属性。 columns 响应式引用未定义&#xff1a;确保 columns 是一个使用 ref 或 reactive 创建的响应式引用。 异步数据问题&#xff1a;如果 data 是通过异步操…

vue 搭建 pinia

文章目录 环境设置存储读取数据【 storeToRefs】借助storeToRefs将store中的数据转为ref对象&#xff0c;方便在模板中使用【getters】当state中的数据&#xff0c;需要经过处理后再使用时&#xff0c;可以使用getters配置【$subscribe】通过 store 的 $subscribe() 方法侦听 s…

用动态规划算法均分纸牌,谈谈理解思路

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

升级自动交易!打通miniQMT接口!股票量化分析工具QTYX-V2.8.7

前言 我们的股票量化系统QTYX在实战中不断迭代升级!!! 我们用Python搭建自己的量化交易系统&#xff0c;之前主要以手动交易或者是easytrader库为主&#xff0c;属于曲线救国的方案。 在大家的强烈推荐下&#xff0c;我们决定使用正规的量化交易平台作为下单的最后环节——QMT&…

【Visual Studio】Visual Studio使用技巧及报错解决合集

目录 目录 一.概述 二.Visual Studio报错问题及解决方法 三.Visual Studio操作过程中遇到的问题及解决方法 四.Visual Studio编译优化选项 五.Visual Studio快捷键 一.概述 持续更新Visual Studio报错及解决方法&#xff0c;包括Visual Studio报错问题及解决方法、Visua…

mac安装win10到外接固态硬盘

1、制作win10系统 1.1 下载 winToUSB&#xff0c;打开后选择第一个 1.2 选择本地下载镜像&#xff0c; 我用的分区方案是适用于UEFI的GPT模式 1.3 点右下角执行&#xff0c;等待执行完成即可 2、mac系统下载win驱动 2.1 comman空格 搜索启动转换助理&#xff0c;打开后选择…

Ubuntu 22.04.4 LTS (linux) 安装certbot 免费ssl证书申请 letsencrypt

1 安装certbot sudo apt update sudo apt-get install certbot 2 申请letsencrypt证书 sudo certbot certonly --webroot -w 网站目录 -d daloradius.域名.com 3 修改nginx 配置ssl 证书 # 配置服务器证书 ssl_certificate /etc/letsencrypt/live/daloradius.域名.com/f…

浅谈后置处理器之正则表达式提取器

浅谈后置处理器之正则表达式提取器 JMeter是一款功能强大的开源负载测试工具&#xff0c;广泛应用于Web应用、数据库等的性能测试。在进行接口测试或负载测试时&#xff0c;经常需要从服务器响应中提取某些数据作为后续请求的参数。这时&#xff0c;“正则表达式提取器”&…

Web开发:<br>标签的作用

br作用 介绍基本用法常见用途注意事项使用CSS替代 介绍 在Web开发中&#xff0c;<br> 标签是一个用于插入换行符的HTML标签。它是“break”的缩写&#xff0c;常用于需要在文本中强制换行的地方。<br> 标签是一个空标签&#xff0c;这意味着它没有结束标签。 基本…

HTML+CSS+JS用户管理(可储存用户数据)

使用cookies记录账号密码信息&#xff0c;可以注册、登录、注销账号。 点赞❤️收藏⭐️关注&#x1f60d; 效果图 源代码在效果图后面 源代码 HTML <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <…