迭代器以及迭代器失效问题的深度剖析

迭代器的应用实例(vector为例)

在博客上看到了许多大佬对于迭代器即迭代器失效问题的解读,都没有讲到其底层原理,于是就想着剖析一下为什么迭代器会失效,以及应用场景。写此博客简单剖析一下STL——iterator底层的实现原理以及失效原理

#include <vector>

using namespace std;

int main()
{
	std::vector<int> vec;//实例化容器对象
	for(int i = 0;i < 10;i++)
	{
		vec.push_back(i);	
	}

	vector::iterator it = vec.begin();//实例化迭代器对象,使其底层指针指向容器首地址
	for(;it != vec.end();++it)
	{
		cout << *it << " ";
	}
	return 0;
}

迭代器遍历容器
以上代码就是用迭代器遍历了容器中的所有元素

迭代器基础概念

  • 迭代器是什么?
    迭代器是一种检查容器内部元素并遍历元素的数据类型
  • 迭代器用来做什么?
    迭代器可以透明化的遍历容器的底层元素,又不暴露对象的内部表示,用迭代器来遍历容器,我们无需关心容器底层的数据结构到底是什么,因为在底层已经封装好了迭代器的一些运算操作,迭代器可以用同样的方式来遍历不同类型的容器
  • 使用迭代器的好处
    对于用户而言不用知道不同容器底层的数据结构,可以通过迭代器进行遍历,方便了使用者
  • 迭代器的运算操作
    包括it++,it–,ti1 - it2,it1 != it2等运算操作,底层都是通过运算符重载来完成的。
  • 迭代器的类型
    Input iterator:向前读取
    output iterator:向前写入
    forward iterator:向前读取和写入
    Bidirectional iterator:向前和向后读取和写入
    Random access:随机存取,可以读取和写入
    我们一般使用的迭代器是Random

string类迭代器简单实现

#include <iostream>
#include <string>

using namespace std;

class myString
{
public:
	myString(const char *p = nullptr)//普通构造
	{
		if (p != nullptr)
		{
			str = new char[strlen(p) + 1];
			strcpy(str,p);
		}
		else
		{
			str = new char[1];
			str[0] = 0;
		}
	}

	myString(const myString& scr)//拷贝构造,因为是深拷贝,所以得自己写拷贝构造
	{
		str = new char[strlen(scr.str) + 1];
		strcpy(str,scr.str);
	}
	
	myString& operator=(const myString& scr)
	{
		if (this == &scr)
		{
			return *this;
		}
		delete[]str;
		str = new char[strlen(scr.str) + 1];
		strcpy(str, scr.str);
	}

	~myString()
	{
		delete[]str;
		str = nullptr;
	}

	bool operator>(const myString &scr)
	{
		return strcmp(str, scr.str) > 0;
	}

	bool operator<(const myString &scr)
	{
		return strcmp(str, scr.str) < 0;
	}

	bool operator==(const myString &scr)
	{
		return strcmp(str, scr.str) == 0;
	}

	char& operator[](int index)
	{
		return str[index];
	}

	int length()
	{
		return strlen(str);
	}

	const char* c_str()const
	{
		return str;
	}

	class iterator//是用来透明化遍历容器的
	{
	public:
		iterator(char *src = nullptr)
		{
			p = src;
		}

		bool operator!=(const iterator &it)
		{
			return p != it.p;
		}

		iterator& operator++()
		{
			++p;
			return *this;
		}

		iterator operator++(int)
		{
			return iterator(p++);
		}
		
		char& operator*()
		{
			return *p;
		}
	private:
		char *p;
	};
	iterator begin() { return iterator(str); }
	iterator end() { return iterator(str + length()); }

	friend ostream& operator << (ostream &out, const myString scr);
	friend istream& operator >> (istream& in, const myString& scr);
	friend myString operator+(const myString& str1, const myString& str2);
private:
	char *str;
};
myString operator+(const myString& str1, const myString& str2)
{
	myString temp;
	temp.str = new char[strlen(str1.str) + strlen(str2.str) + 1];
	strcpy(temp.str, str1.str);
	strcat(temp.str, str2.str);
	return temp;
}

ostream& operator << (ostream &out, const myString scr)
{
	out << scr.str;
	return out;
}

istream& operator >> (istream& in, const myString& scr)
{
	in >> scr.str;
	return in;
}

int main()
{
	myString str1;
	myString str2 = "aaa";
	myString str3 = "bbb";
	myString str4 = str2 + str3;
	myString str5 = str2 + "ccc";
	myString str6 = "ddd" + str2;
		
	cout << "str6:" << str6 << endl;

	if (str5 > str6)
	{
		cout << str5 << ">" << str6 <<endl;
	}
	else
	{
		cout << str5 << "<" << str6<<endl;
	}

	int len = str6.length();

	for (int i = 0; i < len; i++)
	{
		cout << str6[i] << " ";
	}

	cout << endl;

	char buff[1024] = { 0 };
	strcpy(buff,str6.c_str());
	cout << "buff:" << buff << endl;

	for (auto it = str6.begin(); it != str6.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
	for (char ch : str6)//也是使用迭代器进行访问的
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

将iterator类嵌套在vector和string内部,我们会发现迭代器的底层就是一个指针,而迭代器和指针的区别在哪儿呢,如果说我们在类外定义一个指针来迭代容器元素,首先,会破坏容器底层数据的私有性,还有就是对于不同的容器迭代的方法也有所不同,而迭代器就实现了,对所有容器都可以使用同一种方式来迭代,至于迭代的一些运算符重载都封装成了迭代器的成员方法里,包括了++

vector迭代器简单实现

#include "pch.h"
#include <iostream>
using namespace std;

#if 0
// 定义容器的空间配置器,和C++标准库的allocator实现一样
template<typename T>
struct Allocator
{
	T* allocate(size_t size) // 负责内存开辟
	{
		return (T*)malloc(sizeof(T) * size);
	}
	void deallocate(void *p) // 负责内存释放
	{
		free(p);
	}
	void construct(T *p, const T &val) // 负责对象构造
	{
		new (p) T(val); // 定位new
	}
	void destroy(T *p) // 负责对象析构
	{
		p->~T(); // ~T()代表了T类型的析构函数
	}
};

/*
容器底层内存开辟,内存释放,对象构造和析构,都通过allocator空间配置器来实现
*/
template<typename T, typename Alloc = Allocator<T>>
class vector
{
public:
	vector(int size = 10)
	{
		// 需要把内存开辟和对象构造分开处理
		//_first = new T[size];
		_first = _allocator.allocate(size);
		_last = _first;
		_end = _first + size;
	}
	~vector()
	{
		// 析构容器有效的元素,然后释放_first指针指向的堆内存
		// delete[]_first;
		for (T *p = _first; p != _last; ++p)
		{
			_allocator.destroy(p); // 把_first指针指向的数组的有效元素进行析构操作
		}
		_allocator.deallocate(_first); // 释放堆上的数组内存
		_first = _last = _end = nullptr;
	}
	vector(const vector<T> &rhs)
	{
		int size = rhs._end - rhs._first;
		//_first = new T[size];
		_first = _allocator.allocate(size);
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		{
			//_first[i] = rhs._first[i];
			_allocator.construct(_first + i, rhs._first[i]);
		}
		_last = _first + len;
		_end = _first + size;
	}
	vector<T>& operator=(const vector<T> &rhs)
	{
		if (this == &rhs)
			return *this;

		//delete[]_first;
		for (T *p = _first; p != _last; ++p)
		{
			_allocator.destroy(p); // 把_first指针指向的数组的有效元素进行析构操作
		}
		_allocator.deallocate(_first);

		int size = rhs._end - rhs._first;
		//_first = new T[size];
		_first = _allocator.allocate(size);
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		{
			//_first[i] = rhs._first[i];
			_allocator.construct(_first + i, rhs._first[i]);
		}
		_last = _first + len;
		_end = _first + size;
		return *this;
	}
	void push_back(const T &val) // 向容器末尾添加元素
	{
		if (full())
			expand();
		//*_last++ = val;   _last指针指向的内存构造一个值为val的对象
		_allocator.construct(_last, val);
		_last++;
	}
	void pop_back() // 从容器末尾删除元素
	{
		if (empty())
			return;
		// erase(it);  verify(it._ptr, _last);
		// insert(it, val); verify(it._ptr, _last);
		verify(_last - 1, _last);
		//--_last; // 不仅要把_last指针--,还需要析构删除的元素
		--_last;
		_allocator.destroy(_last);
	}
	T back()const // 返回容器末尾的元素的值
	{
		return *(_last - 1);
	}
	bool full()const { return _last == _end; }
	bool empty()const { return _first == _last; }
	int size()const { return _last - _first; }
	T& operator[](int index) // vec[2]
	{ 
		if (index < 0 || index >= size())
		{
			throw "OutOfRangeException";
		}
		return _first[index]; 
	}

	// insert erase

	// #1迭代器一般实现成容器的嵌套类型
	class iterator
	{
	public:
		friend class vector<T, Alloc>;
		iterator(vector<T, Alloc> *pvec=nullptr
			, T *ptr = nullptr)
			:_ptr(ptr), _pVec(pvec)
		{
			Iterator_Base *itb = 
				new Iterator_Base(this, _pVec->_head._next);
			_pVec->_head._next = itb;/构造好了的迭代器的节点进行头插法
		}
		bool operator!=(const iterator &it)const
		{
			// 检查迭代器的有效性
			if (_pVec == nullptr || _pVec != it._pVec)//如果_pVec为空,说明迭代器失效
			{
				throw "iterator incompatable!";
			}
			return _ptr != it._ptr;
		}
		void operator++()
		{
			// 检查迭代器的有效性
			if (_pVec == nullptr)
			{
				throw "iterator invalid!";
			}
			_ptr++;
		}
		T& operator*() 
		{ 
			// 检查迭代器的有效性
			if (_pVec == nullptr)
			{
				throw "iterator invalid!";
			}
			return *_ptr; 
		} 
		const T& operator*()const 
		{ 
			// 检查迭代器的有效性
			if (_pVec == nullptr)
			{
				throw "iterator invalid!";
			}
			return *_ptr; 
		}
	private:
		T *_ptr;
		// 当前迭代器迭代器是哪个容器对象
		vector<T, Alloc> *_pVec;
	};
	// 需要给容器提供begin和end方法
	iterator begin() { return iterator(this, _first); }
	iterator end() { return iterator(this, _last); }

	// 检查迭代器失效,如果失效,将——pVec置为空
	void verify(T *first, T *last)
	{
		Iterator_Base *pre = &this->_head;
		Iterator_Base *it = this->_head._next;
		while (it != nullptr)
		{
			if (it->_cur->_ptr > first && it->_cur->_ptr <= last)
			{
				// 迭代器失效,把iterator持有的容器指针置nullptr
				it->_cur->_pVec = nullptr;
				// 删除当前迭代器节点,继续判断后面的迭代器节点是否失效
				pre->_next = it->_next;
				delete it;
				it = pre->_next;
			}
			else
			{
				pre = it;
				it = it->_next;
			}
		}
	}

	// 自定义vector容器insert方法的实现
	iterator insert(iterator it, const T &val)
	{
		/* 
		1.不考虑扩容 verify(_first - 1, _last);
		2.不考虑it._ptr的指针合法性
		*/
		verify(it._ptr - 1, _last);
		T *p = _last;
		while (p > it._ptr)
		{
			_allocator.construct(p, *(p-1));
			_allocator.destroy(p - 1);
			p--;
		}
		_allocator.construct(p, val);
		_last++;
		return iterator(this, p);
	}

	// 自定义vector容器erase方法的实现
	iterator erase(iterator it)
	{
		verify(it._ptr - 1, _last);
		T *p = it._ptr;
		while (p < _last-1)
		{
			_allocator.destroy(p);
			_allocator.construct(p, *(p + 1));
			p++;
		}
		_allocator.destroy(p);
		_last--;
		return iterator(this, it._ptr);
	}

private:
	T *_first; // 指向数组起始的位置
	T *_last;  // 指向数组中有效元素的后继位置
	T *_end;   // 指向数组空间的后继位置
	Alloc _allocator; // 定义容器的空间配置器对象

	// 容器迭代器失效增加代码
	struct Iterator_Base
	{
		Iterator_Base(iterator *c=nullptr, Iterator_Base *n=nullptr)
			:_cur(c), _next(n) {}
		iterator *_cur;
		Iterator_Base *_next;
	};
	Iterator_Base _head;

	void expand() // 容器的二倍扩容
	{
		int size = _end - _first;
		//T *ptmp = new T[2 * size];
		T *ptmp = _allocator.allocate(2 * size);
		for (int i = 0; i < size; ++i)
		{
			//ptmp[i] = _first[i];
			_allocator.construct(ptmp + i, _first[i]);
		}
		//delete[]_first;
		for (T *p = _first; p != _last; ++p)
		{
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first);
		_first = ptmp;
		_last = _first + size;
		_end = _first + 2 * size;
	}
};

int main()
{
	vector<int> vec(200);
	for (int i = 0; i < 20 ; ++i)
	{
		vec.push_back(rand() % 100 + 1);
	}

	auto it = vec.begin();
	while (it != vec.end())
	{
		if (*it % 2 == 0)
		{
			// 迭代器失效的问题,第一次调用erase以后,迭代器it就失效了
			it = vec.erase(it); // insert(it, val)   erase(it)
		}
		else
		{
			++it;
		}
	}

	for (int v : vec)
	{
		cout << v << " ";
	}
	cout << endl;



#if 0
	auto it1 = vec.end();
	vec.pop_back(); // verify(_last-1, _last)
	auto it2 = vec.end();
	cout << (it1 != it2) << endl;


	int size = vec.size();
	for (int i = 0; i < size; ++i)
	{
		cout << vec[i] << " ";
	}
	cout << endl;

	auto it = vec.begin();
	for (; it != vec.end(); ++it)
	{
		cout << *it << " ";
	}
	cout << endl;

	// foreach
	for (int val : vec) // 其底层原理,就是通过容器的迭代器来实现容器遍历的
	{
		cout << val << " ";
	}
	cout << endl;
#endif
	return 0;
}
#endif

以上是STL——vector容器比较完整的仿写,包括了迭代器的失效原理,迭代器失效,我们会在后面讲到,读者应该会发现vector迭代器的底层多了一个指针,而vector底层多了一个自由链表,他们的作用我们会在下面的迭代器失效部分讲到

迭代器失效问题

什么是迭代器失效

迭代器失效分为一下两种类型

  • 当对容器底部插入元素时,size == capicity时,需要扩容,扩容后,容器的所用迭代器均失效,因为,迭代器的底层就是一个指针,而扩容后,容器开辟了新的空间,指针指向的空间被释放,相对应容器的迭代器也就失效了
  • 当对容器插入元素或者删除元素后,插入点以及插入点之后,删除点以及删除点之后的迭代器全部失效。

从底层源码理解迭代器失效

在上述vector容器简单实现中,读者应该会发现,迭代器底层不只是封装了一个指针,还有pVec,这个指的是迭代器所迭代的容器对象,还定义了一个链表结构,结构如下:
在这里插入图片描述链表结构储存的是指向已定义迭代器的指针,每当定义一个迭代器的时候就会将迭代器的地址头插入链表中。代码中的verify函数是使迭代器失效的函数,当插入或者删除的时候,会调用verify函数,遍历链表,看链表里的迭代器是否在插入点(或删除点之后),如果在的话,就将迭代器的pVec置为空,读者们应该也会发现在it++等运算符操作的时候会判断pVect是否为空,如果为空,就说明对应的迭代器失效,就不能在进行对应的运算符操作了,迭代器也就因此而失效了

如何处理迭代器失效问题

首先,我们先举两个迭代器失效的例子,再看看如何解决迭代器的失效问题:
1、在删除的时候迭代器失效

int main()
{
	vector<int> vec;

	for (int i = 1; i <= 10; i++)
	{
		vec.push_back(i);
	}

	for (auto it = vec.begin(); it != vec.end(); ++it)
	{
		if (*it % 2 == 0)
		{
			it++;
		}
		else
		{
			vec.erase(it);
		}
	}
	return 0;
}

以上程序是用迭代起来遍历容器元素按,并删除将奇数删除。
运行后会发现会报错
在这里插入图片描述是因为在调用erase()成员方法后,删除点的以及删除点之后的迭代器均会失效,要解决这个问题,我们得知道erase的具体操作,erase会将删除点的下一个有效迭代器返回,所以,我们只需要对迭代器更新就可以了。如下程序:

int main()
{
	vector<int> vec;

	for (int i = 1; i <= 10; i++)
	{
		vec.push_back(i);
	}

	auto it = vec.begin();

	while (it != vec.end())
	{
		if (*it % 2 == 0)
		{
			it++;
		}
		else
		{
			it = vec.erase(it);
		}
	}
	for (int ch : vec)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

这里需要注意的是我们不能在删除了元素后再对it++,因为删除后返回的就是删除点的下一个元素的有效迭代器。这样就有效的解决了迭代器失效问题。
2、在插入时迭代器失效问题

int main()
{
	vector<int> vec;

	for (int i = 1; i <= 10; i++)
	{
		vec.push_back(i);
	}

	auto it = vec.begin();

	for (; it != vec.end(); it++)
	{
		if(*it % 2 == 0)
		{
			vec.insert(it+1, *(it) - 1);
		}
	}

	for (int ch : vec)
	{
		cout << ch << " ";
	}
	return 0;
}

运行程序后会报错,和上述删除代码错误原因一样
在这里插入图片描述
上述代码是用迭代器在偶数后插入比当前小1的数,是因为在插入元素后,插入点的迭代器就失效了,所以不能够进行遍历,程序就崩溃了,insert方法返回的是插入后插入点的有效迭代器,我们只需要对迭代器进行更新即可,修改后的代码如下:

int main()
{
	vector<int> vec;

	for (int i = 1; i <= 10; i++)
	{
		vec.push_back(i);
	}

	auto it = vec.begin();

	for (; it != vec.end(); it++)
	{
		if(*it % 2 == 0)
		{
			it = vec.insert(it+1, *(it) - 1);
		}
	}

	for (int ch : vec)
	{
		cout << ch << " ";
	}
	return 0;
}

运行结果如下:
在这里插入图片描述
综上,在vector中插入和删除元素时,我们应该根据需求,对删除和插入操作后的迭代器对原迭代器进行更新即可

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值