菜鸟笔记
提升您的技术认知

vector.erase()函数的常见陷阱

对 vector 尾部进行插入和删除操作可以很方便地通过 vector.push_back() 和 vector.pop_back() 函数来进行。

但是想要对中间部分的数据进行增删操作,就需要使用到迭代器。

为了对迭代器的使用进行学习,我们先定义一个整形数组v,并且给他赋初值:

vector<int> v = { 10,20,30,40,50,60,70 };

接下来,我们给数组插入5个1000,并且尝试使用迭代器将5个1000全部删除:

v.insert(v.begin(), 5, 1000);

陷阱1:生成野指针,导致程序崩溃

写的时候如果不加思索的话,很可能会写出以下的代码:

for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		if (*it == 1000)
		{
			v.erase(it); 
		}
	}

乍一看,当 it 指针找到值为1000的元素时,将 it 指向的元素从 v 中删除,似乎没有问题,那么我们运行一下看看结果:

 很不幸,程序崩溃了。略加思索后发现原因如下:当执行第一个v.erase(it)时,指针被删除,且没有给其重新定向,it就变成了一个野指针,当循环再次调用时,就会引发程序崩溃!

陷阱2:删除不完全

既然陷阱1导致 it 变成了野指针,那么有什么方法可以避免呢?其实很简单,只要给 it 重新定向就可以:

for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		if (*it == 1000)
		{
			it = v.erase(it); 
		}
	}

这次it不会变成野指针了,程序是可以正常运行了,那么运行结果呢?

 我们预期的结果是将5个1000全部删除,但是结果中竟然还剩下了两个1000,那就只能从程序逻辑再次进行分析:

在一次循环内部,当执行了 v.erase() 后, it 指针会自动指向被删除了的元素的下一个位置,这个时候再执行循环体中的 it++,就会导致 it 直接指向了下下个元素! 当第一个1000被删除后, it 指向了第三个1000,并且将其删除,然后又指向了第5个1000,将其删除。因此结果中,第二个和第四个1000就被保留了,没有删除干净。

其实,这种写法的后果不仅仅是删除不干净那么简单:如果在数组最后的位置还有一个1000呢?当最后一个1000被删除后, it 直接指向了数组的最后一位数字的后一位,超出了数组的界限,就会引发程序崩溃!

正确使用:

既然原因找到了,那么就针对性地对程序进行修改:如果我们不在 for 循环条件中让 it 指向下一位,而是在函数体中,做一个条件判定呢?

for (vector<int>::iterator it = v.begin(); it != v.end();)
	{
		if (*it == 1000)
		{
			it = v.erase(it);
		}
		else
		{
			it++;
		}
	}

分析一下上面的程序:在一次循环中,如果执行了 v.erase() 函数,那么 it 会自动指向下一个位置,在for()循环条件中并未传达 it++ 的指令,因此 it 能够正常地指向下一个元素而不是下下个元素;如果没有执行 v.erase() 函数,那么就手动让 it 指向下一个元素。

再次运行程序,结果正确:

 

(ps:有讲的不够详细或者错误的地方,欢迎指正!)