迭代器失效问题

话说回来,今天 ymx 发了个关于 std::list 迭代器失效的问题,正好也让我对这方面的芝士有所了解,故作文以记之((

首先先贴一张 C++ reference 里的图:

我们知道 C++ STL 中的容器大致可分为四类,序列型(如 vector,deque,queue)、关联型(set,map,multiset)和链表型(list,forward_list),这三种类型的容器迭代器失效的情况是不同的,因此这里会对其一一进行阐述:

1. 序列型容器

序列型容器的特点是它内存分配是一段连续的区间,也就是说,当你删除一个元素后会导致后面的元素全部向前移动一格,使得后面元素的迭代器全部失效,因此删除 vector 中的元素不能这么写:

for(vector<int>::iterator it=vec.begin();it!=vec.end();it++){
    if((*it)>100) vec.erase(it);
}

也不能这么写:

for(vector<int>::iterator it=vec.begin();it!=vec.end();it++){
    if((*it)>100) vec.erase(it++);
}

一个解决办法是:利用 vector 类型中 erase 函数的返回值找出下一个合法的地址,即:

for(vector<int>::iterator it=vec.begin();it!=vec.end();){
    if((*it)>100) it=vec.erase(it);
    else ++it;
}

此外,根据 vector 自动伸长的原理(如果不清楚参见 THUSC2021 试机赛 T2,如果没参加过 THUSC2021……那恐怕我也没办法了),若删除一个元素后 vector 的长度变为某个 \(2^k-1(k\in\mathbb{Z})\),那么 vector 分配的内存大小将会减半,导致 vector 内部全部重构,此时整个 vector 的迭代器都会失效。

2. 关联型容器

与序列型容器不同的是,关联型容器内部是二叉树或红黑树,因此它们的内存分配是没有规律的,删除一个元素后只会对使被删除的元素的迭代器失效。但是注意,失效后的迭代器你对它进行任何操作都将会导致 RE,因此你不能这样删除元素:

for(set<int>::iterator it=st.begin();it!=st.end();it++){
    if((*it)>100) st.erase(it);
}

原因是你对失效的迭代器进行自增操作。
而这样写就不会出现问题:

for(set<int>::iterator it=st.begin();it!=st.end();it++){
    if((*it)>100) st.erase(it++);
}

值得注意的一点是,在 std::set 类型中,erase 函数是没有返回值的,因此也就不能通过调用 erase 函数获得下一个有效地址的位置,否则会获得 CE 的好成绩(London Fog

3. 链表型容器

对于链表型容器而言,虽然从外表上看起来它使用了数组型的结构,但实际上它内存的分布也是不连续的,因此删除一个元素并不会对其他元素的地址产生影响,因此可以通过 erase 函数的返回值获得下一个有效地址,也可通过在删除的同时自增迭代器的方式避免迭代器失效

posted @ 2021-06-30 17:09  tzc_wk  阅读(248)  评论(1编辑  收藏  举报