【C++】list的模拟实现+迭代器的设计思维

news2024/11/25 18:43:24

目录

  • 1.认识STL中的list
  • 2.迭代器的设计思维
    • 2.1 迭代器的定义
    • 2.2 迭代器的底层结构
  • 3.list的模拟实现
    • 3.1 list的节点
    • 3.2 list的迭代器
    • 3.3 list类
  • 4.list和vector的比较


1.认识STL中的list

💨相比于vector简单的连续线性结构,list就稍显复杂了。
💨list是一个带头双向循环链表,其结构如图所示。

在这里插入图片描述

list的使用较为简单,不再赘述。这里直接放出list的使用文档,以供参考。
📝list使用文档

本文的重点在于通过list迭代器初步了解STL六大组件之一 —— 迭代器的设计理念


2.迭代器的设计思维

💭学习vector的时候,我们粗略地提过迭代器,当时我们说将其视作一个原生指针来使用(事实也是如此,因为vector迭代器的底层就是一个原生指针)。然而,迭代器不一定是一个指针,也可能是一个类(class type),为什么呢?想搞清楚迭代器的设计思维,要先搞懂迭代器的模式定义。

2.1 迭代器的定义

iterator的模式定义:(引用自《STL源码剖析》)
“提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表述方式。”

💡通俗的理解,迭代器设计的本意就是为了给所有容器提供一种通用的访问方式,让使用者无需关注底层的数据结构。


2.2 迭代器的底层结构

💭了解了迭代器的定义,我们现在要着手于迭代器的实现。我们拿list的迭代器来举例子。
🌰 list不再能够像vector一样直接用原生指针作为迭代器,因为list的节点不保证在存储空间中连续存在。list迭代器必须有能力指向list的节点,并有能力进行正确的递增、递减、取值、成员访问等操作。所谓“递增、递减、取值、成员访问”操作是指:

  • 递增:指向当前节点的下一节点;
  • 递减:指向当前节点的上一节点;
  • 取值:读取当前节点数据域中的值;
  • 成员访问:访问数据域中类对象的成员

🔎很显然,原生指针无法满足这些操作。所以,STL中迭代器的实现再次采用了类封装的思想来解决这一问题,将指针封装成一个迭代器类,并通过运算符重载来控制迭代器的操作,以达到每一种不同的容器都有其特有的迭代器。

假设list有一个迭代器it,那么++it就是让it指向当前指向节点的下一节点(递增),--it让it指向当前指向节点的上一节点(递减),*it是读取当前指向节点数据域中的值(取值),it->member是访问数据域中类对象的成员(这个有点特殊,后面模拟实现时详谈)

🔎况且,即使迭代器是一个类,但由于其只有一个指针类型的成员变量,其大小依然是4个字节(32位平台下),所以我们可以将迭代器视为一种大小与指针相同,行为与指针类似的对象。

迭代器 it 的结构大致如下:
在这里插入图片描述


3.list的模拟实现

💭了解了迭代器的底层构造,接下来我们就可以着手list的模拟实现了!

💦list的实现需要三个类:

  1. list的节点
  2. list的迭代器
  3. list本身

3.1 list的节点

🔎list的节点和list本身的结构是不一样的,因此要分开实现。list节点的结构图如下:
在这里插入图片描述

💬代码实现

// list的节点
template <class T>
struct _list_node
{
	typedef _list_node<T>* pointer;

	pointer prev; // 指向前一个节点
	pointer next; // 指向下一个节点
	T data;// 数据域

	_list_node(const T& val = T()) // 提供一个全缺省构造函数
		:data(val)
	{}
};

3.2 list的迭代器

基于2.2中的分析,我们来实现list的迭代器

⭕初步实现

// list的迭代器
template <class T>
class ListIterator
{
	typedef _list_node<T>* pNode;
	typedef ListIterator<T> Self;
	
public:
	pNode _pNode; //迭代器内当然要有一个指针,指向list的节点。这也是迭代器唯一的成员变量
	
public:
	ListIterator(pNode p = nullptr) // 用一个节点的指针构造迭代器
		:_pNode(p)
	{}
	
	T& operator*() // 取值
	{
		return _pNode->data;
	}

	T* operator->() // 成员访问
	{
		return &_pNode->data;
	}

    // 递增
	Self operator++() // 前置++
	{
		_pNode = _pNode->next;
		return *this;
	}

	Self operator++(int) // 后置++
	{
		Self tmp(_pNode);
		_pNode = _pNode->next;
		return tmp;
	}
    
    // 递减
	Self operator--() // 前置--
	{
		_pNode = _pNode->prev;
		return *this;
	}

	Self operator--(int) // 后置--
	{
		Self tmp(_pNode);
		_pNode = _pNode->prev;
		return tmp;
	}

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

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

❓上面的代码乍一看似乎没什么问题,其实还不够完整。

我们知道,迭代器是一种行为类似指针的对象,而指针的各种行为中最常见也最重要的两种行为就是内容提领成员访问了,因此,迭代器中最重要的就是对operatoroperator->进行重载工作。而上面的代码问题就出在这两个最重要的地方。怎么回事呢?我们一步一步分析。

阅读文档我们知道,list不仅有iterator类型迭代器,还有const_iterator类型的迭代器该类型迭代器的特性是其指向对象受const保护,无法修改。

在这里插入图片描述

const_iterator是区别于iterator的另一种类型的迭代器,而不是简单在iterator前加上cosnt。

⭕区分

// 下面使用的是std中的list
list<int> lt(10, 1);

const list<int>::iterator it1 = lt.begin(); // 保护的是it1本身
//it1 = lt.end(); //错误,it1被const保护了,无法改变

list<int>::const_iterator it2 = lt.begin(); // 保护的是it2指向的对象
//*it2 = 2; //错误,it2指向的对象被const保护了,无法改变

💡由于const_iterator与iterator特性的不同,其实现方式也有所不同。上面我们实现的迭代器实际上是iterator类型的,根据const_iterator的特性,不难发现,只需在iterator的这两个地方做出调整即可变为const_iterator。

const T& operator*() // T& -> const T& // const保护*提取的返回值
{
	return _pNode->data;
}

const T* operator->() // T* -> const T* // const保护->访问的值
{
	return &_pNode->data;
}

那么,要重载一个迭代器类以实现const_iterator吗?不,这样的代码太过冗余,因为const_iterator与iterator的区别微乎其微。实现STL的大佬们可不会这么干。由于二者的区别只有在两个成员函数的返回值类型,因此,可以增加两个模板参数,根据传入的模板参数确定适应的返回值类型。

💬list迭代器最终代码

// list的迭代器类模板
template <class T, class Ref, class Ptr>
class ListIterator
{
	typedef _list_node<T>* pNode;
	typedef ListIterator<T, Ref, Ptr> Self;
	
public:
	pNode _pNode;

public:
	ListIterator(pNode p = nullptr)
		:_pNode(p)
	{}

	Ref operator*()
	{
		return _pNode->data;
	}

	Ptr operator->() //返回节点数据域的指针 
	{
		return &_pNode->data;
	}

	Self operator++() // 前置++
	{
		_pNode = _pNode->next;
		return *this;
	}

	Self operator++(int) // 后置++
	{
		Self tmp(_pNode);
		_pNode = _pNode->next;
		return tmp;
	}

	Self operator--() // 前置--
	{
		_pNode = _pNode->prev;
		return *this;
	}

	Self operator--(int) // 后置--
	{
		Self tmp(_pNode);
		_pNode = _pNode->prev;
		return tmp;
	}

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

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

💡这样一来,只需在list类中加入这两个typedef,即可利用同一类模板实现const_iterator与iterator两个不同的类。

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

3.3 list类

💨实现了list的节点和list的迭代器后,list类就很容易实现了,无非就是各种结构的耦合和增删查改。根据list的结构特性,我们可以先实现insert和erase成员函数,其它成员函数只需复合使用即可,大大提高代码的复合性。废话不多说,直接上代码。

// 链表
template <class T>
class list
{
	typedef _list_node<T> Node;
	typedef Node* pNode;
	// 迭代器
	typedef ListIterator<T, T&, T*> iterator;
	typedef ListIterator<T, const T&, const T*> const_iterator;
	
private:
	// 创建一个空链表,即头结点next和prev都指向自己
	void empty_initialize()
	{
		_head = new Node;
		_head->next = _head;
		_head->prev = _head;
	}
	
	pNode _head; // 指向链表的头结点
	long _size;// 记录链表的长度,解决了size()成员函数O(N)时间复杂度的问题
	
public:
	// 迭代器
	// begin指向头节点的下一位置,end指向头节点
	iterator begin()
	{
		return _head->next;
	}

	const_iterator begin() const
	{
		return _head->next;
	}
	
	iterator end()
	{
		return _head;
	}

	const_iterator end() const
	{
		return _head;
	}

	// 构造函数


	// 1.无参构造函数
	list()
	{
		empty_initialize();
	}

	// 2.fill
	list(size_t n, const T& val = T())
	{
		empty_initialize();
		while (n--)
		{
			push_back(val);
		}
	}

	list(int n, const T& val = T())
	{
		empty_initialize();
		while (n--)
		{
			push_back(val);
		}
	}

	// 3.拷贝构造
	
	//传统写法
	//list(const list<T>& lt)
	//{
	//	empty_initialize();
	//	for (const auto& e : lt) // 范围for的底层是迭代器
	//	{
	//		push_back(e);
	//	}
	//}
	
	//现代写法
	list(const list<T>& lt)
	{
		empty_initialize(); // 防止交换后析构野指针
		list<T> tmp(lt.begin(), lt.end());
		swap(tmp);
	}

	// 4.range
	template <class InputIterator>
	list(InputIterator first, InputIterator last)
	{
		empty_initialize();

		while (first != last)
		{
			push_back(*(first++));
		}
	}

	//"="
	list<T> operator=(list<T> lt)
	{
		swap(lt);
		return *this;
	}

	// Destructor
	~list()
	{
	    //clear()是释放除头节点之外的所有节点,析构是释放所有节点,这里复合使用即可
		clear();
		delete _head;
		_head = nullptr;
	}

	size_t size() const
	{
		return _size;
	}

	bool empty() const
	{
		return _head->next == _head;
	}

	// Modifiers
	void clear()
	{
		while (!empty())
		{
			pop_back();
		}
	}
	
	void swap(list<T>& lt) // 交换两个list的_head指向和_size即可
	{
		std::swap(_head, lt._head);
		std::swap(_size, lt._size);
	}
	
	// 重点
	iterator insert(iterator pos, const T& val = T())
	{
		// 开新节点
		pNode newNode = new Node(val);

		// 链接
		pNode pPrev = pos._pNode->prev;
		pNode pNext = pos._pNode;

		pPrev->next = newNode;
		newNode->prev = pPrev;
		newNode->next = pNext;
		pNext->prev = newNode;


		++_size;
		return iterator(newNode);
	}

	iterator erase(iterator pos)
	{
		assert(pos != end());

		pNode pPrev = pos._pNode->prev;
		pNode pNext = pos._pNode->next;

		delete pos._pNode;

		pPrev->next = pNext;
		pNext->prev = pPrev;

		--_size;
		return iterator(pNext);
	}

	void push_back(const T& val)
	{
		insert(end(), val);
	}

	void pop_back()
	{
		erase(--end());
	}

	void push_front(const T& val)
	{
		insert(begin(), val);
	}

	void pop_front()
	{
		erase(begin());
	}
};

4.list和vector的比较

🔎list和vector是我们最常用的两个容器,它们各有优缺点,相辅相成。由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

在这里插入图片描述
补充一点,在空间利用率方面,vector的扩容机制往往会导致空间浪费,而list则不存在这个问题,因为它是按需开辟空间的,对空间的利用十分精准。

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

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

相关文章

用细节问题撬动自我进化:首届雪浪算力开发者大赛来了!

12月2日&#xff0c;首届【雪浪算力开发者大赛】在无锡雪浪小镇正式揭幕&#xff0c;并由此开启了新一轮智能制造新生力量的博弈。本次大赛不仅将工业界开发者与创新企业置于聚光灯下&#xff0c;也是雪浪算力中心向社会公众展示其强大计算能力与支撑能力的震撼大秀。 在疫情威…

【关于Spring MVC框架中的@RequestBody】

关于Spring MVC框架中的RequestBody 在Spring MVC框架中&#xff0c;可以在POJO类型的请求参数前添加RequestBody。 当服务器端接收请求参数时&#xff0c;使用了RequestBody注解&#xff0c;客户端提交的请求参数必须是对象格式的&#xff01; 如果客户端提交的请求参数不是…

Vue中使用vue-video-player插件播放本地mp4视频文件

场景 若依前后端分离版手把手教你本地搭建环境并运行项目&#xff1a; 若依前后端分离版手把手教你本地搭建环境并运行项目_霸道流氓气质的博客-CSDN博客_前后端分离项目本地运行 在上面搭建项目的基础上&#xff0c;先实现了播放rtmp视频流 Vue中使用vue-video-player和vi…

Unity Animancer插件(一)基本使用

Animancer是什么&#xff1f;资源商店主页 一、快速播放 我们来通过Animancer实现一个最基本的动画播放效果。 首先创建一个脚本PlayAnimationOnEnable&#xff0c;编写如下代码 public AnimancerComponent animancer; public AnimationClip clip;private void OnEnable() …

pinia 持久化存储

pinia刷新数据持久化解决方案 无论是使用vuex 还是pinia都会面临一个问题&#xff1a;页面刷新&#xff0c;状态数据丢失的问题&#xff1b; 为了解决数据状态持久化问题&#xff0c;可以考虑使用插件 pinia-plugin-persistedstate 目录 安装pinia 并引入使用 npm install pin…

CCES软件如何来对ADI的SHARC DSP进行Flash的编程和烧写

如何做 Flash 烧写并实现脱机运行&#xff0c;其实我在之前的文章里有讲过&#xff0c;就可以用 Visual DSP来做&#xff0c;鼠标 点几下&#xff0c;非常简单。但是很多客户用的是 21569、21565这一类的SHARC DSP&#xff0c;不能再用 VDSP 来做烧写了&#xff0c;所以我想了 …

留学Essay写作怎么积累更多词汇量?

大部分留学生们都会感觉Essay挺难写作的&#xff0c;然而这其实是自身平时积累的不够多。当然&#xff0c;还有其他的正确方法&#xff01;高分的Essay都是平时练习出来的&#xff0c;所以我们需要在平时多注意收集一些相关的写作知识。下面是一些常用的Essay写作词汇&#xff…

5 年经验年薪百万,一位阿里 P8 分享自己的成长干货

今天这篇文章&#xff0c;我前后读了 3 遍&#xff0c;主人公是阿里最年轻的 P8 之一&#xff0c;工作五年连升三级&#xff0c;他在一次采访中分享了自己的成长经历和职场心得&#xff0c;非常接地气&#xff0c;我们整理了 一下&#xff0c;真诚地推荐给每一个渴望成长和进步…

基于主成分分析的支持向量机入侵检测系统

基于主成分分析的支持向量机入侵检测系统学习目标&#xff1a;学习内容&#xff1a;A. 数据集分析B. 主成分分析 (PCA)--降维C. 支持向量机 (SVM)核函数数据集预处理--转换数据集预处理 --特征缩放算法过程核函数对比总结不足参考论文申明&#xff1a; 未经许可&#xff0c;禁止…

ESB产品Oracle数据库升级说明

ESB企业服务总线平台作为支撑企业综合集成的产品&#xff0c;在应用集成、数据集成、数据治理等解决方案都发挥着非常重要的作用。随着产品和解决方案的不断优化和升级&#xff0c;ESB企业服务总线平台功能需要逐步进行完善&#xff0c;不断提升产品功能的完备性、易用性和全面…

Github最新霸榜,Alibaba架构师手写的分布式系统核心原理手册

前言&#xff1a; 分布式的重要性就不需要我再强调了吧&#xff0c;它现在已经是大厂面试的“常驻嘉宾”了 前几天有粉丝在后台跟我吐槽&#xff1a;鑫哥&#xff0c;我觉得现在的风气真的变了&#xff0c;之前只觉得网上的情况是个例&#xff0c;结果自己就遇到了很多次&…

东方甄选、交个朋友迎头而上,云集不进则退

配图来自Canva可画 社交电商、会员电商、精选电商&#xff0c;多个定位描绘出云集的“求变史”。 2015年前后&#xff0c;社交电商概念崛起&#xff0c;拼多多靠“砍一刀”、“拼团”快速成长&#xff0c;云集也学着借助社交平台微信的传播途径&#xff0c;通过社交互动、用户…

【论文笔记】DEEP FEATURE SELECTION-AND-FUSION FOR RGB-D SEMANTIC SEGMENTATION

论文 题目&#xff1a;DEEP FEATURE SELECTION-AND-FUSION FOR RGB-D SEMANTIC SEGMENTATION 收录于&#xff1a;ICME 2021 论文&#xff1a;Deep Feature Selection-And-Fusion for RGB-D Semantic Segmentation | IEEE Conference Publication | IEEE Xplore [2105.04102]…

[附源码]Python计算机毕业设计Django学生宿舍维修管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

仿QQ音乐(HTML+CSS)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

[安装] Doris集群搭建环境

参考文献 参考链接 推荐指数 Doris集群安装部署&#xff08;详细&#xff09; [火][火][火][火][火] Doris学习笔记之数据表的创建 [火][火][火] doris官网0.15版本的安装部署 [火][火][火][火][火] Doris0.15升级到1.1.1 [火][火][火][火][火] [说明] Doris使用…

7个有用的Pandas显示选项

Pandas是一个在数据科学中常用的功能强大的Python库。它可以从各种来源加载和操作数据集。当使用Pandas时&#xff0c;默认选项就已经适合大多数人了。但是在某些情况下&#xff0c;我们可能希望更改所显示内容的格式。所以就需要使用Pandas的一些定制功能来帮助我们自定义内容…

Word控件Spire.Doc 【图像形状】教程(13): 如何在C#中对齐word文档上的形状

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

poi-tl实现对Word模板中复杂表格的数据填充

文章目录前言what poi-tlwhy poi-tlHow poi-tl1. 版本问题2. 集成和使用3. SpringEL表达式总结前言 开发时, 我们有时需要进行word类型表格导出, 而对于表格操作. 我们一般可能会倾向于使用 poi 进行操作. 但poi操作比较复杂, 所以就在寻找一种可以快速将内容填充到表格中的工具…

yolov7配置与训练记录(二)

yolov7配置与训练记录(一) 已经完成了环境的配置,下面开始文件内部的操作 yolov7官方下载地址为 git clone https://github.com/WongKinYiu/yolov71 将下载好的预训练权重放在yolov7-main/weights内 需要在yolov7中新建weights文件夹(也是为了方便管理权重文件) 测试 pyth…