什么是迭代器失效
迭代器是一种面向对象的广义指针,用于指向容器中或流中的对象,可以看做是一种指向数据的指针。
迭代器失效时一种效果,由特定操作对容器进行操作,使迭代器不指向容器中的任何元素,或者使得迭代器指向的容器元素发生了变化。
vector扩容引发迭代器失效
vector.erase
void test_vector_erase()
{
vector<int>d;
d.push_back(2);
d.push_back(3);
d.push_back(4);
d.push_back(67);
d.push_back(8);
d.push_back(99);
d.push_back(2);
d.push_back(6);
d.push_back(10);
cout << d.size() << ":" << d.capacity() << endl;
auto pos = find(d.begin(),d.end(),4);
if (pos != d.end())
d.erase(pos);
cout << d.size() << ":" << d.capacity() << endl;
cout << *pos << endl;
//删除pos位置的数据,此时pos这个迭代器指向的位置是被删除位置的下一个
//位置,但是还是会报错,因为此时迭代器的意义变了
}
}
在erase之后迭代器指向的位置是被删除位置的下一个位置,迭代器的意义发生了变化,所以也会报错。
段错误:
段错误就是访问了不可访问的内存,运行时出现了segmentation fault的报错
产生原因:访问不存在的内存地址,访问系统保存的内存地址。访问只读的内存地址。空指针废弃(eg:malloc,new与free,delete释放后继续使用),堆栈溢出,内存越界等。
下面一段代码,删除数组中的偶数
void test_vector_erase1()
{
vector<int>d;
d.push_back(2);
d.push_back(3);
d.push_back(4);
d.push_back(67);
d.push_back(8);
d.push_back(99);
d.push_back(2);
d.push_back(6);
auto it = d.begin();
while (it != d.end())
{
if (*it % 2 == 0)
d.erase(it);
it++;
}
}
同理,删除之后,此时如果继续it++,那么就不知道加到哪去了
通过stl的源码可以发现
其返回的是删除位置后的下一个位置,为了避免这个错误,我们只需要这样改
while (it != d.end())
{
if (*it % 2 == 0)
it = d.erase(it);
else
it++;
}
总结:erase报错大部分都是迭代器的意义发生了变化,或者不在有效访问数据范围之内,一般不会使用缩容方案导致出现野指针,
vector删除数据,都不考虑缩容方案,因为缩容方案是开一个size()大小的空间,拷贝数据,释放旧空间,本质是时间换空间,而因为大部分关注的是时间效率,不会关注空间效率,所以不会这样做,但insert确不一样
vector.insert
void test_vector_insert()
{
vector<int>d;
d.push_back(2);
d.push_back(3);
d.push_back(4);
cout << d.size() << ":" << d.capacity() << endl;
auto pos = find(d.begin(), d.end(), 4);
if (pos != d.end())
{
d.insert(pos, 20);
}
cout << *pos << endl;
*pos = 88;
cout << *pos << endl;
}
insert出现错误可能有两种:
1:与上述问题一样,此时迭代器的指向的意义发生了变化,此时指向的是被加入的元素
2:有可能会出现扩容
因为扩容就是新开辟一段空间,然后将原数据内容拷贝过去,然后释放旧空间,此时会出现野指针问题。
整体总结:
对于insert和erase造成迭代器失效问题,Linux(g++)平台检查的很佛系,基本依靠操作系统自身野指针越界检查机制,window下vs系列检查更严格,使用一些强制检查机制,仅仅是意义变了也可能会检查出来。
list迭代器失效
由上述可知,我们可以将迭代器理解成指针,迭代器失效即迭代器指向的节点无效,也就是指针指向的空间失效,可能是意义发生了变化,也可能是野指针问题。
由于list的底层结构位带头结点的双向循环链表,而list中的insert通过源码可知此时返回的是新加入元素的位置,所以,insert是不会导致迭代器失效,只有erase才会导致迭代器失效,但失效的仅仅是被删除的结点,其他还是好的,因为被删除之后,其指向的空间被系统回收了,我们只要针对这个点就可以了。
void test_list_iterator_erase()
{
list<int>d;
d.push_back(2);
d.push_back(6);
d.push_back(3);
d.push_back(9);
d.push_back(5);
auto pos = find(d.begin(), d.end(), 3);
while (pos != d.end())
d.erase(pos++);
for (auto e : d)
cout << e << " ";
cout << endl;
}
像上述代码一样就可以了,如果仅仅是
while (pos != d.end())
{
d.erase(pos);
pos++;
}
此时会报错,因为erase之后pos的位置已经被系统回收了,pos++其实访问的是野指针,会报错!
而我们这样写是在删除之前就使得pos的值指向了下一个位置。