Substituir parte de uma string por outra string

186

É possível em C ++ substituir parte de uma string por outra string?

Basicamente, eu gostaria de fazer isso:

QString string("hello $name");
string.replace("$name", "Somename");

Mas eu gostaria de usar as bibliotecas C ++ padrão.

Tom Leese
fonte
1
possível duplicata de Qual é a função para substituir a string em C? - opa, desculpe que seja C, não C ++; Eu gostaria de poder cancelar o voto.
polygenelubricants
1
@poly Eu acho que isso também deve ter sido solicitado para C ++, mas não consigo encontrá-lo #
Michael Mrozek
1
Há uma tag std na pergunta, mas talvez você esteja interessado nos algoritmos de string do boost, que também incluem uma ampla variedade de algoritmos de substituição (inplace / copy, case sensitive / case insensitive, substitua first / last / all / n-th )
UncleBens
@ Michael Mrozek Há um em stackoverflow.com/questions/3418231/…, mas é mais recente e seu método replaceAll aqui é mais robusto.
Dave-holm

Respostas:

288

Há uma função para encontrar uma substring dentro de uma string ( find) e uma função para substituir um intervalo específico em uma string por outra string ( replace), para que você possa combiná-las para obter o efeito desejado:

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");

Em resposta a um comentário, acho replaceAllque provavelmente seria algo como isto:

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'
    }
}
Michael Mrozek
fonte
2
Como eu o consertaria se a string original tivesse mais de uma instância de "$ name" e eu quisesse substituí-las.
Tom Leese
1
Por que não são frome topassaram por constreferência? O que sua função se fromnão existe? -1de mim por isso.
SBI
10
@sbi Corrigido, embora você pudesse tê-lo formulado como recomendações em vez de ataques - simplesmente não me ocorreu, eu raramente penso em usar conste se eu escrevesse um método utilitário como esse, eu o chamaria apenas se soubesse o substituição válida
Michael Mrozek
10
@ Michael: Bom, eu transformei meu voto negativo em voto positivo. Descartar consté desconsiderar uma das melhores ferramentas do C ++. Passar por constreferência deve ser o modo padrão para os parâmetros de função. (FTR, sem const, você não poderia mesmo passar strings literais para a sua função, porque você não pode temporários se ligam a não- constreferências Assim, a função não teria sequer fazer o que foi escrito para..)
SBI
19
Essa ainda é a única solução em 2018? Se sim, e qualquer comitê de C ++ estiver lendo isso, resolva-o. É embaraçoso. divida (string, string) e substitua (string, string) por favor!
User997112
96

Com o C ++ 11, você pode usar std::regexassim:

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

A barra invertida dupla é necessária para escapar de um caractere de escape.

Tom
fonte
Tenho certeza std::regex_replaceque não aceita a string do Qt.
BartoszKP
1
Você está certo. Ocorre que o QString fornece um método de substituição que aceita um QRexExp, permitindo o uso do material do Qt. Mas acho que a resposta atual pode ser corrigida substituindo stringpor string.toStdString().
24415 Tom
1
Ou apenas mudando Stringpara std::string, porque a pergunta não está relacionada ao Qt. Por favor, considere fazer isso - de bom grado aprovaremos sua resposta depois.
BartoszKP
5
A cadeia bruta permite escrever em R"(\$name)"vez de "\\$name".
precisa saber é o seguinte
2
Quanto isso será mais lento do que localizar / substituir sem considerar o tempo de construção do std :: regex?
jw_
18

std::stringtem um replacemétodo, é isso que você está procurando?

Você poderia tentar:

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

Ainda não tentei, basta ler a documentação em find() e replace().

SC Madsen
fonte
2
Pelo que eu posso ver, o método std :: string replace não usa duas strings como eu gostaria.
Tom Leese
2
Isso não funciona para mim. sizeof deve ser substituído por string ( "somename") size () -. 1
TimZaman
@ TimZaman: Isso me intriga, a documentação afirma claramente que você pode inicializar a partir de uma string em estilo C.
SC Madsen
5
o segundo argumento deve ser o tamanho de "$ name" (em vez do tamanho de "Somename"), não deveria?
Daniel Kiss
10

Para que a nova string seja retornada, use isto:

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;
}

Se você precisar de desempenho, aqui está uma função otimizada que modifica a sequência de entrada, ela não cria uma cópia da sequência:

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();
    }
}

Testes:

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;

Resultado:

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
Czarek Tomczak
fonte
Sua chamada para subject.replace em ReplaceStringInPlace () está realmente modificando a string inplace?
Damian
Olhei brevemente a fonte e parece que ela usa semântica de movimento para mover a frente da string antiga para o lugar, para que não seja copiada, mas a nova peça inserida é copiada para a string antiga e a cauda da string antiga. é copiado no buffer redimensionado da string antiga. É possível que a string expanda tanto que todo o buffer subjacente seja realocado, mas se você substituir 1 para 1, como no exemplo dele, acho que ocorre "no local" ou sem copiar, mas se você expandir a string, apenas a primeira parte da cadeia antiga não é copiada e somente talvez então.
quer
6

Sim, você pode fazer isso, mas você precisa encontrar a posição da primeira string com o membro find () da string e, em seguida, substituir por seu membro replace ().

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

Se você planeja usar a Biblioteca Padrão, deve adquirir uma cópia do livro A Biblioteca Padrão C ++, que cobre tudo isso muito bem.

Drew Noakes
fonte
1
é size_t e não size_type
revo
1
É std :: string :: size_type, não size_t ou size_type sem adornos.
Jmucchiello
5

Eu geralmente uso isso:

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;
}

Ele chama repetidamente std::string::find()para localizar outras ocorrências da string pesquisada até std::string::find()que não encontre nada. Como std::string::find()retorna a posição da partida, não temos o problema de invalidar iteradores.

Galik
fonte
4

Isso soa como uma opção

string.replace(string.find("%s"), string("%s").size(), "Something");

Você pode agrupar isso em uma função, mas esta solução de uma linha parece aceitável. O problema é que isso mudará apenas a primeira ocorrência; talvez você queira fazer um loop sobre ela, mas também permite inserir várias variáveis ​​nessa string com o mesmo token ( %s)

maxoumime
fonte
1
Como o estilo, mas encontrou as cordas diferentes confundindo ^^str.replace(str.find("%s"), string("%s").size(), "Something");
Paul Würtz
3

Se todas as strings forem std :: string, você encontrará problemas estranhos com o corte de caracteres se estiver usando sizeof()porque se destina a strings C, não a C ++. A correção é usar o .size()método de classe de std::string.

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

Isso substitui o sHaystack inline - não é necessário fazer uma atribuição =.

Exemplo de uso:

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;
Volomike
fonte
2
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);
user3016543
fonte
2

Se você quiser fazer isso rapidamente, poderá usar uma abordagem de duas varreduras. Pseudo-código:

  1. primeira análise. encontre quantos caracteres correspondentes.
  2. expanda o comprimento da string.
  3. segunda análise. Começa no final da string quando obtemos uma correspondência que substituímos; caso contrário, apenas copiamos os caracteres da primeira string.

Não tenho certeza se isso pode ser otimizado para algo no local.

E um exemplo de código C ++ 11, mas eu só procuro um caractere.

#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";

}
Damian
fonte
1
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;
}
Lucas Civali
fonte
2
Você pode explicar esse código (na sua resposta)? Você pode obter mais votos dessa maneira!
O cara com o chapéu
1

Minha própria implementação, levando em consideração que a string precisa ser redimensionada apenas uma vez, e a substituição pode acontecer.

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;
}

O uso pode ser, por exemplo, assim:

std::string dirSuite = replaceAll(replaceAll(relPath.parent_path().u8string(), "\\", "/"), ":", "");
TarmoPikaro
fonte
0

Agora estou aprendendo C ++, mas, editando parte do código publicado anteriormente, provavelmente usaria algo assim. Isso oferece a flexibilidade de substituir 1 ou várias instâncias e também permite especificar o ponto de partida.

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;
}
someprogrammer
fonte
0

Isso poderia ser ainda melhor para usar

void replace(string& input, const string& from, const string& to)
{
    while(true)
    {
        size_t startPosition = input.find(from);
        if(startPosition == string::npos)
            break;
        input.replace(startPosition, from.length(), to);
    }
}
Yashwanth Kumar
fonte