C++【深入 STL--list 之 迭代器与反向迭代器】

        接前面的手撕list(上)文章,由于本人对于list的了解再一次加深。本文再次对list进行深入的分析与实现。旨在再一次梳理思路,修炼代码内功。

1、list 基础架构

        list底层为双向带头循环链表,问题是如何来搭建这个list类。可以进行下面的考虑:       

1、list为带头双向循环链表,链表离不开节点。要能在list内部创建节点。
2、list内部没有数据时,也应该存在哨兵位头节点。
3、list内部的数据类型是未知的,需要写成类模板。
4、list支持迭代器访问数据,但是由于链表的结构来说,普通指针类型的迭代器不能实现++和解引用等基础访问操作,这就需要封装一个迭代器对象。

        由于链表是双向的,所以节点的成员属性主要为三个:  

  1. 节点指针类型的_next:指向该节点的下一个节点。
  2. 节点指针类型的_prev:指向该节点的前一个结点。
  3. 未知数据类型的数据_val:链表中存放的数据。

        那么list的成员属性应该有什么?

  1. 头节点的指针_head: 作为链表的起始点,通过它可以访问链表的第一个元素和最后一个元素。在双向循环链表中,头节点的_next 指针指向链表的第一个有效节点,_prev指针指向链表的最后一个有效节点。
  2. 节点的个数_size:记录链表中有效节点的数量,方便快速获取链表的长度,而不需要遍历整个链表来计算。在插入和删除节点时,需要相应地更新这个计数器。

        迭代器主要依赖于节点,利用节点才能找到节点当中的数据,并可以通过对运算符的重载实现迭代器本身的基础操作。故迭代器的成员属性为节点的指针。

        由于在list当中要使用到节点当中的所有成员变量,所以这里直接就将节点类写为struct主要就是在内部的访问限定符默认为public,迭代器类型也是同样的道理。

namespace ltq
{
	template<class T>
	struct __list_node
	{
		__list_node<T>* _prev;
		__list_node<T>* _next;
		T _val;
	};

	template<class T>
	struct __list_iterator
	{
		typedef __list_node<T> Node;
		Node* _node;
	};

	template<class T>
	class list
	{
		typedef __list_node<T> Node;
	public:
        typedef __list_iterator<T> iterator;

	private:
		Node* _head;
		size_t _size;
	};
}

        下面需要完善的就是三个类型的构造函数, 首先需要明确关系:list要使用的是节点类和迭代器类,在list类中创建了节点和迭代器对象就会去调用它们自己的构造函数。当然,在外部要是使用到list创建了对象,那么也会调用list自身的构造函数。

		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}

        new先开辟空间,然后调用Node的构造函数,由于list的哨兵位节点中可以不用存放数据,所以直接调用Node的默认构造即可。下面就需要完善Node的默认构造。

	template<class T>
	struct __list_node
	{
		__list_node<T>* _prev;
		__list_node<T>* _next;
		T _val;

		__list_node(const T& x = T())
			:_prev(nullptr)
			,_next(nullptr)
			,_val(x)
		{}
	};

        这里的默认构造使用缺省参数,当没有形参传过来时就创建T类型的匿名对象对节点中的数据进行初始化。 

2、void push_back(const T& x)

        双向带头循环链表的末尾插入需要找到末尾的节点,再创建新的节点进行链接。这里需要更新_size。

		void push_back(const T& x)
		{
			Node* tail = _head->_prev;
			Node* newNode = new Node(x);

			tail->_next = newNode;
			newNode->_prev = tail;
			newNode->_next = _head;
			_head->_prev = newNode;

			++_size;
		}

3、迭代器

        迭代器开始位置返回的是哨兵位头节点的下一个节点,迭代器的末尾返回的是哨兵位头节点的指针,这样设计就能实现左闭右开的区间。

		iterator begin()
		{
			return _head->_next;
		}

		iterator end()
		{
			return _head;
		}

         在前面我故意没有写迭代器的构造函数,其实这里就会很明显的发现,不管是什么类型的迭代器在返回的时候都是传递节点的指针。由于单参数的构造函数支持隐式类型的转换,那么节点的指针就会通过迭代器的构造函数构造出一个迭代器对象并返回,这里需要注意的是,传值返回会生成临时对象,临时对象具有常性。

        那么,我们现在来实现一下迭代器的构造函数。

	template<class T>
	struct __list_iterator
	{
		typedef __list_node<T> Node;
		Node* _node;

		__list_iterator(Node* node)
			:_node(node)
		{}
	};

3.1、必要的运算符重载

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

    __list_iterator<T>& operator++()
    {
	    _node = _node->_next;
	    return *this;
    }    

    __list_iterator<T> operator++(int)
    {
	    __list_iterator<T> tmp(*this);
	    _node = _node->_next;
	    return tmp;
    }

    bool operator!=(const __list_iterator<T>& it)
    {
	    return _node != it._node;
    }

        有了迭代器就支持范围for了,现在我们来测试一下目前的list是否可用:

3.2、箭头的重载

        假如链表中存放的是结构体类型的数据,假设结构体为:

struct A
{
	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{}

	int _a1;
	int _a2;
};

        如果要访问A内部的数据,此时对迭代器进行解引用是不能访问到A内部的数据的。此时重载箭头,通过拿到A对象的地址,使用A的地址来达到访问内部数据的内容。重载箭头可以通过解引用再取地址的方式进行实现。

        当然也可以通过使用对象+.的方式来进行访问。

4、iterator insert(iterator pos, const T& x)

		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newNode = new Node(x);
			
			newNode->_next = cur;
			cur->_prev = newNode;
			newNode->_prev = prev;
			prev->_next = newNode;

			++_size;

			return newNode;
		}

         虽然链表的插入不像vector的插入会产生扩容问题而引发迭代器失效,这里返回新插入节点的迭代器主要是方面插入之后的链式操作。其次与STL保持一致。

5、iterator erase(iterator pos)

       删除当前迭代器位置,这里也不像vector,删除当前位置的数据并不影响后续节点的迭代器,但是当list删除当前节点时,会进行释放节点。那么当前节点的迭代器就会悬空,对悬空的迭代器进行操作就会引发错误,所以,在删除之后也会返回下一个节点的迭代器。

		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;

			prev->_next = next;
			next->_prev = prev;
			delete pos._node;

			--_size;
			return next;
		}

6、void clear()

        内部调用erase函数对节点进行清除释放,但保留头节点。

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
			_size = 0;
		}

7、拷贝构造 

        这里要进行深拷贝,先为新对象创建一个头结点,再使用范围for拿出目标链表中的每一个数据,直接进行push_back()操作即可。

		list(const list<T>& lt)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;

			for (auto& e:lt)
			{
				push_back(e);
			}
		}

8、赋值重载

        直接采用传值传参,传值传参调用拷贝构造,之后进行对象交换即可。

		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		list<T>& operator=(list<T> tmp)
		{
			swap(tmp);
			return *this;
		}

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

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

相关文章

Games104——游戏引擎Gameplay玩法系统:基础AI

这里写目录标题 寻路/导航系统NavigationWalkable AreaWaypoint NetworkGridNavigation Mesh&#xff08;寻路网格&#xff09;Sparse Voxel Octree Path FindingDijkstra Algorithm迪杰斯特拉算法A Star&#xff08;A*算法&#xff09; Path Smoothing Steering系统Crowd Simu…

2024最新版Node.js详细安装教程(含npm配置淘宝最新镜像地址)

一&#xff1a;Node.js安装 浏览器中搜索Nodejs&#xff0c;或直接用网址:Node.js — 在任何地方运行 JavaScript 建议此处下载长期支持版本&#xff08;红框内&#xff09;: 开始下载&#xff0c;完成后打开文件: 进入安装界面&#xff0c;在此处勾选&#xff0c;再点击n…

高效 MyBatis SQL 写法一

高效 MyBatis SQL 写法一 前言 MyBatis 作为一款优秀的持久层框架&#xff0c;极大地简化了数据库操作。 然而&#xff0c;在实际开发中&#xff0c;XML 配置的编写仍然可能显得繁琐。 本文将分享一些 MyBatis 动态 SQL 的优质写法&#xff0c;帮助开发者提升效率并减少错误…

C语言按位取反【~】详解,含原码反码补码的0基础讲解【原码反码补码严格意义上来说属于计算机组成原理的范畴,不过这也是学好编程初级阶段的必修课】

目录 概述【适合0基础看的简要描述】&#xff1a; 上述加粗下划线的内容提取版&#xff1a; 从上述概述中提取的核心知识点&#xff0c;需背诵&#xff1a; 整数【包含整数&#xff0c;负整数和0】的原码反码补码相互转换的过程图示&#xff1a; 过程详细刨析&#xff1a;…

如何安装PHP依赖库 更新2025.2.3

要在PHP项目中安装依赖&#xff0c;首先需要确保你的系统已经安装了Composer。Composer是PHP的依赖管理工具&#xff0c;它允许你声明项目所需的库&#xff0c;并管理它们。以下是如何安装Composer和在PHP项目中安装依赖的步骤&#xff1a; 一. 安装Composer 对于Windows用户…

【通俗易懂说模型】线性回归(附深度学习、机器学习发展史)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;深度学习_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. …

硬件电路基础

目录 1. 电学基础 1.1 原子 1.2 电压 1.3 电流 1.电流方向&#xff1a; 正极->负极,正电荷定向移动方向为电流方向&#xff0c;与电子定向移动方向相反。 2.电荷&#xff08;这里表示负电荷&#xff09;运动方向&#xff1a; 与电流方向相反 1.4 测电压的时候 2. 地线…

【含文档+PPT+源码】基于Python爬虫二手房价格预测与可视化系统的设计与实现

项目介绍 本课程演示的是一款基于Python爬虫二手房价格预测与可视化系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 带你从零开始部署运行本套系统 该项…

【数据结构】树哈希

目录 一、树的同构1. 定义2. 具体理解(1) 结点对应(2) 孩子相同(3) 递归性质 3. 示例 二、树哈希1.定义2.哈希过程&#xff08;1&#xff09;叶节点哈希&#xff08;2&#xff09;非叶节点哈希&#xff08;3&#xff09;组合哈希值 3.性质&#xff08;1&#xff09; 唯一性 \re…

渗透测试之文件包含漏洞 超详细的文件包含漏洞文章

目录 说明 通常分为两种类型&#xff1a; 本地文件包含 典型的攻击方式1&#xff1a; 影响&#xff1a; 典型的攻击方式2&#xff1a; 包含路径解释&#xff1a; 日志包含漏洞&#xff1a; 操作原理 包含漏洞读取文件 文件包含漏洞远程代码执行漏洞: 远程文件包含…

Mysql:数据库

Mysql 一、数据库概念&#xff1f;二、MySQL架构三、SQL语句分类四、数据库操作4.1 数据库创建4.2 数据库字符集和校验规则4.3 数据库修改4.4 数据库删除4.4 数据库备份和恢复其他 五、表操作5.1 创建表5.2 修改表5.3 删除表 六、表的增删改查6.1 Create(创建):数据新增1&#…

ChatGPT怎么回事?

纯属发现&#xff0c;调侃一下~ 这段时间deepseek不是特别火吗&#xff0c;尤其是它的推理功能&#xff0c;突发奇想&#xff0c;想用deepseek回答一些问题&#xff0c;回答一个问题之后就回复服务器繁忙&#xff08;估计还在被攻击吧~_~&#xff09; 然后就转向了GPT&#xf…

Vue 中如何嵌入可浮动的第三方网页窗口(附Demo)

目录 前言1. 思路Demo2. 实战Demo 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 1. 思路Demo 以下Demo提供思路参考&#xff0c;需要结合实际自身应用代码 下述URL的链接使用百度替代&#xff01; 方式 1…

【Linux】23.进程间通信(2)

文章目录 3. 进程间通信3.1 进程间通信介绍3.1.1 进程间通信目的3.1.2 进程间通信发展 3.2 什么是进程间通信3.3 管道3.4 匿名管道pipe()3.4.1 站在文件描述符角度-深度理解管道3.4.2 站在内核角度-管道本质3.4.3 用fork来共享管道原理3.4.5 管道相关知识3.4.6 代码一&#xff…

AI大模型开发原理篇-8:Transformer模型

近几年人工智能之所以能迅猛发展&#xff0c;主要是靠2个核心思想&#xff1a;注意力机制Attention Mechanism 和 Transformer模型。本次来浅谈下Transformer模型。 重要性 Transformer模型在自然语言处理领域具有极其重要的地位&#xff0c;为NLP带来了革命性的突破‌。可以…

html2canvas绘制页面并生成图像 下载

1. 简介 html2canvas是一个开源的JavaScript库&#xff0c;它允许开发者在用户的浏览器中直接将HTML元素渲染为画布&#xff08;Canvas&#xff09;&#xff0c;并生成图像。以下是对html2canvas的详细介绍&#xff1a; 2. 主要功能 html2canvas的主要功能是将网页中的HTML元…

基于RK3588/RK3576+MCU STM32+AI的储能电站电池簇管理系统设计与实现

伴随近年来新型储能技术的高质量规模化发展&#xff0c;储能电站作为新能源领域的重要载体&#xff0c; 旨在配合逐步迈进智能电网时代&#xff0c;满足电力系统能源结构与分布的创新升级&#xff0c;给予相应规模 电池管理系统的设计与实现以新的挑战。同时&#xff0c;电子系…

机器学习-线性回归(参数估计之结构风险最小化)

前面我们已经了解过关于机器学习中的结构风险最小化准则&#xff0c;包括L1 正则化&#xff08;Lasso&#xff09;、L2 正则化&#xff08;Ridge&#xff09;、Elastic Net&#xff0c;现在我们结合线性回归的场景&#xff0c;来了解一下线性回归的结构风险最小化&#xff0c;通…

【数据分析】豆瓣电影Top250的数据分析与Web网页可视化(numpy+pandas+matplotlib+flask)

豆瓣电影Top250的数据分析与Web网页可视化(numpy+pandas+matplotlib+flask) 豆瓣电影Top250官网:https://movie.douban.com/top250写在前面 实验目的:实现豆瓣电影Top250详情的数据分析与Web网页可视化。电脑系统:Windows使用软件:PyCharm、NavicatPython版本:Python 3.…

备考蓝桥杯8——EEPROM读写

目录 看手册时间 关于IIC 附录 IIC代码 看手册时间 我们主要是搞编程&#xff0c;所以&#xff0c;我们一般会非常关心我们如何对EEPROM进行编程。特别的&#xff0c;EEPROM要做读写&#xff0c;首先是看它的IIC设备地址。 有趣的是——我们的EEPROM的IIC地址是根据地址进行…