STL容器解析之List

List容器是STL最基础也是最常用的两大容器之一。相比于vector来说,List在设计上与其有巨大差异,首先是内部结构,List在其内部维护一个环形链表的头节点(在概念上并不属于链表中的一节);其次是迭代器设计,List的迭代器实现在链表中的移动操作,也就是递增时上一个节点、递减时指向下一个节点,而不是单纯的指针值(地址)的加减;还有List以链表节点存储元素的单元,而不是数组,这也导致了List在内存分配上的精准性。总而言之,List与Vector等其他容器有着完全不同的特性。(对于链表的概念,在此不做多余叙述)。

我们依旧从不同角度将List解剖,逐个击破。List实现主要体现在以下几个点:

1.迭代器操作

2.内存操作

3.元素操作


在认识List迭代器之前我们先认识List的节点:

	class _list_node
	{
	public:
		_list_node<T>* next;
		_list_node<T>* prev;
		T data;
	};

List节点实际上是只是一个普通的链表节点。但我们要清楚一个概念,我们应将链表结点视作vector中数组中的一个节点,也就是链表的首节点相当于a[0]  (start)。他们都是容器存储元素的单元,而迭代器的实现就是建立于这些单元之上。

那我们如何在List中维护一个链表呢?我们在Vector中使用了三个表示界限的指针来勾勒出了一个数组的轮廓,由此可以想到,我们也可以使用某个具有代表性的节点的指针来表示一个链表,其余的节点都可以通过它到达。最容易想到的是维护一个首节点的指针,但我们还有另外一个难题,我们如何满足STL规范中的前闭后开区间的要求?

因为我们的链表是一个环形链表,所以可以选择在尾节点之后(同时也是首节点之前)放置一个空节点作为缓冲,即可满足最后一个元素之后为无意义空间的要求。

解决了链表结构问题后,我们将视线转回迭代器上来。List迭代器需要单独设计,不仅因为其移动操作的独特性,还因为对元素进行的取值(*)、存取(->)等操作也通过迭代器进行,而这些都因元素存储单元的独特性而需特别设计。

下面是迭代器的实现:

	//list迭代器
	template <typename T, typename Ref, typename Ptr>
	class _list_iterator
	{
		friend class stl_list < T, alloc > ;
		typedef _list_iterator<T, T&, T*> iterator;
		typedef _list_iterator<T, Ref, Ptr> self;

		typedef stl_bidirectional_iterator_tag iterator_category;
		typedef T value_type;
		typedef Ptr Pointer;
		typedef const Ptr const_Pointer;
		typedef Ref Reference;
		typedef const Ref const_Reference;
		typedef ptrdiff_t Distance;

		typedef _list_node<T>* link_type;
		typedef size_t size_type;

	protected:
		link_type node;//内部维护一个指向节点的指针

	public:
		_list_iterator(){}
		_list_iterator(link_type lint) :node(lint){}
		_list_iterator(const _list_iterator& rhs) :node(rhs.node){}
		//使用合成的copy assignment
		bool operator==(const self& rhs)const{ return node == rhs.node; }
		bool operator!=(const self& rhs)const{ return !(*this == rhs); }
		//元素取值、成员存取操作
		Reference operator*(){ return (*node).data; }
		const_Reference operator*()const { return (*node).data; }
		Pointer operator->(){ return &(operator*()); }
		const_Pointer operator->()const{ return &(operator*()); }
		//迭代器移动操作
		self& operator++()
		{
			node = node->next;
			return *this;
		}

		self operator++(int)
		{
			self temp = *this;
			++*this;
			return temp;
		}
		self& operator--()
		{
			node = node->prev;
			return *this;
		}
		self operator--(int)
		{
			self temp = *this;
			--*this;
			return temp;
		}
	};

List迭代器内部维护一个指向节点的指针,在构造操作中将为指针设定初值。其余操作围绕这个指针来进行。


下面进入到内存配置环节。List内存操作分为:

1.节点空间的配置与释放 

2.节点的创建与移除

3.链表的初始化

	protected:
		link_type get_node(){ return list_node_allocator::allocate(); } //在内存中分配一个节点大小的空间
		void put_node(link_type p){ return list_node_allocator::deallocate(p); } //释放节点空间
		link_type create_node(const T& ); //创建节点(get_node+构造节点中元素)
		void empty_initialize();//初始化链表
		void destroy_node(link_type );//移除一个节点(元素的析构+put_node)

节点空间的配置与释放我们直接用STL通用的空间配置器完成,其主要为创建与移除节点服务。

创建一个节点分为两步:配置空间+构造节点中的元素。类似的,移除一个节点也分为两步:元素的析构+释放空间

	template <typename T, typename Alloc>
	typename stl_list<T, Alloc>::link_type stl_list<T, Alloc>::create_node(const T& value)
	{
		link_type p = get_node();
		construct(&p->data, value);
		return p;
	}

	template <typename T, typename Alloc>
	void stl_list<T, Alloc>::destroy_node(link_type p)
	{
		destroy(&(p->data));
		put_node(p);

	}

链表的初始化也分为两步:为特殊的尾后空节点分配一个空间+设置指针使链表成为空链表

	template <typename T,typename Alloc>
	void stl_list<T, Alloc>::empty_initialize()
	{
		node = get_node();
		node->next = node;
		node->prev = node;
	}

将node的前向、后向指针全部指向自己,这样就满足了空链表的验证条件:

		iterator begin(){ return (*node).next; }
		iterator end(){ return node; }
		bool empty(){ return node->next == node; }

这里要特殊说下size()操作,如果容器使用的是RandomAccessIterator(随机存取迭代器),那我们可以简单的用finish-start来完成。但List使用的是:

		typedef stl_bidirectional_iterator_tag iterator_category;

那这就很尴尬了,因为我们不能使用随机迭代器所特有的加减操作计算距离。我们只能通过遍历来记录大小,但是对于一个size()操作使用复杂度为N的实现很浪费。尤其是容器的大小也是其他复杂操作经常使用的一个值,这样会很隐蔽的增加了其余操作的时间。

所以我们再List中为维护一个标志大小的数据成员:

	protected:
		size_type mysize=0;

并设其默认值为0,这样虽增加了类的大小,但与通过遍历来知道一个大小信息来说,这太微不足道了。我们只需在一些改变链表大小的操作之后记得更新它就好了。


List内部链表可以带来一个其他容器所不容易实现的操作:接合。

所谓接合,就是将一段链表插入到另一端链表的内部。这个操作在有些容器那里(如vector、deque),将是个打代码量的工作,且时间复杂度还很糟糕。但对于链表来说,由于其特殊的元素之间的连接方式,则很容易实现一个(一些元素)放到另一些元素之间的操作,并且复杂度为O(1)。下面为接合操作的实现:

	template <typename T,typename Alloc>
	void stl_list<T, Alloc>::transfer(iterator position, iterator first, iterator last)
	{
		if (position != last)
		{
			(first.node->prev)->next = last.node;
			(last.node->prev)->next = position.node;
			(position.node->prev)->next = first.node;
			link_type temp = position.node->prev;
			position.node->prev = last.node->prev;
			last.node->prev = first.node->prev;
			first.node->prev = temp;
			
		}
	}

对于指针之间的操作,光看代码很容易混乱,建议大家画一下图,就会发现实际上很简单。(写类似代码时也是一样)

我们将接合操作扩展一下,就可以实现很多其他更通用的操作:

	template <typename T, typename Alloc>
	typename stl_list<T, Alloc>::iterator stl_list<T, Alloc>::insert(iterator position, const T& value)
	{
		link_type temp = create_node(value);
		temp->next = position.node;
		temp->prev = position.node->prev;
		(link_type(position.node->prev))->next = temp;
		position.node->prev = temp;
		++mysize;
		return temp;

	}

	template<typename T, typename Alloc>
	typename stl_list<T, Alloc>::iterator stl_list<T, Alloc>::erase(iterator position)
	{
		link_type prev_node = position.node->prev;
		link_type next_node = position.node->next;
		prev_node->next = position.node->next;
		next_node->prev = prev_node;
		destroy_node(position.node);
		--mysize;
		return iterator(next_node);//返回被移除元素的下一个元素
	}
	template <typename T, typename Alloc>
	void stl_list<T, Alloc>::remove(const T& value)
	{//清除所有相邻且值相同的元素,在对链表排序之后可使用
		iterator first = begin();
		iterator last = end();
		while (first != last)
		{//因为有可能清除first指向的对象,所以保存first->next的信息
			iterator temp = first.node->next;
			if (*first == value)
				erase(first);
			first = temp;
		}

	}

	template <typename T, typename Alloc>
	void stl_list<T, Alloc>::splice(iterator position, stl_list<T, Alloc>& rhs)
	{//将rhs中的所有元素放置到position之前
		if (!rhs.empty())
			transfer(position, rhs.begin(), rhs.end());
		mysize += rhs.mysize;
		rhs.mysize = 0;
	}

	template <typename T, typename Alloc>
	void stl_list<T, Alloc>::splice(iterator position, stl_list<T, Alloc>& rhs, iterator first, iterator last)
	{//将rhs中的first到last之间的元素放到position之前
		if (this != &rhs)
		{
			if (first == rhs.begin() && last == rhs.end())
				mysize += rhs.mysize;
			else
				mysize += count(first, last);

		}
		if (first != last)
			transfer(position, first, last);

	}

	template <typename T, typename Alloc>
	void stl_list<T, Alloc>::splice(iterator position, iterator i)
	{//将i放到position之前
		iterator j = i;
		++j;
		if (position == j || position == i)
			return;
		transfer(position, i, j);
		++mysize;
	}

 
注意,transfer操作为剪贴性质。 

以上就是List最特殊的操作,其他常规操作下面一并贴出:

	iterator begin(){ return (*node).next; }
	iterator end(){ return node; }
	bool empty(){ return node->next == node; }
	void push_back(const T& value){ insert(end(), value); }
	void push_front(const T& value){ insert(begin(), value); }
	void pop_back();
	void pop_front(){ erase(begin()); };
	void clear();

	template <typename T, typename Alloc>
	void stl_list<T, Alloc>::pop_back()
	{
		iterator temp = end();
		erase(--temp);
	}
	template <typename T, typename Alloc>
	void stl_list<T, Alloc>::clear()
	{
		iterator cur = begin();
		iterator temp;
		while (cur != node)
		{
			temp = cur;
			cur = cur.node->next;
			destroy_node(temp);

		}
		node->next = node;
		node->prev = node;
		mysize = 0;
	}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值