remove_if() 究竟是如何工作的?

How exactly remove_if() works?

我参考了很多资源,但无法理解它究竟是如何修改容器中的元素的部分。 我已经写了一些代码来尝试理解,谁能解释一下发生了什么?

#include<bits/stdc++.h>
using namespace std;

bool test(char c)
{
    return (c=='a');
}

int isPalindrome(string A) 
{
    remove_if(A.begin(), A.end(), test);
    cout<<A<<'\n';   
 }

int main() 
{
    
    string A="aaaaabbbbb";
    isPalindrome(A); 

    return 0;
}

根据我目前的理解,它应该将所有 a 字符移到 b 的右侧,如果我们尝试打印它,我们应该得到

bbbbbaaaaa

相反,我得到

bbbbbbbbbb

如果需要,该算法只是将等于 'b' 的元素移动或复制(取决于使用的迭代器)到存储等于 'a' 的元素的位置。

它不交换容器的元素。

std::remove_if,不是std::move_if。它只是从 test 函数中删除所有 return 为真的元素。现在,为什么额外的 bs?好吧,你不应该看到他们。 std::remove_if return 是一个标记序列新结束的迭代器,您应该使用 return 值相应地调整容器的大小。假设我们有一个字符串 "Hello, World!",我们需要从中删除所有 l。下面是一个示例程序,展示了执行此操作的一种方法:

#include <algorithm>
#include <string>
#include <iostream>

int main()
{
    std::string a = "Hello, World!";
    a.resize(std::distance(
        a.begin(),
        std::remove_if(a.begin(), a.end(), [](char c){return c == 'l';})
    ));
    std::cout << a << '\n';
}

输出:

Heo, Word!

您对 remove_if 的工作方式是正确的。然而,它 returns 是一个迭代器,它指向第一个被移除的元素,它与最后一个未被移除的元素相同。然后你需要删除被移除的元素以获得你想要的答案。因此,在函数 isPalindrome 中,将 remove_if 行替换为

   const auto new_end = remove_if(A.begin(), A.end(), test);
   A.erase(new_end, A.end());

然后你会得到bbbbb

这种先调用 remove_if 后调用 erase 的模式经常发生。这两条线通常合二为一,称为“删除-擦除成语”。在您的情况下,将 remove_if 行替换为

   A.erase( remove_if(A.begin(), A.end(), test), A.end() );

According to my understanding so far it should move all the a characters to the right of b

差不多。它做相反的事情:它(有点)将 b 字符移动到 a 的左边。更准确地说,它在删除的元素上“移动赋值”。

因此,左边的元素是 右边(在左边的 a 之上)移动的 b,右边的元素是 b被移到 左边。由于移动 char 与复制相同,即它不会修改从中移动的参数,因此左元素和右元素都是 b。

由于该算法的预期用例是删除 a,因此容器中没有任何 a 不是问题。请注意,对于不同的输入,一些 a 可能会保留在容器的右端。右边的元素要么是移到左边的元素的残余(不是 a 的),要么是因为要删除它们而没有移动的元素(a 的)。

P.S。您现在可以在 C++20(或将来,假设您已标记 C++17)中使用:

,而不是以前在 GregReese 的 and in François Andrieux's 中显示的惯用模式
std::erase_if(A, test);

我在几条评论中都表达了同样的观点,但我认为需要更详细地解决这个问题。

std::removestd::remove_if 等算法适用于 序列 ,即由一对迭代器指定的元素。第一个迭代器指向序列的第一个元素,第二个迭代器指向元素序列的末尾。实际上,算法从序列中后面的位置移动或复制元素以覆盖正在删除的元素。

请注意,上一段没有使用容器这个词。容器是管理元素序列的一种方式,但不是唯一的方式。算法对容器一无所知。 removeremove_if 无法更改他们一无所知的容器的大小。

将元素传递给 removeremove_if 后查看容器时看到的是修改后的序列(从容器的开头到迭代器指向的元素算法返回),然后是剩下的任何东西。对于像 char 这样的简单类型,这个残基可能就是开头的任何内容。对于具有非平凡移动赋值运算符的更复杂类型,剩余部分由移动赋值留下的任何内容组成。对于 C++ 标准库中定义的类型,这意味着剩余由处于未指定但有效状态的对象组成。对于在标准库以外的地方定义的类型,它们将处于设计者选择的任何状态。