情况一:扩容导致野指针
问题描述:
- 若遇到扩容情况,会因为扩容后旧数据被放到新空间中,但pos还指向原空间对应数据的位置(而其实原空间已经被释放了),故出现了野指针 ==> 即pos失效了
解决方式:更新pos
- ①在扩容后的新空间再find找pos==>代价大,不推荐
- ②通过扩容前记录的pos与_start的相对位置,重更新pos(如下)
- ==>推荐(官方库也是这么处理的)
//解决方式②
if (_finish == _end_of_storage)
{
size_t len = pos - _start;//扩容前记录pos和start的相对距离
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;//扩容后根据新的_start和记录的相对距离,更新pos位置
}
情况二:重复使用同一个pos位置,第二次insert开始出错
错误情况①:发生扩容 ==> 野指针
问题描述:
- 在使用pos插入一次数据后,若想再使用同一个pos,继续插入数据时,第二次插入就报错了。
思考改进:
- 既然形参改变无法改变实参,那是否能将
iterator pos
改成iterator& pos
不就能改变p的值吗?- (×)解决方式不可行,因为若采用引用,会由于出现权限的放大和缩小问题,导致出现连锁的其他错误(只有引用和指针,会导致出现权限的放大和缩小问题)
- 并且库中也没有采用
iterator& pos
,即是考虑到一连锁的其他问题。
结论:
- 总之,在pos位置插入数据以后,不要再次访问pos,因为pos可能失效了
- 因为p是以实参的形式传入形参pos,而更新pos是在形参的形式更新的==>形参的改变无法影响实参
错误情况②:不扩容 ==> 重复指向位置已经不是原来的值了(因为数据挪动)
- 错误描述:it找到第一个偶数后,依次向后挪动一位,再插入其2倍数后,it指向的始终是第一个偶数。
// 如要求:在所有的偶数前面插入一个偶数2倍
vector<int> v;
v.resesrve(10);//提前预留空间,保证不出现空间不足导致扩容
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto it = v.begin();
while (it != v.end())//①
{
if (*it % 2 == 0)
{
v.insert(it, *it * 2);
}
++it;
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl; //结果:1 4 4 …… 4 2 3 4
- 解决方案:修改上方代码①while循环
while (it != v.end())//①
{
if (*it % 2 == 0)
{
it=v.insert(it, *it * 2);//it在新插入元素的位置
++it;
++it;
}
else
{
++it;
}
}
情况三:erase导致的迭代器失效问题
错误情况1:erase缩容==>野指针
问题描述:
- 当erase后,进行了缩容,旧数据移动到新空间中,指向原空间的pos指针就会失效
思考改进:
- stl只是一个规范,规定了功能有什么,但底层具体如何实现没做要求,故不同平台下实现不同。所以在erase的实现中,不保证是否存在有的编译器会进行缩容==>缩容就有可能导致出现迭代器失效的情况
- 缩容是一种以时间换空间,但目前空间都较大,而时间很宝贵,所以不怎么会采用这样的方式。
结论:
- 虽缩容这种方案少见,但也合理。且由于我们无法确定使用的编译器是如何实现earse的?其是否采用了earse的缩容方案?为避免这样的情况,故建议,erase之后,也不再访问pos指针
错误情况2:不涉及缩容情况,erase数据排列偶然性
-
根据数据排列的情况,会出现错误的两种排列方式:
- ①最后一个数是偶数==> 段错误
- ②出现连续的偶数==>连续出现的第偶数个(如第2、4、6…个)偶数会被略过不检查
- (即使以上两种没碰上,结果显示正确,也只是偶然性)
-
在vs下无论哪种错误,只要it改变了指向的值,都会直接报错(如表格左);在g++下,三种各个显示不同情况
-
错误代码:
#include<iostream>
#include<vector>
using namespace std;
void test_vector()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
v.erase(it);
}
++it;
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test_vector();
return 0;
}
- 解决方式:将上部分代码中标记①处的while循环修改如下。
- 修改后不论何种情况都不会出错了
while (it != v.end())
{
if (*it % 2 == 0)
{
it=v.erase(it);
//删除后已经指向了删除元素的下一个位置,故不必再++
}
else
{
++it;
}
}
总结
- 当找到问题所在,但不知道如何解决的时候
- ==>参考源代码,看官方是怎么解决的
- 结论:
- ①insert/erase的pos位置,不要直接访问pos。一定要更新,若直接访问可能各种出乎意料的结果(不论什么情况,均认为pos失效),这就是所谓迭代器失效。
- 不同平台出错的结果可能不同,因为底层具体实现机制不同。
- ②修改方案:接收insert/erase的值
- insert返回值是:在新插入元素的位置
- erase返回值是:删除的值的下一个位置
- ①insert/erase的pos位置,不要直接访问pos。一定要更新,若直接访问可能各种出乎意料的结果(不论什么情况,均认为pos失效),这就是所谓迭代器失效。
- 整理、画图不易,希望本文能帮助大家理清迭代器失效的各个情况 ~