【浅尝C++】STL第三弹=>list常用接口使用示例/list底层结构探索/list模拟实现代码详解

news2024/11/24 16:49:04

在这里插入图片描述

🏠专栏介绍:浅尝C++专栏是用于记录C++语法基础、STL及内存剖析等。
🎯每日格言:每日努力一点点,技术变化看得见。

文章目录

  • list介绍
  • list常用接口使用示例
    • 构造类函数
    • 迭代器
    • 属性与元素获取
    • 增删改操作
  • list底层结构探索
  • list模拟实现
    • 正向迭代器实现
    • 增删操作
    • 属性获取操作
    • 构造类函数
    • 整体代码汇总
  • list与vector比较


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常用接口使用示例

下面仅给出list部分常用接口及使用示例,更多关于list接口的介绍请参阅->list参考文档

构造类函数

接口声明接口描述
list(size_type n, const value_type& val = value_type())用n个值为val的元素构造list
list()构造空的list
list(const list& x)拷贝构造函数
list(InputIterator first, InputIterator last)用[first,last)区间元素构造list

下面给出上述接口的示例代码↓↓↓

#include <iostream>
#include <string>
#include <list>
using namespace std;

void testList()
{
	list<int>lt1(5,8);
	for(auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;

	list<int>lt2;
	cout << "lt2's size is " << lt2.size() << endl;

	list<int>lt3(lt1);
	for(auto e : lt3)
	{
		cout << e << " ";
	}
	cout << endl;

	string s = "Jammingpro";
	list<char>lt4(s.begin(), s.end());
	for(auto e : lt4)
	{
		cout << e << " ";
	}
	cout << endl;
}

int main()
{
	testList();
	return 0;
}

在这里插入图片描述

迭代器

接口声明接口描述
begin + end返回第一个元素的迭代器/返回最后一个元素的下一位置的迭代器
rebegin + rend返回最后一个元素的下一位置/返回第一个元素的位置

在这里插入图片描述

list底层是带头双向链表,begin指向第一个有效元素(头结点的后继节点),end指向头结点,迭代器begin每次++,每次向后移动,当与end重合时,则正向迭代结束。rbegin指向头结点,end指向第一个有效元素(头结点的后继节点),当要对rbegin解引用时,rbegin底层会执行*(rbegin->prev),返回rbegin指向节点的前驱节点的数据。即使rbegin指向头结点,但对它解引用获得的是头节后前驱节点的数据;当rbegin与end重合时则迭代结束。

下面来看一下list迭代器的使用示例代码↓↓↓

#include <iostream>
#include <string>
#include <list>
using namespace std;

void testList()
{
	string s = "Jammingpro";
	list<char>lt(s.begin(), s.end());
	
	list<char>::iterator it = lt.begin();
	while(it != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	cout << "===================================" << endl;
	list<char>::reverse_iterator rit = lt.rbegin();
	while(rit != lt.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

int main()
{
	testList();
	return 0;
}

在这里插入图片描述
对于const类型的list容器,需要配套使用const_iterator/const_reverse_iterator来进行正反向迭代。

★ps:begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动;rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

属性与元素获取

接口声明接口声明
empty检查list是否为空
size返回list中有效节点的个数
front返回list的第一个节点中的值的引用
back返回list的最后一个节点中的值的引用

上面接口相较简单,这里这届给出示例代码↓↓↓

#include <iostream>
#include <string>
#include <list>
using namespace std;

void testList()
{
	string s = "Jammingpro";
	list<char>lt;
	cout << "lt is empty?" << lt.empty() << endl;
	cout << "The size of lt is " << lt.size() << endl;

	for(auto ch : s)
	{
		lt.push_back(ch);
	}
	cout << "lt is empty?" << lt.empty() << endl;
	cout << "The size of lt is " << lt.size() << endl;
	cout << "The first element is " << lt.front() << endl;
	cout << "The last element is " << lt.back() << endl;
}

int main()
{
	testList();
	return 0;
}

在这里插入图片描述

增删改操作

接口声明接口说明
push_front在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
pop_back删除list中最后一个元素
insert在pos位置中插入值为val的元素
erase删除pos位置的元素
swap交换两个list中的元素
clear情况list中的有效元素

上面接口的使用示例代码如下↓↓↓

#include <iostream>
#include <string>
#include <list>
using namespace std;

void testList()
{
	string s = "Jammingpro";
	list<char>lt(s.begin(), s.end());
	lt.push_back('!');
	lt.push_front('@');
	for(auto e : lt)
	{
		cout << e;
	}
	cout << endl;

	lt.pop_back();
	lt.pop_front();
	for(auto e : lt)
	{
		cout << e;
	}
	cout << endl;
	
	lt.insert(++lt.begin(), '#');
	for(auto e : lt)
	{
		cout << e;
	}
	cout << endl;
	
	lt.erase(++lt.begin());
	for(auto e : lt)
	{
		cout << e;
	}
	cout << endl;

	string s = "xiaoming";
	list<char> lt2(s.begin(), s.end());
	lt.swap(lt2);
	for(auto e : lt)
	{
		cout << e;
	}
	cout << endl;
	cout << "before clear size is " << lt.size() << endl;
	lt.clear();
	cout << "after clear size is " << lt.size() << endl;
}

int main()
{
	testList();
	return 0;
}

在这里插入图片描述

list底层结构探索

由监视窗口可以看到,list容器中包含指向头结点地址的指针及容器内有效元素的个数。每个节点包含前驱指针、后继指针及值域,故list底层是带头双向循环链表
在这里插入图片描述

list模拟实现

在模拟list之前,由于list的结构是双向链表,因而需要定义节点类型。↓↓↓

template<class T>
struct node
{
	node* _prev = nullptr;
	node* _next = nullptr;
	T _data;

	node(const T& x = T())
		:_data(x)
	{}
};

正向迭代器实现

由于链表的各个节点无法实现++或者–操作,因此,我们需要将迭代器封装为一个类(结构体)。在该类(接口体)中重载迭代器的各种操作。其中Ptr就是T*,Ref就是T&。

若定义list<char>::iterator it,则*it是为了获取节点中存的数据,因此operator*中中需要返回节点的数值,即_node->_data。对于it的其他运算符重载如下方代码所示↓↓↓

template<class T, class Ptr, class Ref>
struct __list_iterator
{
	typedef __list_iterator<T, Ptr, Ref> Self;

	node<T>* _node;
	__list_iterator(node<T>* node)
		:_node(node)
	{}
	Ref operator*()
	{
		return _node->_data;
	}
	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	Self operator++(int)
	{
		Self tmp(_node);
		_node = _node->_next;
		return tmp;
	}
	Self& operator--()
	{
		_node = _node->_prev;
		return this;
	}
	Self operator--(int)
	{
		Self tmp(_node);
		_node = _node->_prev;
		return tmp;
	}
	bool operator==(const Self& lt)
	{
		return _node == lt._node;
	}
	bool operator!=(const Self& lt)
	{
		return _node != lt._node;
	}
};

增删操作

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

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

	_size++;
}
void pop_back()
{
	assert(!empty());
	Node* tail = _head->_prev;
	Node* tailPrev = tail->_prev;
	
	tailPrev->_next = _head;
	_head->_prev = tailPrev;

	delete tail;
	_size--;
}
void push_front(const T& val)
{
	Node* first = _head->_next;
	Node* newnode = new Node(val);

	_head->_next = newnode;
	newnode->_prev = _head;
	newnode->_next = first;
	first->_prev = newnode;

	_size++;
}
void pop_front()
{
	assert(!empty());
	Node* first = _head->_next;
	Node* second = first->_next;

	_head->_next = second;
	second->_prev = _head;
	delete first;
	_size--;
}
void insert(iterator pos, const T& val)
{
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* newnode = new Node(val);

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

	_size++;
}
void erase(iterator it)
{
	Node* cur = it._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;

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

	delete cur;
	_size--;
}

属性获取操作

bool empty() const
{
	return _size == 0;
}
size_t size() const
{
	return _size;
}

构造类函数

list()
	:_head(new Node)
{
	_head->_next = _head;
	_head->_prev = _head;
}
list(const list<T>& lt)
{
	Node* prev = _head;
	Node* cur = _head;
	Node* p = lt._head->_next;
	while (p != lt._head)
	{
		cur = new Node(p->_data);
		prev->_next = cur;
		cur->_prev = prev;
		p = p->_next;
	}
	_head->_prev = cur;
}
template<class InputIterator>
list(InputIterator first, InputIterator last)
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

整体代码汇总

由于我们自己模拟实现的list与库中重名,因此需要将其定义在命名空间内。

#include <iostream>
#include <cassert>

using namespace std;

namespace jammingpro
{
	template<class T>
	struct node
	{
		node* _prev = nullptr;
		node* _next = nullptr;
		T _data;

		node(const T& x = T())
			:_data(x)
		{}
	};

	template<class T, class Ptr, class Ref>
	struct __list_iterator
	{
		typedef __list_iterator<T, Ptr, Ref> Self;

		node<T>* _node;
		__list_iterator(node<T>* node)
			:_node(node)
		{}
		Ref operator*()
		{
			return _node->_data;
		}
		Self* operator->()
		{
			return _node;
		}
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		Self operator++(int)
		{
			Self tmp(_node);
			_node = _node->_next;
			return tmp;
		}
		Self& operator--()
		{
			_node = _node->_prev;
			return this;
		}
		Self operator--(int)
		{
			Self tmp(_node);
			_node = _node->_prev;
			return tmp;
		}
		bool operator==(const Self& lt)
		{
			return _node == lt._node;
		}
		bool operator!=(const Self& lt)
		{
			return _node != lt._node;
		}
	};

	template<class T>
	class list
	{
		typedef node<T> Node;
	public:
		//=====构造类函数=====
		typedef __list_iterator<T, T*, T&> iterator;
		typedef __list_iterator<const T, const T*, const T&> const_iterator;
		iterator begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}
		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}
		const_iterator end() const
		{
			return const_iterator(_head);
		}
		list()
			:_head(new Node)
		{
			_head->_next = _head;
			_head->_prev = _head;
		}
		list(const list<T>& lt)
		{
			Node* prev = _head;
			Node* cur = _head;
			Node* p = lt._head->_next;
			while (p != lt._head)
			{
				cur = new Node(p->_data);
				prev->_next = cur;
				cur->_prev = prev;
				p = p->_next;
			}
			_head->_prev = cur;
		}
		template<class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}
		//=====增删操作=====
		void push_back(const T& val)
		{
			Node* tail = _head->_prev;
			Node* newnode = new Node(val);

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

			_size++;
		}
		void pop_back()
		{
			assert(!empty());
			Node* tail = _head->_prev;
			Node* tailPrev = tail->_prev;
			
			tailPrev->_next = _head;
			_head->_prev = tailPrev;

			delete tail;
			_size--;
		}
		void push_front(const T& val)
		{
			Node* first = _head->_next;
			Node* newnode = new Node(val);

			_head->_next = newnode;
			newnode->_prev = _head;
			newnode->_next = first;
			first->_prev = newnode;

			_size++;
		}
		void pop_front()
		{
			assert(!empty());
			Node* first = _head->_next;
			Node* second = first->_next;

			_head->_next = second;
			second->_prev = _head;
			delete first;
			_size--;
		}
		void insert(iterator pos, const T& val)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(val);

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

			_size++;
		}
		void erase(iterator it)
		{
			Node* cur = it._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

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

			delete cur;
			_size--;
		}
		//=====属性获取类函数=====
		bool empty() const
		{
			return _size == 0;
		}
		size_t size() const
		{
			return _size;
		}
	private:
		Node* _head;
		size_t _size = 0;
	};

	void test()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		lt.push_back(5);
		lt.pop_back();
		lt.pop_back();
		lt.insert(lt.begin(), 888);
		lt.erase(lt.begin());
		lt.push_front(666);
		lt.pop_front();
		list<int>::iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << *it << " ";
			++it;
		}
		string s = "jammingpro";
		list<char>lt2(s.begin(), s.end());
		for (char ch : lt2)
		{
			cout << ch << endl;
		}
	}
}

list与vector比较

vectorlist
底层结构动态顺序表,一段连续空间带头双向循环链表
随机访问支持随机访问,访问某个元素效率为O(1)不支持随机访问,访问某个元素效率为O(N)
插入和删除任意位置插入与删除效率低,需要移动数据,时间复杂度为O(N);同时,插入元素时可能需要扩容(开辟空间并拷贝旧数据,释放旧空间),导致效率较低任意位置插入与删除效率高,无需移动数据,时间复杂度为O(N)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层为动态开辟节点,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生指针对原生指针进行封装
迭代器失效在插入元素是,要给迭代器重新赋值;因为插入元素可能导致扩容,导致迭代器指向旧空间(迭代器失效);删除数据时,也需要给迭代器重新赋值(VS下迭代器失效,g++下不失效)插入元素不会导致迭代器失效,删除元素会导致迭代器失效
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问效率

🎈欢迎进入浅尝C++专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d

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

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

相关文章

【保姆级讲解下Docker容器】

&#x1f308;个人主页:程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

CTK插件框架学习-信号槽(05)

CTK插件框架学习-事件监听(04)https://mp.csdn.net/mp_blog/creation/editor/137171155 一、主要流程 信号发送者告诉服务要发送的信号信号发送者发送信号信号接收者告诉服务当触发某个订阅的主题时通知槽函数信号接收者处理槽函数信号槽参数类型必须为&#xff08;const ctk…

Keepalived+MySQL简单搭建实现数据库高可用

需求&#xff1a;想要实现当MySQL服务挂了之后&#xff0c;能够自动切换到另一台&#xff0c;不对当前服务造成过多影响。查找了很多实现数据库高可用方案&#xff0c;比较常见的有MHA&#xff08;至少三台&#xff0c;一主多从&#xff09;、开源数据库中间件&#xff08;Myca…

推箱子_java源码_50张地图_带背景音乐

一. 演示视频 推箱子_java源码_50张地图_带背景音乐 二. 实现步骤 完整项目获取 https://githubs.xyz/y23.html 部分截图 map地图实现 00000000000000000000 00000000000000000000 00000000000000000000 00000000000000000000 00000000000000000000 00000000000000000000 00…

golang和Java的简单介绍和对比

一、golang 1、Golang简介 Golang&#xff0c;也称为Go&#xff0c;是由Google公司在2009年推出的开源编程语言&#xff0c;由罗伯特格瑞史莫(Rob Pike)、肯汤普逊(Ken Thompson)、罗勃派克(Robert Griesemer)等人设计。Go语言的目标是在保持简单高效的编程模型的同时&#xf…

NumPy创建ndarray数组大揭秘

1.使用 np.array() 创建 使用 np.array() 由 python list 创建 n np.array(list) 注意 numpy 默认 ndarray 的所有元素的类型是相同的 如果传进来的列表中包含不同的类型&#xff0c;则统一为同一类型&#xff0c;优先级&#xff1a;str > float > int ndarray 的常…

自动驾驶中基于Transformer的传感器融合:研究综述

自动驾驶中基于Transformer的传感器融合&#xff1a;研究综述 论文链接&#xff1a;https://arxiv.org/pdf/2302.11481.pdf 调研链接&#xff1a;https://github.com/ApoorvRoboticist/Transformers-Sensor-Fusion 附赠自动驾驶学习资料和量产经验&#xff1a;链接 摘要 本…

【论文笔记】Text2QR

论文&#xff1a;Text2QR: Harmonizing Aesthetic Customization and Scanning Robustness for Text-Guided QR Code Generation Abstract 二维码通常包含很多信息但看起来并不美观。stable diffusion的出现让平衡扫描鲁棒性和美观变为可能。 为了保证美观二维码的稳定生成&a…

【PowerDesigner】PGSQL反向工程过程已中断

问题 反向工程过程已中断,原因是某些字符无法通过ANSI–&#xff1e;UTF-16转换进行映射。pg导入sql时报错&#xff0c;一查询是power designer 反向工程过程已中断&#xff0c;某些字符无法通过ANSI–>UTF-16转换进行映射&#xff08;会导致数据丢失&#xff09; 处理 注…

获取用户位置数据,IP定位离线库助您洞悉消费者需求

获取用户位置数据是现代互联网应用中非常重要的一环。通过获取用户的位置数据&#xff0c;可以了解用户所在的地理位置&#xff0c;从而更好地为用户提供个性化的服务和推荐。而IP归属地离线库就是一种非常有用的工具&#xff0c;可以帮助企业准确地获取用户的位置信息。 IP归…

Linux系统编程--管道

1、管道&#xff08;一&#xff09; 1.1、什么是管道 例如&#xff1a;ls | wc -w这条命令&#xff0c;ls是一个进程&#xff0c;把结果通过|管道输出到wc这个进程中&#xff0c;所以管道本质上是一个内核缓冲区 1.2、管道限制 1.3、匿名管道pipe 1.4、创建管道后示意图 管道…

E-SOP电子指导书系统在日用品生产中的作用

在当今高速发展的日用品生产行业中&#xff0c;E-SOP 电子指导书系统正发挥着越来越重要的作用。它以其独特的优势&#xff0c;为日用品生产带来了许多积极的影响。 1、E-SOP 电子指导书系统提高了生产效率。 在传统的生产方式中&#xff0c;工人往往需要查阅纸质指导书&#…

vue+springboot实现文件上传

①后端springboot创建controller FileController: package com.example.springboot.controller;import cn.hutool.core.io.FileUtil; import com.example.springboot.common.AuthAccess; import com.example.springboot.common.Result; import org.springframework.beans.fact…

【电源专题】电池不均衡的影响与原因

在使用多节电池设计产品时,大家都知道如果多节电池不均衡会影响电池寿命与充电安全。特别是在充电末端与放电末端时表现较为明显。 电池不均衡的影响 那么为什么会影响安全与寿命呢?其原因如下: 如果电池不均衡时,相当于木桶的短板效应。一方面没法充满,充电时电压高的那一…

YOLOv8结合SCI低光照图像增强算法!让夜晚目标无处遁形!【含端到端推理脚本】

这里的"SCI"代表的并不是论文等级,而是论文采用的方法 — “自校准光照学习” ~ 左侧为SCI模型增强后图片的检测效果,右侧为原始v8n检测效果 这篇文章的主要内容是通过使用SCI模型和YOLOv8进行算法联调,最终实现了如上所示的效果:在增强图像可见度的同时,对图像…

2024最新软件测试【测试理论+ python 编程 】面试题(内附答案)

一、测试理论 3.1 你们原来项目的测试流程是怎么样的? 我们的测试流程主要有三个阶段&#xff1a;需求了解分析、测试准备、测试执行。 1、需求了解分析阶段 我们的 SE 会把需求文档给我们自己先去了解一到两天这样&#xff0c;之后我们会有一个需求澄清会议&#xff0c; …

教你一文搞懂cookie

cookie 1、cookie是什么&#xff1f; cookie的中文翻译是曲奇&#xff0c;小甜饼的意思。cookie其实就是一些数据信息&#xff0c;类型为“小型文本文件”&#xff0c;存储于电脑上的文本文件中。 2、cookie有什么用&#xff1f; Cookie主要用于维持用户会话、个性化服务、…

HarmonyOS 应用开发之LifecycleForm接口切换LifecycleApp接口切换 LifecycleApp接口切换

LifecycleForm接口切换 FA模型接口Stage模型接口对应d.ts文件Stage模型对应接口onCreate?(want: Want): formBindingData.FormBindingData;ohos.app.form.FormExtensionAbility.d.tsonAddForm(want: Want): formBindingData.FormBindingData;onCastToNormal?(formId: string…

基于Springboot的一站式家装服务管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的一站式家装服务管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体…

Linux系统下安装jdk与tomcat【linux】

一、yum介绍 linux下的jdk安装以及环境配置&#xff0c;有两种常用方法&#xff1a; 1.使用yum一键安装。 2.手动安装&#xff0c;在Oracle官网下载好需要的jdk版本&#xff0c;上传解压并配置环境。 这里介绍第一种方法&#xff0c;在此之前简单了解下yum。 yum 介绍 yum&…