说我有以下代码:
typedef std::map< int, std::string >::iterator Iterator;
Iterator iter = myMap.begin();
while (iter != myMap.end())
{
Iterator current = iter;
++iter;
maybeDeleteElement( current ) // may call erase.
}
鉴于std::map
是作为红黑树实现的,是否可以保证地图中的每个元素都只被访问一次?或者修改地图会导致树重新平衡,从而改变迭代序列吗?
注意:这不是关于任何迭代器是否将被无效的问题。但是迭代器仍然有效并不一定意味着递增它会给你与之前相同的下一个元素。
答案 0 :(得分:5)
在std::map
中,将按顺序访问元素。
如果存储一个迭代器,该迭代器引用一个未被删除的元素,因此不会失效,迭代器仍会引用同一个元素。 (如果它是结束迭代器,它仍然是结束迭代器,因为它没有失效)。
当你推进迭代器时,它将在你引用的元素之后按顺序前进到下一个元素。
对于您的特定示例,是的,每个元素将被访问一次,因为所有元素删除都是在循环的当前迭代器状态之前的元素。
如果您在用于迭代的任何迭代器之前插入元素,那么当您使用迭代器迭代时,最终会到达它们。如果在用于迭代的任何迭代器之前删除元素,那么如果使用该迭代器进行迭代,它们将不再是未来元素的一部分。
如果插入或删除迭代器当前位置之前的元素,除非您开始调用--
或类似函数,否则当前迭代将继续,而不会注意到它们已消失。
这是因为有序容器中有效迭代器上的++
保证返回顺序中的下一个元素,而其他不使迭代器失效的迭代器上的操作不会改变迭代器的不变量(如他们所指的是什么元素。)
答案 1 :(得分:2)
是的,插入/擦除可以修改迭代序列。在您的示例中不会发生这种情况,因为您擦除了已经传递的迭代器,但是如果您擦除/插入位于当前迭代器的前面的元素,那么它将修改其余的序列
这是一个显示此类行为的简短代码:
int main (){
map<int,int> mapa;
for(int i = 0; i < 5; ++i) mapa[i] = i;
bool add = false;
for(auto it = mapa.begin(); it != mapa.end(); ++it){
int x = it->second;
printf("%d\n", x);
if(add) mapa.erase(x+1);
add = !add;
}
return 0;
}
上面的示例将打印0 1 3 4
(而不是0 1 2 3 4
)。另外,如果擦除当前迭代器,它对下一个元素的引用将失效,程序将在下一次迭代时崩溃。
或者,您也可以通过上面的if(add)
替换:
if(add) mapa[x+5] = x+5;
else mapa[x-20] = x-20;
该示例将打印额外的元素{6,8,11,16},而不打印否定元素,因为这些元素被插入到当前元素之前的位置。
答案 2 :(得分:1)
从map
中删除元素不会使迭代器失效,所以我希望它能够继续正确迭代。
答案 3 :(得分:0)
是的,迭代序列会发生变化。这是由于§23.2.4.1/ 10和/ 11:
(p10)关联容器的迭代器的基本属性是它们以密钥的非降序顺序遍历容器,其中非降序由用于构造它们的比较定义。对于任何两个可解除引用的迭代器i和j,使得从i到j的距离为正,
value_comp(*j, *i) == false
(P11) 对于具有唯一键的关联容器,更强的条件成立,
value_comp(*i, *j) != false.
如果在插入之后,无论元素的排序如何,都会在迭代序列的开头添加新元素(以便不在任何现有迭代器位置之前修改序列),将违反上述要求
答案 4 :(得分:0)
尽管你注意到,对于erase
这个问题,这个问题完全关于迭代器失效,因为如果没有迭代器失效,顺序必然保持不变。这是因为map
是一个已排序的容器,因此无论内部表示发生何种更改,迭代顺序都必须保持完全相同。
为了具体说明你的例子,它将遍历每个元素一次,因为你保存了迭代器以检查和递增遍历迭代器。
在插入的情况下,如果在当前迭代点之前插入,则不会访问该元素。在当前迭代器之后插入将导致遍历新项目。