1

我正在阅读 Josuttis “The C++ Standard Library, 2nd ed.”。在第 6.7.1 节中,作者解释说下面给出的代码会产生意想不到的结果。我仍然不知道如何std::remove()运作,以及为什么我会得到这个奇怪的结果。(虽然我知道你需要使用std::erase()才能真正删除元素,实际上最好使用list::erase()而不是std::remove()& `std::remove() 的组合更好)。

list<int> coll;
// insert elements from 6 to 1 and 1 to 6
for (int i=1; i<=6; ++i) {
     coll.push_front(i);
     coll.push_back(i);
}

// print
copy (coll.cbegin(), coll.cend(), // source
      ostream_iterator<int>(cout," ")); // destination
      cout << endl;

// remove all elements with value 3
remove (coll.begin(), coll.end(), // range
        3); // value
// print (same as above)   

结果是

pre:  6 5 4 3 2 1 1 2 3 4 5 6
post: 6 5 4 2 1 1 2 4 5 6 5 6 (???) 
4

4 回答 4

4

这个解释应该有助于:

删除是通过移动范围内的元素以覆盖要擦除的元素来完成的。保留元素的相对顺序,容器的物理大小不变。指向新逻辑结束和范围物理结束之间的元素的迭代器仍然是可解引用的,但元素本身具有未指定的值。调用 remove 之后通常会调用容器的擦除方法,该方法会擦除未指定的值并减小容器的物理大小以匹配其新的逻辑大小。

请注意, from 的返回值std::remove()是代表新end的迭代器。因此,调用std::erase()这个新端和旧端将释放你多余的空间。

于 2013-09-26T20:39:07.440 回答
4

std::remove实际上并没有缩短列表。它不能——因为它只获取迭代器而不是容器本身。

它所做的是复制剩余的值,以便您在容器的开头获取它们。但是容器的最终元素(在你的情况下 - 最后两个:'5'和'6')实际上仍然存在..

使用后,std::remove必须自己缩短到容器以删除剩余的“垃圾”副本。

于 2013-09-26T20:39:10.213 回答
2

您要求算法删除“3”元素。因此,在枚举容器时,如果从中间移除某些东西,算法会改变内容。这种转变在您的情况下发生了 2 次,这就是为什么您会在末尾看到“5 6”元素(因为实际末尾已向前移动到 2 个项目)。然后,“std::erase”将解决尾部僵尸的问题。

于 2013-09-26T20:39:52.420 回答
2

引用大家最喜欢的 c++ 网站的话:

该函数不能更改包含元素范围的对象的属性(即,它不能更改数组或容器的大小):删除是通过将比较等于 val 的元素替换为下一个不等于 val 的元素来完成的。 ,并通过将迭代器返回到应该被视为其新的过去元素的元素来发出缩短范围的新大小的信号。

所以std::remove不会改变列表的大小。它删除匹配的元素并返回一个表示列表新结尾的迭代器。要真正删除无关元素,您需要执行以下操作:

auto it = remove(coll.begin(), coll.end(), 3);
coll.erase(it, coll.end());
于 2013-09-26T20:37:12.287 回答