STL序列式容器:list(基本操作+模拟实现)

前言

list类是STL中封装的链表模板类,并且底层实现是以带头双向链表作为基础进行封装的,甚至一些 STL 版本中(比如 SGI STL),list容器的底层实现使用的是带头双向循环链表

list 容器中各个元素的前后顺序是靠指针来维系的,每个元素都配备了两个指针,分别指向它的前一个元素和后一个元素,list容器中的元素还可以分散存储在内存空间里(逻辑结构连续,物理结构分散)。

  • list的优点:

1.在任意位置插入删除数据的效率高
2.不存在扩容的问题(拷贝数据、释放空间的代价)
2.list支持前后双向迭代

  • list的缺点:

1.不支持下标的随机访问
2.list需要额外的空间保存每个结点的相关联信息
3.底层空间不连续可能会导致空间利用率低

  • 应用场景:

应用于有大量插入和删除操作,且不用关心随机访问时间复杂度不友好的问题的场景。

一:标准库中的list类

list接口的使用与vector接口的使用非常的类似,这里我们就不做过多的介绍。

vector类接口的使用请参照我的另一篇博客:STL容器_vector类

1.1 list类的迭代器失效问题

迭代器失效即迭代器所指向的结点无效,即该节点被删除了。

因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入数据时是不会导致list的迭代器失效的(不需要挪动数据和扩容),只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

举个例子:删除list中的所有偶数

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

int main(){
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.push_back(6);

	list<int>::iterator it = lt.begin();
	while (it != lt.end()){
		if (*it % 2 == 0){
			// lt.erase(it):会造成迭代器失效
			it = lt.erase(it);
		}
		else{
			it++;
		}
	}

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

输出结果:1 3 5

二:list类的模拟实现

在我们了解了list类的接口使用和底层结构的相关知识之后,下面我们来模拟实现一个list类。

2.1 模拟实现list
#include<assert.h>
namespace WJL{
	// 1.List的结点类
	template<class T>
	struct _List_node{
		_List_node<T>* _next;
		_List_node<T>* _prev;
		T _data;

		_List_node(const T& x = T())
			:_data(x)
			, _next(nullptr)
			, _prev(nullptr)
		{}
	};

	// 2.List类的迭代器
	template<class T, class Ref, class Ptr>
	struct _List_iterator{
		typedef _List_node<T> Node;
		typedef _List_iterator<T, Ref, Ptr> Self;
		Node* _node;

		_List_iterator(Node* node)
			:_node(node)
		{}

		// 解引用
		Ref operator*(){
			return _node->_data;
		}

		// operator++(++it)
		Self& operator++(){
			_node = _node->_next;
			return *this;
		}

		// operator++(it++)
		Self operator++(int){
			Self tmp(*this);
			//_node = _node->_next;
			++(*this);

			return tmp;
		}

		// operator--(--it)
		Self& operator--(){
			_node = _node->_prev;
			return *this;
		}

		// operator--(it--)
		Self operator--(int){
			Self tmp(*this);
			//_node = _node->_prev;
			--(*this);

			return tmp;
		}

		// !=
		bool operator!=(const Self& it){
			return _node != it._node;
		}

		// ==
		bool operator==(const Self& it){
			return _node == it._node;
		}

		 // ->
		Ptr operator->(){
			return &_node->_data;
		}
	};

	// 3.List类
	template<class T>
	class List{
		typedef _List_node<T> Node;
	public:
		typedef _List_iterator<T, T&, T*> iterator;
		typedef _List_iterator<T, const T&, const T*> const_iterator;

		iterator begin(){
			return iterator(_head->_next);
		}

		iterator end(){
			return iterator(_head);
		}

		// 返回值是const迭代器(不可修改)
		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){
			// 头结点
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;

			/*const_iterator it = lt.begin();
			while (it != lt.end()){
				push_back(*it);
				++it;
			}*/
			for (const auto& e : lt){
				push_back(e);
			}
		}

		/*List<T>& operator=(const List<T>& lt){
			if (this != &lt){
				clear();
				for (const auto& e : lt){
					push_back(e);
				}
			}

			return *this;
		}*/

		// operator现代写法
		List<T>& operator=(List<T> lt){
			// lt是传值拷贝构造出来的
			swap(_head, lt._head);
			// 交换之后 lt除了作用域释放掉被赋值的空间
			return *this;
		}

		~List(){
			clear();
			delete _head;
			_head = nullptr;
		}

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

		// 尾插
		void push_back(const T& x){
			//Node* newnode = new Node(x);
			//Node* tail = _head->_prev; // 原list中的最后一个结点

			//tail->_next = newnode;
			//newnode->_prev = tail;
			//newnode->_next = _head;
			//_head->_prev = newnode;
			insert(end(), x);
		}

		// 头插
		void push_front(const T& x){
			insert(begin(), x);
		}

		// 尾删
		void pop_back(){
			//erase(iterator(_head->_prev));
			erase(--end());
		}

		// 头删
		void pop_front(){
			erase(begin());
		}

		// pos位置插入
		void insert(iterator pos, const T& x){
			// 取出pos位置结点的指针
			Node* cur = pos._node;
			Node* newnode = new Node(x);
			Node* prev = cur->_prev;
			// prev cur newnode
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
		}

		// pos位置删除
		void erase(iterator pos){
			// 头结点不能删
			assert(pos != end());
			// 取出pos位置结点的指针
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

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

	private:
		Node* _head;

	};

	void test_list1(){
		List<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		lt.push_back(5);

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

		lt.pop_back();

		List<int> lt2(lt);

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

		List<int> lt3;
		lt3 = lt;
		for (const auto& e : lt3){
			cout << e << " ";
		}
		cout << endl;
	}
}
2.2 vector和list的对比
  • vector是一个可动态增长的数组

支持随机访问(很好的支持排序、二分查找等算法),但在头部或中间插入和删除数据的效率低,并且存在扩容问题。

  • list是一个带头结点的双向循环链表

不支持随机访问,但在任意位置插入和删除数据的效率高,并且不存在扩容的问题。

  • 小结

vector和list是两个相辅相成、互补的容器,在使用时根据具体场景再选择使用哪种容器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值