C++容器list底层迭代器的实现逻辑~list相关函数模拟实现

news2024/11/15 8:29:08

目录

1.两个基本的结构体搭建

2.实现push_back函数

3.关于list现状的分析(对于我们如何实现这个迭代器很重要)

3.1和string,vector的比较

3.2对于list的分析

3.3总结

4.迭代器类的封装

5.list容器里面其他函数的实现

6.个人总结

7.代码附录


1.两个基本的结构体搭建

首先就是我们的这个list结构体,这个里面包含了我们的基本的函数的实现,以便于我们后期的测试;

这个里面的成员变量就是现在的这个_head即我们的哨兵位节点,这个节点是我们链表的第一个节点,但是是没有实际含义的,他的下一个节点才是我们的链表里面的真正的第一个节点;

因为我们的这个list表示的就是一个双向的链表,因此这个里面的每一个元素都是我们的节点,我们的节点里面又包含了数据域和指针域,因此这个需要我们重新定义一个node的结构体,方便我们在这个list里面进行使用;

其中这个里面的list_node结构体里面包含的内容就是我们的data元素和next指针变量;


  • 这个下面的list_node就是一个模版类,list_node<T>表示的就是模版类的类型,其中这个里面的T可以是任意的数据类型,例如这个T就是我们熟悉的int类型,这个时候的,list_node<int>表示的就是这个类里面的数据就是int类型的数据,这个前驱指针和后继指针就是int*类型的;
  • _naxt表示的就是后继指针,_prev就是前驱指针,都是英文的缩写;


  • 这个里面为了简答起见,我们对于这个list_node进行重定义为Node,这样我们后面使用的时候就会方便一些;
  • 这个里面是对于 list链表进行初始化的工作,list()就是一个构造函数,首先我们使用new开辟新的节点,这个时候这个节点就是我们的链表里面的唯一的节点;
  • 所以这个_haed头结点的前驱指针和后继指针指向的都是自己

2.实现push_back函数

为什么首先实现这个push_back函数,这个函数实现的就是向这个链表里面插入数据,我们想要使用迭代器进行遍历首先这个链表里面需要有数据,因此我们使用这个push_back函数向这个链表里面添加数据;

代码解读

  • 首先使用new开辟一个新的节点(第一行代码),让原来的tai的next指针l指向这个新的节点(第三行代码);
  • 我们的tail这个时候就是_haed这个头结点的前驱指针(第二行代码),因为我们的这个链表是一个双向的链表,第一个头结点和最后一个尾结点之间有联系;
  • newnode的前后建立联系,前面是我们的这个原来的tail节点,后面的后继指针指向的就是我们的头结点(第4,5行代码);
  • 最后一行表示的是这个_head头结点的前驱节点就是我们的这个新开辟的newnode节点;


此外,我们可以实现一些基本的函数供我们使用,这个包括了size函数计算这个链表里面的节点的个数,以及使用这个_size成员变量进行判断我们的这个链表是不是空的;

3.关于list现状的分析(对于我们如何实现这个迭代器很重要)

3.1和string,vector的比较

我们的迭代器就是对于这个容器里面的元素进行遍历的,我们的之前介绍的string和vector都是支持这个迭代器的,这个list也支持,但是没有那么随意;

什么是随意,就是我们的这个string vector因为自身的这个连续性,因为string就是相当于我们学过的这个字符串,vector就是类似于我们学习的这个数组,他们的这个空间都是连续的,我们可以使用这个++,--运算符对于这个迭代器的指针进行移动,进而对于这个容器里面的元素进行遍历;

而且我们对上面的两个string,vector使用这个迭代器解引用就可以直接拿到这个对应位置的数据,这些都是我们list链表无法直接做到的;

3.2对于list的分析

因为我们的list里面的节点通过指针进行连接,这个里面的指针分为前驱指针和后继指针,其中这个指针里面存放了下一个元素的地址;

这个时候我们无法使用++,--直接拿到相邻位置的元素,因为我们的++,--只是拿到的物理上连续空间的地址,但是这个list节点之间的物理地址不是连续的;

但是我们可以通过一定的手段去拿到,因为我们知道下一个元素的地址;

同理,我们通过解引用也没有办法得到这个位置的数据,因为我们拿到的是节点,这个里面有数据域和指针域,而我们想要实现的效果就是通过解引用直接得到这个数据;

但是我们可以通过一定的方法,例如使用这个运算符的重载,把这个解引用操作符重载成为直接拿到这个节点对应的数据的操作符;

3.3总结

因此,我们需要封装一个类,实现这个operator*和operator++的重载,进而可以让我们达到我们想要的效果;

4.迭代器类的封装

这个里面封装了我们的operator*和operator++两个运算符,都是我们经过上面的这个对于list容器的现状的分析之后得到的结论:我们应该使用这个*运算符的重载直接得到这个节点对应位置的数据,使用这个++运算符直接找到这个链表里面的下一个节点;

当我们在进行遍历的时候,我们的两个迭代器不相等就需要接着进行遍历,当相等的时候就需要停止这个便利的过程,因此我们还重载了这个里面的operator!=运算符方便我们对于这个遍历的过程进行控制;

其中在这个++运算符的重载里面,我们就是直接把下一个节点的指针赋值给当前的这个节点,返回值就是赋值之后的这个新的节点,这样就实现了这个++运算符的重载;

我们的list里面也是对于这个list_iterator进行重定义,这个名字和上面的这个self的意义是一样的,就是这个表达上不相同罢了,因为我们实际上进行遍历还是使用的这个iterator迭代器,这个重命名就是为了我们使用方便;

我们的这个begin函数返回值是一个迭代器,但是我们return的就是一个指针,但是我们的list_iterator里面是一个单参数的构造函数,因此这个是可以支持隐式类型转换的;

完整代码:写到这个地方,我们基本的这个逻辑就已经实现了,这个时候就可以使用这个迭代器对于这个list容器里面的元素进行遍历了;

//#define _CRT_SECURE_NO_WARNINGS 1
//list.h文件

#pragma once
#include<iostream>
using namespace std;

namespace bite
{
	template<class T>

	//对于节点定义一个类
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;

		list_node(const T& data = T())
			:_data(data)
			, _prev(nullptr)
			, _next(nullptr)
		{

		}
	};
	template<class T>
	
	struct list_iterator
	{
		typedef list_node<T> Node;

		typedef list_iterator<T>  self;
		Node* _node;

		list_iterator(Node* node)
			:_node(node)
		{

		}

		//使用引用是因为我们可以修改这个里面的数据
		T& operator*()
		{
			return _node->_data;
		}

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

		bool operator!=(const self& s)
		{
			return _node != s._node;
		}
	};


	template<class T>

	class list
	{
		typedef list_node<T> Node;
	public:
		typedef list_iterator<T>  iterator;

		//返回的是一个节点,接受的是迭代器,但是这个是单参数构造函数支持隐式类型转换
		iterator begin()
		{
			//iterator it(_head->next);

			return _head->_next;
		}

		iterator end()
		{
			//最后一个元素的下一个位置
			return _head;
		}
		list()
		{
			_head = new Node(T());
			_head->_next = _head;
			_head->_prev = _head;
		}

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

			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;
		}

		size_t size() const
		{
			return _size;
		}

		bool empty() const
		{
			return _size == 0;
		}
	private:
		Node* _head;
		size_t _size;
	};

	void test_list1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		list<int>::iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

}
#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
//#include<algorithm>
//#include<list>
//#include<vector>
//using namespace std;

#include"list.h"

int main()
{
	bite::test_list1();
	return 0;
}

 这个时候,我们对于这个迭代器进行测试,发现这个迭代器就可以正常的跑起来了;

5.list容器里面其他函数的实现

首先就是这个insert和erase,即链表里面的数据的插入和删除;

插入数据的话,就是新建一个节点,在这个节点的前后和这个节点建立联系,因为是双向的,就是指针之间的相互指向的确定(4行代码);

erase就是把这个节点的前面和后面的节点确定,让前后两个节点相互指向,然后把这个节点释放掉,这个节点就被删除掉了;

实现了上面的这个insert和erase之后,我们想要在这个指定的位置进行数据的插入和删除就比较容易了,我们只需要调用上面的函数即可;

push_back不需要之前写的那么复杂,直接在这个end()前面插入数据即可;

push_front就是在这个begin()前面插入数据(这个里面的begin指向的是第一个有实际意义的节点,不是我们的哨兵位头结点,这个一定要注意!!!!!!!!!!!!!

pop_front就是删除这个头结点的数据,直接调用这个erase把这个begin()传递进去即可;

pop_back函数就是删除这个最后的数据,因为我们的end指向的是最后一个数据的下一个位置,因此这个地方我们进行传参的时候进行了--操作;

6.个人总结

在实现这个迭代器的时候,我们需要搞清楚这个架构,实际上就是三个结构体,一个是节点的,一个是链表的,一个就是我们自己封装的这个迭代器,其中这个节点的结构体就是为了方便使用;

其次这个不断地进行typedef对于初学者也是一个挑战,不是因为它很难,而是在这个重命名之后,我们需要这个结构重命名之前是什么,这个结构的里面有哪些内容,例如pos._node我就理解了很久,就是因为重命名之后对于这个结构里面的内容不清楚导致的;

实际上,这个pos就是我们的参数iterator,本质上就是迭代器,这个list_iterator结构体里面就有我们的这个_node成员,因此这个pos._node就不难理解了,实际上这个struct使用就是因为我们的struct默认的就是公有的,符合我们的使用要求,使用class也是可以的,但是要加上这个public表示我们的权限,因此class里面的内容默认是私有的,使用class之后,我们这个代码风格里面的pos._node就应该相应的修改;

另外,我们的这个迭代器使用的时候看似和其他的这个string .vector没有区别,就是进行遍历,但是实际上我们是封装了一个类的,为了封装这个类,我们定义了其他的两个类,这些都是我们在调用时候看不到的,我们只看到了这个迭代器可以进行遍历,但是实际上这个背后的功夫确是我们无法估量的,如果你认为很简单,自己独立实现一下就知道这个过程的难度了;

这个就好比我们普通的孩子和生来就条件好的孩子,生来条件就好的孩子好比string,vector人家就是可以直接调用这个*找到这个位置的数值,使用这个++进行这个循环的控制,但是我们普通人家的孩子就是list,我们没有他们的优势,但是我们可以通过自己的努力,实现一个类的封装,我们也可以实现相同的效果,就看肯不肯去克服实现这个类的路上的困难了;

看似就是一个list,却让我们从中看到了大部分人的影子,因为我们大部分都是list,没有先天的优势,但是只要我们肯付出努力,就可以实现相同的遍历的效果,因此,努力吧少年~我命由我不由天,努力奋进改尘寰~~我们要相信自己的努力,就是这个迭代器封装的类,我们最后也是会实现相同的效果的,list就是证明~~

7.代码附录

//#define _CRT_SECURE_NO_WARNINGS 1
//list.h文件,包含迭代器和一些常用的函数

#pragma once
#include<iostream>
using namespace std;

namespace bite
{
	template<class T>

	//对于节点定义一个类
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;

		//内置类型:0,0.0,空指针
		//自定义类型:调用默认构造,调用自己的模版
		list_node(const T& data = T())//第一次修改,-------给默认构造
			:_data(data)
			, _prev(nullptr)
			, _next(nullptr)
		{

		}
	};
	template<class T>
	
	struct list_iterator
	{
		typedef list_node<T> Node;

		typedef list_iterator<T>  self;
		Node* _node;

		list_iterator(Node* node)
			:_node(node)
		{

		}

		//使用引用是因为我们可以修改这个里面的数据
		T& operator*()
		{
			return _node->_data;
		}

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

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

		bool operator!=(const self& s) const
		{
			return _node != s._node;
		}

		bool operator==(const self& s) const
		{
			return _node == s._node;
		}
	};


	template<class T>

	class list
	{
		typedef list_node<T> Node;
	public:
		typedef list_iterator<T>  iterator;

		//返回的是一个节点,接受的是迭代器,但是这个是单参数构造函数支持隐式类型转换
		iterator begin()
		{
			//iterator it(_head->next);

			return _head->_next;
		}

		iterator end()
		{
			//最后一个元素的下一个位置
			return _head;
		}
		list()
		{
			_head = new Node(T());//这个是第一次报错的原因,---------------修改的地方
			//第一次报错是因为没有写这个地方的node的构造函数
			//new Node(x);需要我们传递匿名对象
			_head->_next = _head;
			_head->_prev = _head;
		}

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

			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;*/
			insert(end(), x);
		}

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

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

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

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

			Node* newnode = new Node(x);

			// prev newnode cur
			newnode->_next = cur;
			cur->_prev = newnode;
			newnode->_prev = prev;
			prev->_next = newnode;

			++_size;
		}

		void 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;
		}

		size_t size() const
		{
			return _size;
		}

		bool empty() const
		{
			return _size == 0;
		}
	private:
		Node* _head;
		size_t _size;
	};

	void test_list1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		list<int>::iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

}
#define _CRT_SECURE_NO_WARNINGS 1
//test.cpp测试文件,验证我们的迭代器的功能,是否可以正常的遍历链表
#include<iostream>
//#include<algorithm>
//#include<list>
//#include<vector>
//using namespace std;

#include"list.h"

int main()
{
	bite::test_list1();
	return 0;
}

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

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

相关文章

【C++ Primer Plus习题】17.1

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: #include <iostream> using namespace std;int main() {char …

移动登录页:让用户开启一段美好的旅程吧。

Hi,大家好&#xff0c;我是大千UI工场&#xff0c;移动登录页千千万&#xff0c;这里最好看&#xff0c;本期分享一批移动端的登录页面&#xff0c;供大家欣赏。 本次分享的是毛玻璃/3D风格的登录页。

【Unity设计模式】Unity MVC/MVP架构介绍,及MVC/MVP框架的简单应用

文章目录 什么是MVC&#xff1f;MVC眼花缭乱设计图MVP和MVC最经典的MVC的业务流程Unity MVC 框架示例1. 创建项目结构2. 实现模型3. 实现视图4. 实现控制器5. 使用示例 总结参考完结 什么是MVC&#xff1f; MVC自1982年被设计出来&#xff0c;至今都有着很大比重的使用率&…

前端项目代码开发规范及工具配置

在项目开发中&#xff0c;良好的代码编写规范是项目组成的重要元素。本文将详细介绍在项目开发中如何集成相应的代码规范插件及使用方法。 项目规范及工具 集成 EditorConfig集成 Prettier1. 安装 Prettier2. 创建 Prettier 配置文件3. 配置 .prettierrc4. 使用 Prettier 集成 …

python--基础语法(2)

1.顺序语句 默认情况下&#xff0c;Python的代码执行顺序是按照从上到下的顺序&#xff0c;依次执行的。 2.条件语句 条件语句能够表达“如果 ...否则 ...”这样的语义这构成了计算机中基础的逻辑判定条件语&#xff0c; 也叫做 分支语句。表示了接下来的逻辑可能有几种走向…

HOSTS文件劫持--导致笔记本网络卡顿

写在前面&#xff1a; 因为笔记本网速卡顿&#xff0c;去维修店维修网卡&#xff0c;网卡咱们测试都没有问题&#xff0c;一直吐槽售后服务一般。自己也装过几次系统了 点击任务栏中的搜索图标&#xff0c;输入"cmd"&#xff0c;点击"命令提示符"选择&qu…

笔记整理—内核!启动!—linux应用编程、网络编程部分(2)linux的文件管理策略

关于硬盘中的静态文件与inode&#xff1a;例如文件存储在扇区中&#xff0c;一个文件占用10个字节&#xff0c;一个扇区为512字节&#xff0c;这样的情况下一个扇区就只放了一个实际为10字节的文件&#xff0c;余下的502字节不可存放其他文件&#xff0c;因为扇区已经是可以访问…

C++入门(07)标准输入输出_cin

文章目录 4.cin4.1 基本功能4.2 常见数据类型的输入4.3 cin多项输入中的分隔符如果需要将空格作为输入的一部分读入 4.4 使用 cin 一次读取多个整数方法一方法二 接上一篇 cout C入门(07)标准输入输出_cout、缓冲、\n endl 4.cin 4.1 基本功能 C 标准输入 cin 是一个控制台输…

LLMs之MemLong:《MemLong: Memory-Augmented Retrieval for Long Text Modeling》翻译与解读

LLMs之MemLong&#xff1a;《MemLong: Memory-Augmented Retrieval for Long Text Modeling》翻译与解读 导读&#xff1a;MemLong 是一种新颖高效的解决 LLM 长文本处理难题的方法&#xff0c;它通过外部检索器获取历史信息&#xff0c;并将其与模型的内部检索过程相结合&…

IPsec-VPN中文解释

网络括谱图 IPSec-VPN 配置思路 1 配置IP地址 FWA:IP地址的配置 [FW1000-A]interface GigabitEthernet 1/0/0 [FW1000-A-GigabitEthernet1/0/0]ip address 10.1.1.1 24 //配置IP地址 [FW1000-A]interface GigabitEthernet 1/0/2 [FW1000-A-GigabitEthernet1/0/2]ip a…

C#测试调用PdfiumViewer浏览PDF文件的基本用法

印章管理项目后续准备实现打开浏览PDF文件并进行盖章的功能&#xff0c;需要在Winform中使用控件在线浏览PDF文件&#xff0c;在网上找了几个开源的PDF浏览控件进行测试&#xff0c;以便应用于印章管理项目。本文测试调用PdfiumViewer模块打开及浏览PDF文件。   PdfiumViewer…

VisionPro - 基础 - 模板匹配技术和在VP中的使用 - PMAlign - PatMax(4)- 控制模板的匹配

前言&#xff1a; 针对PatMax 的高级应用和原理&#xff0c;在这一节继续进行说明&#xff1a;这一节主要考虑的是PatMax模板匹配的原理&#xff1a;如何控制模板的匹配。 本节先介绍了几个模板匹配的衡量标准&#xff0c;比如模板匹配分数&#xff0c;和模板的几种模板匹配的…

JAVA并发编程系列之Semaphore信号量剖析

腾讯T2面试&#xff0c;现场限时3分钟限最多20行代码&#xff0c;模拟地铁口安检进站。其中安检入口10个&#xff0c;当前排队人数是100个&#xff0c;每个人安检进站耗时5秒。开始吧! 候选人&#xff0c;心中万马奔腾&#xff01;&#xff01;&#xff01;吐了一口82年老血&am…

re题(37)BUUCTF-[GWCTF 2019]xxor

BUUCTF在线评测 (buuoj.cn) 用ida打开文件&#xff0c;ctrle定位main函数 也可以用shiftF12查找字符串&#xff0c;找与我们解题有关的字符串 通过字符串定位到引用字符串的函数 进入main entry 但还不是我们要分析的代码 进入__libc_start_main中的main参数&#xff0c;是我们…

C++20 std::format

一、前言 1、传统 C 格式化的问题与挑战 可读性差&#xff1a;使用 C 中的 printf 和 scanf 家族函数进行格式化输出和输入时&#xff0c;它们的语法较为复杂&#xff0c;难以阅读。在较大的代码项目中&#xff0c;可读性差会导致维护困难。类型安全性差&#xff1a;printf 和…

IS-ISv4/6双栈

文章目录 IS-ISv4/6双栈实验要求配置 IS-ISv4/6双栈 实验要求 配置双栈 R1、2、3、4配置 IS-ISv4 和 IS-ISv6&#xff0c;配置IPv6多拓扑 上面为Level-1类型、中间为Level-1-2、下面是Level-2类型 还有就是说ATT位置1有一定要求连接L1/2连接L1或者L2类型路由器&#xff0c;至…

java23发布啦

2024年9月java23发布啦&#xff01;&#xff01;! JDK 23 提供了12 项增强功能&#xff0c;这些功能足以保证其自己的JDK 增强提案 - JEP &#xff0c;其中包括 8 项预览功能和 1 项孵化器功能。它们涵盖了对 Java 语言、API、性能和 JDK 中包含的工具的改进。除了 Java 平台上…

KVM环境下制作ubuntu qcow2格式镜像

如果是Ubuntu KVM环境是VMware虚拟机&#xff0c;需要CPU开启虚拟化 1、配置镜像源 wget -O /etc/apt/sources.list https://www.qingtongqing.cc/ubuntu/sources.list2、安装kvm qemu-img libvirt kvm虚拟化所需环境组件 apt -y install qemu-kvm virt-manager libvirt-da…

安装黑群晖,并使用NAS公网助手实现DDNS动态域名解析

很多人都会安装安装一个黑群晖进行练手&#xff0c;黑群晖有很多玩法和NAS套件&#xff0c;而且黑群晖安装比较简单&#xff0c;没有复杂的步骤&#xff0c;这也是很多人玩黑裙的理由&#xff0c;这里教大家如何安装黑群晖&#xff0c;并且安装神卓互联NAS公网助手实现DDNS动态…

arthas -- xxljob本地调试

方案一&#xff1a;测试类 package cn.wanda.wic.content.job.xxljob;import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;SpringBootTest public class ShopResourceMigrationJobTest {Reso…