C++List详解

C++List详解_第1张图片

前言:

看到list肯定有朋友要问,这东西数据结构不也有吗,你C++还能把它搞出花来?

哎,你别说它还真就是个链表C++List详解_第2张图片

不过C++还真把它修成了花。那到底是怎么修的呢?


目录

前言:

一、list介绍

list:

二、list使用

list的构造

begin和end

size和empty

list基本函数

三、list实现

四、迭代器和空间配置器

迭代器iterator

空间配置器allocator

迭代器失效问题


一、list介绍

list:

list是可以在常数范围内任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 

list的底层是双向循环链表结构

list和forward_lsit非常相似,最主要的区别是后者是单链表,只能向前迭代,已让其更简单高效。

❤️‍与其他的序列式容器相比(array,vector,deque),list通常在任一位置进行插入、移除的效率更高。(毕竟是链表)

 虽然但是,list和forward_lsit不支持随机访问(链表通病),不能像数组那样可以按照下标跳转。

看完这些,肯定有哥们小声嘟囔,能不能别说废话,说说它跟双向循环链表有什么区别。

C++List详解_第3张图片

好,先来说说list大概都有什么,大体是什么。

list是一个容器,我们前面学过模板和类了,这里的list正是我们通过模板类实现的一个通用容器。

我们数据结构中的链表常常是取决于你定义的数据类型,没次要新建一个链表都要重新实现一下它的构造和方法实现,没错这里的list是一个通用模板,就是说我们把它的实现方法和成员变量都都封装起来的,我们并不需要去关注它是怎么实现的,我们要做的就是学会它提供的接口如何去使用。

那它会不会出现不能满足我需求的情况?

目前来看不太会,它的底层代码相当之美丽,真的很美,感兴趣的可以自己去看看它的源码。

虽然本文把list使用放在了实现之前,但还是建议在使用接口之前先去了解一下它的底层实现,盲目的看可能会看不懂,建议是先去看看私有成员的定义,再去顶部看一下类型萃取,然后是list的六大函数(这里面使用频率最高的大概是重载),在源码中几乎没个函数每个成员都能看到封装的身影。

什么?你问我先看源码还是先看我的文章?

我跳起来就是给你一拳。C++List详解_第4张图片

如果你也能先看完我的文章,那真的是

 C++List详解_第5张图片!!!

废话不多说,我们先来看一下list的使用

二、list使用

list的构造

构造函数( (constructor)
接口说明
list()
构造空的 list
list (size_type n, const value_type& val = value_type())
构造的 list 中包含 n 个值为 val 的元素
list (const list& x)
拷贝构造函数
list (InputIterator first, InputIterator last)
[first, last) 区间中的元素构造 list                   

示例://使用list前,记得加头文件

1.
list lt;

2.
list lt1(10, 2);

3.
listlt2(lt1);
//auto lt2(lt1);
//这里可以用auto类型推断来解放双手

4.
int arr[] = { 1,2,3,4,5,6,7,8,9 };
listlt5(arr,arr+sizeof(arr)/sizeof(int));
list::iterator it2 = find(lt5.begin(), lt5.end(), 3);
list::iterator it3 = find(lt5.begin(), lt5.end(), 7);
list l6(it2, it3);
//这里的it2、it3、是迭代器,后文会去介绍

begin和end

1. begin end 为正向迭代器,对迭代器执行 ++ 操作,迭代器向后移动(这里他们的返回值是迭代器类型并不是说它本身是一个迭代器)。
2. rbegin(end) rend(begin) 为反向迭代器,对迭代器执行 ++ 操作,迭代器向前移动

这里可以和front、back做一个区别:

front返回list的第一个节点中值的引用,back返回list的最后一个节点中值的引用。

size和empty

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

    int arr[] = { 1,2,3,4,5,6,7,8,9 };
	listlt(arr, arr + sizeof(arr) / sizeof(int));
	cout << "size" << lt.size() << endl;
	cout << "empty" << lt.empty();
	cout << endl;

list基本函数

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

注意:

erase的参数是迭代器,而remove是一个值,也就是erase可以删除一个区间,而remove只能删除一个值。假如一个数组有多个相同的数值,例如12344444,使用remove时会删除所有的4,而erase则会删除迭代器所指的数值。

添加和删除(Insert erase)都会有返回值: 返回添加位置的迭代器、删除位置后往后顺延的迭代器。

swap对于要交换的双方没有大小要求,并不是不一样大就不能交换。

void main() {
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	listlt(arr, arr + sizeof(arr) / sizeof(int));
	cout << "arr = ";
	for (const auto& e : lt)
		cout << e << " ";
	cout << endl;
	

	int br[] = { 100,200,300 };
	listlt2(br, br + sizeof(br) / sizeof(int));
	cout << "br = ";
	for (const auto& e : lt2)
		cout << e << " ";
	cout << endl;

	lt.swap(lt2);
	for (const auto& e : lt)
		cout << e << " ";
	cout << endl;
	for (const auto& e : lt2)
		cout << e << " ";
	cout << endl;
	lt.clear();
	
}

除此之外还有下面这就几个函数也比较常用:

sort排序可以按照从前到后也可从后到前,

resize可以调大也可以调小,重新设置容器尺寸,如果想要的长度比原长度小,那么截处后面的元素,如果要比原尺寸大,那就后面补默认值。

unique去重必须是所重复的数据是连续的,遇到1 2 3 2 3 2 4这种就会失效。

void main() {
	int arr[] = { 2,2,4,7,2,6,8,1,3,9,5 };
	listlt(arr, arr + sizeof(arr) / sizeof(int));
	cout << "arr = ";
	for (const auto& e : lt)
		cout << e << " ";
	cout << endl;
	lt.sort();
	cout << "arr = ";
	for (const auto& e : lt)
		cout << e << " ";
	cout << endl;

	/*lt.remove(2);
	cout << "arr = ";
	for (const auto& e : lt)
		cout << e << " ";
	cout << endl;*/
	 
	lt.unique();
 cout << "arr = ";
	for (const auto& e : lt)
		cout << e << " ";
	cout << endl;
  lt.resize(10);
 cout << "arr = ";
	for (const auto& e : lt)
		cout << e << " ";
	cout << endl;
	lt.resize(2);
	cout << "arr = ";
	for (const auto& e : lt)
		cout << e << " ";
	cout << endl;
}

merge合并两个链表之前,必须保证两个链表有序, 有序(只要两个链表有顺序就行如 3 6 9 和 1 4 8 9)

splice拼接没有顺序要求

void main() {
	int arr[] = { 1,2,3,7,8,9,4,5,6 };
	listlt(arr, arr + sizeof(arr) / sizeof(int));
	cout << "arr = ";
	for (const auto& e : lt)
		cout << e << " ";
	cout << endl;


	int br[] = { 100,200,300 };
	listlt2(br, br + sizeof(br) / sizeof(int));
	cout << "br = ";
	for (const auto& e : lt2)
		cout << e << " ";
	cout << endl;

	/*lt.merge(lt2);
	cout << "arr = ";
	for (const auto& e : lt)
		cout << e << " ";
	cout << endl;*/
	list::iterator pos = find(lt.begin(), lt.end(), 9);
	list::iterator start = find(lt2.begin(), lt2.end(), 100);
	lt.splice(pos, lt2, start);//从lt2中删除 插入lt中
	cout << "arr = ";
	for (const auto& e : lt)
		cout << e << " ";
	cout << endl;
}

三、list实现

直接上代码

namespace mlist {

	template
	class list {
	public:
		struct _Node;
	public:
		typedef _Node* _Nodeptr;

		typedef size_t _size_type;
		typedef _Ty value_type;


	public:
		struct _Node {
			_Nodeptr _Next, _Prev;
			_Ty _value;
		};

		struct Acc {
			typedef _Node*& _Nodepref;//节点指针的引用
			typedef _Ty& _Vref;      //值的引用


			static _Nodepref _Next(_Nodeptr P) {
				return (_Nodepref)(P->_Next);
			}
			static _Nodepref _Prev(_Nodeptr P) {
				return (_Nodepref)(P->_Prev);
			}
			static _Vref Value(_Nodeptr P) {
				return (_Vref)(P->_value);
			}

		};

		class iterator {
		public:
			iterator() :_Ptr(nullptr)
			{}
			iterator(_Nodeptr P) :_Ptr(P)
			{}
			_Ty& operator*() {
				return Acc::Value(_Ptr);
			}
			_Ty* operator->()
			{
				return &Acc::_Value(_Ptr);
			}
			iterator& operator++() {
				_Ptr = Acc::_Next(_Ptr);
				return *this;
			}
			iterator operator++(int) {
				iterator tmp = *this;
				_Ptr++;
				return tmp;
			}
			iterator& operator--()
			{
				_Ptr = Acc::_Prev(_Ptr);
				return *this;
			}
			iterator operator--(int)
			{
				iterator _Tmp = *this;
				--* this;
				return _Tmp;
			}
			
			bool operator !=(const iterator& p) {
				return _Ptr != p._Ptr;
			}
			bool operator ==(const iterator& p) {
				return _Ptr == p._Ptr;
			}
			_Nodeptr mynode()const {
				return _Ptr;
			}
		private:
			_Nodeptr _Ptr;
		};
	public:
		iterator begin() {
			return iterator(Acc::_Next(_Head));
		}
		iterator end() {
			return iterator(_Head);
		}
	public:
		list() :_Head(_Buynode()), _Size(0)
		{}


		~list()
		{
			/*erase(begin(), end());
			_Freenode(_Head);
			_Head = 0, _Size = 0;*/
		}
		size_t size() {
			size_t size = 0;
			_Nodeptr p = _Head->_Next;
			while (p != end()) {
				size++;
				p = p->_Next;
			}
			return size;
		}
		bool empty() {
			return size() == 0;
		}
		void clear() {
			iterator t = begin();
			while (t != end()) {
				erase(t++);
			}

		}
		
		void push_back(const _Ty& a) {
			insert(end(), a);
		}
		void push_front(const _Ty& a) {
			insert(begin(), a);
		}

      看这里!看这里!                    这里有个零初始化
		iterator insert(iterator P, const _Ty& X = _Ty()) {
			_Nodeptr T = P.mynode();
			Acc::_Prev(T) = _Buynode(T, Acc::_Prev(T));
			//_Nodeptr T = _Buynode(P.mynode(), Acc::_Prev(P.mynode()));
			//Acc::_Next(Acc::_Prev0(T)) = T;
			//Acc::_Prev(Acc::_Next(T)) = T;

			T = Acc::_Prev(T);
			Acc::_Next(Acc::_Prev(T)) = T;
			Acc::Value(T) = X;
			++_Size;
			return iterator(T);
		}
		iterator erase(iterator P) {
			_Nodeptr S = (P++).mynode();

			Acc::_Prev(Acc::_Next(S)) = Acc::_Prev(S);
			Acc::_Next(Acc::_Prev(S)) = Acc::_Next(S);
			--_Size;
			free(S);
			return P;
		}
		

       看这里!看这里!看这里!

		_Nodeptr _Buynode(_Nodeptr _Narg = 0, _Nodeptr _Parg = 0) {
			_Nodeptr _S = (_Nodeptr)malloc(sizeof(struct _Node));

			Acc::_Next(_S) = _Narg != 0 ? _Narg : _S;
			Acc::_Prev(_S) = _Parg != 0 ? _Parg : _S;
			return _S;

		}

		/*	_Nodeptr _Buynode(_Nodeptr _Narg,_Parg) {
			_Nodeptr _S = (_Nodeptr)mallco(sizeof(struct _Node));
			_S->_Next = _Narg != 0 ? _Narg : _S;
			_S->_Prev = _parg != 0 ? _Parg : _s;
		}*/

	private:
		_Nodeptr _Head;
		_size_type  _Size;
	};

很好,你最好是看完上面的代码再看的这句话,仔细看里面的Acc,Buynode,insert是不是很美丽的代码,特别是insert的代码和我注释起来的对比一下。它源码的封装是不是很严谨。

四、迭代器和空间配置器

迭代器iterator

迭代器,顾名思义,是一个允许重复迭代的器具,我们没个容器内都有迭代器的实现,类似于list中++、--、+=等的重载、前序遍历、后序遍历等的实现,都有迭代器的功劳,这里我们可以把他理解为一个指针(当然它不仅仅是指针啊),我称它为面向对象的指针。

由于源码不太好理解,这里模拟实现一下,可当参考。(上面模拟实现list中也有迭代器,那个也会很接近源码)

List 的迭代器
 迭代器有两种实现方式,具体应根据容器底层数据结构实现:
 1. 原生态指针,比如:vector
 2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下
方法:
 1. 指针可以解引用,迭代器的类中必须重载operator*()
 2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
 3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
 至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可
以向前 移动,所以需要重载,如果是forward_list就不需要重载--
 4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()
 */
template
class ListIterator
{
	typedef ListNode* PNode;
	typedef ListIterator Self;
public:
	ListIterator(PNode pNode = nullptr)
		: _pNode(pNode)
	{}
	ListIterator(const Self& l)
		: _pNode(l._pNode)
	{}
	T& operator*() { return _pNode->_val; }
	T* operator->() { return &(operator*()); }
	Self& operator++()
	{
		_pNode = _pNode->_pNext;
		return *this;
	}

	Self operator++(int)
	{
		Self temp(*this);
		_pNode = _pNode->_pNext;
		return temp;
	}

	Self& operator--();
	Self& operator--(int);
	bool operator!=(const Self& l) { return _pNode != l._pNode; }
	bool operator==(const Self& l) { return _pNode != l._pNode; }
	PNode _pNode;
};

空间配置器allocator

空间配置器,对于list基本所有的有关空间的操作都可以通过空间配置器实现,你一个小小的指针我都给你封装起来了,我这个操作空间的大工程不得包装的更精美,于是乎,空间配置器应运而生

等什么呢,我问你等什么呐,你不会以为我会把空间配置器代码也放出来吧,知道他是什么就行了,不想敲了。

迭代器失效问题

前面说过,面向对象的指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响

void TestListIterator1()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		//erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,
        //必须先给其赋值
			l.erase(it);
		++it;
	}
}
// 改正
void TestListIterator()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		l.erase(it++); // it = l.erase(it);
	}
}

总结一下,stl各个容器所含有的主要器件相差不多,学会list不吃亏

都看到这里给俺个赞呗C++List详解_第6张图片

对于文章内容有疑惑或者是建议请评论或者私信俺​​​​​​

你可能感兴趣的:(C++基础,笔记,c++,开发语言,list)