C++ Vector迭代器失效问题的解决方法

 更新时间:2022年08月03日 15:28:01   作者:Rookiep  
最近我学习了C++中的迭代器失效问题,迭代器失效问题是非常非常重要的,所以特意整理出来一篇文章供我们一起复习和学习

一、迭代器失效

主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装。比如:vector的迭代器就是原生态指针T*。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

二、可能引起的迭代器失效的操作

2.1、野指针引起迭代器失效

凡是涉及到扩容操作,都有可能引起迭代器失效,因为vector扩容是分配一个新的数组,然后全部元素移到新的数组中。

下面我们就以Insert函数来举例说明!

示例1:

void test02()
{
	// 在所有的偶数的前面插入2
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	cout << v.size() << ":" << v.capacity() << endl;
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			v.insert(it, 20);
			++it; //这里++是为了解决第二种迭代器失效,防止原地踏步
		}
		++it;
	}
	cout << v.size() << ":" << v.capacity() << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

程序崩溃!

代码解释:如果我们没有预先分配空间,那么在insert的时候会发生扩容,根据我们模拟实现vector可知,STL标准库的vector中insert函数是实现了对迭代器的更新,但是形参列表没有使用输出型参数,所以我们只有通过返回值来接收新的迭代器!

示例2:

如果我们用返回值来接受新的迭代器,则不会崩溃!

void test02()
{
	// 在所有的偶数的前面插入2
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	cout << v.size() << ":" << v.capacity() << endl;
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			it = v.insert(it, 20);//stl中的insert如果发生了扩容是实现了对it位置的更新,并用返回值输出了形参的改变
			++it; //这里++是为了解决第二种迭代器失效,防止原地踏步
		}
		++it;
	}
	cout << v.size() << ":" << v.capacity() << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

6:6
9:9
1 20 2 3 20 4 5 20 6
请按任意键继续. . .

代码解释:

STL中的insert如果发生了扩容是实现了对it位置的更新,并用返回值输出了形参的改变。

示例3:

如果我们预先预留(reserve)了空间,再插入过程中没发生扩容,那么自然也不会失效了。

void test02()
{
	// 在所有的偶数的前面插入2
	vector<int> v;
	v.reserve(20);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	cout << v.size() << ":" << v.capacity() << endl;
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			//it = v.insert(it, 20);
			v.insert(it, 20);
			++it; 
		++it;
	}
	cout << v.size() << ":" << v.capacity() << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

2.2、迭代器指向的位置意义改变

一般vector删除数据,都不考虑缩容的方案。缩容方案: size() < capacity()/2时,可以考虑开一个size()大小的空间,拷贝数据,释放旧空间。缩容方案本质是时间换空间。一般设计都不会考虑缩容,因为实际比较关注时间效率,不关注空间效率,因为现在硬件设备空间都比较大,空间存储也比较便宜。

示例4:

void test03(){
	vector<int> v;
	cout << v.size() << ":" << v.capacity() << endl;
	v.reserve(10);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	cout << v.size() << ":" << v.capacity() << endl;
	auto pos = find(v.begin(), v.end(), 2);
	if (pos != v.end())
	{
		v.erase(pos);
	}
	cout << v.size() << ":" << v.capacity() << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << *pos << endl; //只要一访问 系统强制检查(怎么检查的不知道!), 就报错(Linux没报错)
	*pos = 10;
	cout << *pos << endl << endl;
}

代码解释:可见代码确实是实现了删除,但是程序却崩了,原因就是erase后pos失效了,pos的意义变了,(但是在不同平台下对于访问pos的反应是不一样的,因此我们使用的时候要特别小心,统一以失效的角度去看待)。但如果不访问pos指向的内容就不会崩溃!

erase导致的失效:

  • erase失效都是意义变了。
  • 一般不会有缩容方案,那么erase的失效,一般也不存在野指针的失效。

😄😄😄😄😄😄😄😄😄😄😄😄😄

下面我们举个实例:

要我们删除容器中所有偶数:

示例5:

void test05()
{
	std::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(2);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(4);
	v.push_back(4);
	v.push_back(5);
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			v.erase(it);//删除了就不移动
		}
		else
		{
			++it;
		}
	}
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

代码解释:毫无疑问上诉代码会崩溃,因为erase后迭代器it所指向的位置失效,(虽然感觉是可以继续使用的,但在vs下就是不可以使用,在Linux下就可以对这个位置进行访问),所以下面我们用返回值来更新迭代器。

示例6:

void test05()
{
	std::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(2);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(4);
	v.push_back(4);
	v.push_back(5);
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			it = v.erase(it);//删除了就不移动
		}
		else
		{
			++it;
		}
	}
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

1 3 5
请按任意键继续. . .

代码解释:可见成功的删除了其中的偶数!

其中缘由:erase删除pos位置元素后,pos位置之后的元素会往前移动,没有导致底层空间的改变,理论上讲迭代器不会失效,但是如果pos位置刚好是最后一个元素,删完之后pos刚好是end的位置,而end的位置是没有有效元素的,那么pos就失效了。因此删除vector中任意位置元素时,vs均认为该位置上迭代器失效了!

除erase导致意义失效外,insert也可能导致意义失效,但是编译器却检查不出来!!!

示例7:

void test01(){
	vector<int> v;
	cout << v.size() << ":" << v.capacity() << endl;
	v.reserve(10);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	cout << v.size() << ":" << v.capacity() << endl;
	auto pos = find(v.begin(), v.end(), 2);
	if (pos != v.end())
	{
		v.insert(pos, 20);
	}
	cout << v.size() << ":" << v.capacity() << endl;
	cout << *pos << endl;
	*pos = 10;
	cout << *pos << endl << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

0:0
4:10
5:10
20
10

1 10 2 3 4

代码解释:同样地,不是因为扩容而引起的意义失效,这个时候我们访问了pos指向的位置,编译器却不报错,但此时并不意味着一定对,后续如果进一步操作,还是会发生各种各样的错误!

2.3、总结

总结:

  • 对于insert和erase造成迭代器失效问题,linux g++平台检查很佛系,基本依靠操作系统自身野指针越界检查机制,windows下vs系列检查更严格,使用一些强制检查机制,意义变了也可能会检查出来。
  • 虽然g++对于erase迭代器失效检查时非常佛系的,但是套在实际场景中,迭代器意义变了,也会出现各种问题,所以我们要有正确处理迭代器失效的方式,比如用函数返回值来更新迭代器。
  • windows下vs系列对意义失效的检查很双标,由insert函数引起的意义失效检查不出来,而且可以访问pos位置,但是由erase函数引起的意义失效却检查很严格,丝毫不准访问pos位置。(Linux却可以)

到此这篇关于C++ Vector迭代器失效问题的解决方法的文章就介绍到这了,更多相关C++ Vector迭代器失效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++图论基本概念与存储结构

    C++图论基本概念与存储结构

    这篇文章主要介绍了C++图论基本概念与存储结构,图是由顶点集合及顶点间的关系组成的一种数据结构,一个图既有顶点也有边,图的存储结构中就需要保存它们,下面将进行详细的介绍,需要的朋友可以参考下
    2026-02-02
  • C++计数排序详解

    C++计数排序详解

    计数排序的思想我们之前接触过的例如:插入排序,归并排序,快速排序,堆排序等都是基于集合元素之间的比较这一基本的思想,它们执行的时间复杂度最优是趋于O(nlgn),而计数排序的运行机制不是基于集合元素之间的大小比较
    2016-04-04
  • va_list(),va_start(),va_arg(),va_end() 详细解析

    va_list(),va_start(),va_arg(),va_end() 详细解析

    这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件.下面我们写一个简单的可变参数的函数,该函数至少有一个整数参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值
    2013-09-09
  • C语言数组应用实现扫雷游戏

    C语言数组应用实现扫雷游戏

    这篇文章主要为大家详细介绍了C语言数组应用实现扫雷游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • C语言中关于计算字符串长度的几种方式

    C语言中关于计算字符串长度的几种方式

    这篇文章主要介绍了C语言中关于计算字符串长度的几种方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • C++ namespace命名空间解析

    C++ namespace命名空间解析

    考虑一种情况,当我们有两个同名的人,Zara,在同一个班里。当我们需要对它们进行区分我们必须使用一些额外的信息和它们的名字,比如它们生活在不同的区域或者兴趣爱好什么的,在C++程序中也会遇到同样的情况,所以命名空间就此产生
    2021-11-11
  • C语言数据结构之迷宫求解问题

    C语言数据结构之迷宫求解问题

    这篇文章主要为大家详细介绍了C语言数据结构之迷宫求解问题,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-03-03
  • C语言实现基于最大堆和最小堆的堆排序算法示例

    C语言实现基于最大堆和最小堆的堆排序算法示例

    这篇文章主要介绍了C语言实现基于最大堆和最小堆的堆排序算法示例,分别是基于最大堆的升序排序和基于最小堆的降序排序实例,需要的朋友可以参考下
    2016-06-06
  • c语言switch反汇编的实现

    c语言switch反汇编的实现

    本文主要介绍了c语言switch反汇编,在分支较多的时候,switch的效率比if高,在反汇编中我们即可看到效率高的原因,感兴趣的可以了解一下
    2021-06-06
  • C++实现LeetCode(99.复原二叉搜索树)

    C++实现LeetCode(99.复原二叉搜索树)

    这篇文章主要介绍了C++实现LeetCode(99.复原二叉搜索树),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07

最新评论