Bootstrap

C++STL之<list>

🌈前言

本篇文章进行STL中list(双向链表)序列容器的学习!!!


🌸 list

🌷1、list的介绍及使用

🌸1.1、list的介绍

list的文档介绍

  1. list是可以在常数范围内在任意位置进行插入和删除的序列容器,并且该容器可以前后双向迭代
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不关联的独立节点中,在节点中听过指针指向其前一个元素和后一个元素来进行链接
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能往前进行迭代,已让其简单高效,而list可以往前和往后进行迭代
  4. list与其他容器相比(array、vector、deque),list通常在任意位置进行插入、移除元素的执行效率更好,尾插尾删效率慢,需要迭代一遍
  5. list与其他序列容器相比,list和forward_list最大的缺陷是不支持任意位置的访问
    比如:
  • 要访问list的第n个元素,必须从已知的位置(头结点或尾节点)迭代到该位置,在这段位置上迭代需要线性时间的开销
  • list还需要一些额外的空间,以保持每个节点的相关信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

🌹1.2、list的使用

list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口


🌹1.2.1、list容器常见的构造函数

(constructor)构造函数接口说明
list() (重点)构造空的list
list(size_type n, const value_type& val = value_type())构造list中包含n个值尾val的元素
list (const listr& l) (重点)拷贝构造函数
list (InputIterator first, InputIterator last)使用区间[first, last)中的元素来构造list

举个栗子🌰:

void TestList()
{
	list<int> l1; 		  				// 构造空的list
	list<int> l2(5, 100); 				// 构造5个值为100的list
	list<int> l3(l2); 	  				// 拷贝构造函数
	list<int> l4(l3.begin(), l3.end()); // 使用迭代器来构造list

	int Array[] = { 1, 2, 3, 4 };
	// 以数组为迭代器区间来构造list[Array, Array + 4)
	list<int> l5(Array, Array + sizeof(Array) / sizeof(int));

	// 使用迭代器进行遍历输出
	list<int>::iterator It = l5.begin();
	while (It != l5.end())
	{
		cout << *It << ' ';
		++It;
	}
	cout << endl;

	// 使用C++11基于for范围循环进行遍历输出
	for (auto e : l5)
		cout << e << ' ';
	cout << endl;
}

在这里插入图片描述


🌺1.2.2、list iterator(迭代器的使用)

函数声明接口说明
begin + end返回第一个元素的迭代器 + 返回最后一个元素的下一个位置的迭代器
rbegin + rend返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reserve_iterator,及begin位置

【注意】

  1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

举个栗子🌰:

void print_list(const list<int>& l)
{
	// 注意这里调用的是list的 begin()const,返回list的常量迭代器
	for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it)
	{
		cout << *it << " ";
		// *it = 10; // 编译不通过 --- 被const修饰不能修改
	}
	cout << endl;
}

int main()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	// 使用正向迭代器正向list中的元素
	for (list<int>::iterator it = l.begin(); it != l.end(); ++it)
		cout << *it << " ";
	cout << endl;

	// 使用反向迭代器逆向打印list中的元素
	for (list<int>::reverse_iterator it = l.rbegin(); it != l.rend(); ++it)
		cout << *it << " ";
	cout << endl;
	return 0;
}

在这里插入图片描述


🌻1.2.3、list capacity

函数声明接口说明
empty检测list是否为空,是返回true,否返回false
size返回list节点的有效个数

举个栗子🌰:

void TestListCapacity()
{
	list<int> v1;
	list<int> v2{ 1, 2, 3, 4 };
	cout << "v1的有效节点个数为: " << v1.size() << '\t'
		<< "v1是否为空: " << v1.empty() << endl;

	cout << "v2的有效节点个数为: " << v2.size() << '\t'
		<< "v是否为空: " << v2.empty() << endl;
}

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

在这里插入图片描述


🌼1.2.4、list element access

函数声明接口说明
front返回list第一个节点中值(val)的引用
back返回list最后一个节点中值(val)的引用

举个栗子🌰:

void TestList()
{
	list<int> v{ 1, 2, 3, 4, 5 };

	// 输出list中第一个节点的数据
	cout << v.front() << endl;
	
	// 输出list中最后一个节点的数据
	cout << v.back() << endl;

	// 将list第一个节点数据修改成100
	v.front() = 100;
	for (auto It = v.begin(); It != v.end(); ++It)
		cout << *It << ' ';
	cout << endl;

	// 将list最后一个节点数据修改成200
	v.back() = 200;
	for (auto It = v.begin(); It != v.end(); ++It)
		cout << *It << ' ';
	cout << endl;
}

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

注意:二个接口返回的是这个数据的引用,引用可以充当左值…
在这里插入图片描述


🌿1.2.5、list modifiers

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

举个栗子🌰1️⃣:

void PrintList(list<int>& l)
{
	for (auto& e : l)
		cout << e << " ";
	cout << endl;
}

void TestList1()
{
	// 测试push_back、pop_back、pusk_front、pop_front
	int array[] = { 1, 2, 3 };
	list<int> L(array, array + sizeof(array) / sizeof(array[0]));
	// 在list的尾部插入4,头部插入0

	L.push_back(4);
	L.push_front(0);
	PrintList(L);

	// 删除list尾部节点和头部节点
	L.pop_back();
	L.pop_front();
	PrintList(L);
}

在这里插入图片描述

举个栗子🌰2️⃣:

// 测试insert、erase接口
void TestList2()
{
	int Array1[] = { 1, 2, 3 };
	list<int> L(Array1, Array1 + sizeof(Array1) / sizeof(Array1[0]));
	
	// 获取list中第二个节点
	auto pos = ++L.begin();
	cout << *pos << endl;

	// 在pos前插入值为4的元素
	L.insert(pos, 4);
	PrintList(L);

	// 在pos前插入5个值为5的元素
	L.insert(pos, 5, 5);
	PrintList(L);

	// 在pos前插入[v.begin(), v.end)区间中的元素
	vector<int> v{ 7, 8, 9 };
	L.insert(pos, v.begin(), v.end());
	PrintList(L);

	// 删除pos位置上的元素
	L.erase(pos);
	PrintList(L);

	// 删除list中[begin, end)区间中的元素,即删除list中的所有元素
	L.erase(L.begin(), L.end());
	PrintList(L);
}

在这里插入图片描述

举个栗子🌰3️⃣:

void PrintList(list<int>& l)
{
	for (auto& e : l)
		cout << e << " ";
	cout << endl;
}

void TestList3()
{
	// 测试swap、clear接口
	// 用数组来构造list
	int array1[] = { 1, 2, 3 };
	list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));
	PrintList(l1);

	list<int> l2{ 3, 2, 1 };
	PrintList(l2);

	// 交换l1和l2中的元素
	l1.swap(l2);
	PrintList(l1);
	PrintList(l2);

	// 将l2中的元素清空
	l2.clear();
	cout << l2.size() << endl;
}

在这里插入图片描述


🍀1.2.5、list迭代器失效问题

迭代器失效:

  • 可以将迭代器暂时理解成原生态指针,迭代器失效即所指向的节点已被销毁或释放,即该节点被删除了。
  • list的底层结构为双向链表,因此在list中进行插入时,是不会导致list迭代器失效的(list底层存储空间不连续)
  • 只有在删除节点时才会失效,并且失效的指针指向被删除节点的迭代器,其他迭代器不受影响

举个栗子🌰:

// 错误写法
void TestListIterator1()
{
	int Array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	list<int> l(Array, Array + sizeof(Array) / sizeof(int));

	auto It = l.begin();
	while (It != l.end())
	{
		// erase()接口被执行后,It所指向的节点已被删除,导致It迭代器无效,在下一次使用时,必须重新赋值
		l.erase(It);
		++It;
	}
}

// 正确写法
void TestListIterator2()
{
	int Array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	list<int> l(Array, Array + sizeof(Array) / sizeof(int));

	auto It = l.begin();
	while (It != l.end())
	{
		// erase()接口被执行后,对It重新赋值
		l.erase(It++); // It = l.erase(It) -- 后置++先使用后自增
	}
}

🍁2、list模拟实现

🍂2.1、模拟实现list(vs2022)

list.h

#ifndef LIST_H_
#define LIST_H_

#include "reverse_iterator.h"
#include <iostream>
#include <cassert>
using namespace std;

namespace mylist
{
	template <typename T>
	struct ListNode
	{
		ListNode(const T& val = T())
			: pre(nullptr)
			, next(nullptr)
			, date(val)
		{}
		ListNode<T>* pre;   // 前驱指针
		ListNode<T>* next;  // 后驱指针
		T date;				// 值域
	};


	// Ref:引用,Ptr:指针 --- 主要用于后面处理"常量迭代器"(用于解引用运算符重载(*)和自定义类型成员运算符重载(->))
	template <typename T, typename Ref, typename Ptr>
	struct List_Iterator
	{
		typedef ListNode<T>* PNode;
		typedef List_Iterator<T, Ref, Ptr> self;

		// 封装反向迭代器时需要访问该类中的内嵌类型
		typedef Ref reference;
		typedef Ptr pointer;
	public:
		//========================== constructe =====r=============================
		List_Iterator(PNode pNode = nullptr)
			: _PNode(pNode)
		{}
		// 拷贝构造函数和=运算符重载可以不写,默认为浅拷贝(按字节拷贝),迭代器不涉及空间分配的问题...
		List_Iterator(const self& s)
			: _PNode(s._PNode)
		{}
		//========================== operator =====================================
		Ref operator*() { return _PNode->date; }
		Ptr operator->() { return&(this->operator*()); } // 为了访问自定义类型而重载,相当于(*).
		bool operator==(const self& s) { return _PNode == s._PNode; }
		bool operator!=(const self& s) { return _PNode != s._PNode; }

		self& operator++()
		{
			_PNode = _PNode->next;
			return *this;
		}
		self operator++(int)
		{
			self tmp(*this);
			_PNode = _PNode->next;
			return tmp;
		}
		self& operator--()
		{
			_PNode = _PNode->pre;
			return *this;
		}
		self operator--(int)
		{
			self tmp(*this);
			_PNode = _PNode->pre;
			return tmp;
		}
	public:
		PNode _PNode;
	};


	template <typename T>
	class list
	{
		typedef ListNode<T>* PNode;
		typedef ListNode<T> Node;
	public:
		//========================== 正向 iterator =====================================
		typedef List_Iterator<T, T&, T*> iterator;
		typedef List_Iterator<T, const T&, const T*> const_iterator;

		iterator begin() { return iterator(pNode->next); }
		iterator end() { return iterator(pNode); }
		const_iterator cbegin() const { return const_iterator(pNode->next); }
		const_iterator cend() const { return const_iterator(pNode); }

		//========================== 反向 iterator =====================================
		// 单模板类型参数反向迭代器
		typedef Reverse_Iterator<iterator> reverse_iterator;
		typedef Reverse_Iterator<const_iterator> const_reverse_iterator;

		//typedef Reverse_Iterator<iterator, T&, T*> reverse_iterator;
		//typedef Reverse_Iterator<const_iterator, const T&, const T*> const_reverse_iterator;

		reverse_iterator rbegin() { return reverse_iterator(end()); }
		reverse_iterator rend() { return  reverse_iterator(begin()); }
		const reverse_iterator crbegin() const { return  const_reverse_iterator(end()); }
		const reverse_iterator crend() const { return  const_reverse_iterator(begin()); }
		//========================== constructor =====================================
		list() { empty_init(); }
		list(const list& l)
		{
			empty_init();
			// 用l中的元素构造临时的temp, 然后与当前对象交换(复用区间构造函数进行拷贝构造)
			list<T> tmp(l.cbegin(), l.cend());
			this->swap(tmp);
		}

		list<T>& operator=(list<T> l)
		{
			this->swap(l);
			return *this;
		}

		list(int n, const T& value = T())
		{
			empty_init();
			while (n--)
				push_back(value);
		}

		template <typename InputIterator>
		list(InputIterator first, InputIterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		~list()
		{
			if (pNode != nullptr)
			{
				this->clear();
				delete pNode;
				pNode = nullptr;
			}
		}

		//========================== Node insertion and deletion =============================
		void push_back(const T& val) { insert(pNode, val); }
		void pop_back() { erase(pNode->pre); }
		void push_front(const T& val) { insert(pNode->next, val); }
		void pop_front() { erase(pNode->next); }
	
		iterator insert(iterator pos, const T& val)
		{
			PNode newNode = new Node(val);
			PNode pCur = pos._PNode;
			PNode posPre = pCur->pre;

			newNode->next = pCur;
			pCur->pre = newNode;

			posPre->next = newNode;
			newNode->pre = posPre;

			return pos;
		}
	
		iterator erase(iterator pos)
		{
			// 删除哨兵位头节点会崩溃
			assert(pos != end()); 
			PNode cur = pNode->next;
			PNode posNext = pos._PNode->next;
			PNode posPre = pos._PNode->pre;

			posPre->next = posNext;
			posNext->pre = posPre;
			delete pos._PNode;

			// 删除pos节点后,导致迭代器失效,返回pos节点的下一个节点做为新的迭代器
			return iterator(posNext);
		}

		size_t size() const
		{
			PNode pCur = pNode->next;
			size_t Size = 0;
			while (pCur != pNode)
			{
				++Size;
				pCur = pCur->next;
			}
			return Size;
		}
		size_t capacity() const { return this->size(); }
		
		//======================= Head and tail data access ==================================
		T& front() { return pNode->next->date; }
		const T& front()const { return pNode->next->date; }
		T& back() { return pNode->pre->date; }
		const T& back()const { return pNode->pre->date; }

		//========================== Auxiliary interface =====================================
		void empty_init()
		{
			// 构造头节点,带头双向循环链表一开始指向自己
			pNode = new Node;
			pNode->next = pNode;
			pNode->pre = pNode;
		}
		void swap(list& l)
		{
			std::swap(pNode, l.pNode);
		}
		iterator find(const T& val)
		{
			auto it = begin();
			while (it != pNode)
			{
				auto it = begin();
				while (it != end())
				{
					if (*it == val)
						return iterator(it);
					++it;
				}
			}
			return iterator(end());
		}

		void clear()
		{
			auto it = begin();
			while (it != end())
			{
				it = erase(begin());
			}
			pNode->next = pNode;
			pNode->pre = pNode;
		}
	private:
		PNode pNode;
	};
}

#endif

reverse_iterator.h

  1. 这里的反向迭代器实现使用是迭代器萃取技术,需要对模板特化的技术的了解,可以省略
#ifndef REVERSE_ITERATIR_H_
#define REVERSE_ITERATIR_H_

namespace mylist
{
	template <typename Iterator>
	struct Iterator_traits
	{
		typedef typename Iterator::reference reference;
		typedef typename Iterator::pointer pointer;
	};

	template <typename T>
	struct Iterator_traits<T*>
	{
		typedef T& reference;
		typedef T* pointer;
	};

	template <typename T>
	struct Iterator_traits<const T*>
	{
		typedef const T& reference;
		typedef const T* pointer;
	};


	template <typename Iterator>
	struct Reverse_Iterator
	{
		typedef typename Iterator_traits<Iterator>::reference reference;
		typedef typename Iterator_traits<Iterator>::pointer pointer;
	public:
		typedef Reverse_Iterator<Iterator> self;
	public:
		//========================== constructe =====r=============================
		Reverse_Iterator(Iterator It = Iterator())
			: _It(It)
		{}
		Reverse_Iterator(const self& rit)
			: _It(rit._It)
		{}
		//========================== operator =====================================
		reference operator*()
		{
			Iterator tmp(_It);
			return *(--tmp);
		}
		pointer operator->()
		{
			return &(this->operator*());
		}
		//========================== operator =====================================
		self& operator++()
		{
			--_It;
			return *this;
		}
		self operator++(int)
		{
			Reverse_Iterator tmp(*this);
			--_It;
			return tmp;
		}
		self& operator--()
		{
			++_It;
			return *this;
		}
		self operator--(int)
		{
			Reverse_Iterator tmp(*this);
			++_It;
			return tmp;
		}
		bool operator!=(const self& s) { return _It != s._It; }
		bool operator==(const self& s) { return _It == s._It; }
		//========================== 成员数据 =====================================
	public:
		Iterator _It;
	};
}

#endif

🍃2.2、对mylist接口进行测试

#include "list.hpp"

// 正向打印list
template <typename T>
void Printlist(mylist::list<T>& l)
{
	auto It = l.cbegin();
	while (It != l.cend())
	{
		cout << *It << ' ';
		++It;
	}
	cout << endl;
}

// 测试list构造
void Test_list_constructor()
{
	mylist::list<int> l1;
	mylist::list<int> l2(10, 5);
	Printlist<int>(l2);

	int Array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	mylist::list<int> l3(Array, Array + sizeof(Array) / sizeof(int));
	Printlist(l3);

	mylist::list<int> l4(l3);
	Printlist(l4);

	l1 = l4;
	Printlist(l1);
}

// 测试pusk_back、pop_back、pusk_front、pop_front
void Testlist2()
{
	// 测试push_back、pop_back
	mylist::list<int> l;
	for (int i = 0; i < 5; ++i)
		l.push_back(i);
	Printlist(l);

	for (int i = 0; i < 5; ++i)
		l.pop_back();

	cout << l.size() << endl;

	// 测试pusk_front、pop_front
	for (int i = 0; i < 5; ++i)
		l.push_front(i);
	Printlist(l);

	for (int i = 0; i < 5; ++i)
		l.pop_front();

	cout << l.size() << endl;

}

void Testlist3()
{
	int array[] = { 1, 2, 3, 4, 5 };
	mylist::list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto pos = l.begin();
	l.insert(l.begin(), 0);
	Printlist(l);

	++pos;
	l.insert(pos, 2);
	Printlist(l);

	l.erase(l.begin());
	l.erase(pos);
	Printlist(l);

	// pos指向的节点已经被删除,pos迭代器失效
	cout << *pos << endl;

	auto it = l.begin();
	while (it != l.end())
		it = l.erase(it);

	cout << l.size() << endl;
}

struct T
{
	T(int _a = 0, double _b = 0.0)
		: a(_a)
		, b(_b)
	{}
	int a;
	double b;
};

void Testlist4()
{
	mylist::list<T> l;
	l.push_back(T(1, 1.1));
	l.push_back(T(2, 2.2));
	l.push_back(T(3, 3.3));

	for (auto it = l.begin(); it != l.end(); ++it)
		// 底层调用原理:it.operator()->->a/b, 下面it->a/b是编译器为了可读性进行了优化(省略了一个->)
		cout << it->a << ' ' << it->b << endl;
}

void reverve_iterator()
{
	int Array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	mylist::list<int> l3(Array, Array + sizeof(Array) / sizeof(int));
	for (auto rit = l3.rbegin(); rit != l3.rend(); ++rit)
		cout << *rit << ' ';
	cout << endl;
}

int main()
{
	Test_list_constructor();
	Testlist2();
	Testlist3();
	Testlist4();
	reverve_iterator();
	return 0;
}

🍄 3、list与vector的对比

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

vectorlist
底层结构动态顺序表,一段连续空间带头节点的双向循环链表
随机访问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素效率O(N)
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时可能需要增容。增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1),不存在增容问题
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点随机动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对原生态指针(节点指针)进行封装
迭代器失效在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入删除操作,不关心随机访问
;