看了本人前面关于“STL容器的删除元素问题”的博客后,相信大家对erase()函数的作用都已经有一定了解了,再使用的时候也会有所注意了,那么我们再看看下面的代码:
vector<int> vec(5);
//插入数据
vec[0] = 0;
vec[1] = 1;
vec[2] = 2;
vec[3] = 3;
vec[4] = 4;
//过滤数据
vector<int>::iterator iter = vec.begin();
vector<int>::iterator itEnd = vec.end();
for (; iter != itEnd;)
{
//删除值为3的数据
if (3 == *iter)
{
iter = vec.erase(iter);
}
else
{
++iter;
}
}
上面的代码,乍一看完全没有问题,程序也运行正常,但如果将“vec[4] = 4”改为“vec[4] = 3"之后,一运行程序就会core掉。上面代码对于erase的使用是正确的,那为什么还会出现问题呢?
在写C++程序的时候,为了提高效率,减少重复计算,我们往往会将一些计算结果不变的数据放置在循环的外面,上面的代码为了减少vec.end()的调用,把它移到for循环的外面了,因为这里认定了vec.end()并不会变。但是,这里却没有考虑vector的实现,以erase()函数为例,当删除了一个元素后,会将后面的元素覆盖删除的元素,因此,end的位置也必然会发生改变,由于只删除值为3的元素,当"vec[4] = 4"时,程序不会对越界的iter进行操作;当"vec[4] = 3"时,由于满足删除的条件,因此程序会对越界的iter进行删除操作,从而导致了crash的产生。下面将会画图介绍上面代码的操作过程。
当vec[4] = 4时:
vec的初始状态是:
当删除3之后的,vec的状态变为:
从上图可见,当删除3之后,end的位置已经发生了改变,已经向前移动了一个位置,即指向原来vec[4]的位置,而vec[3]的值变为原来vec[4]的值(即4),因为上面的示例代码中的for循环使用了旧的end,因此for循环会一直执行,直到到达旧的end位置位置,由于新的end到旧的end之间再没有3存在,所以程序并不会执行具体的操作,因此程序正常运行。
当vec[4] = 3时:
vec的初始状态是:
当删除第二个3之后的,end的位置再次发生改变,指向了原来vec[3]的位置,而vec的状态变为:
当删除第二个3之后,end的位置指向了原来vec[3]的位置,而因为上面的示例代码中的for循环使用了旧的end,因此for循环会继续执行,此时iter指向原来vec[3]的位置,程序发现vec[3] = 3,因此会执行erase操作,而此时iter所指位置已变为只读内存,尝试对其进行erase操作时,程序crash。
引用Donald Knuth大师的一句名言:
不成熟的优化是一切恶果的根源(Permature optimization is the root of all evil)
因此,大家在尝试对旧的代码进行优化的时候,请多思考几遍,确定为什么要优化?优化会生效?怎么优化?不要单纯的以为代码量减少了,程序就能到优化,要知道存在即合理,旧的代码或许已经过时了,但是当时这样写也必然有它的道理,要深入分析之后才动手啊!切记!切记!