STL — STL迭代器的原理以及迭代器失效

  STL迭代器

                                                                                                       



作为STL六大组件之一 Iterator(迭代器)模式又称Cursor(游标)模式,通俗一点来讲就是用来遍历容器的,并对容 进行一定 的操

作. 或许就有人 问了,我不用迭代器也一样可以遍历容器还要它干啥? 这样想是不对的. 现在我们常 容器vector,list, map,set

,multiset,map ,multimp,deque 这 几个容器的内部是实现有顺序表,链表,红黑树. 那 我们遍 历这些容器就要明白它的底 层构造,

很不方便. Iterator就是在底层给你定义好,然后 任何容器都是同样 的遍历方 式. 这样就算你不懂底层实现也可以 使用.所以就是可

以操 作容器, 又不需暴露该对象的内 部表示。或者这样说 可能更容易理 Iterator模式是运用于聚合对象的一种 模式,通过运用该

模式, 使得 我们可以在不知道对象内 部表 示的情况下,按照一 定顺序(由 iterator提供的 方法)访问聚合对象中 的各个元素。


迭代器的作用:能够让迭代器与算法不干扰的相互发展,最后又能无间隙的粘合起来,重载了*,++,==,!=, 运算符。用

操作复杂的数据 结构,容器提供迭代器,算法使用迭代器;常见的一些迭代器类型:

iterator、 const_iterator、 reverse_iterator 和const_reverse_iterator. 你就 可以更简单的理解就是,我不知道该对象内部 的实

,但是我还是可以很轻易 遍历它以及访问修 改数据. 迭代器其实是一个蛮好用的STL组件. 我一直以为空间配置器是STL六大组件

中最难总结和理解的,那么迭代器对于空间配置器来说属于蛮好理解的知识 点, 对容器和 仿函数来说就带一 点抽象啦,不过呢这

打紧我用只要会实现它我们就一定会使用它,不过呢我们 就先开 始了 解它是如何使用的,


迭代器的使用

                                                                           

首先我们对iterator进行一个最简单的操作,用它来遍历list. 等等介绍迭代器原理的时候,我会实现一个List容器 ,并且实现该

器的迭代器,到时 候大家就明白迭代器的原理了.代码如下:

	list arr(5, 3);

	list::iterator it = arr.begin();
	//这里就是申请一个迭代器,
	
	while (it != arr.end())
	{
		cout << *it << " ";
		++it;
	}


这是最简单的应用,遍历一遍容器并且输出他们的值,我们先来看看结果:



如果不会使用库的list容器的话,可以去看我这个博客:手把手教你使用list.现在我们看到了iterator访问到了list 的每一个元

其实大家都知道, 当你可以访问到一个元素时,那么操作它就很容易了什么增删查改都不在话下. 相信大家对iterator基本的使

用已经有一点掌握了,不过呢这里还是有 几点要注意的.比如const_ iterator迭代器的使 前面的程序用vector::iterator改变

vector中的元素值。每种容器类型还定义了一种名为 const_iterator的类型, 该类型只能访问 容器内元素,但不能改变其值。


当我们对普通iterator类型解引用时,得到对某个元素的非const引用。而如果我们对const_iterator类型解引用时,可以得到

一个指向const对象的引用,如同任何常量一样,该对象不能进行重写。例如,如果text是vector类型,程序员想要

历它,输出每个元素,可以这样编写程序:


  // use const_iterator because we won't change the elements


  for (vector::const_iterator iter = text.begin();


  iter != text.end(); ++iter)


  cout << *iter << endl; // print each element in text


除了是从迭代器读取元素值而不是对它进行赋值之外,这个循环与前一个相似。由于这里只需要借助迭代器进行读
,不需要写,这

把iter定义为 const_iterator类型。当对const_iterator类型解引用时,返回的是一个const值。不允许用const_iterator进行赋值:


  for (vector::const_iterator iter = text.begin();

  iter != text.end(); ++ iter)

  *iter = " "; // error: *iter is const

使用const_iterator类型时,我们可以得到一个迭代器,它自身的值可以改变,但不能用来改变其所指向的元素的值。可以对迭代

器进行自增以及使用解引用操作符来读取值,但不能对该元素值赋值.



迭代器的实现原理

                                                                                                                                           


当你创造出来一个东西的时候,那么你对他的使用或者对于他的bug可能烂熟于心了吧.首先我们知道迭代器就是通过一定的封装

然后暴露出接口,再然后进行使用. 我们不需要知道底层的封装,我们记住它的方法如何使用就可以了. 但是我今天偏偏就要明

迭代器的底层实现,通过我最近查看源代码,其实跟iterator跟指针是非常相似的,拥有 *-> 操作,并且具有

 ++,--,== ,!= ,目前为止我们就只实现这几个就够了.现在我要写一个山寨版的list容器的iterator的定义.  

其他容器大概都是这么一个框架实现,只是实现具体方法不同.


代码实现:


template
struct __ListIterator
{
	typedef __ListNode Node;
	typedef __ListIterator Self;

	Node* _node;

	__ListIterator(Node* x)
		:_node(x)
	{}

	Ptr operator->()
	{
		return &(operator*())
	}
	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->() const
	{
		return &(operator*()) const;
	}

	Ref operator*() const
	{
		return _node->_data const;
	}
	bool operator != (const Self& s)
	{
		return _node != s._node;
	}

	inline Self& operator++()//前置++
	{
		_node = _node->_next;
		return *this;
	}
	Self& operator++(int)  //后置++
	{
		//第一种先初始化再返回.
		/*Self tmp(*this);
		_node = _node->_next;
		return tmp;*/

		//第二种返回的时候再初始化.
		Node* cur = _node;
		_node = _node->_next;
		return Self(cur);

		//第一种做了三件事情. 首先调用了构造函数构造一个tmp,返回的时候再调用拷贝构造把自己拷给外部存储,最后调用析构函数让自己析构.
		//第二种做了一件事情,在外部存储初始化一个tmp就Ok了. 所以效率来说第二种方法是最优解决方案.
	}

	inline Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	Self& operator--(int)
	{
		Node* cur = _node;
		_node = _node->_prev;
		return Self(cur);
	}

	bool operator != (const Self& s) const
	{
		return this->_node != s._node;
	}

	bool operator ==(const Self& s) const
	{
		return !(operator!=(s));
	}
};


我们看到迭代器的构造函数是一个单参数构造函数,其实我们发现迭代器跟智能指针蛮相似的,都是底层封装一个指针,最后对

个类进行各种各样的运算符重载. 让它成为一个拥有指针功能的特殊类. 接下来我们在list容器当中定义它. 

template
class List
{
typedef __ListNode Node;
public:
 
typedef __ListIterator Iterator;
 
typedef __ListIterator ConstIterator;
}


可能上面有的朋友看不懂为什么会有三个模板参数?  现在明白了吧.这些都是为了代码的复用.要不然当你要实现const_iterator

的时候又得重新写一个const__ListIterator的类了. T& T* 这些都是要在代码中高频出现所以我们也给他们开出来一个模板参数

位置,到时候iterator和const_iterator都要使用相同的模板框架,直接嵌套进去就ok. 如图所示:


 STL — STL迭代器的原理以及迭代器失效_第1张图片


因为iterator指向节点所以他就可以访问并操作节点节点的内容,而const_iterator只能访问到元素不能够操作节点. 这是迭代器

定义.有木有觉得它其实没有你想的那么抽象. 仔细想一想我相信你可以想明白了.接下来是一个迭代器很重要的问题> 迭代器

失效. 接下来我们引出来这个问题吧. 首先我们开始完善我们的山寨List,什么push_back,popback这些我们写过很多遍. 我下面

会有完整代码的.  我们今天着重研究一下erase删除函数. 像我们正常实现的erase函数只是将参数改为迭代器即可. 

下面是我对该函数的实现;

void erase(Iterator& it)
{
assert(it != End() && it._node);
 
Node* cur = it._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
 
prev->_next = next;
next->_prev = prev;
 
delete cur;
}

这个删除对于双向循环链表应该是没有bug的. 然后这个时候我做一个操作, 我利用迭代器删掉元素当中所有偶数:


void Test()
{
List l;
l.PushBack(1);
l.PushBack(2);
l.PushBack(3);
l.PushBack(4);
 
List::Iterator it = l.Begin();
 
while (it != l.End())
{
if (*it % 2 == 0)
{
l.erase(it);
}
++it;
}
it = l.Begin();
while (it != l.End())
{
cout << *it << " ";
++it;
}
cout << endl;
}


运行结果:


STL — STL迭代器的原理以及迭代器失效_第2张图片


正常情况就是最后窗口打印出来1 3. 好的那我们运行程序.结果程序崩了,居然还是越界. 经过我调试之后我发现程序在调用

earse之前没问题,调用earse之后就出现问题. 我再继续调试,发现第一次删除后,节点被删除了是没有问题的,而且连接也

没有问题,但是接下来的一次*it就访问越界了,

所以我有理由相信这里的问题出在it上面,所以我返回第一次删除前调试.

STL — STL迭代器的原理以及迭代器失效_第3张图片

我们发现第一次earse之前,It的值还没问题. 

 STL — STL迭代器的原理以及迭代器失效_第4张图片

但是earse之后,我们发现it变成一个随机值,原来这个就是我们程序崩溃的罪魁祸首.当删除掉该节点之后,该节点的iterator

为随机值,所以后面的++it,生成的还是一个随机值,最后下次*it是后就会发生访问越界. 这个就是我们的迭代器失效问题.

 

突然一个很重要的问题就被引出来了,这就是迭代器失效,就是你删除该节点后节点的迭代器因为变成一个随机值,没有办

法跳转到下一个节点. 这个时候你的迭代器++之后就是一个随机值,最后程序再次使用到it时,程序崩溃. 这就类似于野指

针的问题.

 

那么问题来了,我们该如何解决它? 既然节点被删了,它的迭代器也就成随机值了,那我每次删除完就要对iterator重新定

义一次,我们每次删除结束之后我们把就是删除节点的上一个节点的Iterator,赋给it,这时候++it的时候,迭代器就会跳

转到下一个节点的位置. 具体如何实现呢? 那我们就来尝试一下.

void earse(Iterator& it)
{
assert(it != End() && it._node);
 
Node* cur = it._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
 
prev->_next = next;
next->_prev = prev;
 
delete cur;
it = prev;//这里发生了隐式转换 类似于 it = Iterator(prev); 单参数的构造函数. 就会发生隐式转换.
}

我把it的引用传进去,然后在删除cur之后,把it赋值为cur前面的节点的迭代器. 接下来我们来看程序结果:



从结果上面来看,我们应该是成功了,迭代器失效每个容器导致的原因好像都不一样,但是他们的原理都是因为不当操作使得

iterator失去它自己的内容变为随机值,我们应该避免这些东西. 就能够熟练掌握迭代器. 下面我把整个山寨List的全套代码

贴在下面,有空我们可以去尝试山寨别的容器,提高自己对容器的理解和认识.


List实现代码:


template
struct __ListNode
{
	__ListNode* _next;
	__ListNode* _prev;
	T _data;

	__ListNode(const T& x)
		:_data(x)
		, _next(NULL)
		, _prev(NULL)
	{}
};

template
struct __ListIterator
{
	typedef __ListNode Node;
	typedef __ListIterator Self;

	Node* _node;

	__ListIterator(Node* x)
		:_node(x)
	{}

	//T& operator*()
	Ptr operator->()
	{
		return &(operator*())
	}
	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->() const
	{
		return &(operator*()) const;
	}

	Ref operator*() const
	{
		return _node->_data const;
	}
	bool operator != (const Self& s)
	{
		return _node != s._node;
	}

	inline Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	Self& operator++(int)
	{
		//第一种先初始化再返回.
		/*Self tmp(*this);
		_node = _node->_next;
		return tmp;*/

		//第二种返回的时候再初始化.
		Node* cur = _node;
		_node = _node->_next;
		return Self(cur);

		//第一种做了三件事情. 首先调用了构造函数构造一个tmp,返回的时候再调用拷贝构造把自己拷给外部存储,最后调用析构函数让自己析构.
		//第二种做了一件事情,在外部存储初始化一个tmp就Ok了. 所以效率来说第二种方法是最优解决方案.
	}

	inline Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	Self& operator--(int)
	{
		Node* cur = _node;
		_node = _node->_prev;

		return Self(cur);
	}

	bool operator != (const Self& s) const
	{
		return this->_node != s._node;
	}

	bool operator ==(const Self& s) const
	{
		return !(operator!=(s));
	}
};

template
class List
{
	typedef __ListNode Node;
public:

	typedef __ListIterator Iterator;

	typedef __ListIterator ConstIterator;

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

	Iterator Begin()
	{
		return Iterator(_head->_next);
	}

	ConstIterator Begin() const
	{
		return ConstIterator(_head->_next);
	}

	Iterator End()
	{
		return Iterator(_head);
	}

	ConstIterator End() const
	{
		return ConstIterator(_head);
	}

	T& Front()
	{
		return _head->next;
	}

	T& back()
	{
		return _head->_prev;
	}
	void PushBack(const T& x)
	{
		Insert(End(), x);
	}

	void PushFront(const T& x)
	{
		Insert(Begin(), x);
	}

	void popFront()
	{
		earse(Begin());
	}

	void popBack()
	{
		earse(--End());
	}

	void Insert(Iterator pos, const T& x)
	{
		assert(pos._node);

		Node* next = pos._node;
		Node* prev = next->_prev;
		Node* cur = new Node(x);

		prev->_next = cur;
		cur->_prev = prev;

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


	void earse(Iterator& it)
	{
		assert(it != End() && it._node);

		Node* cur = it._node;
		Node* prev = cur->_prev;
		Node* next = cur->_next;

		prev->_next = next;
		next->_prev = prev;

		delete cur;
		it = prev;//这里发生了隐式转换 类似于 it = Iterator(prev); 单参数的构造函数. 就会发生隐式转换.	
	}

	void Clear()
	{
		Iterator it = Begin();
		while (it != End())
		{
			Node* del = it._node;
			++it;
			delete del;
		}
		_head->_next = _head;
		_head->_prev = _head;
	}

	Iterator Find(const T& x)
	{
		Iterator it = Begin();
		while (it != End())
		{
			if (*it == x)
				return it;
			++it;
		}

		return End();
	}

protected:
	Node* _head;
};

你可能感兴趣的:(c++概念)