【C++】list详解及模拟实现

news2024/11/17 3:32:55

目录

1. list介绍 

2. list使用

2.1 修改相关

2.2 遍历 

2.3 构造

2.4 迭代器

2.5 容量相关

2.6 元素访问

2.7 操作相关

3. 模拟实现

3.1 节点类

3.1.1 初始结构

3.1.2 节点的构造函数

3.2 迭代器类

3.2.1 初始结构

3.2.2 迭代器++

3.2.3 迭代器-- 

3.2.4 解引用重载 

3.2.5 比较重载 

3.3 链表类

3.3.1 初始结构

3.3.2 初始化链表

3.3.3 默认成员函数 

3.3.3.1 无参构造

3.3.3.2 析构

3.3.3.3 拷贝构造

3.3.3.4 赋值重载

3.3.4 Iterators(迭代器)

3.3.4.1 const迭代器

3.3.5 Modifiers(修改相关) 

3.3.5.1 insert(pos插)

3.3.5.2 erase(pos删)

3.3.5.3 push_back(尾插)与push_front(头插)

3.3.5.4 pop_front(头删)与pop_back(尾删)

3.3.5.5 clear(清除节点)

3.3.6 size(返回数据个数)

4. 打印

4.1 不同容器的打印

5. list和vector的区别


1. list介绍 

1. list是一个带头双向循环链表。

2. list的声明,一个模板参数还有一个空间配置器。

3. Lists are sequence containers that allow constant time insert and erase operations anywhere within the sequence, and iteration in both directions.

4. 大框架:默认成员函数,迭代器,容量相关,元素访问,修改相关,排序,合并等。

2. list使用

2.1 修改相关

1. 支持头插,头删,尾插,尾删,pos插,pos删。

void test1()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
}

2. assign是赋值,可以用迭代器区间赋值或n个val赋值。

2.2 遍历 

1. list不再支持方括号[ ]。

2. 主要还是用迭代器进行遍历,迭代器是内嵌类型,一般用typedef或内部类实现。

3. 支持了迭代器就支持范围for,因为范围for的底层就是迭代器。

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

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

	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

2.3 构造

1. 无参构造,n个val构造,迭代器区间构造,拷贝构造。 

2.4 迭代器

1. 有正向迭代器和反向迭代器,并且各自有const版本。

2. 虽然说新加了cbegin这些const系列,但其实begin本身也有const版本。

2.5 容量相关

1. 判空,返回数据个数,max_size没什么意义。

2.6 元素访问

1. 访问头和尾。

2.7 操作相关

1. reverse,逆置

void test2()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.reverse();
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

2. sort,排序,默认是升序,降序需要用到仿函数。

void test3()
{
	list<int> lt;
	lt.push_back(5);
	lt.push_back(3);
	lt.push_back(1);
	
	lt.sort();
	//lt.sort(greater<int>());

	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

3. merge,两个链表归并到一起。

4. unique,去重,要求有序。

void test4()
{
	list<int> lt(5, 1);
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.unique();
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

5. remove,直接删除val值。

6. splice,转移节点。 

3. 模拟实现

3.1 节点类

3.1.1 初始结构

1. 管理节点的类模板,包含节点的数据,节点的前驱指针和后继指针。

	template<typename T>
	struct list_node
	{
		T _data; //节点数据
		list_node<T>* _prev; //前驱指针
		list_node<T>* _next; //后继指针
	};

3.1.2 节点的构造函数

1. 参数是节点数据,缺省值是该类型的无参匿名对象。

2. 初始化列表进行成员初始化,节点数据用传入的参数,指针初始为空。 

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

3.2 迭代器类

3.2.1 初始结构

1. 这里用struct而不是class,因为所有都可以公开,不受访问限定符限制。

2. 成员是一个节点类型的指针。

3. 构造函数,传入一个节点类型的指针,在初始化列表初始化。

	template<typename T>
	struct list_iterator
	{
		typedef list_node<T> node;

		node* _cur; //迭代器成员

		//构造
		list_iterator(const node*& cur)
			:_cur(cur)
		{}
	};

3.2.2 迭代器++

1. 前置++,指针指向自己的后一个节点,返回自己的引用。

		list_iterator& operator++()
		{
			_cur = _cur->_next;
			return *this;
		}

2. 后置++,先把自己拷贝给tmp然后++,返回tmp。后置的参数需要一个int作为标识。

		list_iterator operator++(int)
		{
			list_iterator tmp(*this);
			_cur = _cur->_next;
			return tmp;
		}

3.2.3 迭代器-- 

1. 前置--,指针指向自己的前一个节点,返回自己的引用。

		list_iterator& operator--()
		{
			_cur = _cur->_prev;
			return *this;
		}

2. 后置--,先把自己拷贝给tmp然后--,返回tmp。

		list_iterator operator--(int)
		{
			list_iterator tmp(*this);
			_cur = _cur->_prev;
			return tmp;
		}

3.2.4 解引用重载 

1. *,解引用重载,返回节点数据的引用。

2. ->,箭头解引用,返回节点数据的地址。这里有两个箭头但被编译器省略了一个,第一个箭头是获取节点中数据成员的地址,第二个箭头是通过这个地址获取数据成员内的成员,因为数据成员可能是自定义类型。

		T& operator*()
		{
			return _cur->_data;
		}
		T* operator->()
		{
			return &_cur->_data;
		}

3.2.5 比较重载 

1. !=重载,和其他迭代器比较,传入迭代器的引用,比较它们的成员。

2. ==重载,传入一个迭代器,进行成员比较。

		bool operator==(const list_iterator& it)
		{
			return _cut == it._cut;
		}
		bool operator!=(const list_iterator& it)
		{
			return !(*this == it);
		}

3.3 链表类

3.3.1 初始结构

1. 管理链表的类模板,成员只有头结点,这个链表是带头双向循环链表。

namespace lyh
{
	//带头循环双向链表
	template<typename T>
	class list
	{
	public:
		typedef list_node<T> node;

	private:
		node* _head; //链表头结点
	};
}

3.3.2 初始化链表

1. 创建第一个节点也就是头结点。

2. 前驱指针指向自己,后继指针指向自己。

		void ListInit()
		{
			_head = new node;
			_head->_prev = _head;
			_head->_next = _head;
		}

3.3.3 默认成员函数 

3.3.3.1 无参构造

1. 直接调用初始化。​​​​​​​

		list()
		{
			ListInit();
		}
3.3.3.2 析构

1. 复用clear,再释放头结点然后置空,因为clear不清头结点。 

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
3.3.3.3 拷贝构造

1. 先调用初始化函数生成头结点。

2. 用范围for遍历传入的链表,不断给自己尾插。

		list(cosnt list& lt)
		{
			ListInit();
			for (auto e : lt)
			{
				push_back(e);
			}
		}
3.3.3.4 赋值重载

1. 传入一个链表,拷贝构造给形参。

2. 将自己和形参交换,交换成员。

3. 返回自己的引用。

		void swap(const list& lt)
		{
			std::swap(_head, lt._head);
		}
		list& operator=(list lt)
		{
			swap(lt);
			return *this;
		}

3.3.4 Iterators(迭代器)

1. list类外写了迭代器对象,list类内提供begin和end。

2. begin,返回开始位置的迭代器,返回头结点的下一个节点,会隐式类型转换。

3. end,返回最后一个位置的下一个位置的迭代器,就是头节点,返回地址会隐式类型转换成迭代器。

		iterator begin()
		{
			return _head->_next;
		}
		iterator end()
		{
			return _head;
		}
3.3.4.1 const迭代器

1. 在写一个const迭代器类,相比原本的迭代器就是不能修改指向的内容。

2. 修改内容是通过解引用修改的,所以需要把和解引用的函数修改一下,把解引用的返回值加一个const。

3. 同时在list类提供const版本的begin和end,参数加一个const,返回值是const迭代器。


1. 上面这种方法会造成代码冗余,接下来的方法可以复用代码。

2. 就算类模板相同,只要模板参数不同就是不同的类型。

3. 增加两个模板参数,一个代表引用,一个代表指针,这两个模板参数写在解引用返回值的位置,根据你传什么模板参数,我的返回值就是什么样的,因为迭代器与const迭代器只有解引用的返回值有差别,所以把这两个返回值变成模板参数自己传,其他代码都可以复用。

4. 在list类里面定义迭代器类型和cosnt迭代器类型,这里可以把模板参数写好,然后提供const版本的begin和end。

​​​​​​​

3.3.5 Modifiers(修改相关) 

3.3.5.1 insert(pos插)

1. 传入一个迭代器和节点数据,返回的迭代器指向新插入的节点。

2. 先把迭代器指向的位置赋值给cur,再获取迭代器指向位置的前一个节点prev,建新节点new,new插入在prev和cur之间。

3. 这里迭代器不失效。

		iterator insert(iterator pos, const T& val)
		{
			node* cur = pos._cur;
			node* prev = cur->_prev;
			node* newnode = new node(val);

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

			return newnode;
		}
3.3.5.2 erase(pos删)

1. 传入迭代器,返回下一个位置的迭代器。

2. 把迭代器指向的位置赋值给cur,获取迭代器的前一个位置prev和后一个位置next。

3. 将prev和next连接,释放cur。

4. 删除后迭代器失效。

		iterator erase(iterator pos)
		{
			node* cur = pos._cur;
			node* prev = cur->_prev;
			node* next = cur->_next;

			prev->_next = next;
			next->_prev = prev;
			delete cur;

			return next;
		}
3.3.5.3 push_back(尾插)与push_front(头插)

1. 传入新节点的节点数据,无返回值。

2. 复用insert,尾插在end迭代器前面插入,头插在begin迭代器前面插入。

		//尾插
		void push_back(const T& val)
		{
			insert(end(), val);
		}
		//头插
		void push_front(const T& val)
		{
			insert(begin(), val);
		}
3.3.5.4 pop_front(头删)与pop_back(尾删)

1. 无参无返。

2. 复用erase,头删删除begin迭代器指向的位置,尾删删除end前一个位置。

		//头删
		void pop_front()
		{
			erase(begin());
		}
		//尾删
		void pop_back()
		{
			erase(--end());
		}
3.3.5.5 clear(清除节点)

1. 用迭代器遍历,然后不断erase。 

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

3.3.6 size(返回数据个数)

1. 这里需要加一个size成员用来记录数据个数。

2. 初始化函数把size置0,insert函数++size,erase函数--size,交换成员也要多一个。

		size_t size()
		{
			return _size;
		}

	private:
		node* _head; //链表头结点
		size_t _size; //数据个数

完整代码:List/List · 林宇恒/code-cpp - 码云 - 开源中国 (gitee.com)

4. 打印

1. 这里的list<T>只是类模板还没实例化,不能去取const_iterator。 

2. 因为静态成员变量也可以通过类域访问,然后list<T>是未实例化的类模板,编译器不能去里面找,所以这里编译器分不清是静态成员变量还是内嵌类型。

3. 在前面加一个typename就是告诉编译器这是一个内嵌类型,等实例化之后再去里面取。

4.1 不同容器的打印

 

5. list和vector的区别

1. 随机访问肯定用vector。大量头部,中部的修改肯定用list。vector适合尾插。 

2. 迭代器erase都会失效,list insert不会,vector会。

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

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

相关文章

1.随机事件与概率

第一章 随机时间与概率 1. 随机事件及其运算 1.1 随机现象 ​ 确定性现象&#xff1a;只有一个结果的现象 ​ 确定性现象&#xff1a;结果不止一个&#xff0c;且哪一个结果出现&#xff0c;人们事先并不知道 1.2 样本空间 ​ 样本空间&#xff1a;随机现象的一切可能基本…

ML 系列:机器学习和深度学习的深层次总结(05)非线性回归

图 1.不同类型的回归 一、说明 非线性回归是指因变量和自变量之间存在非线性关系的模型。该模型比线性模型更准确、更灵活&#xff0c;可以获取两个或多个变量之间复杂关系的各种曲线。 二、关于 当数据之间的关系无法用直线预测并且呈曲线形式时&#xff0c;我们应该使用非线性…

MySQL篇(索引)(持续更新迭代)

目录 一、简介 二、有无索引情况 1. 无索引情况 2. 有索引情况 3. 优劣势 三、索引结构 1. 简介 2. 存储引擎对于索引结构的支持情况 3. 为什么InnoDB默认的索引结构是Btree而不是其它树 3.1. 二叉树&#xff08;BinaryTree&#xff09; 3.2. 红黑树&#xff08;RB&a…

6、等级保护政策内容

数据来源&#xff1a;6.等级保护政策内容_哔哩哔哩_bilibili 信息安全产品管理与响应 等级管理 对信息系统中使用的信息安全产品实行按等级管理&#xff0c;信息安全事件应分等级响应与处置。 预测评服务由测评公司和咨询公司提供预测评服务&#xff0c;根据技术要求和测评要…

高校心理辅导系统:Spring Boot技术实现指南

目 录 摘 要 I ABSTRACT II 1绪 论 1 1.1研究背景 1 1.2设计原则 1 1.3论文的组织结构 2 2 相关技术简介 3 2.1Java技术 3 2.2B/S结构 3 2.3MYSQL数据库 4 2.4Springboot框架 4 3 系统分析 6 3.1可行性分析 6 3.1.1技术可行性 6 3.1.2操作可行性 6 3.1.3经济可行性 6 3.1.4法律…

[OpenGL]使用OpenGL绘制带纹理三角形

一、简介 本文介绍了如何使用使用OpenGL绘制带纹理三角形。 在绘制带纹理的三角形时&#xff0c; 首先使用.h读取准备好的.png格式的图片作为纹理&#xff0c;然后在fragment shader中使用 ... in vec2 textureCoord; uniform sampler2D aTexture1; void main() {FragColor …

嵌入式 开发技巧和经验分享

文章目录 前言嵌入式 开发技巧和经验分享目录1.1嵌入式 系统的 定义1.2 嵌入式 操作系统的介绍1.3 嵌入式 开发环境1.4 编译工具链和优化1.5 嵌入式系统软件开发1.6 嵌入式SDK开发2.1选择移植的系统-FreeRtos2.2FreeRtos 移植步骤2.3 系统移植之中断处理2.4系统移植之内存管理2…

奥比中光深度相机相关使用内容

奥比中光深度相机相关使用内容 Windows平台测试官方软件关于python环境的配置1、安装CMake2、安装Visual Studio3、项目地址下载4、配置Visual Studio5、完成基于Python的SDK配置官网教学视频地址 3D视觉开发者社区 官方文档地址 效果: Windows平台测试官方软件 Window…

《高等代数》线性相关和线性无关(应用)

说明&#xff1a;此文章用于本人复习巩固&#xff0c;如果也能帮到大家那就更加有意义了。 注&#xff1a;1&#xff09;线性相关和线性无关的证明方法中较为常用的方法是利用秩和定义来证明。 2&#xff09;此外&#xff0c;线性相关和线性无关的证明常常也会用到反证法。 3&…

简单水印通过python去除

简单水印通过python去除 先看效果&#xff0c;如果效果不是你需要的就可以不用浪费时间。 注意&#xff1a;这种主要还是对应的文字在水印上方的情况&#xff0c;同时最好不要有渐变水印否则可能最后输出的图片的水印还会有所残留&#xff0c;不过还是学习使用&#xff0c;相信…

DOS(Disk Operating System,磁盘操作系统)常用指令

目录 背景: 早期探索: DOS之父&#xff1a; 发展历程&#xff1a; 常用指令&#xff1a; 进入命令&#xff1a; 操作1.进入和回退&#xff1a; 操作2.增、删&#xff1a; 操作3.其它&#xff1a; 总结: 背景: 早期探索: DOS(Disk Operating System,磁盘操作系统)在…

【Web】PolarCTF2024秋季个人挑战赛wp

EZ_Host 一眼丁真命令注入 payload: ?host127.0.0.1;catf* 序列一下 exp: <?phpclass Polar{public $lt;public $b; } $pnew Polar(); $p->lt"system"; $p->b"tac /f*"; echo serialize($p);payload: xO:5:"Polar":2:{s:2:"…

VSCode C++ Tasks.json基本信息介绍

前言 上文介绍了VSCode在Windows环境下如果创建C项目和编译多个文件项目&#xff0c;但是只是粗略的说明了一下Tasks.json文件。今天对tasks.json进行进一步的了解。 内容 Tasks文件 {"version": "2.0.0","tasks": [{"type": &quo…

C#进阶-基于雪花算法的订单号设计与实现

在现代电商系统和分布式系统中&#xff0c;高效地生成全局唯一的订单号是一个关键需求。订单号不仅需要唯一性&#xff0c;还需要具备一定的趋势递增性&#xff0c;以满足数据库索引和排序的需求。本文将介绍如何在C#中使用雪花算法&#xff08;Snowflake&#xff09;设计和实现…

IntelliJ IDEA 2024创建Java项目

一、前言 本文将带领大家手把手创建纯Java项目&#xff0c;不涉及Maven。如有问题&#xff0c;欢迎大家在评论区指正说明&#xff01; 二、环境准备 名称版本jdk1.8idea2024 1.4操作系统win10 jdk的安装教程 idea的安装教程 三、创建项目 首先我们点击新建项目 然后我们…

基于SpringBoot+Vue+MySQL的国产动漫网站

系统展示 用户前台界面 管理员后台界面 系统背景 随着国内动漫产业的蓬勃发展和互联网技术的快速进步&#xff0c;动漫爱好者们对高质量、个性化的国产动漫内容需求日益增长。然而&#xff0c;市场上现有的动漫平台大多以国外动漫为主&#xff0c;对国产动漫的推广和展示存在不…

网页聊天——测试报告——Selenium自动化测试

一&#xff0c;项目概括 1.1 项目名称 网页聊天 1.2 测试时间 2024.9 1.3 编写目的 对编写的网页聊天项目进行软件测试活动&#xff0c;揭示潜在问题&#xff0c;总结测试经验 二&#xff0c;测试计划 2.1 测试环境与配置 服务器&#xff1a;云服务器 ubuntu_22 PC机&am…

十六、电压比较电路

电压比较电路 1、电压比较器的作用和工作过程 2、比较器的输入-输出特性曲线, 3、如何构成迟滞比较器。

Transformer模型-7- Decoder

概述 Decoder也是N6层堆叠的结构&#xff0c;每层被分3层: 两个注意力层和前馈网络层&#xff0c;同Encoder一样在主层后都加有Add&Norm&#xff0c;负责残差连接和归一化操作。 Encoder与Decoder有三大主要的不同&#xff1a; 第一层 Masked Multi-Head Attention: 采用…

SpringCloud Alibaba 工程搭建详细教程

使用 Spring Cloud Alibaba 的主要目的是解决单体应用的痛点&#xff0c;并利用微服务架构的优势来构建高扩展性、可靠的分布式系统。 1. 单体应用的痛点 单体应用虽然在小型项目中优势明显&#xff0c;但随着业务复杂性增加&#xff0c;逐渐暴露出许多问题&#xff1a; 代码…