STL-list模拟实现

在使用list的时候,因为不怎么常用这个容器,里面的一些函数经常会忘记。为了加深对于list的容器的熟悉,于是我便开始了list的模拟实现。废话不多说,直接上代码。


//为了与STL的区分,我把代码都包进了自己的命名空间-

  • 首先是节点的结构
点击查看代码
	//节点
	template<class T>
	struct ListNode
	{
		ListNode<T> * _prev;//指向前一个节点
		ListNode<T> * _next;//指向后一个节点
		T  _date;//存储的数据

		ListNode(const T& date = T())//默认构造,
			:_prev(nullptr)
			,_next(nullptr)
			,_date(date)
		{}
	};

  • 下面就是list的主体了,list是双向带头循环的链表,所以带一个哨兵卫的节点,
点击查看代码
template <class T>	
	class list
	{
		typedef ListNode<T> Node;
	public:
		//构造
		list()
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head; 
		}
		//析构
		~list()
		{
			Node* prev= _head ->_next;
			Node* next = prev->_next;
			while (prev != _head)
			{
				delete prev;
				prev = next;
				next = next->_next;
			}
			delete _head;
		}
	private:
		Node* _head;
	};

  • 然后是链表的一些基本插入删除操作,
点击查看代码
		//自己实现尾插
		//void push_back(const T& date)
		//{
		//	Node* tail = _head->_prev;
		//	Node* nextNode = new Node(date);
		//	tail->_next = nextNode;
		//	nextNode->_prev = tail;
		//	nextNode->_next = _head;
		//	_head->_prev = nextNode;
		//}
		//复用insert
		void push_back(const T& date)
		{
			insert(iterator(_head), date); //insert是在节点前面插入数据
		}

		// 尾删
		void pop_back()
		{
			erase(iterator(_head->_prev));
		}
		//头插
		void push_front(const T& date)
		{
			insert(iterator(_head->_next));
		}
		//头删
		void pop_front()
		{
			erase(iterator(_head->next));
		}

		//pos位置前面插入
		iterator insert(iterator pos, const T& x)
		{
			Node* tail = pos._node->_prev;
			Node* nextNode = new Node(x);
			pos._node->_prev = nextNode;
			nextNode->_next = pos._node;
			tail->_next = nextNode;
			nextNode->_prev = tail;
			return iterator(nextNode);//插入时给的是迭代器指向位置,返回也应该是一个迭代器,迭代器位置是新数据的节点
		}


		//删除pos位置节点
		iterator erase(iterator pos)
		{
			assert(pos._node != _head);
			iterator temp(pos._node->_next);
			pos._node->_prev->_next = pos._node->_next;
			pos._node->_next->_prev = pos._node->_prev;
			delete pos._node;
			return temp; //与插入同理,返回迭代器,位置是被删除的下一个节点
		}

  • 然后链表是最关键的迭代器了,相较于vector和string, list最不同就是迭代器不再是原生指针,所以,不能再像原生指针那样直接++或--的操作了,所以需要自己实现一个迭代器模板。
    ps:在一开始查看文档时,我对于为何会有三个模板参数属实无法理解。但在实现完list之后,我对大佬写出来的代码便是更加佩服。
点击查看代码
	//迭代器模板
	template <class T, class Ref, class Ptr> //Ref控制*解引用操作时const对象重载,Ptr控制->操作时const对象重载
	struct _list_iterator
	{
		typedef ListNode<T> Node;
		typedef _list_iterator<T, Ref, Ptr> Self;
		Node* _node;
		//构造
		_list_iterator(Node* x)
			:_node(x)
		{}
		
		//++重载  返回还是迭代器
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		 }

		Self operator++(int)
		{
			Self temp(*this);
			_node = _node->_next;
			return temp;
		}
		//--重载
		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		Self operator--(int)
		{
			Self temp(*this);
			_node = _node->_prev;
			return temp;
		}

		//*引用重载,返回的是解引用的数据 T
		Ref operator*()
		{
			return  _node->_date;
		}

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


		//!=
		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}
		// ==
		bool operator==(const Self& it)
		{
			return _node == it._node;
		}
	};
  • 有了迭代器模板,迭代器的操控就完全掌握在自己手里了,但是随之而来又出现了一个问题,const对象该如何重载,将const对象赋值给非const迭代器权限放大肯定是不行的,
    const对象中主要是解引用操作可以修改对象的数据,那就重载一个const的调用const<T>& operator*() const。一开始本以为这样就好了,但还是不行。
    仔细一看,发现const的是容器,但是调用的it的迭代器,迭代器还是那个迭代器,原本的想法就只能再重写一份const_的迭代器,但是这样又会代码冗余,两份迭代器区别只有重载的返回值是否是const。
    百思不得其解,我便去参考了list的源码,发现了迭代器的模板是iterator<T,Ref,Ptr>,仔细一研究,发现,重载中的返回值是Ref,然后在list中修改传参,如果是const,Ref就是const T&,返回值就是const。
    还有一个Ptr参数,与之同理,是为了->重载准备,如果是const,Ptr就传const T
    ,返回值也就是const;

点击查看代码
		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_iterator begin() const
		{
			return const_iterator(_head->_next);
		}
		const_iterator end() const
		{
			return const_iterator(_head);
		}


最关键的迭代器完了后,就剩一些边边角角的补充

点击查看代码

		//清空
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);//剩哨兵卫_head erase会自己调整链接关系
			}
		}

		//迭代器区间构造
		template <class InputIterator> 
		list(InputIterator first, InputIterator last)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;


			while (first != last)
			{
				push_back(*first);//复用尾插
				++first;
			}
		}


		//赋值重载
		list<T>& operator=(list<T> lt)
		{
			swap(_head, lt._head);//lt不用引用传参,会调用拷贝构造,与this指针交换后,出作用域会自动调用析构 
			return *this;
		}


		//返回第一个数据
		T& front()
		{
			return _head->_next->_date;
		}

		//const重载
		const T& front() const
		{
			return _head->_next->_date;
		}

		//返回最后一个数据
		T& back()
		{
			return _head->_prev->_date;
		}
		
		//const重载
		const T& back() const
		{
			return _head->_prev->_prev;
		}

这样一来,list容器也算是简单的完成了。list相较vector和string,最大区别就是迭代器要更加巧妙一点了。
image

最后,感谢大佬看到最后,欢迎评论改进。

posted @ 2022-04-16 18:37  小小鞠  阅读(35)  评论(0编辑  收藏  举报