list底层详解

news2024/9/25 9:40:39

目录

介绍

list的实现 

1.自定义节点

2.迭代器封装

构造函数

前置++和后置++

前置--和后置--

*操作符和->操作符

==和!=操作符

iterator和const_iterator

3. list类

构造函数和析构函数

=赋值操作

头尾迭代器

插入和删除

头插头删尾插尾删

list接口函数总代码


介绍

1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

List原文档,我们可以去cplusplus网站去查看详情

https://cplusplus.com/reference/list/list/?kw=list

list的实现 

1.自定义节点

因为是双向链表,所以我们只需要定义前指针和后指针,以及该节点的值data,因为我们以后要经常访问里面的内容所以使用结构体更合适.另外需要一个默认构造函数来方便以后的拷贝和初始化;

template<class T>
struct Node
{
	struct Node* next;
	struct Node* prev;  
	T data;
	Node(const T& val=T())
		:prev(nullptr), next(nullptr), data(val)//初始化
	{}
};

2.迭代器封装

迭代器作为STL中一个万能的访问方式,在任何一个容器中都是可以使用++,*像这样的操作的,我们之前在vector中iterator是对T*进行别名的,因为vector是连续性的容器,所以T*是可以++来进行遍历操作的,但是list是链表,其地址是不连续的,所以无法直接对迭代器进行++等操作,因此我们需要对Node* 和多个重载运算符来封装成iterator 和const iterator;

迭代器包装的还是T* ,在这里是Node*,后面++操作需要对迭代器本身处理,所以我们也把listiterator<T>简化成self; 

构造函数

	//封装迭代器  
	template<class T>
	class listiterator
	{
		typedef Node<T> node;
		typedef listiterator <T>   self;
	public:
		listiterator(node* node)   //拷贝节点  迭代器还是node* 只是多添加了些运算符操作
			:_node(node)
		{}
		
		node* _node;
	};

iterator只需要一个拷贝构造函数即可;直接将_node初始化为node;

前置++和后置++

这个迭代器本身就是一个类,所以自增后返回的是一个类,这就用到了self,前置++:直接将_node指针后移指向next即可;然后返回*this;后置的就需要一个中间变量来储存操作前的迭代器,然后返回临时变量,需要注意的是后置++返回的是临时变量,因此返回的类型不能使用引用.

	//前置++
	self operator ++()
	{
		_node = _node->next;
		return *this;
	}
	//后置++
	self  operator++(int)
	{
		self  tmp(*this);
		_node = _node->next;
		return tmp;
	}

前置--和后置--

与上面的操作模版相同,返回的是上一个节点;

	//前置--
	self& operator --()
	{
		_node = _node->prev;
		return *this;
	}
	//后置--
	self  operator --(int)
	{
		self tmp(*this);
		_node = _node->prev;
		return tmp;
	}

*操作符和->操作符

解引用操作直接返回节点的值即可,这里比较难以理解的就是->返回的是值data的地址,这里调用时省略了一个operator->(),因为迭代器本身就是一个类重载的操作符->指向的就是_node,然后_node才能访问data,这里这样写可以直接访问到data;

		//*迭代器
		T& operator*()
		{
			return _node->data;
		}
		//->迭代器
		T* operator->()
		{
			return &_node->data;
		}

==和!=操作符

这里直接比较就行了;

bool operator !=(const self& it)const
{
	return it._node != _node;
}
//迭代器相等
bool operator ==(const self& it)const
{
	return it._node == _node;
}

iterator和const_iterator

我们知道const_iterator是不能够改变内部的值的,所以我们为了写出一个const_iterator直接通过模版来让编译器来写;其中与能够改变内部值的只有*和->操作,我们只需要把返回值改成const T&和const T*就能实现常迭代器,为此,我们直接将这两个函数的返回值定义为模版ref和ptr;这样就能轻松完成两种迭代器了;

template<class T,class ref,class ptr>
class listiterator
{
	typedef Node<T> node;  
	typedef listiterator <T ,ref,ptr>   self;            
public:  
	//*迭代器
	ref operator*()
	{
		return _node->data;                    
	} 
	//->迭代器
	ptr  operator->() 
	{
		return &_node->data;            
	}
}

3. list类

list的源码中私有成员是只有一个节点哨兵位_head的;我们先来把封装好的节点和迭代器造个别名再实现下构造函数;

构造函数和析构函数

因为构造函数和拷贝构造函数都需要先创造一个哨兵位,所以我们把哨兵位的创建写成函数.

拷贝构造函数直接遍历形参调用尾插push_back即可;

析构函数需要挨个的把所有的节点释放掉,最后释放掉哨兵位;这里消除节点我们可以直接用后面实现的erase;

template<class T>  
class list
{
	typedef Node<T>  node;//节点
public:
	typedef listiterator<T,T&,T*>  iterator;  //迭代器
	typedef listiterator  <T, const T&, const T*>const_iterator;   //常量迭代器
	//创建哨兵位
	void init()
	{
		_head = new node();                  
		_head->next = _head->prev =_head ;                                                                          
	}
	//构造函数
	list()   
	{
		init();   
	}
	//拷贝构造函数
	list(const list<T>&it)
	{
		init();       
		for (const auto& x : it)
		{
			push_back(x);     
		}
	}
//析构函数
~list()
{
	clear(); 
	delete _head;   
	_head = nullptr;   
}
//销毁函数
void clear()
{
	auto it = begin();      
	while (it != end())
	{
		it = erase(it);    
	}
}

=赋值操作

将一个类赋值给另一个类,我们现代的写法就是,形参不引用,使用swap与形参交换数据.

//list赋值操作
list<T>& operator=(list<T>  x)     
{
 	swap(_head, x._head);  //直接交换哨兵位
	return *this;   
}

头尾迭代器

//头迭代器
iterator  begin()              //it是局部变量,返回值不能引用   
{
		iterator it(_head->next);    
		return it;   
}
//常量迭代器
const_iterator  begin()const
{
	const_iterator it(_head->next);
	return it;
}
//尾迭代器
iterator  end()
{
	iterator it(_head);
	return it;  
}
//常量尾迭代器
const_iterator  end()const
{
	const_iterator it(_head);   
	return it;  
}

插入和删除

插入就比较简单了并且没有迭代器失效的风险,改变对应两个前后两个节点的指向就行了.虽然不需要返回迭代器,但是最好按照文档来;
删除需要注意的是需要将删掉的节点释放掉,返回pos原来节点的下一个位置的迭代器;

//插入
iterator     insert(iterator pos, T x)
{
	node* cur = pos._node;       
	node* net = cur->next;    
	node* newnode = new node(x);    

	cur->next = newnode;
	newnode->prev = cur;
	newnode->next = net;
	net->prev = newnode;   
	return iterator(newnode);      //返回迭代器     
}
//删除
iterator  erase(iterator  pos)
{
	assert(pos  !=  end());                                                   
	node* cur = pos._node;   
	node* pre = cur->prev;   
	node* net = cur->next;    

	pre->next = net;     
	net->prev = pre;
	delete cur;  
	return iterator(net);
}

头插头删尾插尾删

直接调用对应的insert和erase,begin()就是_head后面的第一个有效数据,end()就是哨兵位,所以我们需要--一下,回到尾节点;

		//头插
		void push_front(T x)
		{
			insert(begin(), x); 
		}
		//头删
		void pop_front()  
		{
			erase(begin());
		}
		//尾插
		void push_back(T x)  
		{
			insert(--end(), x);  
		}
		//尾删
		void pop_back()
		{
			erase(--end());    
		}

list接口函数总代码

#pragma once 
#include<iostream>
#include<assert.h>
#include<algorithm>
using namespace std;
namespace bit
{
	template<class T>
	struct Node
	{
		struct Node* next;
		struct Node* prev;  
		T data;
		Node(const T& val=T())
			:prev(nullptr), next(nullptr), data(val)//初始化
		{}
	};
//封装迭代器  
template<class T,class ref,class ptr>
class listiterator
{
	typedef Node<T> node;  
	typedef listiterator <T ,ref,ptr>   self;            
public:  
	listiterator(node* node)   //拷贝节点  迭代器还是node* 只是多添加了些运算符操作
		:_node(node)               
	{}
	//前置++
	self operator ++()
	{
		_node = _node->next;        
		return *this;   
	}
	//后置++
	self  operator++(int)
	{
		self  tmp(*this);
		_node = _node->next;
		return tmp;         
	}
	//前置--
	self& operator --()  
	{
		_node = _node->prev;    
		return *this;     
	}    
	//后置--
	self  operator --(int)
	{
		self tmp(*this);
		_node = _node->prev;      
		return tmp;   
	}
	//*迭代器
	ref operator*()
	{
		return _node->data;                    
	} 
	//->迭代器
	ptr  operator->() 
	{
		return &_node->data;            
	}
	//bool operaotr != ( const self& it)              
	//{
	//	return _node != iit.    
	//}
	//迭代器不相等
	bool operator !=(const self& it)const   
	{ 
		return it._node != _node;                  
	}
	//迭代器相等
	bool operator ==(const self& it)const 
	{
		return it._node == _node;    
	}
	node* _node;    
};
//list实现
	template<class T>  
	class list
	{
		typedef Node<T>  node;//节点
	public:
		typedef listiterator<T,T&,T*>  iterator;  //迭代器
		typedef listiterator  <T, const T&, const T*>const_iterator;   //常量迭代器
		//创建哨兵位
		void init()
		{
			_head = new node();                  
			_head->next = _head->prev =_head ;                                                                          
		}
		//构造函数
		list()   
		{
			init();   
		}
		//拷贝构造函数
		list(const list<T>&it)
		{
			init();       
			for (const auto& x : it)
			{
				push_back(x);     
			}
		}
		//析构函数
		~list()
		{
			clear(); 
			delete _head;   
			_head = nullptr;   
		}
		//销毁函数
		void clear()
		{
			auto it = begin();      
			while (it != end())
			{
				it = erase(it);    
			}
		}
		//list赋值操作
		list<T>& operator=(list<T>  x)     
		{
		 	swap(_head, x._head);          
			return *this;   
		}
		//头迭代器
		iterator  begin()              //加不加引用都可以,加引用就是引用的常量;不加引用就是拷贝的常量    
		{
				iterator it(_head->next);    
				return it;   
		}
		//常量迭代器
		const_iterator  begin()const
		{
			const_iterator it(_head->next);
			return it;
		}
		//尾迭代器
		iterator  end()
		{
			iterator it(_head);
			return it;  
		}
		//常量尾迭代器
		const_iterator  end()const
		{
			const_iterator it(_head);   
			return it;  
		}

		void print()
		{
			node* cur = _head->next;   
			while (cur != _head)
			{
				cout << cur->data<<" ";
				cur = cur->next;       
			}
			cout << endl; 
		}
		//插入
		iterator     insert(iterator pos, T x)
		{
			node* cur = pos._node;       
			node* net = cur->next;    
			node* newnode = new node(x);    

			cur->next = newnode;
			newnode->prev = cur;
			newnode->next = net;
			net->prev = newnode;   
			return iterator(newnode);      //返回迭代器     
		}
		//删除
		iterator  erase(iterator  pos)
		{
			assert(pos  !=  end());                                                   
			node* cur = pos._node;   
			node* pre = cur->prev;   
			node* net = cur->next;    

			pre->next = net;     
			net->prev = pre;
			delete cur;  
			return iterator(net);
		}
		//头插
		void push_front(T x)
		{
			insert(begin(), x); 
		}
		//头删
		void pop_front()  
		{
			erase(begin());
		}
		//尾插
		void push_back(T x)  
		{
			insert(--end(), x);  
		}
		//尾删
		void pop_back()
		{
			erase(--end());    
		}
	private:
		node* _head;   
	};
	void test()
	{
		bit::list<int>t;
		t.push_back(1);
		t.push_back(2);
		t.push_back(3);
		t.push_back(4);   
		t.push_back(5);
		 
		t.print();
		bit::list<int>t2;
		t2 = t;
		auto it = t2.begin();   //验证赋值操作
		t2.print();
		bit::list<int>t3(t2);
		auto it3 = t3.begin();
		while (it3 != t3.end())
		{
			cout << *it3 << " ";
			it3 = t3.erase(it3);
		}
		/*it++;
		cout << typeid(it).name()<<*it<<endl;
		auto it2 = t.end();
		cout << typeid(it2).name()<<*it << endl;
		bit::list<int>::iterator ita = t.begin();
		cout << *(it++) << endl; */


	}
}

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

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

相关文章

【开端】Java中最常用的一个类型String的使用

一、绪论 Java中最常用的一个类型是String&#xff0c;其实从JDK1.0 到JDK20 Java 开发工具包其实也经过很多演变&#xff0c;很多功能做了一些优化。这一节就把String类里的方法拎出来看看哪些是常用的&#xff0c;哪些经常用不到得到&#xff0c;哪些是已经有的&#xff0c;…

BaseCTF [第 2 周] lk

前言&#xff1a;做题笔记。 下载解压查壳。 64IDA打开。 查找字符串去。 跟进BaseCTF{ 找到 main 头部&#xff1a; 尾部&#xff1a; 程序&#xff1a; 选择题&#xff0c;咳&#xff0c;动漫迷可以做&#xff0c;也能得flag。(我不怎么看动漫。。) 告知我们&#xff0c;输…

【开发笔记】Notepad++配置

Notepad配置 Notepad保护色配置 settings --> Style Configurator 选择 Enable olobal foreground colourEnable global background colour 设置背景色 点击 Save & Close按钮&#xff0c;完成保存。 设置 Unix换行符

计算机网络 TCPUDP、IP、ARPRARP、NAT总结

文章目录 TCP 和 UDPUDPTCPTCP 三次握手半连接队列&#xff08;SYN队列&#xff09;全连接队列&#xff08;Accept队列&#xff09;TCP四次挥手为什么四次挥手为什么需要TIME_WAIT状态TIME_WAIT的危害为什么是2MSL 重传机制滑动窗口流量控制拥塞控制 IPIP地址分类A、B、C类地址…

javaee、ssm(maven)、springboot(maven)项目目录结构以及编译后文件目录存放路径

javaee项目目录结构&#xff1a; src下的文件或者是源码编译后都会放在WebRoot&#xff08;项目根目录&#xff09;文件夹\WebRoot\WEB-INF\classes目录中。 编译后的文件夹目录如下&#xff1a; 以上为普通的javaee项目目录结构&#xff0c;同maven工程目录结构是不一样的。…

Segment Anything:如何导出完整的ONNX模型?

在本文中&#xff0c;我将讨论 Segment Anything - 例如分割的神经网络&#xff0c;可用于从图像中分割任何对象而无需知道其类型。但是&#xff0c;这不是关于如何使用它的教程&#xff0c;因为它已经在官方存储库和其他类似文章中进行了描述。在这里&#xff0c;我将解释如何…

《计算机网络期末复习知识点大全》

目录 一、第一章 概述 1. TCP/IP分层网络体系结构、分层原因、作用 2. 时延、发送时延、传播时延 2.1 速率相关性能指标 2.1.1 速率 2.1.2 带宽 2.2 时间相关性能指标 2.2.1 发送时延 2.2.2 传播时延 2.3 考点例题 二、第二章 物理层 1. 编码与调制 2. 常用编码方…

人眼检测(单张图像-原始版)

目录 实验原理 实验代码 运行结果 改进代码 实验原理 要在C中使用OpenCV来检测图像中的人眼&#xff0c;你需要完成以下步骤&#xff1a; 安装OpenCV库并设置好开发环境。加载预训练的级联分类器&#xff08;通常是用于人脸和眼睛检测的XML文件&#xff09;。读取图像或视…

SQL-函数ing

1、字符串函数 # 字符函数 select concat(hello , mysql!); select lower(HELLO); select upper(hello); select lpad(01,5,-);# 左填充 select rpad(01,5,-);# 右填充 select trim( hello mysql ! );# 去除前后空格 select substring(hello mysql!,1,7);# 截取一部分字符前7…

【Java设计模式】非循环访问者模式:简化对象交互

文章目录 【Java设计模式】非循环访问者模式&#xff1a;简化对象交互一、概述二、非循环访问者设计模式的意图三、非循环访问者模式的详细解释及实际示例四、Java中非循环访问者模式的编程示例五、非循环访问者模式类图六、Java中何时使用非循环访问者模式八、非循环访问者模式…

XTuner微调个人小助手认知 #书生浦语大模型实战营#

1.任务&#xff1a; 本次的任务是使用 XTuner 微调 InternLM2-Chat-1.8B 实现自己的小助手认知&#xff0c;从而让模型能够个性化的回复&#xff0c;让模型知道他是我们的小助手&#xff0c;在实战营帮我们完成XTuner微调个人小助手认知的任务。并截图打卡。 任务打卡&#x…

深入探索【Hadoop】生态系统:Hive、Pig、HBase及更多关键组件(下)

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《大数据前沿&#xff1a;技术与应用并进》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、什么是Hadoop 2、Hadoop生态系统的构成概览 二…

【html+css 绚丽Loading】 000019 五行轮回剑

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽Loading&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495…

【云原生】Mysql 集群技术

PS&#xff1a;MySQL的源码编译进行实验环境操作 1、MySQL安装及初始化 &#xff08;1&#xff09;生成启动脚本 &#xff08;2&#xff09; 修改环境变量 &#xff08;3&#xff09;生成配置文件 &#xff08;4&#xff09;数据库初始化建立mysql基本数据 &#xff08;5&…

UnrealEngine学习(03):虚幻引擎术语

1. 项目 虚幻引擎5项目&#xff08;Unreal Engine 5 Project&#xff09; 中包含游戏的所有内容。项目中包含的大量文件夹都在磁盘上&#xff0c;例如 Blueprints 和 Materials 。你可以按照自己的意愿命名文件夹并将其整理到项目中。虚幻编辑器&#xff08;Unreal Editor&…

云网络/云探测+零信任架构初阶知识扫盲

一、关键&#xff08;边界&#xff09;节点 1、边界 &#xff08;1&#xff09;CiscoASA Firepower 思科 Firepower NGFW&#xff08;下一代防火墙&#xff09;是专注于威胁防御的下一代防火墙&#xff0c;它将多种功能完全集于一身&#xff0c;采用统一管理&#xff0c;可在…

荣耀应用商城——被下架应用申诉指南

申诉背景 为了营造良好的荣耀应用市场生态环境&#xff0c;保障开发者权益及提升用户体验&#xff0c;此流程针对应用被下架后开发者后续的具体申诉操作流程及详细介绍。 该流程适用于开发者在荣耀应用市场提交的应用&#xff0c;且应用当前处于被下架状态。 申诉流程 申诉定义…

PHP概述-环境搭建-开发工具安装

老师建议注册使用百度文心一言&#xff1b;讯飞星火大模型-AI大语言模型-星火大模型-科大讯飞&#xff1b;Kimi.ai - 帮你看更大的世界 等人工智能工具软件的一个到两个&#xff0c;也可下载文心一言、讯飞星火、kimi等APP软件使用&#xff0c;对于我们在读的大二学生来说有什么…

XR虚拟拍摄和VP有什么区别

XR 虚拟拍摄技术最早源于舞台屏当中,当前衍生出“VP 虚拟制片”、“XR 扩展现实"两大类: 扩展现实&#xff08;xR&#xff09;拍摄&#xff0c;扩展是指LED屏以外区域在画面中被虚拟图形所覆盖&#xff0c;扩展无限的虚拟空间&#xff1b;现实是指LED屏为导演和演员提供肉…

51、Python之模块和包:Python的包和文件夹有何区别

引言 大学有云&#xff1a;“苟日新&#xff0c;又日新&#xff0c;日日新”。 看到一些教材或者文章&#xff0c;介绍到包的时候&#xff0c;一定会提到一定要在文件夹中新建一个__init__.py的文件&#xff0c;哪怕空文件也可以…… 我只想说&#xff0c;有些人的知识真的是…