学一学C++:vector迭代器失效学习小结
什么是迭代器失效?
我理解的迭代器失效,是指迭代器不能正确指示我们想要操作的对象了,对迭代器进行的操作会产生错误的结果。我们举个栗子来看看迭代器失效是怎么回事~
// 先定义一个用来输出vector<string>对象的函数show_vec
void show_vec(vector<string>::iterator first, vector<string>::iterator last)
{
while(first != last)
{
std::cout << *first << " ";
first++;
}
std::cout << " " << std::endl;
}
我们来看一个会使迭代器失效的栗子~
int main()
{
vector<string> vecString; // 定义一个空的vector<string>对象vecString
string name; // 定义一个空的string对象name
while(cin>>name) // 向vecString输入元素
vecString.push_back(name);
show_vec(vecString.begin(), vecString.end()); // 调用show_vec函数输出vecString
vector<string>::iterator i;
for(i=vecString.begin(); i!=vecString.end() ; i++) // 这个循环会出问题
{
if (*i == "LiHonghe")
vecString.erase(i);
}
show_vec(vecString.begin(), vecString.end());
return 0;
}
看一下它的输入和对应的输出结果。注意到我输入了两次“LiHonghe”,然而它只删除了一次,问题来了这个栗子是怎么失效的呢?
下面我们详细分析一下迭代器在循环中是怎么变化的。
元素 | LiHonghe | LiHonghe | LiLu | LiJiaxin | LiMing | LiDongbiao |
---|---|---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
我们用“iter”和“↓”来表示当前迭代器。
iter↓ | ||||||
---|---|---|---|---|---|---|
元素 | LiHonghe | LiHonghe | LiLu | LiJiaxin | LiMing | LiDongbiao |
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
进入循环,我们发现第一个元素是“LiHonghe”,需要使用erase成员函数删除它(见erase的执行条件)。erase成员函数如何删除元素,对于我们分析iter的变化至关重要,因此我们要插播一下erase的实现方法(仅分析删除单个元素的情况)。
iterator erase(iterator position)
{
if(position + 1 != end()) // 如果下一个位置不是“尾后”
copy(position + 1, finish, position);// 将position+1至finish的元素移到position至finish-1
--finish; // 新finish是原finish的前一位
destroy(finish); // 删除原finishi
return position; // 返回position
}
看完代码我们可以知道,erase在删除元素的时候,是把该元素后边的所有元素向前移动一位,而返回的迭代器还是原来的迭代器。所以在删除“LiHonghe”之后,迭代器“iter”还在下标“0”的位置。
iter↓ | ||||||
---|---|---|---|---|---|---|
元素 | LiHonghe | LiLu | LiJiaxin | LiMing | LiDongbiao | (destroy) |
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
回到循环,当我们删除完“LiHonghe”之后系统会执行“iter++”操作,这使“iter”移到“LiLu”,也就错过了第二个“LiHonghe”。因此我们在进入循环中,不满足if (*i == “LiHonghe”)的执行条件,这就使得输出中还有一个“LiHonghe”存在。迭代器也就失效了。
iter↓ | ||||||
---|---|---|---|---|---|---|
元素 | LiHonghe | LiLu | LiJiaxin | LiMing | LiDongbiao | (destroy) |
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
更改它的方法,就是把对迭代器的操作,同循环体分开。局部代码更改如下:
for(i=vecString.begin(); i!=vecString.end() ; ) // 将i++与erase分开执行,这个循环就没问题了
{
if (*i == "LiHonghe")
vecString.erase(i); // 执行erase,则不执行i++
else
i++; // 不执行erase,则执行i++
}
有的人把vecString.erase(i)写成i = vecString.erase(i),我们知道erase的返回值是迭代器,不过经过刚才的分析,我们知道i是不变的,所以用不用返回值重新给i赋值,对结果没有影响。输出结果如下:
这样重复的“LiHonghe”就被删除啦。
使得vector迭代器失效的操作有:
(1)执行erase方法时,指向删除节点及其之后的全部迭代器均失效;
(2)执行push_back方法时,end操作返回的迭代器失效;
(3)插入一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时begin和end操作返回的迭代器都会失效;(capacity是指在发生realloc前能允许的最大元素数,即预分配的内存空间)
(4)插入一个元素后,如果空间未重新分配,指向插入位置之前的元素的迭代器仍然有效。
(其中,第三条我不是很理解,因为还没有遇到改变capacity返回值的情况)
谨记,但凡使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
其他容器的迭代器失效还没学习~目前就总结了vector。