我很好奇以下代码背后的基本原理。对于给定的地图,我可以使用以下代码删除最多但不包括end()
(显然)的范围:
map<string, int> myMap;
myMap["one"] = 1;
myMap["two"] = 2;
myMap["three"] = 3;
map<string, int>::iterator it = myMap.find("two");
myMap.erase( it, myMap.end() );
使用该范围删除最后两项。但是,如果我使用了擦除的单个迭代器版本,我一半期望传递myMap.end()
导致无法执行操作,因为迭代器显然位于集合的末尾。这与腐败或无效的迭代器不同,后者显然会导致未定义的行为。
然而,当我这样做时:
myMap.erase( myMap.end() );
我只是遇到了分段错误。我不会认为map检查迭代器是否等于end()
并且在这种情况下不采取行动。这是否有一些微妙的原因让我失踪?我注意到即便如此:
myMap.erase( myMap.end(), myMap.end() );
(即什么也没做)
我问的原因是我有一些代码接收到集合的有效迭代器(但可能是end()
)并且我想简单地将其传递给擦除而不必像这样先检查:
if ( it != myMap.end() )
myMap.erase( it );
对我来说似乎有点笨拙。另一种方法是重新编码,这样我可以使用按键式擦除重载,但如果我能帮助它,我宁愿不要重写太多。
答案 0 :(得分:5)
关键是标准库中由两个迭代器确定的范围是半开范围。在数学符号[a,b)
中它们包括第一个但不是最后一个迭代器(如果它们都相同,则范围为空)。同时,end()
返回一个超出最后一个元素的一个迭代器,它完全匹配半开范围表示法。
使用erase
的范围版本时,它永远不会尝试删除最后一个迭代器引用的元素。考虑一个修改过的例子:
map<int,int> m;
for (int i = 0; i < 5; ++i)
m[i] = i;
m.erase( m.find(1), m.find(4) );
在执行结束时,地图将包含两个键0
和4
。请注意,第二个迭代器引用的元素不从容器中删除。
另一方面,单个迭代器操作将擦除迭代器引用的元素。如果上面的代码更改为:
for (int i = 1; i <= 4; ++i )
m.erase( m.find(i) );
将删除键为4
的元素。在您的情况下,您将尝试删除不引用有效对象的结束迭代器。
我不会认为map检查迭代器是否等于end()并且在这种情况下不采取行动。
不,这并不难,但是函数的设计考虑了不同的契约:调用者必须将迭代器传递给容器中的元素。造成这种情况的部分原因是,在C ++中,大多数功能都经过精心设计,以便尽可能降低成本,使用户能够平衡自身的安全/性能。用户可以在调用erase
之前测试迭代器,但是如果该测试在库内,则当用户知道迭代器有效时,将无法选择退出测试。
答案 1 :(得分:1)
n3337 23.2.4表102
a.erase( q1, q2)
擦除范围[q1,q2] 中的所有元素。返回q2。
因此,iterator
返回的map::end()
在myMap.erase(myMap.end(), myMap.end())
的情况下不在范围内;
a.erase(q)
擦除q指向的元素。返回一个迭代器,指向在删除元素之前紧跟在q之后的元素。如果不存在这样的元素,则返回a.end()。
我不会想到地图检查是否有困难 迭代器等于end(),在这种情况下不采取行动。在那儿 我错过了一些微妙的原因?
原因相同,std::vector::operator[]
无法检查,当然,该索引在范围内。
答案 2 :(得分:1)
当使用两个迭代器来指定范围时,范围由第一个迭代器指向的元素中的元素组成,但不包括第二个迭代器指向的元素。因此,erase(it, myMap.end())
表示要删除it
到end()
之间的所有内容。同样可以传递一个指向“真实”元素的迭代器作为第二个元素,并且迭代器指向的元素不会被删除。
当您使用erase(it)
时,它会删除it
指向的元素。 end()
迭代器不指向有效元素,因此erase(end())
没有做任何明智的事情。库有可能诊断出这种情况,并且调试库会这样做,但它会在每次调用erase
时花费一大笔费用来检查迭代器指向的内容。标准库不会对用户施加成本。你是独自一人。 <g>