Cpp::STL—list类的模拟实现(上)(13)

news2024/11/26 3:49:03

文章目录

  • 前言
  • 一、结点类的实现
  • 二、迭代器类的实现
    • 迭代器类的存在意义
    • 迭代器类的模板参数
    • 构造函数
    • ++运算符的重载
    • --运算符的重载
    • ==、!=运算符的重载
    • *运算符的重载
    • ->运算符的重载
  • 总结


前言

  注意本篇难度偏高,其主要体现在迭代器类的实现
  什么,list类的迭代器还要单独封装成类!?

  还真是,毕竟它的元素存储在物理意义上不是连续的
  正文开始!


一、结点类的实现

  但是首先我们得先来实现一下节点类,因为我们说list底层是个链表,其实更准确地说是双向链表
在这里插入图片描述
而实现一个结点类。而一个结点需要存储的信息有:数据、前一个结点的地址、后一个结点的地址,于是该结点类的成员变量也就出来了(数据、前驱指针、后继指针)

而对于该结点类的成员函数来说,我们只需实现一个构造函数即可。因为该结点类只需要根据数据来构造一个结点即可,而结点的释放则由list的析构函数来完成

// List的节点类
// 直接设置为公开访问即可,后面很明显有访问成员变量的必要
	template<class T>
	struct ListNode
	{
	// 若构造结点时未传入数据,则默认以list容器所存储类型的默认构造函数所构造出来的值为传入数据
	// 对内置类型和自定义类型都是如此
		ListNode(const T& val = T())
			: _prev(nullptr)
			, _next(nullptr)
			, _val(val)
		{}

		ListNode<T>* _prev;
		ListNode<T>* _next;
		T _val;
	};

二、迭代器类的实现

来了来了

在这里插入图片描述
  这是我们要实现的三个类,而迭代器类的实现基础就是我们刚才实现的节点类
  你可能注意到我们是把迭代器类给公开的,数据公开意味着,内部数据可以被任意修改。但是在这里没人会去跳过封装,使用内部的数据,没有意义。因为不同编译器中底层实现是不一样的(实现逻辑、名称),这本身就是一种隐式设置为私有的作用

迭代器类的存在意义

之前模拟实现string和vector时都没有说要实现一个迭代器类,为什么实现list的时候就需要实现一个迭代器类了呢?其实就像前言说的,因为string和vector对象都将其数据存储在了一块连续的内存空间,我们通过指针进行自增、自减以及解引用等操作,就可以对相应位置的数据进行一系列操作,因此string和vector当中的迭代器就是原生指针,typedef一下就行
在这里插入图片描述
但是对于list来说,其各个结点在内存当中的位置是随机的,并不是连续的,我们不能仅通过结点指针的自增、自减以及解引用等操作对相应结点的数据进行操作,因为链表的各个元素只在逻辑上连续
在这里插入图片描述
而迭代器的意义就是,让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问,我们也想有个list迭代器,可以简单的++、- -、*,理解成本低,所以,封装节点指针,重载运算符就是我们要做的工作

比如说++,我们重载的时候,其实就是在内部让p = p -> _next;只是运用的时候没必要知道而已

迭代器类的模板参数

	template<class T, class Ref, class Ptr>

你可能会感到诧异,为什么迭代器类会有三个模板参数?其实,迭代器分为两种,普通迭代器和const迭代器,我们在list类的模拟实现中会运用到这两个

	typedef ListIterator<T, T&, T*> iterator;
	typedef ListIterator<T, const T&, const T&> const_iterator;

这体现了一种封装智慧,因为两种迭代器的自增自减等等几乎没区别,区别就仅仅在于元素的访问和获取指向元素的指针这两个成员函数的返回值上(比如说元素的访问,一个是T&,一个是const T&),如果是仅仅因为这点小区别,我们就写两个类,那这代码就不算好,于是我们选择丢给编译器,让它实例化的时候帮我们自动生成,尽管这也是实现两个类,但是我们少写了一个

好风凭借力,请说谢谢编译器先生
还是那句话,没有什么岁月静好,都是有人在帮你负重前行

在这里插入图片描述
所以Ref和Ptr分别代表的其实是引用类型和指针类型

构造函数

迭代器类实际上就是对结点指针进行了封装,其成员变量就只有一个,那就是结点指针,其构造函数直接根据所给结点指针构造一个迭代器对象即可

	ListIterator(Node* node = nullptr)
		: _node(node)
	{}

++运算符的重载

前置++原本的作用是将数据自增,然后返回自增后的数据。我们的目的是让结点指针的行为看起来更像普通指针,那么对于结点指针的前置++,我们就应该先让结点指针指向后一个结点,然后再返回“自增”后的结点指针即可

而对于后置++,我们则应该先记录当前结点指针的指向,然后让结点指针指向后一个结点,最后返回“自增”前的结点指针即可

	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	
	//有拷贝构造就需要考虑深浅拷贝的问题。
	//这里需要使用到浅拷贝,指向同一块空间,并且不需要考虑重复析构的问题,也说明了浅拷贝并都是坏处。
	//临时对象tmp同指向一块空间,调用完临时对象被销毁,指向空间资源保留
	//这也导致了返回类型是指针还是引用
	Self operator++(int)
	{
		Self temp(*this);
		_node = _node->_next;
		return temp;
	}

typedef其实可以将一个较长的类型变短,这里的Self其实就是迭代器类自己
typedef ListIterator< class T, class Ref, class Ptr> Self;

–运算符的重载

对于前置- -,我们应该先让结点指针指向前一个结点,然后再返回“自减”后的结点指针即可

而对于后置- -,我们则应该先记录当前结点指针的指向,然后让结点指针指向前一个结点,最后返回“自减”前的结点指针即可

	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	Self operator--(int)
	{
		Self temp(*this);
		_node = _node->_prev;
		return temp;
	}

==、!=运算符的重载

当使用==、!=运算符比较两个迭代器时,我们实际上想知道的是这两个迭代器是否是同一个位置的迭代器,也就是说,我们判断这两个迭代器当中的结点指针的指向是否相同即可

	// 迭代器间的比较并不是比较指向数据,而是比较迭代器指向的位置
	bool operator!=(const Self& it) const
	{ 
		return _node == it._node;
	}

	bool operator==(const Self& it) const
	{ 
		return _node != it._node;
	}

*运算符的重载

当我们使用解引用操作符时,是想得到该位置的数据内容。因此,我们直接返回当前结点指针所指结点的数据即可,但是这里需要使用引用返回,因为解引用后可能需要对数据进行修改

	Ref operator*() 
	{ 
		return _node->_val;
	}

->运算符的重载

我们使用迭代器的时候可能会用到->运算符,一般发生在T也是自定义类型的时候

int main()
{
	list<Date> lt;
	
	Date d1(2021, 8, 10);
	Date d2(1980, 4, 3);
	Date d3(1931, 6, 29);
	
	lt.push_back(d1);
	lt.push_back(d2);
	lt.push_back(d3);
	
	list<Date>::iterator pos = lt.begin();
	cout << pos->_year << endl; //输出第一个日期的年份,此时Date的成员变量为公有
	
	return 0;	
}

这个时候我们只需要返回节点所存储数据的指针即可:

	Ptr operator->() 
	{ 
		return &(operator*()); // 复用,毕竟*就是拿元素,&又是取地址符
	}

可是你可能会感觉不对,按道理来说,这种情况不是应该两个->吗?怎么pos->_year就拿到年份这一变量了?
其实这还是编译器弄的鬼,因为假如一个地方出现两个箭头,程序的可读性太差了,所以编译器做了特殊识别处理,为了增加程序的可读性,省略了一个箭头

所以说pos->_year,其实本质上是pos->->_year,更准确的说是pos.operator->()->_year

第一个箭头是pos ->去调用重载的operator->返回Date* 的指针,第二个箭头是Date* 的指针去访问对象当中的成员变量_year
那能不能自己显式写出两个->呢?你自己试试,pos->->_year不行;pos.operator->()->_year可以

在这里插入图片描述


总结

  本篇暂时就先介绍两个类,剩下最后一个list类我们下篇再介绍
  铠甲要合体了!

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

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

相关文章

【论文#码率控制】ADAPTIVE RATE CONTROL FOR H.264

目录 摘要1.前言2.基本知识2.1 蛋鸡悖论2.2 基本单元的定义2.3 线性MAD预测模型 3.GOP级码率控制3.1 总比特数3.2 初始化量化参数 4.帧级码率控制4.1 非存储图像的量化参数4.2 存储图像的目标比特 5.基本单元级码率控制6.实验结果7.结论 《ADAPTIVE RATE CONTROL FOR H.264》 A…

望繁信科技创始人索强出席2022福布斯中国·青年海归菁英100人评选颁奖典礼

2022年12月20日&#xff0c;由福布斯中国和福科无限共同举办的“2022福布斯中国青年海归菁英100人评选”颁奖典礼在上海圆满落幕。 来自福布斯中国的高层、知名企业家、投资人齐聚一堂&#xff0c;围绕全球化趋势、海归创业机遇等话题&#xff0c;共同把脉数字时代发展风向&am…

vim实用笔记

函数跳转功能 想要使用函数跳转功能需要先安装 ctags sudo apt-get install exuberant-ctags接着&#xff0c;在源文件目录树执行如下命令&#xff1a; ctags -R . 即可在该目录下生成一个tags文件&#xff0c; 这个文件就是所有函数和变量的索引 接着打开用vim打开任一文件…

使用node+prisma+socket+vue3实现一个群聊功能,拓展功能:使用lottie实现入场动画

使用nodeprisma和vue3实现一个群聊功能 后端代码编写 node环境初始化 新建一个空文件夹node&#xff0c;初始化node环境 npm init -y修改 packages.json&#xff0c;添加 type 为 module&#xff0c;删除 main {"name": "node","version": …

iLogtail 开源两周年:UC 工程师分享日志查询服务建设实践案例

作者&#xff1a;UC 浏览器后端工程师&#xff0c;梁若羽 传统 ELK 方案 众所周知&#xff0c;ELK 中的 E 指的是 ElasticSearch&#xff0c;L 指的是 Logstash&#xff0c;K 指的是 Kibana。Logstash 是功能强大的数据处理管道&#xff0c;提供了复杂的数据转换、过滤和丰富…

如何写好SCI论文的Abstract

摘要是一篇论文的缩影&#xff0c;是对全文内容的高度浓缩和提炼&#xff0c;也是整篇论文的精髓和灵魂。读者通常先通过摘要快速获得文章信息&#xff0c;然后决定是否要进一步仔细阅读全文&#xff0c;因此&#xff0c;写好摘要至关重要! 那么如何才能写好论文摘要呢&#xf…

线性代数在大一计算机课程中的重要性

线性代数在大一计算机课程中的重要性 线性代数是一门研究向量空间、矩阵运算和线性变换的数学学科&#xff0c;在计算机科学中有着广泛的应用。大一的计算机课程中&#xff0c;线性代数的学习为学生们掌握许多计算机领域的关键概念打下了坚实的基础。本文将介绍线性代数的基本…

睡眠小乖 2.2.19 | 免费改善睡眠神器

睡眠小乖是一款完全免费的睡眠监测软件、冥想软件、改善睡眠软件。支持的功能包括&#xff1a;睡眠监测、梦话鼾声记录、睡眠报告、多种白噪音、冥想板块。白噪音板块提供了近80种白噪音&#xff0c;支持任意选择4种白噪音组合成一个混音&#xff0c;支持单独调节每种白噪音的音…

高标准农田建设专项整治行动拉开序幕,建设监管如何破局?

近日&#xff0c;高标准农田建设工程质量“回头看”和专项整治行动拉开序幕&#xff0c;将全面梳理2020年以来立项实施的高标准农田建设项目&#xff0c;围绕前期工作、施工建设、管护利用等环节&#xff0c;聚焦方案设计是否规范、监管责任是否落实、建后利用是否到位、建后管…

安全认证:oath2

一、一些概念&#xff1a; 1、认证&#xff1a;主要解决的是你是谁的问题。 三个层面认证&#xff1a;信道认证&#xff08;SSL等&#xff09;&#xff0c;协议认证&#xff08;例如用http协议的时候的格式&#xff09;&#xff0c;内容认证&#xff08;比如浏览网页的时候&a…

ASO优化截图如何影响 App Store 和 Google Play 的安装数量

无论您的应用程序多么吸引人和有用&#xff0c;用户在下载它之前都不知道它。但是&#xff0c;您如何让潜在用户在众多竞争对手应用程序中选择您的应用呢&#xff1f;关键是让您的应用程序引人注目。在本文中&#xff0c;我将向您介绍应用程序的视觉组件以及如何增强它以从其他…

自主性革命:人工智能赋予人形机器人大脑

大家好&#xff0c;我是Shelly&#xff0c;一个专注于输出AI工具和科技前沿内容的AI应用教练&#xff0c;体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具&#xff0c;拥抱AI时代的到来。 人工智能&#xff08;AI&#xff09;是人形…

蛋鸡养殖场饲料粉碎加工机器设备

蛋鸡养殖场饲料粉碎加工机器设备种类繁多&#xff0c;包括粉碎机、混合机、制粒机等多种设备&#xff0c;用于将饲料原料进行粉碎、混合、制粒等处理&#xff0c;以生产出营养均衡、易于消化吸收的蛋鸡饲料‌。具体来说&#xff1a;‌粉碎机‌&#xff1a;用于将谷物、豆类等饲…

安卓手机termux安装ubuntu24桌面环境

要在 proot-distro 中的 Ubuntu 上安装桌面环境并实现 远程连接&#xff0c;可以按照以下步骤进行。这将包括安装轻量级桌面环境&#xff08;如 LXDE 或 XFCE&#xff09;、VNC 服务器&#xff0c;并配置远程访问。 步骤 1&#xff1a;安装 Ubuntu 安装并登录 Ubuntu&#xff1…

数据结构 ——— 单链表oj题:环状链表(判断链表是否带环)

目录 题目要求 手搓简易环状单链表 代码实现 问题1&#xff1a;slow 指针和 fast 指针一定会相遇吗 问题2&#xff1a;slow 每次走一步&#xff0c;fast 每次走 n 步是否还能判断链表带环&#xff1f;&#xff08;n > 2&#xff09; 题目要求 有一个单链表的头节点 …

适合新手的快速搭建全景VR服务器教程

近期有一些朋友在使用BZ全景可视化编辑器的过程中, 不了解如何把全景编辑器生成的静态全景VR HTML项目部署到自己的服务器上, 本篇文章将详细介绍如何使用宝塔面板来搭建一个全景VR服务器 我们将从安装宝塔面板开始&#xff0c;配置静态网页服务器&#xff0c;上传全景静态HTM…

英伟达股价分析:英伟达股价能否上涨到150美元,接下来该如何操作?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经​ 猛兽财经核心观点&#xff1a; &#xff08;1&#xff09;华尔街投行Oppenheimer已将英伟达的目标价上调到了150美元。 &#xff08;2&#xff09;产品方面的最新进展和合作伙伴关系进一步提升了英伟达的市场地位。 &…

Java生成Excel_低内存占用_更快

EasyExcel&#xff1a;高效Java Excel工具&#xff0c;解决大文件读写难题 EasyExcel是一个基于Java的、快速简洁且能有效解决大文件内存溢出问题的Excel处理工具。它使得用户可以在无需过多关注性能和内存消耗的情况下&#xff0c;轻松实现Excel文件的读写功能。相较于传统的…

微信小程序后台搭建—node+mysql

想必大家都有一个困扰&#xff0c;想要用微信小程序作为前端&#xff0c;但是后端不知道如何用node连接微信小程序&#xff0c;我最近也一直困扰许久&#xff0c;所以我就想用node写后端接口在连接微信小程序&#xff0c;记录一下学习笔记 前言 前端:微信小程序 后端:nodeexpr…

linux mqtt 服务记录

查看已连接的clientId 如果MQTT服务使用的是系统服务管理器&#xff08;如systemd&#xff09;&#xff0c;你可以使用journalctl命令来查看服务的日志 journalctl -u mqtt.service 这里的mqtt.service是MQTT服务的名称&#xff0c;根据实际情况替换为你的服务名称&#xff…