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

目录

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.mfbz.cn/a/882193.html

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

相关文章

基于VUE的医院抗生素使用审核流程信息化管理系统

开发背景 随着医疗行业的快速发展和信息技术的不断进步&#xff0c;医院内部管理系统的信息化建设变得尤为重要。抗生素作为治疗感染性疾病的重要药物&#xff0c;在临床使用过程中需要严格控制以避免滥用导致的耐药性问题。传统的抗生素使用审核流程往往依赖于人工审核&#x…

第十一章 从0-1搭建一个简单的JavaWeb系统(三)

目录 一、工程代码结构 二、代码实现 三、运行效果 四、未完待续 本章节的每一段代码&#xff0c;建议全部自己敲一遍&#xff0c;加深印象&#xff0c;切勿直接复制黏贴。 一、工程代码结构 本章节实现注销&#xff08;退出&#xff09;功能&#xff0c;以下图片中标红的…

苹果CMS插件:优化蜘蛛访问内容,提升百度收录率

确保蜘蛛抓取原始内容 专为苹果CMS设计的广告管理插件&#xff0c;能够智能识别搜索引擎蜘蛛与普通访客&#xff0c;确保蜘蛛访问时展示原始内容&#xff0c;从而提升被百度等搜索引擎收录的几率。 广告显示提升收益 对于普通访客&#xff0c;该插件则优先显示广告内容&#…

【网络】高级IO——select版本TCP服务器

目录 前言 一&#xff0c;select函数 1.1.参数一&#xff1a;nfds 1.2.参数二&#xff1a; readfds, writefds, exceptfds 1.2.1.fd_set类型和相关操作宏 1.2.2.readfds, writefds, exceptfds 1.2.3.怎么理解 readfds, writefds, exceptfds是输入输出型参数 1.3.参数三…

面试速通宝典——1

1. 内存有哪几种类型&#xff1f; ‌‌‌‌  内存分为五个区&#xff0c;堆&#xff08;malloc&#xff09;、栈&#xff08;如局部变量、函数参数&#xff09;、程序代码区&#xff08;存放二进制代码&#xff09;、全局/静态存储区&#xff08;全局变量、static变量&#…

2024-1.2.12-Android-Studio配置

本地博客: https://k1t0111.github.io/ K1T0 最近在做一些app方向的移动技术开发学习&#xff0c;但是由于AS的配置问题&#xff0c;市面上找不到最新的2024版本的AS的相关配置。笔者也是踩了很多坑&#xff0c;因此想写一篇文章记录一下最新的AS 2024 1.2.12的对应java环境的一…

springboot框架VUE3学院网站系统开发mysql数据库设计java编程计算机网页源码maven项目

博主介绍&#xff1a;专注于Java vue .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的…

python 识别省市、区县并组建三级信息数据库

一、网址&#xff1a; 全国行政区划信息查询平台 二、分析并搭建框架 检查网页源码&#xff1a; 检查网页源码可以发现&#xff1a; 所有省级信息全部在javaScript下的json中&#xff0c;会在页面加载时加载json数据&#xff0c;填充到页面的option中。 1、第一步&#xff1a…

探秘 Web Bluetooth API:连接蓝牙设备的新利器

引言 随着物联网技术的快速发展&#xff0c;蓝牙设备在日常生活中扮演着越来越重要的角色。而在 Web 开发领域&#xff0c;Web Bluetooth API 的出现为我们提供了一种全新的方式来连接和控制蓝牙设备。本文将深入探讨 Web Bluetooth API 的使用方法和原理&#xff0c;帮助开发…

浅显易懂的Git教程

Git概述 SVN与Git的对比 SVN&#xff08;Subversion&#xff09; 类型&#xff1a;集中式版本控制系统 工作流程&#xff1a; 从中央服务器下载最新版本到本地。在本地进行开发。提交更改回中央服务器。 优点&#xff1a; 简单易用&#xff0c;适合小型团队。版本历史清…

vs2022快捷键异常不起作用解决办法

安装了新版本的vs2022&#xff0c;安装成功后&#xff0c;发现快捷键发生异常&#xff0c;之前常用的快捷键要么发生改变&#xff0c;要么无法使用&#xff0c;比如原来注释代码的快捷键是ctrlec&#xff0c;最新安装版本变成了ctrlkc&#xff0c;以前编译代码的快捷键是F6或者…

初始MYSQL数据库(6)—— 事务

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; MYSQL 目录 事务的概念 事务的ACID特性 使用事务 查看支持事务的存储引擎 事务的语法 保存点 自动/手动提交事务 事务的隔离性和…

Python模拟鼠标轨迹[Python]

一.鼠标轨迹模拟简介 传统的鼠标轨迹模拟依赖于简单的数学模型&#xff0c;如直线或曲线路径。然而&#xff0c;这种方法难以捕捉到人类操作的复杂性和多样性。AI大模型的出现&#xff0c;能够通过深度学习技术&#xff0c;学习并模拟更自然的鼠标移动行为。 二.鼠标轨迹算法实…

【原创】java+swing+mysql仓库管理系统设计与实现

个人主页&#xff1a;程序员杨工 个人简介&#xff1a;从事软件开发多年&#xff0c;前后端均有涉猎&#xff0c;具有丰富的开发经验 博客内容&#xff1a;全栈开发&#xff0c;分享Java、Python、Php、小程序、前后端、数据库经验和实战 文末有本人名片&#xff0c;希望和大家…

Qt开发技巧(四)“tr“使用,时间类使用,Qt容器取值,类对象的删除,QPainter画家类,QString的转换,用好 QVariant类型

继续讲一些Qt技巧操作 1.非必要不用"tr" 如果程序运行场景确定是某一固定语言&#xff0c;就不需要用tr,"tr"之主要针对多语种翻译的&#xff0c;因为tr的本意是包含英文&#xff0c;然后翻译到其他语言比如中文&#xff0c;不要滥用tr&#xff0c;如果没有…

‌内网穿透技术‌总结

内网穿透是一种网络技术&#xff0c;通过它可以使外部网络用户访问内部网络中的设备和服务。一般情况下&#xff0c;内网是无法直接访问的&#xff0c;因为它位于一个封闭的局域网中&#xff0c;无法从外部访问。而通过内网穿透&#xff0c;可以将内部网络中的设备和服务暴露在…

底盘四轮转向运动学解析(含代码)

目录 写在前面的话四轮转向运动学解析四轮转向理论图解robot_control.py 完整代码关键参数完整代码 公式解析&#xff08;根据代码&#xff09;反相--模式1详细图解 正相--模式2轴心--模式3 写在前面的话 网上找了很多资料&#xff0c;对于四轮转向运动学描述的很少&#xff0…

爬虫过程 | 蜘蛛程序爬取数据流程(初学者适用)

蜘蛛程序&#xff08;也称网络爬虫&#xff0c;是搜索引擎的重要组成部分&#xff09; 主要功能&#xff1a;遍历互联网&#xff0c;抓取网站信息并建立索引&#xff0c;便于用户在搜索引擎中检索到最新的网页内容工作原理&#xff1a;从初始网站页面的URL开始&#xff0c;发送…

最适配达梦、人大金仓的sql工具是什么?

SQLynx是一款功能强大的数据库管理工具&#xff0c;它不仅支持Oracle、MySQL等国际主流数据库&#xff0c;还很好地支持了武汉达梦、人大金仓等国产数据库。这款工具具有以下几个特点&#xff1a; 1.广泛支持&#xff1a;SQLynx支持多种数据库系统&#xff0c;包括PostgreSQL、…

MySQL学习笔记(持续更新中)

1、Mysql概述 1.1 数据库相关概念 三个概念&#xff1a;数据库、数据库管理系统、SQL 名称全称简称数据库存储数据的仓库&#xff0c;数据是有组织的进行存储DataBase&#xff08;DB&#xff09;数据库管理系统操纵和管理数据库的大型软件DataBase Mangement System&#xf…