我们先来看看下面的代码:
#include<iostream>
#include<vector>
#include<windows.h>
using namespace std;
int main()
{
vector<int>v2(6, 8);
v2.push_back(3);
v2.push_back(5);
vector<int>::iterator pos = v2.begin();
while (pos != v2.end())
{
if (*pos % 2 == 0)
{
v2.erase(pos);
++pos;
}
}
system("pause");
return 0;
}
编译运行这串代码,你会发现它毫不犹豫的崩溃了。当进行v.erase()操作时,作用点前的元素没有改变,但作用点后的元素向前移动了一位,使得pos后的各元素迭代器失效。同样v.insert()操作也会使得迭代器失效,失效和v.erase()操作同理。
那么如何解决这个问题呢?看如下代码:
#include<iostream>
#include<vector>
#include<windows.h>
using namespace std;
int main()
{
vector<int>v2(6, 8);
v2.push_back(3);
v2.push_back(5);
vector<int>::iterator pos = v2.begin();
while (pos != v2.end())
{
if (*pos % 2 == 0)
{
pos=v2.erase(pos);
}
else
{
++pos;
}
}
system("pause");
return 0;
}
进行删除操作后,erase会重新返回一个有效的迭代器,这时重新赋值个pos,所以这个迭代器的操作就能正常进行下去了。所以,对于在定义迭代器之后进行的插入和删除操作必须格外小心,防止出现迭代器失效问题。或者说尽量不要在迭代器定义之后进行插入和删除操作。
第二种情况代码如下:
#include<iostream>
#include<vector>
#include<windows.h>
using namespace std;
int main()
{
vector<int>v2(6, 8);
v2.push_back(3);
v2.push_back(5);
vector<int>::iterator pos = v2.begin();
v2.push_back(3);//当v2.capacity满的时候,发生内存拷贝,所有的迭代器均会失效
while (pos != v2.end())
{
if (*pos % 2 == 0)
{
pos=v2.erase(pos);
}
else
{
++pos;
}
}
return 0;
}
这里我们在迭代器定义之后进行了尾插操作,但程序运行正常,其结果和预期相符。这是因为pos后没有元素。这个例子佐证了作用点pos插入和删除并不影响作用点前面元素。
再来看如下代码:
#include<iostream>
#include<vector>
#include<windows.h>
using namespace std;
int main()
{
vector<int>v2(6, 8);
v2.push_back(3);
v2.push_back(5);
v2.push_back(9);//此时v2.size()=v2.capacity()
vector<int>::iterator pos = v2.begin();
v2.push_back(3);//当v2.capacity满的时候,发生内存拷贝,所有的迭代器均会失效
while (pos != v2.end())
{
if (*pos % 2 == 0)
{
pos=v2.erase(pos);
}
else
{
++pos;
}
}
return 0;
}
v2.capacity=9,初始时我们插入了6个元素,随后在定义迭代器前插入了两个元素,此时v2.capacity=8;为什么要关注v2.capacity呢?因为只有当我们实际需要的容量大于当前分配的容量时,vector为了存放多余的元素,它必须申请重新开辟空间,这个时候就会发生内存拷贝,这个时候迭代器将会全部失效。
根据以上情况我们得出迭代器失效情况:
- 在进行插入、删除等操作
- 扩容时发生内存拷贝
如何避免迭代器失效问题的发生
最简单的方法就是尽量不要在定义迭代器后进行插入和删除等操作。如果必须这样操作,请务必谨慎。你可能发现,如果当迭代器返回的时begin的值且容量足够,之后进行push_back并不会让迭代器失效。尽管可以这样,但我还是不建议你这样做。
C++的灵活赋予了它强大的功能,但同时特带来了极大的危险性。在C++中需要注意的问题有很多,难免不会操作失误,我们能做的就是将失误的可能性降到最低。规范的使用才能更好的运用。
下面给出教材上对于迭代器失效问题的总结:
《C++标准程序库》第六章STL容器
- 使用者在一个较小索引位置上安插和移除操作
- 由于容量变化而引起内存分配
- 当在vector中的某一位置安插(v.insert())或移除(v.erase())某个元素时,且当安插操作时,容器还有一定的容量(v.capacity())来容纳这个元素。如此一来,安插和移除操作不会因容器满而重新分配内存。在安插和移除操作后,作用点位置前的元素并没改变,而操作位置后的元素向后或向前移动一位。因此作用点后的各元素迭代器失效。注意此处的失效只是与安插或移位前定义的迭代器的意图出现稍微偏差。例如安插之前定义了一个逆向迭代器,安插后便无法通过该迭代器遍历所有元素(会丢掉一个元素}。
- 首先,我们知道vector实际上是连续存储的动态数组,因此当容器满时,为了保证连续存储需要重新开辟空间并将其原有数组拷贝到新空间,这使得原来空间的迭代器全部失效。此处的失效会在调试时出现debug assertion failed错误,这是由访问一个野指针而引起。注意vector为其分配内存的机制为:每次当容器满而重新分配内存时,都会分配比所需内存多的空间,因此,大多数安插和移除元素并不会发生内存的重新分配。这在一定程度上优化了vector的效率,避免了每次插入都进行内存重新分配这一耗时的操作。但是,尽管如此,我们也应坚决杜绝使用安插操作前定义的迭代器。
以下是对于vector接口的练习代码:
#include<iostream>
#include<vector>
#include<windows.h>
using namespace std;
int main()
{
//Vector 创建和打印
int a[] = { 1,2,3,4,5 };
vector<int>v1(a, a + sizeof(a) / sizeof(a[0]));
vector<int>v2(6, 8);//6个整型空间,值为8
vector<int>v3(v2.begin(), v2.end());//迭代器构造v3
vector<int>v4(v2);//拷贝构造
//vector<int>::iterator it = v2.begin();
//while (it != v2.end())//正向迭代器打印
//{
// cout << *it << " ";
// ++it;
//}
//cout << endl;
//for (unsigned int i = 0; i < v3.size(); i++)//数组方式打印
//{
// cout << v3[i] << " ";
// //cout << v3.at(i);
//}
//cout << endl;
//
//for (auto i : v4)//范围for打印
//{
// cout << i<<" ";
//}
//cout << endl;
//v2.push_back(1);//尾插数据
//v2.push_back(3);
//vector<int>::reverse_iterator rit = v2.rbegin();
//while (rit != v2.rend())//反向迭代器打印
//{
// cout << *rit << " ";
// ++rit;
//}
//cout << endl;
//
////capacity
//unsigned int size = v2.size();//获取元素个数
//unsigned int capa = v2.capacity();//获取容量
//int ifempty = v2.empty();//检查是否为空
//cout << size << " " << capa << " " << ifempty << endl;
//v2.resize(7,8);//如果resize比原来size大且比capacity大,首先扩展capacity,然后将多余的位置使用设置的字符填充,如不设置则默认为0。否则只改变size,不改变capacity。
//v2.reserve(7);//设置capacity,只有要设置的容量比原来的大,capacity才会改变
//cout << v2.capacity() << endl;
//
////vector的增删改查
//v2.push_back(2);
//v2.pop_back();//尾删
//v2._Pop_back_n(2);//尾删2个数据
//v2.push_back(4);
//vector<int>::iterator pos = find(v2.begin(), v2.end(), 4);// 使用find查找4所在位置的iterator
//v2.insert(pos, 6);
//pos = find(v2.begin(), v2.end(), 8);//从前到后查找第一个8所在位置的iterator
//v2.erase(pos);
//v2.swap(v3);//v2与v3交换
//for (auto i : v2)//范围for打印
//{
// cout << i<<" ";
//}
//cout << endl;
//vector的失效问题
v2.push_back(3);
v2.push_back(9);//此时v2.size()=v2.capacity()
vector<int>::iterator pos = v2.begin();
/*vector<int>::iterator pos=v2.end();
cout << v2.size() << " " << v2.capacity() << endl;
v2.push_back(3);//end返回的迭代器操作之后进行插入操作,修改了迭代器,使得迭代器失效*/
v2.push_back(3);//当v2.capacity满的时候,发生内存拷贝,所有的迭代器均会失效
while (pos != v2.end())
{
if (*pos % 2 == 0)
{
pos=v2.erase(pos);
}
else
{
++pos;
}
}
for (auto i : v2)
{
cout << i << " ";
}
system("pause");
return 0;
}