【C++】迭代器 && vector中迭代器失效

1、什么是迭代器?

在STL中,容器的迭代器(Iterator)被作为容器元素对象或者IO流中的对象位置指示器.

因此,我们可以把迭代器理解为面向对象的指针(一种泛型指针通用指针),它不依赖元素的真实类型。

如何理解这里的通用

迭代器的“通用”是概念上的一种通用。所有的泛型容器和泛型算法都是用“迭代器”来指示元素对象。所有的迭代器都具有相同或相似的访问接口,但是每一种容器都有他自己的迭代器类型,毕竟每一种容器的底层存储结构不尽相同,所以迭代器的实现方式就不同。

因此,并不是存在一种“通用的迭代器”,让他可以应用于任何类型的容器

简而言之,迭代器是为了降低容器和泛型算法之间的耦合性而设计的,泛型算法的参数不是容器,而是迭代器。迭代器的概念图如下所示:
在这里插入图片描述
可以看到,容器迭代器的作用类似于数据库中的游标(cursor),它屏蔽了底层存储空间的不连续性,在上层使容器元素维持一种”逻辑连续“的假象。

2、迭代器与指针

迭代器只是在某些操作上与我们学过的指针有些类似,但是迭代器并不是指针。

指针代表真正的内存地址,即对象在内存中的存储位置

迭代器则代表元素在容器中的相对位置。

3、迭代器的分类

3.1具体分类

主要分为5类,具体如下表:
在这里插入图片描述
对于上述的这5中迭代器,他们的能力大小关系如下:
在这里插入图片描述
因此,这些迭代器必然存在以下关系:

随机访问迭代器是一种双向迭代器

双向迭代器使一种 前进迭代器

前进迭代器是一种输入迭代器

上述的关系是可以传递的

对于完全连续的容器(例如vector),没有必要重新定义迭代器类型,其元素的职责和你就可以完全直接充当迭代器。
vector::iterator 和const_iterator一般定义如下:

typedef T* iterator;
typedef const T* const_iterator;

对于不连续存储或以其他方式存储的容器,例如list等,需要自己定义定义迭代器类(class),一般情况下他们是对元素指针的封装,即模拟指针。

注意:一些特殊容器如vector和bitset等,由于他们的存储单位为bit而不是Byte,因此无法直接使用元素指针或封装来定义他们的迭代器。如果想要保持上层接口与普通容器一致,就必须做特殊处理。

清楚一些常用的容器的迭代器类别

vector的迭代器为随机访问迭代器
list的迭代器是双向迭代器
slist的迭代器是前进迭代器
deque的迭代器是随机访问迭代器
set/map的迭代器是双向迭代器

3.2为什么要对迭代器分类?

主要是泛型算法可以根据不同类别的迭代器具有不同的能力来实现不同性能的版本,使得能力大的迭代器用于这些算法时具有更高的效率。

连续存储的容器,其元素的位置指示器有两种:下标迭代器

下标的类型为unsigned int(size_t),有效范围为0 ~ (size_t)-1
迭代器的有效范围是begin() ~ end()

这类容器的接口中都会提供相应的两种元素访问方法,典型的就是string和vector,他们都支持begin(),end(),operator[]等操作

3.3迭代器的使用建议

1、尽量使用迭代器类型,而不是显式地使用指针。
2、只使用迭代器提供的标准操作,不使用任何非标准操作,以避免STL版本更新的时候出现不兼容的问题
3、当不会改动容器中的元素值的时候,使用const迭代器(const_iterator)

4、vector迭代器失效

4.1迭代器失效及其危害

迭代器失效是指当容器底层存储发生变动时,原来指向容器中某个或某些元素的迭代器由于元素存储位置发生了改变而不再指向他们,从而成为无效的迭代器。
图示:
在这里插入图片描述

4.2哪些操作会导致迭代器失效 && 如何解决

引起迭代器失效的主要操作有:
改变容器容量的方法:reserve()、resize()、push_back()、pop_back()、insert()、erase()、clear()
一些泛型算法:sort()、 copy() 、replace()、 remove()、 unipue()
集合操作算法等
下面通过两个示例说明问题:
示例一:push_back()接口

int main()
{
	vector<int> ages;
	ages.push_back(20);

	vector<int>::const_iterator iter = ages.begin();
	for (int i = 0; i < 10; i++)
	{
		ages.push_back(21);//引起若干次内存重分配操作
	}
	
	//此时访问的迭代器iter已经失效!
	cout << "The first age:" << *iter << endl;
	return 0;
}

在VS2013环境下
在这里插入图片描述

在Linux环境下
在这里插入图片描述
由于Windows和Linux平台使用的STL版本不一样,因此在对迭代器失效方面的处理方式也会有所不同。

可以看出,Windows的P.J.版本检测更加严格,而Linux的SGI版本相对宽松。

当然,从结果来看,也能够表述出迭代器失效的问题。对于严重越界的情况,Linux也会报出 Segmentation fault的错误(类比数组访问越界理解)

解决办法:
①在调用上述操作后重新获取迭代器

int main()
{
	vector<int> ages;
	ages.push_back(20);

	vector<int>::const_iterator iter = ages.begin();
	for (int i = 0; i < 10; i++)
	{
		ages.push_back(21);//引起若干次内存重分配操作
	}
	iter = ages.begin();//重新获取迭代器
	cout << "The first age:" << *iter << endl;

	return 0;
}

②在修改容器前为其预留足够的空间空间以避免存储空间重新分配
例如可以使用reserve接口来预留空间或者调整他们的大小

示例2:erase接口

int main()
{
	vector<int> v{ 1, 2, 3, 4, 5 };

	//找到并删除非末尾元素
	//vector<int>::iterator iter = find(v.begin(), v.end(),3);

	//找到并删除末尾元素
	vector<int>::iterator iter = find(v.begin(), v.end(), 5);
	v.erase(iter);

	cout << *iter << endl;
	return 0;
}

VS2013环境下
在这里插入图片描述

Linux环境下
在这里插入图片描述

由于erase的返回值时被删除元素的下一个位置,所以会存在一种情况:删除末尾元素,这样该函数的返回值就是end()的位置,鉴于这种情况,vs直接认为在执行erase后,没更新迭代器就使用的属于是非法行为。

如何解决?
删除非末尾元素:使用语句iter = v.erase(iter)在删除元素的同时更新迭代器
在这里插入图片描述

删除末尾元素:此时erase返回的时end()的位置,因此要想继续使用该迭代器,应该重新为其赋值为合法位置

在这里插入图片描述
以上就是对迭代器的简单理解和vector迭代器失效情况及处理方式的总结!

下篇带来vector容器的模拟实现~ 感觉有所帮助的读友们多多评论交流呀~
在这里插入图片描述

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Suk-god

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值