将字符串的一部分替换为另一个字符串

Replace part of a string with another string

提问人:Tom Leese 提问时间:8/6/2010 最后编辑:Mateen UlhaqTom Leese 更新时间:4/7/2023 访问量:460459

问:

如何使用标准 C++ 库将字符串的一部分替换为另一个字符串?

QString s("hello $name");  // Example using Qt.
s.replace("$name", "Somename");
C++ 替换 子字符串 std

评论

1赞 UncleBens 8/6/2010
这个问题上有一个 std 标签,但也许您可能对 boost 的字符串算法感兴趣,其中还包括多种替换算法(就地/复制、区分大小写/不区分大小写、替换第一个/最后一个/全部/第 n 个)。
3赞 Jet 6/24/2015
如何在标准字符串中搜索/查找和替换的可能副本?

答:

42赞 S.C. Madsen 8/6/2010 #1

使用 std::string::replace:

s.replace(s.find("$name"), sizeof("$name") - 1, "Somename");

评论

3赞 Tom Leese 8/6/2010
据我所知,std::string replace 方法并不像我想要的那样接受两个字符串。
5赞 TimZaman 4/10/2014
这对我不起作用。sizeof 应替换为 string(“Somename”).size()-1
0赞 S.C. Madsen 4/11/2014
@TimZaman:这让我感到困惑,文档明确指出你可以从 C 样式字符串进行初始化。
6赞 Daniel Kiss 1/14/2016
第二个参数应该是“$name”的长度(而不是“Somename”的长度),不是吗?
3赞 bimbi 5/31/2021
如果 in 字符串多次出现,这将不起作用。"$name"s
6赞 anon 8/6/2010 #2

是的,你可以这样做,但你必须用字符串的 find() 成员找到第一个字符串的位置,然后用它的 replace() 成员替换。

string s("hello $name");
size_type pos = s.find( "$name" );
if ( pos != string::npos ) {
   s.replace( pos, 5, "somename" );   // 5 = length( $name )
}

如果你打算使用标准库,你真的应该得到一份C++标准库这本书的副本,它很好地涵盖了所有这些东西。

评论

1赞 revo 1/9/2013
这是size_t而不是size_type
1赞 jmucchiello 5/19/2013
它是 std::string::size_type,而不是size_t或朴素的size_type。
376赞 Michael Mrozek 8/6/2010 #3

有一个函数用于查找字符串中的子字符串 (find),还有一个函数用于将字符串中的特定范围替换为另一个字符串 (replace),因此您可以将它们组合在一起以获得所需的效果:

bool replace(std::string& str, const std::string& from, const std::string& to) {
    size_t start_pos = str.find(from);
    if(start_pos == std::string::npos)
        return false;
    str.replace(start_pos, from.length(), to);
    return true;
}

std::string string("hello $name");
replace(string, "$name", "Somename");

在回应评论时,我认为可能看起来像这样:replaceAll

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

评论

3赞 Tom Leese 8/6/2010
如果原始字符串有多个“$name”实例并且我想替换所有这些实例,我将如何修复它。
3赞 sbi 8/6/2010
为什么不按引用通过?如果没有,你的功能是什么? 从我这里。fromtoconstfrom-1
15赞 Michael Mrozek 8/6/2010
@sbi 已修复,尽管你可以将其表述为建议而不是攻击 - 它根本没有发生在我身上,我很少想到使用,如果我编写这样的实用方法,我只会在我知道替换有效时调用它const
14赞 sbi 8/6/2010
@Michael:很好,我把我的反对票变成了赞成票。解雇就是无视C++最好的工具之一。传递每个常量引用应该是函数参数的默认模式。(FTR,如果没有 ,您甚至无法将字符串文本传递给您的函数,因为您无法将临时值绑定到非引用。因此,该函数甚至不会执行写入的操作。constconstconst
78赞 user997112 7/31/2018
这仍然是2018年唯一的解决方案吗?如果是这样并且任何C++委员会正在阅读此内容,请将其整理出来。这很尴尬。请拆分(string, string) 和 replace(string, string)!
13赞 Czarek Tomczak 2/4/2013 #4

若要返回新字符串,请使用以下命令:

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 modified: " 
          << 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 modified: abc abc def
ReplaceStringInPlace() input string modified: a?? a?? def

评论

0赞 Damian 7/13/2016
您在 ReplaceStringInPlace() 中对 subject.replace 的调用真的是在就地修改字符串吗?
0赞 Motomotes 10/14/2016
我简要地查看了源代码,看起来它确实使用移动语义将旧字符串的前面移动到位,因此不会被复制,但插入的新片段被复制到旧字符串中,旧字符串的尾部被复制到旧字符串的调整大小缓冲区中。字符串可能扩展得如此之多,以至于整个底层缓冲区被重新分配,但是如果您像他示例中那样将 1 替换为 1,我认为它确实“就地”发生,或者没有任何复制,但是如果您扩展字符串,只有旧字符串的第一部分不会被复制,而且可能只有那时。
1赞 Lucas Civali 4/17/2014 #5
std::string replace(std::string base, const std::string from, const std::string to) {
    std::string SecureCopy = base;

    for (size_t start_pos = SecureCopy.find(from); start_pos != std::string::npos; start_pos = SecureCopy.find(from,start_pos))
    {
        SecureCopy.replace(start_pos, from.length(), to);
    }

    return SecureCopy;
}

评论

3赞 The Guy with The Hat 4/17/2014
你能解释一下这个代码(在你的回答中)吗?这样你可能会得到更多的赞成票!
0赞 Daemon 2/14/2023
替换字符串中包含旧字符串(如替换为)的输入将导致无限循环。可以通过将位置偏移到from和to之间的长度差来进行修复。,,,
6赞 Galik 9/29/2014 #6

我通常使用这个:

std::string& replace(std::string& s, const std::string& from, const std::string& to)
{
    if(!from.empty())
        for(size_t pos = 0; (pos = s.find(from, pos)) != std::string::npos; pos += to.size())
            s.replace(pos, from.size(), to);
    return s;
}

它反复调用以查找搜索字符串的其他匹配项,直到找不到任何内容。因为返回匹配的位置,所以我们没有使迭代器无效的问题。std::string::find()std::string::find()std::string::find()

166赞 Tom 3/23/2015 #7

使用 C++11,您可以像这样使用:std::regex

#include <regex>
...
std::string string("hello $name");
string = std::regex_replace(string, std::regex("\\$name"), "Somename");

转义转义字符需要双反斜杠。

评论

0赞 BartoszKP 7/24/2015
我很确定不接受 Qt 的字符串。std::regex_replace
2赞 Tom 7/25/2015
你是对的。碰巧的是,QString提供了一个接受QRexExp的替换方法,允许使用Qt自己的东西。但我认为目前的答案可以通过替换为 来纠正。stringstring.toStdString()
1赞 BartoszKP 7/25/2015
或者只是改成 ,因为这个问题与 Qt 无关。请考虑这样做 - 我很乐意在之后投票支持您的答案。Stringstd::string
8赞 Jarod42 7/22/2016
原始字符串允许写入而不是 .R"(\$name)""\\$name"
10赞 jw_ 12/29/2019
在不考虑 std::regex 的构造时间的情况下,这会比查找/替换慢多少?
9赞 maxoumime 4/5/2015 #8
string.replace(string.find("%s"), string("%s").size(), "Something");

你可以把它包装在一个函数中,但这个单行解决方案听起来是可以接受的。 问题是这只会改变第一次出现,你可能想循环它,但它也允许你使用相同的标记 () 将多个变量插入到这个字符串中。%s

评论

4赞 Paul Würtz 4/23/2017
喜欢这种风格,但发现不同的字符串令人困惑^^str.replace(str.find("%s"), string("%s").size(), "Something");
0赞 someprogrammer 12/3/2015 #9

我现在正在学习C++,但是编辑之前发布的一些代码,我可能会使用这样的东西。这使您可以灵活地替换 1 个或多个实例,还可以指定起点。

using namespace std;

// returns number of replacements made in string
long strReplace(string& str, const string& from, const string& to, size_t start = 0, long count = -1) {
    if (from.empty()) return 0;

    size_t startpos = str.find(from, start);
    long replaceCount = 0;

    while (startpos != string::npos){
        str.replace(startpos, from.length(), to);
        startpos += to.length();
        replaceCount++;

        if (count > 0 && replaceCount >= count) break;
        startpos = str.find(from, startpos);
    }

    return replaceCount;
}
3赞 user3016543 1/28/2016 #10
wstring myString = L"Hello $$ this is an example. By $$.";
wstring search = L"$$";
wstring replace = L"Tom";
for (int i = myString.find(search); i >= 0; i = myString.find(search))
    myString.replace(i, search.size(), replace);
4赞 Volomike 2/4/2016 #11

如果所有字符串都是 std::string,那么如果使用,你会发现字符截断的奇怪问题,因为它适用于 C 字符串,而不是 C++ 字符串。解决方法是使用 的 类方法。sizeof().size()std::string

sHaystack.replace(sHaystack.find(sNeedle), sNeedle.size(), sReplace);

这取代了内联的 sHaystack -- 无需对它进行 = 赋值。

用法示例:

std::string sHaystack = "This is %XXX% test.";
std::string sNeedle = "%XXX%";
std::string sReplace = "my special";
sHaystack.replace(sHaystack.find(sNeedle),sNeedle.size(),sReplace);
std::cout << sHaystack << std::endl;
2赞 Damian 7/13/2016 #12

如果你想快速完成,你可以使用两次扫描的方法。 伪代码:

  1. 首先解析。查找匹配的字符数。
  2. 展开字符串的长度。
  3. 第二次解析。当我们得到一个匹配项时,从字符串的末尾开始,我们替换,否则我们只是从第一个字符串复制字符。

我不确定这是否可以优化为就地算法。

还有一个 C++11 代码示例,但我只搜索一个字符。

#include <string>
#include <iostream>
#include <algorithm>
using namespace std;

void ReplaceString(string& subject, char search, const string& replace)
{   
    size_t initSize = subject.size();
    int count = 0;
    for (auto c : subject) { 
        if (c == search) ++count;
    }

    size_t idx = subject.size()-1 + count * replace.size()-1;
    subject.resize(idx + 1, '\0');

    string reverseReplace{ replace };
    reverse(reverseReplace.begin(), reverseReplace.end());  

    char *end_ptr = &subject[initSize - 1];
    while (end_ptr >= &subject[0])
    {
        if (*end_ptr == search) {
            for (auto c : reverseReplace) {
                subject[idx - 1] = c;
                --idx;              
            }           
        }
        else {
            subject[idx - 1] = *end_ptr;
            --idx;
        }
        --end_ptr;
    }
}

int main()
{
    string s{ "Mr John Smith" };
    ReplaceString(s, ' ', "%20");
    cout << s << "\n";

}
1赞 TarmoPikaro 4/19/2019 #13

我自己的实现,考虑到该字符串只需要调整一次大小,然后就可以进行替换。

template <typename T>
std::basic_string<T> replaceAll(const std::basic_string<T>& s, const T* from, const T* to)
{
    auto length = std::char_traits<T>::length;
    size_t toLen = length(to), fromLen = length(from), delta = toLen - fromLen;
    bool pass = false;
    std::string ns = s;

    size_t newLen = ns.length();

    for (bool estimate : { true, false })
    {
        size_t pos = 0;

        for (; (pos = ns.find(from, pos)) != std::string::npos; pos++)
        {
            if (estimate)
            {
                newLen += delta;
                pos += fromLen;
            }
            else
            {
                ns.replace(pos, fromLen, to);
                pos += delta;
            }
        }

        if (estimate)
            ns.resize(newLen);
    }

    return ns;
}

例如,用法可以如下所示:

std::string dirSuite = replaceAll(replaceAll(relPath.parent_path().u8string(), "\\", "/"), ":", "");
4赞 Yashwanth Kumar 11/18/2019 #14

这可能更好使用

void replace(string& input, const string& from, const string& to)
{
    auto pos = 0;
    while(true)
    {
        size_t startPosition = input.find(from, pos);
        if(startPosition == string::npos)
            return;
        input.replace(startPosition, from.length(), to);
        pos += to.length();
    }
}

评论

4赞 Jonathan Wakely 8/12/2020
string s = "ha"; replace(s, "h", "uhoh");
1赞 v2v1 10/20/2021
与其发表冷嘲热讽的评论@JonathanWakely不如明确描述您在评论中暗示的问题
0赞 Jonathan Wakely 10/20/2021
@v2v1这不是冷嘲热讽,我不是在暗示,我是在示威。教一个人钓鱼,等等。找出该函数不起作用的原因是一个有用的练习。如果你想有一个评论来解释它,你可以添加一个。我注意到你也没有这样做,你宁愿批评我的评论。
1赞 v2v1 10/21/2021
@JonathanWakely使用“呃”和“哈”对我来说是冷嘲热讽。当然,“演示”并强迫阅读本文的人运行无限循环?为什么不直接告诉他们呢?这并不是说他们自己炸毁代码会得到任何东西。解释很简单,所以你不妨把它放进去。
0赞 Kröw 10/12/2023
@JonathanWakely,您至少可以做的是在评论中的任何位置添加“无限循环”一词。唉,没有人再有时间提供帮助了。编辑答案以修复或指出这个问题本来是很棒的,但是冷嘲热讽的评论真的会给回答者一个教训。最好坚持下去。
-2赞 mubasshir00 8/5/2020 #15

您可以使用此代码来删除减法,也可以替换 ,还可以删除多余的空格。 法典:

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

void removeSpaces(string &str)
{   
    int n = str.length();
    int i = 0, j = -1;

    bool spaceFound = false;
    while (++j <= n && str[j] == ' ');

    while (j <= n)
    {
        if (str[j] != ' ')
        {
          
            if ((str[j] == '.' || str[j] == ',' ||
                 str[j] == '?') && i - 1 >= 0 &&
                 str[i - 1] == ' ')
                str[i - 1] = str[j++];
            else str[i++] = str[j++];
 
            spaceFound = false;
        }
        else if (str[j++] == ' ')
        {
            if (!spaceFound)
            {
                str[i++] = ' ';
                spaceFound = true;
            }
        }
    }

    if (i <= 1)
         str.erase(str.begin() + i, str.end());
    else str.erase(str.begin() + i - 1, str.end());
}
int main()
{
    string s;
    cin >> s;

    for(int i = s.find("WUB"); i >= 0; i = s.find("WUB"))
        s.replace(i,3," ");
    removeSpaces(s);
    cout << s << endl;

    return 0;
}

评论

0赞 Jonathan Wakely 10/20/2021
这取代了 但这不是 OP 要求的,删除空格也不是(你的大多数答案都在这样做)。如果替换项也与要替换的字符串匹配,则查找/替换解决方案不起作用。"WUB"" "
0赞 AustinWBryan 11/20/2022
没有人要求你成为一个超常成就者,只要按原样回答问题,你不会因为解决他们没有要求的问题而使你的代码复杂化而获得额外的分数。
0赞 AustinWBryan 11/20/2022
就像问题是“如何用另一个字符串删除字符串的一部分”,你用一个名为“RemovedSpaces”的函数来回答,这对于简单的任务来说过于复杂,顺便说一句,这将删除所需的空格。因此,“This is a text with WUB in it”变成了“Thisisatextwithinit”。
4赞 camp0 5/25/2022 #16

升压解决方案怎么样:

boost::replace_all(value, "token1", "token2");
0赞 Daemon 2/14/2023 #17

这是一个使用 c++ 标准库的行。

替换最好不要包含旧字符串(例如:替换为 ),否则您将遇到 INFINITE LOOP。此外,与其他技术相比,对于大型字符串来说,它的速度很慢,因为每次查找操作都从字符串调用的开头开始。如果您不太懒,请寻找更好的解决方案。我把它放进去是为了完整和启发其他人。你已经被警告过了。,,,

while(s.find(old_s) != string::npos) s.replace(s.find(old_s), old_s.size(), new_s);

还有一个 lambda 选项

auto replaceAll = [](string& s, string o, string n){ while(s.find(o) != string::npos) s.replace(s.find(o), o.size(), n); };

// EXAMPLES:
// Used like
string text = "hello hello world";
replaceAll(text, "hello", "bye"); // Changes text to "bye bye world"
// Do NOT use like
string text = "hello hello world";
replaceAll(text, "hello", "hello hello"); // Loops forever
1赞 Ignat Loskutov 4/7/2023 #18

它需要一些案例分析来为所有输入编写最佳(或至少不是二次)算法。

朴素算法(也是在撰写本文时获得最多票数的答案)在最坏的情况下是二次的,因为它在每次迭代时都会移动整个后缀,因此由于这种移动,它是 O(n) 调用 replace(),O(n) 每个调用。

从本质上讲,大海捞针字符串可以看作是一系列等于 的字符串,由其他一些字符串(没有子字符串)分隔。因此,为了避免二次运行时,我们需要做的就是确保我们只复制每个这样的字符串一次,而不是每次都复制整个后缀或前缀。它可以通过“双指针技术”来实现,我们这样做的确切方式取决于谁更长:whatwhat

  • 如果替换将缩小字符串(即短于 ),那么让我们从字符串的开头开始并保持两个偏移量 - 读取和写入一个 - 并且写入的偏移量永远不会更大。遍历整个字符串(只需一次就地遍历)后,写入偏移量代表我们复制的最后一个字符,因此它也是字符串的新大小。withwhat
  • 如果替换将增加字符串( 长于 ),我们将做类似的事情,但向后。要知道从哪个写入偏移量开始,我们必须知道出现的次数并提前调整字符串的大小,否则它与前一种情况非常对称。withwhat
  • 如果 和 的长度相等,我们就不必移动字符串,所以几乎任何方法都足够了——第一种方法看起来更好,因为它只需要一次传递。withwhat
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <string>
#include <string_view>

size_t CountOccurrences(std::string_view s, std::string_view needle) {
    size_t res = 0;
    size_t pos = 0;
    while ((pos = s.find(needle, pos)) != std::string_view::npos) {
        ++res;
        pos += needle.size();
    }
    return res;
}

std::string ReplaceNotLonger(std::string s, std::string_view what, std::string_view with) {
    assert(what.size() >= with.size());
    std::string_view::size_type wpos = 0;
    std::string_view::size_type rpos = 0;
    while (true) {
        auto new_rpos = s.find(what, rpos);
        if (new_rpos == std::string::npos) {
            new_rpos = s.size();
        }
        auto n = new_rpos - rpos;
        std::copy(s.begin() + rpos, s.begin() + new_rpos, s.begin() + wpos);
        wpos += n;
        rpos = new_rpos;
        if (rpos == s.size()) {
            break;
        }
        std::copy(with.begin(), with.end(), s.begin() + wpos);
        wpos += with.size();
        rpos += what.size();
    }
    s.resize(wpos);
    return s;
}

std::string ReplaceLonger(std::string s, std::string_view what, std::string_view with) {
    assert(what.size() < with.size());
    auto occurrences = CountOccurrences(s, what);
    auto rpos = s.size();
    auto wpos = rpos + occurrences * (with.size() - what.size());
    s.resize(wpos);
    
    while (wpos != rpos) {
        auto new_rpos = s.rfind(what, rpos - what.size());
        if (new_rpos == std::string::npos) {
            new_rpos = 0;
        } else {
            new_rpos += what.size();
        }
        auto n = rpos - new_rpos;
        std::copy_backward(s.begin() + new_rpos, s.begin() + rpos, s.begin() + wpos);
        wpos -= n;
        rpos = new_rpos;
        if (wpos == rpos) {
            break;
        }
        std::copy_backward(with.begin(), with.end(), s.begin() + wpos);
        wpos -= with.size();
        rpos -= what.size();
    }
    return s;
}

std::string Replace(std::string s, std::string_view what, std::string_view with) {
    assert(!what.empty());
    if (what.size() >= with.size()) {
        return ReplaceNotLonger(std::move(s), what, with);
    }
    return ReplaceLonger(std::move(s), what, with);
}