STL中的remove函数没有按预期工作?

15
int main()
{

        const int SIZE = 10;
        int a[SIZE] = {10, 2, 35, 5, 10, 26, 67, 2, 5, 10};
        std::ostream_iterator< int > output(cout, " ");
        std::vector< int > v(a, a + SIZE);
        std::vector< int >::iterator newLastElement;

        cout << "contents of the vector: ";
        std::copy(v.begin(), v.end(), output);

        newLastElement = std::remove(v.begin(), v.end(), 10);
        cout << "\ncontents of the vector after remove: ";
        //std::copy(v.begin(), newLastElement, output); 
                         //this gives the correct result : 2 35 5 26 67 2 5
        std::copy(v.begin(), v.end(), output);
          //this gives a 10 which was supposed to be removed : 2 35 5 26 67 2 5 2 5 10

        cout << endl;
        return 0;
}

数组a中有三个10。

使用remove函数将所有的10移除后,为什么数组v中仍包含一个10。

你可以在这里看到编译输出。


顺便问一下,这个注释(//this gives the correct result : 2 35 5 26 67 2 5 2 5)是程序实际输出的吗?你真正想说的应该是2 35 5 26 67 2 5,对吗? - Robᵩ
是的,绝对正确。感谢指出。 - munish
4个回答

37
实际上,std::remove 不会从容器中移除项目。引用自这里

Remove 从范围 [first, last) 中移除所有等于 value 的元素。也就是说,Remove 返回一个迭代器 new_last,使得范围 [first, new_last) 不包含任何等于 value 的元素。范围 [new_last, last) 中的迭代器仍然可以解引用,但它们指向的元素是未指定的。 Remove 是稳定的,这意味着不等于 value 的元素的相对顺序不会改变。

也就是说,std::remove 只适用于一对迭代器,并不知道实际包含项目的容器。事实上,std::remove 不可能知道底层容器,因为它无法从一对迭代器中获取关于所属容器的信息。因此,std::remove 实际上并没有删除项目,只是因为它不能。从容器中实际删除项目的唯一方法是在该容器上调用成员函数。
因此,如果要删除项目,请使用Erase-Remove Idiom
 v.erase(std::remove(v.begin(), v.end(), 10), v.end()); 

erase-remove成语 如此常见和有用,以至于 std::list 添加了另一个成员函数,称为 list::remove,它产生与 erase-remove 成语相同的效果。

 std::list<int> l;
 //...
 l.remove(10); //it "actually" removes all elements with value 10!

这意味着,当你使用std::list时,你不需要使用erase-remove习语,而是可以直接调用它的成员函数list::remove


12

原因是STL算法不会修改序列的大小。remove并没有真正地删除元素,而是将它们移动并返回一个指向“新”结尾的迭代器。然后,可以将该迭代器传递给容器的erase成员函数来实际执行删除操作:

v.erase(std::remove(v.begin(), v.end(), 10), v.end());

顺便提一下,这被称为“擦除-移除习语”。

编辑:我错了。请参阅评论以及Nawaz的回答。


3
Nawaz的回答指出,新结尾后面的元素是未指定的,不一定等于已删除的元素,所以你看到的是特定实现的产物。改变函数名称是不合适的。 - Mark Ransom
1
std::remove并不需要像你所想的那样将匹配的值移动到序列的末尾 - Nawaz

2

C++20引入了一个新的非成员函数std::erase,为所有标准库容器简化了这个任务。

多个旧答案提出的解决方案如下:

v.erase(std::remove(v.begin(), v.end(), 10), v.end());

现在可以写成:
std::erase(v, 10);

1
因为std::remove实际上并不会缩小容器,它只是将所有元素向下移动以填补“已删除”元素使用的位置。例如,如果您有一个序列1 2 3 4 5并使用std::remove删除值2,则您的序列将变为1 3 4 5 5。如果您然后删除值4,您将得到1 3 5 5 5。在任何时候,序列都没有被告知要缩短。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接