文章目录
STL中迭代器失效的问题
1. 关联性容器的迭代器失效问题
当删除一个STL容器(比如map, vector)中的某个元素时, 会引起迭代器失效, 所以, 我们务必提高警惕。 某次笔试, 我遇到这样一个题目: 删除map<int, int>中value为5的倍数的元素。 该题看起来很自然很简单, 实则有迭代器失效的陷阱。
如果对迭代器失效问题一无所知, 则很容易写出如下的错误代码:
#include <iostream>
#include <map>
using namespace std;
typedef map<int, int> Map;
typedef map<int, int>::iterator MapIt;
void print(Map &m)
{
MapIt it;
for(it = m.begin(); it != m.end(); it++)
{
cout << it->second << " ";
}
cout << endl;
}
void deleteValueFromMap(Map &m, int n = 5)
{
MapIt it;
for(it = m.begin(); it != m.end(); it++)
{
if(0 == it->second % n)
{
m.erase(it); //此时没有更新迭代器
}
}
}
int main()
{
Map m;
int i = 0;
for(i = 0; i < 21; i++)
{
m[i] = i;
}
print(m);
deleteValueFromMap(m); // 程序在此处崩溃
return 0;
}
运行上述程序, 结果程序崩溃,打印不出来删除后的容器内容。 什么原因呢? 原来, 对于关联的容器map来说, m.erase(it);后, it就失效了, 而for循环中有it++, 自然而然会出问题啊。 那怎么办呢? 且看:
#include <iostream>
#include <map>
using namespace std;
typedef map<int, int> Map;
typedef map<int, int>::iterator MapIt;
void print(Map &m)
{
MapIt it;
for(it = m.begin(); it != m.end(); it++)
{
cout << it->second << " ";
}
cout << endl;
}
void deleteValueFromMap(Map &m, int n = 5)
{
MapIt it;
MapIt tmp;
for(it = m.begin(); it != m.end(); /*不能再自增了*/)
{
if(0 == it->second % n)
{
tmp = it++;
m.erase(tmp);
}
else
{
it++;
}
}
}
int main()
{
Map m;
int i = 0;
for(i = 0; i < 21; i++)
{
m[i] = i;
}
print(m);
deleteValueFromMap(m); // 程序ok
print(m);
return 0;
}
运行结果如下:
当然, 上述程序也可以继续简化为如下:
#include <iostream>
#include <map>
using namespace std;
typedef map<int, int> Map;
typedef map<int, int>::iterator MapIt;
void print(Map &m)
{
MapIt it;
for(it = m.begin(); it != m.end(); it++)
{
cout << it->second << " ";
}
cout << endl;
}
void deleteValueFromMap(Map &m, int n = 5)
{
MapIt it;
for(it = m.begin(); it != m.end(); /*不能再自增了*/)
{
if(0 == it->second % n)
{
m.erase(it++);
}
else
{
it++;
}
}
}
int main()
{
Map m;
int i = 0;
for(i = 0; i < 21; i++)
{
m[i] = i;
}
print(m);
deleteValueFromMap(m); // 程序ok
print(m);
return 0;
}
结果也OK!
肯定有人会问, m.erase(it++);就不会产生迭代器失效么?
确实不会! 为什么呢? 这样从it++说起了。
例如:* ptr++ ;很多人错误地以为是先执行* p, 然后执行p++, 其实, 这是个天大的误解。 大家可以查一下* 和++的执行顺序, 虽然* 和++的优先级相同, 但此处采取的是右结合方式, 实际上先执行的是p++, 不过, p++的返回值是原来的p, 也就是说, *p++的值还是原值.
如果大家看过源码就知道他的内部大致是这样子的:
temp=*p;
p++;
return temp;
所以, 在m.erase(it++);中,it++先执行, 此时还没有erase, 程序自然不会崩溃. 当it++执行完后, 已经指向了下一个元素了, 但it++的返回值还是当前元素, 此时再删除它, 合情合理。
2. 序列性容器的迭代器失效问题
#include <iostream>
#include <vector>
using namespace std;
typedef vector<int> Vec;
typedef vector<int>::iterator VecIt;
void print(Vec &v)
{
VecIt it;
for(it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
void deleteValueFromVector(Vec &v, int n = 5)
{
VecIt it;
for(it = v.begin(); it != v.end(); it++)
{
if(0 == *it % n)
{
v.erase(it);
}
}
}
int main()
{
Vec v;
int i = 0;
for(i = 0; i < 21; i++)
{
v.push_back(i); // 不能再傻傻地用下标了
}
print(v);
deleteValueFromVector(v); // 程序崩溃
print(v);
return 0;
}
运行结果如下:
结果程序崩溃,打印不出来删除后的容器内容。
可见, vector也存在迭代器失效的问题, 怎么改呢? 如下:
#include <iostream>
#include <vector>
using namespace std;
typedef vector<int> Vec;
typedef vector<int>::iterator VecIt;
void print(Vec &v)
{
VecIt it;
for(it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
void deleteValueFromVector(Vec &v, int n = 5)
{
VecIt it;
for(it = v.begin(); it != v.end(); /*不能再自增了*/)
{
if(0 == *it % n)
{
it=v.erase(it); // vector元素自动向前挪动了(关联的map容器元素不会自动挪动), 所以不能再把it进行++了
}
else
{
it++;
}
}
}
int main()
{
Vec v;
int i = 0;
for(i = 0; i < 21; i++)
{
v.push_back(i); // 不能再傻傻地用下标了
}
print(v);
deleteValueFromVector(v); // 程序ok
print(v);
return 0;
}
运行结果如下:
总结:
序列性容器::(vector和list和deque)
erase迭代器不仅使所指向被删元素的迭代器失效,而且使被删元素之后的所有迭代器失效,所以不能使用erase(iter++)的方式,但是erase的返回值为下一个有效的迭代器。
所以正确方法为::
for( iter = c.begin(); iter != c.end(); )
iter = c.erase(iter);
关联性容器::(map和set比较常用)
erase迭代器只是被删元素的迭代器失效,但是返回值为void,所以要采用erase(iter++)的方式删除迭代器,
所以正确方法为::
for( iter = c.begin(); iter != c.end(); )
c.erase(iter++);