【问题标题】:Issues cleaning a string only to include alphabet characters仅清理字符串以包含字母字符的问题
【发布时间】:2021-12-02 09:42:36
【问题描述】:

我在编写用于编码和解码 Playfair 密码的 C++ 程序的一部分时遇到了一些问题。我遇到问题的程序部分是第一步(不幸的是,我知道),这涉及删除所有空格、标点符号和非字母字符。这就是我所拥有的:

std::string step1(std::string plaintext)
{
    plaintext.erase(remove_if(plaintext.begin(), plaintext.end(),
        std::not1(std::ptr_fun(std::isalpha))));
    plaintext.erase(remove_if(plaintext.begin(), plaintext.end(),
        std::ptr_fun(std::ispunct)));
    return plaintext;
}

输出很好,除了它在清除后在字符串末尾添加字符。通常,额外的字符是在已清理输入末尾找到的字符的副本。对于我的一生,我无法弄清楚为什么会发生这种情况。有什么想法吗?

【问题讨论】:

    标签: c++ algorithm c++11


    【解决方案1】:

    std::remove/_if() 实际上并没有删除任何东西,它只是将匹配的项目移动到容器的末尾,然后将一个迭代器返回到该项目范围的开头。然后调用者可以使用该迭代器从容器中实际删除项目。

    您将该迭代器传递给std::string::erase() 的重载,该重载将1 个迭代器作为输入。因此,它最多只会擦除 1 个字符(如果 std::remove_if() 没有找到任何内容,它将返回字符串的 end() 迭代器,并且在该迭代器上调用 erase()未定义的行为)。如果超过 1 个字符被“删除”到字符串的末尾,则剩余的未擦除字符将被留下。这就是您在输出中看到的内容。

    要清除所有“已删除”的字符,您需要使用 std::string::erase() 的重载,它需要 2 个表示范围的迭代器,例如:

    std::string step1(std::string plaintext)
    {
        plaintext.erase(
            std::remove_if(plaintext.begin(), plaintext.end(),
                std::not1(std::ptr_fun(std::isalpha))
            ),
            plaintext.end()
        );
        plaintext.erase(
            std::remove_if(plaintext.begin(), plaintext.end(),
                std::ptr_fun(std::ispunct)
            ),
            plaintext.end()
        );
        return plaintext;
    }
    

    请注意,使用 std:::isalpha() 删除所有非字母字符将包括所有空格和标点字符,因此您使用 std::ispunct() 进行的第二次搜索将找不到任何要删除的内容,因此您可以完全放弃该搜索,例如:

    std::string step1(std::string plaintext)
    {
        plaintext.erase(
            std::remove_if(plaintext.begin(), plaintext.end(),
                std::not1(std::ptr_fun(std::isalpha))
            ),
            plaintext.end()
        );
        return plaintext;
    }
    

    话虽如此,如 cmets 中所述,std::not1()std::ptr_fun 在现代 C++ 中已被弃用。在 C++11 及更高版本中,您可以使用 lambda:

    std::string step1(std::string plaintext)
    {
        plaintext.erase(
            std::remove_if(plaintext.begin(), plaintext.end(),
                [](unsigned char ch){ return !std::isalpha(ch); }
            ),
            plaintext.end()
        );
        return plaintext;
    }
    

    在 C++20 及更高版本中,您可以使用std::erase_if()

    std::string step1(std::string plaintext)
    {
        std::erase_if(plaintext,
            [](unsigned char ch){ return !std::isalpha(ch); }
        );
        return plaintext;
    }
    

    【讨论】:

    • 虽然这个问题被标记为 C++11,但有几点可能值得注意。首先,std::ptr_fun 在 C++11 中已被弃用,并在 C++17 中被删除。其次,std::not1 在 C++17 中被弃用,在 C++20 中被删除。最后,从 C++20 开始,获取标准库函数的地址,例如 std::isalpha,会导致未指定的行为并可能无法编译(唯一的例外是专门标记为 可寻址的 I/O 操纵器函数)。有关基本原理,请参阅this answer。因此,在新代码中,最好使用 lambda。
    【解决方案2】:

    也许太明显了。但是有一个专门的功能来完成这样的任务。它被称为:std::regex_replace

    这是非常通用且易于使用的。

    使用正则表达式,您可以轻松定义要替换的内容。

    请看:

    #include <iostream>
    #include <string>
    #include <regex>
    
    int main() {
    
        std::string test{"  ! Hello World 1234 ,;.++"};
    
        std::cout << std::regex_replace(test, std::regex(R"([^a-zA-Z]+)"), "");
    }
    
    

    【讨论】:

      猜你喜欢
      • 2020-11-17
      • 2012-09-07
      • 2013-05-27
      • 2019-03-09
      • 2019-11-11
      • 1970-01-01
      • 2014-10-01
      • 2021-12-13
      • 1970-01-01
      相关资源
      最近更新 更多