如何用另一个字符串替换所有出现的字符串?

44

我在另一个Stack问题中找到了这个:

//https://dev59.com/TXA75IYBdhLWcg3wOGLS
//
void replaceAll(std::string& str, const std::string& from, const std::string& to) {
    size_t start_pos = 0;
    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
        size_t end_pos = start_pos + from.length();
        str.replace(start_pos, end_pos, to);
        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
    }
}

我的方法:

string convert_FANN_array_to_binary(string fann_array)
{
    string result = fann_array;
    cout << result << "\n";
    replaceAll(result, "-1 ", "0");
    cout << result << "\n";
    replaceAll(result, "1 ", "1");
    return result;
}

针对此输入值的操作是:

cout << convert_FANN_array_to_binary("1 1 -1 -1 1 1 ");

现在,输出应该是"110011"

这是方法的输出:

1 1 -1 -1 1 1  // original
1 1 0 1  // replacing -1's with 0's
11 1  // result, as it was returned from convert_FANN_array_to_binary()

我一直在查看replaceAll代码,但是我真的不确定为什么它会将连续的-1替换为一个0,然后在最终结果中不返回任何0(但是有一些1)。=\


2
在这种情况下,似乎使用另一种解决方案可能更合适——即根本不使用字符串操作,而是使用整数/布尔数组。 - Konrad Rudolph
它需要是字符串,因为我们正在从ASCII文件中读取。 - NullVoxPopuli
3
如果你遵循@Konrad的建议,你可以使用std::replace函数来替换值。从ASCII文件中读取数据并不意味着你不能将其表示为整数。 - Björn Pollex
3
将它们转换。程序的流程总是相同的: 1.读取输入,2.转换为适当的格式,3.应用计算,4.转换为输出格式,5.输出。你试图跳过第二步,并使生活变得不必要地艰难。除了文本以外,字符串很少是任何其他东西的适当格式。 - Konrad Rudolph
没错,我们需要重新编写我们库中的一个方法,然后才能读取序列化的文件。我们会做到的。 - NullVoxPopuli
可能是重复问题:https://dev59.com/vG035IYBdhLWcg3wH8Ul - Guy Avraham
6个回答

60

完整的代码:

std::string ReplaceString(std::string subject, const std::string& search,
                          const std::string& replace) {
    size_t pos = 0;
    while ((pos = subject.find(search, pos)) != std::string::npos) {
         subject.replace(pos, search.length(), replace);
         pos += replace.length();
    }
    return subject;
}
如果您需要更好的性能,这里有一个经过优化的函数可以修改输入字符串,而不是创建字符串的副本:
void ReplaceStringInPlace(std::string& subject, const std::string& search,
                          const std::string& replace) {
    size_t pos = 0;
    while ((pos = subject.find(search, pos)) != std::string::npos) {
         subject.replace(pos, search.length(), replace);
         pos += replace.length();
    }
}

测试:

std::string input = "abc abc def";
std::cout << "Input string: " << input << std::endl;

std::cout << "ReplaceString() return value: " 
          << ReplaceString(input, "bc", "!!") << std::endl;
std::cout << "ReplaceString() input string not changed: " 
          << input << std::endl;

ReplaceStringInPlace(input, "bc", "??");
std::cout << "ReplaceStringInPlace() input string modified: " 
          << input << std::endl;

输出:

Input string: abc abc def
ReplaceString() return value: a!! a!! def
ReplaceString() input string not changed: abc abc def
ReplaceStringInPlace() input string modified: a?? a?? def

ReplaceString(string("abc\dir\dir1"), string("\"), string("\\")); 没有起作用。 - qqqqq
8
你应该确实检查 search 字符串是否为空,否则将会出现无限循环。 - newbie
为了修复无限循环,我建议将“pos += replace.length();”更改为“pos += replace.length() + (search.empty() ? 1 : 0);” 或等效方式(可以在循环之前将(search.empty()?1:0)存储在变量中,以避免每次计算search.empty())从而使replaceString("abc", "", ":")返回“:a:b:c:”。我使用过的其他语言中的大多数字符串替换函数都是这样工作的。 - Some Guy

21

这个 bug 出现在 str.replace(start_pos, end_pos, to); 中。

参考 http://www.cplusplus.com/reference/string/string/replace/ 的 std::string 文档。

string& replace ( size_t pos1, size_t n1,   const string& str );

您正在使用一个结束位置,而函数需要一个长度。

因此,请更改为:

while((start_pos = str.find(from, start_pos)) != std::string::npos) {
         str.replace(start_pos, from.length(), to);
         start_pos += to.length(); // ...
}

注意:未经测试。


1
顺便说一句,我不赞成这个代码和风格,但这不是问题所在。 - Sjoerd
58
有没有更好的字符串替换方法?我真的很惊讶这不是 string.h 中内置的功能……就像……说真的……更高级的语言都有它。 - NullVoxPopuli
6
@NullVoxPopuli 我发现了你的问题:C++不是一种高级语言。 - edhurtig
1
@Sjoerd如果您不赞同这段代码和风格,您会建议采用什么替代方案? - j b
2
在C++11中,有一种方法std::regex_replace(str, std::regex(from), to)。@NullVoxPopuli - ericcurtin

12

C++11现在已经包含了头文件<regex>,其中包括正则表达式功能。引用自文档:

// regex_replace example
#include <iostream>
#include <string>
#include <regex>
#include <iterator>

int main ()
{
  std::string s ("there is a subsequence in the string\n");
  std::regex e ("\\b(sub)([^ ]*)");   // matches words beginning by "sub"
  // using string/c-string (3) version:
  std::cout << std::regex_replace (s,e,"sub-$2");
  std::cout << std::endl;
  return 0;
}

当然,现在你有两个问题


11

我发现之前回答中给出的替换函数都使用了内部的就地str.replace()调用,当处理大约2MB长度的字符串时非常缓慢。具体来说,我调用了类似ReplaceAll(str, "\r", "")这样的东西,在我的特定设备上,对于包含许多换行符的文本文件,它需要大约27秒的时间。然后我用一个只是在一个新副本中连接子字符串的函数来代替它,仅需要大约1秒钟的时间。下面是我的ReplaceAll()版本:

void replaceAll(string& str, const string& from, const string& to) {
    if(from.empty())
        return;
    string wsRet;
    wsRet.reserve(str.length());
    size_t start_pos = 0, pos;
    while((pos = str.find(from, start_pos)) != string::npos) {
        wsRet += str.substr(start_pos, pos - start_pos);
        wsRet += to;
        pos += from.length();
        start_pos = pos;
    }
    wsRet += str.substr(start_pos);
    str.swap(wsRet); // faster than str = wsRet;
}

格雷格


4
最后可以使用str.swap(wsRet)来交换字符串的内容,而不是执行赋值操作,这样可以稍微提高一点效率。这种方式可以便宜地交换字符串的内容,而不必进行可能昂贵的复制操作。 - Blastfurnace
@Blastfurnace,非常感谢您的提示!我已经测试过了,可行。我也会在上面的示例代码中更新它。 - gregko
实际上,例如我使用一个专门的函数来替换单个字符。我猜当长度不相等时,每次替换都会导致字符串其余部分的移动,这就是造成延迟的原因。谢谢! - gregko
为什么没有C++11语法?对于新手来说,所有的错误信息都让人感到非常困惑。 - Piotr Kula
好的回答!但我认为仍有一些优化空间。在 while 循环内的第一行 wsRet += str.substr(start_pos, pos - start_pos); 可以改进为 wsRet.append(str, start_pos, pos - start_pos);,因为后者可以消除子字符串的临时副本所需的额外时间和空间。出于同样的原因,在 while 循环后的第一行 wsRet += str.substr(start_pos); 可以改进为 wsRet.append(str, start_pos);。此外,在 while 循环内的第 3-4 行可以合并为一行 start_pos = pos + from.size() - Peng
显示剩余2条评论

11

我将把它加入我的“只需使用Boost库”的答案列表中,但还是要说一下:

您是否考虑过Boost.String? 它比标准库具有更多功能,并且在功能重叠的地方,Boost.String在我看来具有更自然的语法。


9
他所寻找的是这个函数。 - Benjamin Lindley

1

试试这个:

#include <string>

string replace_str(string & str, const string & from, const string & to)
{
  while(str.find(from) != string::npos)
    str.replace(str.find(from), from.length(), to);
  return str;
}

2
如果“to”包含“from”,则此外观永远不会终止。 - Markus
这里每次迭代都会调用两次 str.find(),这是多余的。但更重要的是,它在每次调用时都从字符串的开头开始搜索。string::find() 有一个可选参数可以指定从哪个索引开始搜索,请使用它,这样就不必重新搜索已经处理过的字符串部分。 - Remy Lebeau

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