[C++] vector && list 等容器的迭代器失效问题
创始人
2024-12-27 16:39:26
0

标题:[C++] 容器的迭代器失效问题

@水墨不写bug



正文开始:

什么是迭代器?

        迭代器是STL提供的六大组件之一,它允许我们访问容器(如vector、list、set等)中的元素,同时提供一个遍历容器的方法。然而,在使用迭代器时,我们必须注意所谓的“迭代器失效”问题。

一、插入/删除元素

(1)erase导致删除位置之后的迭代器失效

        当我们在使用vector时,接收到了一组数据。然而这组数据的奇数具有实际意义,偶数需要被删除,这时候我们可能会写出这样的代码:

//删除偶数 vector v1 = { 1,2,3,4,5,6,7,8,9,10,22,1};  for (vector::iterator it = v1.begin();it!= v1.end();) { 	if (*it % 2 == 0) 	{ 		v1.erase(it); 	} 	else 	{ 		it++; 	} }

        乍一看,这段代码看似没有问题,并且在gcc上可以跑过,但是实际上这段代码是不符合C++标准的。

1.换一个编译器,比如VS2022,发现问题了:

 报错翻译过来就是迭代器不兼容。其实VS2022是通过对vector的迭代器进行封装来达到这一功能的。

2.在gcc上可以跑过,本质上只是一个巧合。


 迭代器失效本质

        在STL标准中,vector的erase会返回一个修正后的迭代器,而这样的修正就是为了避免迭代器失效。

        vector中的一组数据,{1,2,3,4,5,6,7,8,9};假设有一个迭代器指向元素5:

         在删除元素“5”后,由于会发生数据拷贝移动,于是这个迭代器就“顺势”指向了下一个元素“6”:

         这一行为是未定义的!如果我们使用的容器不是连续线性的,比如链表,那么结果将不堪设想,会产生野指针!

void test7() { 	list v1 = { 1,2,3,4,5,6,7,8,9,10,22,1 }; 	for (list::iterator it = v1.begin(); it != v1.end();) 	{ 		if (*it % 2 == 0) 		{ 			v1.erase(it); 		} 		else 		{ 			it++; 		} 	} 	cout << v1; } int main() { 	//test1(); 	//test2(); 	//test3(); 	//test4(); 	//test5(); 	//test6(); 	test7(); 	return 0; }

 


 vector的erase()原型:

iterator erase (const_iterator position); iterator erase (const_iterator first, const_iterator last);

        STl的vector返回一个迭代器,指向被函数调用删除的最后一个元素后面元素的新位置。如果操作删除序列中的最后一个元素,则这是容器的末端。

        所以标准的写法是:

 	vector v1 = { 1,2,3,4,5,6,7,8,9,10,22,1}; 	for (vector::iterator it = v1.begin();it!= v1.end();) 	{ 		if (*it % 2 == 0) 		{ 			it = v1.erase(it); 		} 		else 		{ 			it++; 		} 	} 	cout << v1; 

这样就实时更新了迭代器。 


(2)insert导致的迭代器失效

i,插入元素之后位置的迭代器失效

        与删除元素之后位置的迭代器失效的问题本质上是一致的:

vector v1 = { 1,2,3,4,5,6,7,8,9,10 }; vector::iterator it = v1.begin() + 5; cout << *it << endl;  v1.insert(v1.begin(), 4); cout << *it << endl; 

 在运行时报错:

 由于无法保留原来的迭代器,所以直接更新为指向插入元素的迭代器:

vector v1 = { 1,2,3,4,5,6,7,8,9,10 }; vector::iterator it = v1.begin() + 5; cout << *it << endl;  it = v1.insert(v1.begin(), 0); cout << *it << endl;

 

ii,扩容移动导致的迭代器失效

        我们看一段insert()的原型:

//在pos位置插入对象 iterator insert(iterator pos, const T& t) 	//由于可能需要扩容,会发生迭代器失效,对内部而言 	//迭代器pos在扩容前后指向的对象不再相同,对外部也是同样的会发生 { 	if (size() == capacity()) 		//需要扩容 	{ 		int len = pos - _start; 		int Newcapacity = capacity() == 0 ? 4 : capacity() * 2; 		reserve(Newcapacity); 		//改变capacity,不改变size  		//记录len,解决迭代器失效的问题 		pos = _start + len; 	}

        通过分析,我们发现:在insert之前,会有一个是否需要扩容的检验,如果需要扩容,则释放旧空间,开辟新空间,然后拷贝数据。

        在这个过程中,如果需要扩容那么原来指向原旧空间的迭代器就失效了,如果访问失效的迭代器,会出现意想不到的结果。

        这个就解释了VS2022封装为什么迭代器。也许VS将迭代器封装为一个类,并且有这个类内部有一个判断迭代器是否失效的方法,如果我们访问了失效的迭代器就会报错。

二、容器重新分配内存

        其实,只要是导致容器的重新开辟这一动作时,就伴随着迭代器失效。

比如:

(1)std::vector 插入元素导致的重新分配

#include    #include       int main() {       std::vector v{1, 2, 3};       auto it = v.begin(); // 假设 it 指向第一个元素 1          // 插入元素,如果导致重新分配内存,it 将失效       v.push_back(4); // 如果 v 的容量不足以容纳新元素,它将重新分配内存          // 下面的代码在重新分配后可能会导致未定义行为       // *it = 0; // 如果 it 已失效,这是未定义行为          // 更好的做法是重新获取迭代器       it = v.begin(); // 现在 it 指向新的第一个元素(可能是原来的 1,也可能是新内存位置上的 1)          std::cout << *it << std::endl; // 输出:1       return 0;   }

(2)std::vector resize 导致的重新分配

#include    #include       int main() {       std::vector v{1, 2, 3};       auto it = v.begin(); // 假设 it 指向第一个元素 1          // resize 到一个更大的大小,如果导致重新分配内存,it 将失效       v.resize(10); // 如果 v 的容量不足以容纳 10 个元素,它将重新分配内存          // 下面的代码在重新分配后会导致未定义行为       // *it = 0; // 如果 it 已失效,这是未定义行为          // 更好的做法是重新获取迭代器       it = v.begin(); // 现在 it 指向新的第一个元素(原来的 1 或新内存位置上的元素)          std::cout << *it << std::endl; // 输出:1       return 0;   }

        这两个操作都导致了容器的重新开辟也就是 释放旧空间,开辟新空间进行容量调整的过程,所以造成迭代器失效。


三、避免迭代器失效 

        在实际应用中,我们要避免迭代器失效,就需要理解常见的错误及原理,养成良好的变成习惯,形成风格,这样才能在最大程度上减少错误!


目录

一、插入/删除元素:

(1)erase导致删除位置之后的迭代器失效

 vector的erase()原型:

(2)insert导致的迭代器失效

i,插入元素之后位置的迭代器失效

ii,扩容移动导致的迭代器失效

二、容器重新分配内存

(1)std::vector 插入元素导致的重新分配

(2)std::vector resize 导致的重新分配

三、避免迭代器失效 


完~

未经作者同意禁止转载

相关内容

热门资讯

此事备受玩家关注!新超圣辅助透... 此事备受玩家关注!新超圣辅助透视挂,建德麻将存在有挂,一贯有挂细节运新超圣辅助工具,进入游戏界面。进...
今天上午!熊猫麻将辅助透视挂,... 今天上午!熊猫麻将辅助透视挂,大唐撸麻雀确实有挂,确实有挂透视1、下载好大唐撸麻雀正确养号方法之后点...
突发!小吆三打哈辅助透视挂,悠... 突发!小吆三打哈辅助透视挂,悠闲麻将真的是有挂,原来有挂解密一、小吆三打哈游戏安装教程牌型概率发牌机...
今年以来!樱花梦雨辅助透视挂,... 今年以来!樱花梦雨辅助透视挂,新滕州麻将真的是有挂,好像有挂方法今年以来!樱花梦雨辅助透视挂,新滕州...
备受关注的!边锋老友棋牌辅助透... 备受关注的!边锋老友棋牌辅助透视挂,微友麻将是真的有挂,一直有挂攻略运边锋老友棋牌辅助工具,进入游戏...
明白辅助挂!萍乡滚筒辅助透视挂... 明白辅助挂!萍乡滚筒辅助透视挂,大唐麻将游戏是有挂,一直有挂教程1、操作简单,无需大唐麻将游戏手机版...
据报道!丫丫古诗辅助透视挂,琼... 据报道!丫丫古诗辅助透视挂,琼崖海南麻将真的有挂,其实有挂方法丫丫古诗脚本下载中分为三种模型:丫丫古...
随着!多多休闲辅助透视挂,东游... 随着!多多休闲辅助透视挂,东游麻将是真的有挂,竟然有挂存在随着!多多休闲辅助透视挂,东游麻将是真的有...
方法辅助挂!星禾游戏辅助透视挂... 方法辅助挂!星禾游戏辅助透视挂,太浪乾安麻将是真的有挂,果然发现有挂1、下载好星禾游戏脚本下载之后点...
近日!巅峰互娱辅助透视挂,趣咪... 近日!巅峰互娱辅助透视挂,趣咪麻将确实有挂,真是揭秘有挂;1、这是跨平台的巅峰互娱轻量版有透视,在线...