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

🏠专栏介绍:浅尝C++专栏是用于记录C++语法基础、STL及内存剖析等。

🎯每日格言:每日努力一点点,技术变化看得见。

文章目录


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

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

cpp 复制代码
#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迭代器的使用示例代码↓↓↓

cpp 复制代码
#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的最后一个节点中的值的引用

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

cpp 复制代码
#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中的有效元素

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

cpp 复制代码
#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的结构是双向链表,因而需要定义节点类型。↓↓↓

cpp 复制代码
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的其他运算符重载如下方代码所示↓↓↓

cpp 复制代码
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;
	}
};

增删操作

cpp 复制代码
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--;
}

属性获取操作

cpp 复制代码
bool empty() const
{
	return _size == 0;
}
size_t size() const
{
	return _size;
}

构造类函数

cpp 复制代码
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与库中重名,因此需要将其定义在命名空间内。

cpp 复制代码
#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比较

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

🎈欢迎进入浅尝C++专栏,查看更多文章。

如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d

相关推荐
古希腊掌管学习的神2 分钟前
[LeetCode-Python版]相向双指针——611. 有效三角形的个数
开发语言·python·leetcode
赵钰老师3 分钟前
【R语言遥感技术】“R+遥感”的水环境综合评价方法
开发语言·数据分析·r语言
雨中rain9 分钟前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
就爱学编程11 分钟前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法
Oneforlove_twoforjob34 分钟前
【Java基础面试题025】什么是Java的Integer缓存池?
java·开发语言·缓存
emoji11111134 分钟前
前端对页面数据进行缓存
开发语言·前端·javascript
每天都要学信号1 小时前
Python(第一天)
开发语言·python
TENET信条1 小时前
day53 第十一章:图论part04
开发语言·c#·图论
北国无红豆1 小时前
【CAN总线】STM32的CAN外设
c语言·stm32·嵌入式硬件
生信圆桌1 小时前
【生信圆桌x教程系列】如何安装 seurat V5版本R包,最详细安装手册
开发语言·r语言