C++相关概念和易错语法(16)(list)

发布于:2024-07-11 ⋅ 阅读:(30) ⋅ 点赞:(0)

1.list易错点

(1)慎用list的sort,list的排序比vector慢得多,尽管两者时间复杂度一样,甚至不如先把list转为vector,用vector排完序后再转为list

(2)splice是剪切链表,将x的部分剪切到pos后,也可以自己剪自己的

(3)list迭代器不支持[]运算符,但相比较vector多支持了push_front和pop_front,原因在于[]效率不高,push_front和pop_front效率高

2.list模拟实现

list实现中我们会用到多个模板,注意每个template定义的模板参数都只能供当前类或函数使用,不存在一个文件中所有T都是一个意思

(1)结点

为了适应不同数据的存储,我们采用模板的方式来定义结点。为了能够体现封装特性,我们将创建结点并赋值作为一个构造函数写入类ListNode中。因为在接下来的class list中会频繁调用lt->next,即在外部访问LIstNode的成员变量,所以我们的ListNode使用struct,默认访问限定符是public

(2)无参构造和析构

我们的list是带头双向循环链表,所以所有的构造都需要定义一个哨兵位

还有很多带参的构造,虽然这里我们可以去实现,但是它们需要使用到的功能和后面的函数相符合,所以我会先实现后续的函数。

析构只需要遍历所有节点,将它们释放即可

(3)迭代器

list的迭代器和指针就拉开了差距,list的数据是不连续存储的,因此无法使用指针的加减来遍历list。我们需要定义一个iterator的类,在这个类里重载运算符++、*等操作,使它们在使用过程中和指针一致但能够正常访问数据。

我们使用ListNode*作为结点的标识,当++时就调用它的_next,--就调用它的_prev,*就返回对应_data的值。有了这个思路,迭代器的大部分功能我们都可以顺利实现了。


	template<class T, class T1 = T>
	struct List_iterator
	{
		typedef ListNode<T> Node;
		typedef List_iterator<T, T1> iterator;

		List_iterator(Node* node)
			:_curnode(node)
		{}

		iterator operator++()
		{
			_curnode = _curnode->_next;
			return _curnode;
		}
		
		iterator operator--()
		{
			_curnode = _curnode->_prev;
			return _curnode;
		}		
		
		iterator operator++(int)
		{
			_curnode = _curnode->_next;
			return _curnode->_prev;
		}		
		
		iterator operator--(int)
		{
			_curnode = _curnode->_prev;
			return _curnode->_next;
		}

		T1& operator*()//对于const T无法进行修改
		{
			return _curnode->_data;
		}

		T1* operator->()//对于const T无法进行修改
		{
			return &(_curnode->_data);
		}

		bool operator!=(const List_iterator lt) const
		{
			return _curnode != lt._curnode;
		}

		bool operator==(const List_iterator lt) const
		{
			return _curnode == lt._curnode;
		}


		Node* _curnode;
	};

但是这里面还有一些代码需要解释

a.operator->()

要理解这里,我们要思考迭代器本身想要模仿的是指向数据的指针,如果存储类型是int,对应iterator和int*的使用方式一模一样,类似地,像char、double、int*等内置类型是这样,那么自定义类型呢?operator->()就是为了模仿自定义类型的指针而专门设计的。

我们先将T1就看成T,T是一个结构体,里面有自己的成员变量,当我们使用结构体指针想要访问它们时,我们首先要拿到这个结构体的地址,再用->访问,这里的iterator也是如此,_data是结构体类型,我们直接先指向这个结构体内容本身,即_curnode->_data,这个表达式的返回值是一个结构体,也就是我们想要访问的结构体,但是我们是要模拟指针的操作,要使用->而不是.,所以我们要再对它取地址,即&(_curnode->_data),这样返回的就是一个指向_data的指针了,当我们调用it.operator->()->(成员变量)时就能访问结构体内部的成员变量了,简化为it->(成员变量),省略了一个->为了易读性。

b.T1和T的区分

在我们想要创建一个const_iterator时,T1的出现就很重要了。

当不传第二个模板参数时,默认和第一个一样,如果传了const T,那就使用传的参数

对于这两个要返回指针或引用的函数,加上const修饰T可以防止T被修改。那为什么不直接用const T一个参数呢?要注意const int和int是两个类型,对于一些自定义类型来说有很大区别,所以要分成两块来写。

有了上面的基础,反向迭代器也可以很快的完成,思路就是复用,用刚刚实现的正向迭代器稍加修改得到。


	template<class T, class T1 = T>
	struct List_reverse_iterator
	{
		typedef ListNode<T> Node;
		typedef List_iterator<T, T1> iterator;
		typedef List_reverse_iterator<T, T1> reverse_iterator;

		List_reverse_iterator(Node* node)
			:_it(node)
		{}

		List_reverse_iterator(iterator it)
			:_it(it)
		{}

		reverse_iterator operator++()
		{
			return --_it;
		}

		reverse_iterator operator--()
		{
			return ++_it;
		}

		reverse_iterator operator++(int)
		{
			return _it--;
		}

		reverse_iterator operator--(int)
		{
			return _it++;
		}

		T1& operator*()//对于const T无法进行修改
		{
			return *_it;
		}

		T1* operator->()//对于const T无法进行修改
		{
			return &(*_it);
		}

		bool operator!=(const List_reverse_iterator lt) const
		{
			return _it != lt._it;
		}

		bool operator==(const List_reverse_iterator lt) const
		{
			return _it == lt._it;
		}


		iterator _it;
	};

(4)push_back

push_back是非常关键的一个函数,在构造函数中,我们经常用到它

先用val创建一个newnode,找到尾节点并修改尾节点和哨兵位的_prev和_next使得newnode插入链表中

(5)insert

我们实现insert的方式是先实现一个可复用性最强的函数(迭代器版本的insert),再用它实现其它函数

后续的函数只需要调整参数复用即可

iterator insert(iterator pos, int n, int val)//特殊处理,防止定位错误
{
	return insert(pos, (size_t)n, (T)val);
}

iterator insert(iterator pos, size_t n, const T& val)
{
	list<T> tmp;
	for (size_t count = 0; count < n; count++)
	{
		tmp.push_back(val);
	}

	return insert(pos, tmp.begin(), tmp.end());
}		

iterator insert(iterator pos, const T& val)
{
	return insert(pos, 1, val);
}

其中有一个函数需要解释

当我们调用insert(lt.begin(), 1, 3)时,是想在首位置插入1个3,但是编译器会将1,3识别成迭代器(注意看我们之前已经实现了模板函数,1和3同类型是会匹配的),size_t和const int&虽然都能作为1和3的类型,但它们都不叫完美匹配,不完美匹配就会去找模板函数。

注意各大整型家族的区别,10u是unsigned int类型。

因此我们只有针对这一种情况进行单独处理,将它们强转后就可以匹配正确的函数了。

(6)erase

我们的思路也是先实现一个可复用性最强的,再对其它函数复用。这些函数逻辑都很简单,不做过多讲解。

(7)带参构造

这里我们就可以发现,只要有了push_back,insert,我们实现这些带参构造就很快了,我们要学会复用,即实现最关键的几个函数,其它的函数用这几个核心的函数套用就可以了,这不仅节省了我们的时间,还使得代码易于维护,失误率降低。

list(size_t n, const T& val)
{
	_head = new Node;
	_head->_next = _head->_prev = _head;

	for (size_t count = 0; count < n; count++)
	{
		push_back(val);
	}
}

list(const list<T>& lt)
{
	_head = new Node;
	_head->_next = _head->_prev = _head;

	for (const auto& e : lt)
	{
		push_back(e);
	}
}


template<class Init>
list(Init first, Init last)
{
	_head = new Node;
	_head->_next = _head->_prev = _head;

	insert(begin(), first, last);
}
		

(8)全部代码(包含一些边缘性的函数)

注意模板函数是按需实例化,只检查调用的实例化的函数,不调用就不实例化

#pragma once

#include <iostream>
#include <algorithm>
#include <assert.h>

using namespace std;


namespace my_list
{
	template<class T>
	struct ListNode
	{
		ListNode(const T& data = T())
			:_data(data)
			, _next(nullptr)
			, _prev(nullptr)
		{}

		T _data;
		ListNode<T>* _next;
		ListNode<T>* _prev;
	};



	template<class T, class T1 = T>
	struct List_iterator
	{
		typedef ListNode<T> Node;
		typedef List_iterator<T, T1> iterator;

		List_iterator(Node* node)
			:_curnode(node)
		{}

		iterator operator++()
		{
			_curnode = _curnode->_next;
			return _curnode;
		}
		
		iterator operator--()
		{
			_curnode = _curnode->_prev;
			return _curnode;
		}		
		
		iterator operator++(int)
		{
			_curnode = _curnode->_next;
			return _curnode->_prev;
		}		
		
		iterator operator--(int)
		{
			_curnode = _curnode->_prev;
			return _curnode->_next;
		}

		T1& operator*()//对于const T无法进行修改
		{
			return _curnode->_data;
		}

		T1* operator->()//对于const T无法进行修改
		{
			return &(_curnode->_data);
		}

		bool operator!=(const List_iterator lt) const
		{
			return _curnode != lt._curnode;
		}

		bool operator==(const List_iterator lt) const
		{
			return _curnode == lt._curnode;
		}


		Node* _curnode;
	};


	template<class T, class T1 = T>
	struct List_reverse_iterator
	{
		typedef ListNode<T> Node;
		typedef List_iterator<T, T1> iterator;
		typedef List_reverse_iterator<T, T1> reverse_iterator;

		List_reverse_iterator(Node* node)
			:_it(node)
		{}

		List_reverse_iterator(iterator it)
			:_it(it)
		{}

		reverse_iterator operator++()
		{
			return --_it;
		}

		reverse_iterator operator--()
		{
			return ++_it;
		}

		reverse_iterator operator++(int)
		{
			return _it--;
		}

		reverse_iterator operator--(int)
		{
			return _it++;
		}

		T1& operator*()//对于const T无法进行修改
		{
			return *_it;
		}

		T1* operator->()//对于const T无法进行修改
		{
			return &(*_it);
		}

		bool operator!=(const List_reverse_iterator lt) const
		{
			return _it != lt._it;
		}

		bool operator==(const List_reverse_iterator lt) const
		{
			return _it == lt._it;
		}


		iterator _it;
	};

	template<class T>
	class list
	{
	public:

		typedef ListNode<T> Node;

		typedef List_iterator<T> iterator;
		typedef List_iterator<T, const T> const_iterator;

		typedef List_reverse_iterator<T> reverse_iterator;
		typedef List_reverse_iterator<T, const T> const_reverse_iterator;



		list()
		{
			_head = new Node;
			_head->_next = _head->_prev = _head;
		}


		list(size_t n, const T& val)
		{
			_head = new Node;
			_head->_next = _head->_prev = _head;

			for (size_t count = 0; count < n; count++)
			{
				push_back(val);
			}
		}

		list(const list<T>& lt)
		{
			_head = new Node;
			_head->_next = _head->_prev = _head;

			for (const auto& e : lt)
			{
				push_back(e);
			}
		}


		template<class Init>
		list(Init first, Init last)
		{
			_head = new Node;
			_head->_next = _head->_prev = _head;

			insert(begin(), first, last);
		}
		
		~list()
		{
			Node* cur = _head->_next, * next = _head->_next->_next;

			while (cur != _head)
			{
				delete cur;
				cur = next, next = next->_next;
			}

			delete _head;

			_head = nullptr;
		}

		size_t size()
		{
			size_t count = 0;

			for (const auto& e : *this)
			{
				count++;
			}

			return count;
		}

		bool empty()
		{
			return _head->_next == _head;
		}


		list<T>& operator=(const list<T>& lt)
		{
			if (lt._head != _head)
			{
				clear();

				for (const auto& e : lt)
				{
					push_back(e);
				}
			}

			return *this;
		}

		T& front()
		{
			return *begin();
		}		
		
		T& back()
		{
			return *(--end());
		}		
		
		const T& front() const
		{
			return *begin();
		}		
		
		const T& back() const
		{
			return *(--end());
		}

		void swap(const list<T>& lt)
		{
			std::swap(_head, lt._head);
		}

		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);
		}


		reverse_iterator rbegin()
		{
			return reverse_iterator(_head->_prev);
		}

		reverse_iterator rend()
		{
			return reverse_iterator(_head);
		}

		const_reverse_iterator rbegin() const
		{
			return const_reverse_iterator(_head->_prev);
		}

		const_reverse_iterator rend() const
		{
			return const_reverse_iterator(_head);
		}

		void push_back(const T& val)
		{
			Node* newnode = new Node(val);
			Node* tail = _head->_prev;

			tail->_next = newnode;
			newnode->_prev = tail;

			newnode->_next = _head;
			_head->_prev = newnode;
		}		
		
		void push_front(const T& val)
		{
			insert(begin(), 1, val);
		}

		void pop_front()
		{
			erase(begin());
		}

		void pop_back()
		{
			erase(--end());
		}

		template<typename Input>
		iterator insert(iterator pos, Input first, Input last)
		{
			Node* cur = pos._curnode->_prev, * next = cur->_next;
			Node* ret = cur;

			while (first != last)
			{
				Node* newnode = new Node((T)(*first));

				cur->_next = newnode, newnode->_prev = cur;
				newnode->_next = next, next->_prev = newnode;

				cur = cur->_next;
				first++;
			}

			return ret->_next;//新插入的第一个结点对应的迭代器,有隐式类型转换
		}

		iterator insert(iterator pos, int n, int val)//特殊处理,防止定位错误
		{
			return insert(pos, (size_t)n, (T)val);
		}

		iterator insert(iterator pos, size_t n, const T& val)
		{
			list<T> tmp;
			for (size_t count = 0; count < n; count++)
			{
				tmp.push_back(val);
			}

			return insert(pos, tmp.begin(), tmp.end());
		}		
		
		iterator insert(iterator pos, const T& val)
		{
			return insert(pos, 1, val);
		}

		iterator erase(iterator first, iterator last)
		{

			Node* cur = first._curnode->_prev, * next = last._curnode;
			
			iterator nextit = first;

			while (first != last)
			{
				nextit++;

				delete first._curnode, first = nextit;
			}

			cur->_next = next, next->_prev = cur;

			return cur->_next;
		}


		iterator erase(iterator pos)
		{
			iterator cur = pos;

			return erase(pos, ++cur);
		}


		void clear()
		{
			erase(begin(), end());
		}

	private:
		Node* _head;
	};


}