在C++中查找和删除特定字符的字符串

3
我是一名初学者,正在学习如何使用C++。我正在尝试完成CodeWars上的挑战。程序应该接受一个字符串输入,并删除字符串中包含的所有元音字母。
首先,我创建了一个包含小写和大写元音字母的字符数组。 然后我使用std :: find函数搜索输入。我想要发生的事情是: 如果它能在数组中找到当前字符,它将删除该字符,并重新开始循环。它能够分离出元音字母,但当我尝试返回修改后的字符串时,遇到了一个内存越界的错误。
我仍然不太明白内存是如何工作的,所以我希望得到一些帮助。
#include <string>
#include <iostream>
#include <conio.h>
#include <algorithm>

using namespace std;

string disemvowel(string str)
{

    char vowels[] = { 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U' };
    char *finder;

    for (int i = 0; i < str.length(); i++)
    {
        char active = str[i];
        finder = find(vowels, vowels + 10, active);
        if (finder != vowels + 10)
        {
            str.erase(str[i], 0);
        }
    }

    return str;
}

int main() {

    string str;
    cout << "say something \n";
    cin >> str;
    cout << disemvowel(str);

    _getch();
    return 0;
}

谢谢你的帮助。

你想使用erase-remove惯用语。请查看remove_if的参考页面以查看示例 https://en.cppreference.com/w/cpp/algorithm/remove - JohnFilleau
您目前使用的 string::erase 函数将 str[i] 的字符值转换为整数,并尝试删除从 str[i] 开始的 0 个字符。根据您的使用方式,您应该使用 str.erase(i, 1) 来删除从索引 i 开始的 1 个字符。 - JohnFilleau
1
我建议使用std::begin(vowels)代替vowels,并使用std::end(vowels)代替vowels + 10作为您的范围。您的代码可以正常工作,这只是一种风格问题,但我发现这些包装器使其明确它们是迭代器。这还使您的代码更加健壮,以应对vowels大小的任何更改,这不太可能发生(除非您的要求发生变化,现在必须包括yY)。 - JohnFilleau
1
消除手写循环。如前所述,请在有机会的情况下使用算法函数。您需要的函数是std::remove_if。您的问题标题正是remove_if的目的——根据特定条件从值序列中删除项目。 - PaulMcKenzie
2
我正在尝试完成CodeWars上的挑战。-- 如果这是一个计时挑战,你的解决方案可能会因超时问题而失败。请注意,每次发现有元音字母时,您都在不断地从字符串中擦除。如果该字符串有一百万个字符,并且其中一半具有元音字母,那么您将擦除五十万次。使用std::remove_if,您只需擦除一次。remove_if只是交换以将“坏”字符移动到序列的末尾。然后在最后执行单个erase调用。 - PaulMcKenzie
1个回答

6

当前代码存在问题:

    str.erase(str[i], 0);

这是不正确的。让我们看看在这种情况下string::erase函数的签名:

basic_string& erase( size_type index = 0, size_type count = npos );

第一个参数是size_type,基本上是一个无符号长整数。它是您要删除的字符的索引。第二个参数也是相同类型的,它是您要从index位置删除的字符数,这是1。您传递给函数的是str[i],它是char类型,这是不正确的。

修正版本:

str.erase(i, 1);

此外:
finder = find(vowels, vowels + 10, active);

std::find 返回一个迭代器,即使它编译通过也不要将其赋值给 char*。修正后:

auto finder = find(vowels, vowels + 10, active);

你可以使用标准模板库(STL)中的现成算法,在一行代码中解决这个问题:
只需使用remove_ifstring::erase:
  std::string str = "hello, world";
  
  str.erase (std::remove_if (str.begin (), str.end (), [](char c)
                 {
                 return c == 'a' || c == 'e' || c == 'i'
                 || c == 'o' || c == 'u' || c == 'A' || c == 'E' || c == 'I'
                 || c == 'O' || c == 'U';
                 }),
                 str.end ());
  std::cout << str; 

正如@PaulMckenzie所提到的,使用erase-remove惯用语在这里比循环变量+erase更快(快两倍)。 quick-bench上的基准测试 那么为什么它会更快呢?
假设我们有字符串:"Hello cpp" 使用erase(),每次“删除”一个字符时,其后面的所有字符都需要向后移动一个位置。在这种情况下,我们需要删除'e'和'o',即位置1和位置4。
  • 从位置1删除字符并将字符2移动到字符8,向后移动一个位置。这意味着我们移动了7个字符。
  • 从位置4删除字符并将字符5移动到字符8一个位置向后。这意味着我们移动了4个字符。
  • 总共:移动了11个字符
< p > remove 的工作方式不同。它只是把不需要删除的元素向后移动,可能会覆盖即将被删除的元素。这样可以减少元素的移动,使速度更快。

请参阅 SO Post以获取更详细和更好的解释。

注意:您需要#include <algorithm>进行此操作。


1
你还可以解释一下为什么使用remove_if的第二种方法很可能比OP的方法运行得更快(没有测量,但是可以观察到)。 - PaulMcKenzie

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