STL容器_list類以及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是兩個相輔相成、互補的容器,在使用時根據具體場景再選擇使用哪種容器。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章