Como faço para pesquisar / localizar e substituir em uma string padrão?

94

Existe uma maneira de substituir todas as ocorrências de uma substring por outra string em std::string?

Por exemplo:

void SomeFunction(std::string& str)
{
   str = str.replace("hello", "world"); //< I'm looking for something nice like this
}
Adam Tegen
fonte

Respostas:

74

Por que não implementar seu próprio substituto?

void myReplace(std::string& str,
               const std::string& oldStr,
               const std::string& newStr)
{
  std::string::size_type pos = 0u;
  while((pos = str.find(oldStr, pos)) != std::string::npos){
     str.replace(pos, oldStr.length(), newStr);
     pos += newStr.length();
  }
}
Yves Baumes
fonte
3
Você está bagunçando um pouco a memória aqui com todas as chamadas para "substituir": a complexidade seria n² se você remover "o" de "ooooooo ... o". Acho que dá para fazer melhor, mas essa solução tem o mérito de ser fácil de entender.
Zonko,
1
Por que este não é um loop for real, em vez de um loop for ofuscado?
Shirik de
Estou acostumado a aplicar o princípio da "menor surpresa". Os loops For são para uso de incremento de índice simples, na maioria das vezes. Aqui, na minha opinião, um loop while é mais claro.
yves Baumes
1
@aldo Como regra geral, é melhor evitar a complexidade e, por exemplo, usar regex como mencionado em outras respostas. Mas, dependendo da sua necessidade, você pode querer controlar as dependências do projeto. Um pequeno trecho de código que faz exatamente o que você precisa, nada mais, às vezes é melhor.
yves Baumes
158
#include <boost/algorithm/string.hpp> // include Boost, a C++ library
...
std::string target("Would you like a foo of chocolate. Two foos of chocolate?");
boost::replace_all(target, "foo", "bar");

Aqui está a documentação oficial sobre replace_all.

TheNamelessOne
fonte
1
Observe que você não tem que criar explicitamente std :: string's para o padrão e substituição: boost :: replace_all (target, "foo", "bar");
Alexis Wilke
4
+1, com uma ressalva: replace_allserá o segfault para versões de boost> 1,43 no Sun Studio para qualquer versão <12.3
Brian Vandenberg
3
boostaumenta o tempo de compilação consideravelmente em dispositivos embarcados. Mesmo quad core ARMv7. 100 linhas de código compiladas em 2 minutos, sem aumento, 2 segundos.
Piotr Kula
4
@ppumkin: isso significa que seu compilador (ou configuração de compilação, ou qualquer outra coisa) é uma droga, não a arquitetura alvo, que não tem nada a ver com isso.
Daniel Kamil Kozar
Se o seu compilador suportar cabeçalho pré-compilado, é altamente recomendável usá-lo ao usar o boost. Isso realmente economiza tempo.
Alexey Omelchenko
33

No C ++ 11, você pode fazer isso em uma linha com uma chamada para regex_replace:

#include <string>
#include <regex>

using std::string;

string do_replace( string const & in, string const & from, string const & to )
{
  return std::regex_replace( in, std::regex(from), to );
}

string test = "Remove all spaces";
std::cout << do_replace(test, " ", "") << std::endl;

resultado:

Removeallspaces
Brent Bradburn
fonte
Obrigado, muito fácil de usar e lembrar!
Julian Declercq
Observe também que frompode ser uma expressão regular - portanto, você pode usar critérios de correspondência mais sofisticados, se necessário. O que não vejo é como fazer isso sem aplicar alguma forma de análise de expressão regular - em vez de usar apenas uma interpretação direta dos fromcaracteres.
Brent Bradburn
Isso pode exigir um compilador atualizado. Funcionou com o gcc 5.0, mas tive alguns problemas com o gcc 4.8.4.
Brent Bradburn
@nobar, sim, se bem me lembro o suporte regex em 4.8.x não estava completo. Além disso, você pode ter pesquisas mais sofisticadas, mas será penalizado com o tempo ... Vai ser mais lento do que as outras funções de pesquisa e substituição mais simples.
Alexis Wilke
2
Observe que isso funcionará apenas para caracteres alfanuméricos básicos e nada mais sem fazer muito pré-processamento, dependendo do tipo de string. Eu não encontrei uma substituição de string baseada em regex de propósito geral ainda.
Piyush Soni
17

Por que não retornar uma string modificada?

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ê precisa de desempenho, aqui está uma função otimizada que modifica a string de entrada, mas não cria uma cópia da string:

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 changed: " 
          << 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
6

Meu modelo de localizar e substituir no local embutido:

template<class T>
int inline findAndReplace(T& source, const T& find, const T& replace)
{
    int num=0;
    typename T::size_t fLen = find.size();
    typename T::size_t rLen = replace.size();
    for (T::size_t pos=0; (pos=source.find(find, pos))!=T::npos; pos+=rLen)
    {
        num++;
        source.replace(pos, fLen, replace);
    }
    return num;
}

Ele retorna uma contagem do número de itens substituídos (para uso se você deseja executá-lo sucessivamente, etc). Para usá-lo:

std::string str = "one two three";
int n = findAndReplace(str, "one", "1");
Marius
fonte
4
Tentei este exemplo no GCC, mas não compilou - ele não gostou do uso de T :: size_t. Substituir T :: size_t por typename T :: size_type corrige o problema.
Andrew Wyatt
3

A maneira mais fácil (oferecendo algo próximo ao que você escreveu) é usar Boost.Regex , especificamente regex_replace .

std :: string incorporou os métodos find () e replace (), mas eles são mais complicados de trabalhar, pois exigem lidar com índices e comprimentos de string.

Alan
fonte
3
Existem também os algoritmos de sequência de impulso, incluindo replace_all (regex pode ser um pouco pesado para essa substituição simples).
UncleBens
3

Eu acredito que isso funcionaria. Leva const char * 's como parâmetro.

//params find and replace cannot be NULL
void FindAndReplace( std::string& source, const char* find, const char* replace )
{
   //ASSERT(find != NULL);
   //ASSERT(replace != NULL);
   size_t findLen = strlen(find);
   size_t replaceLen = strlen(replace);
   size_t pos = 0;

   //search for the next occurrence of find within source
   while ((pos = source.find(find, pos)) != std::string::npos)
   {
      //replace the found string with the replacement
      source.replace( pos, findLen, replace );

      //the next line keeps you from searching your replace string, 
      //so your could replace "hello" with "hello world" 
      //and not have it blow chunks.
      pos += replaceLen; 
   }
}
Adam Tegen
fonte
Dado que size_typepara uma string é unsigned, sua >=verificação na condição de loop sempre será true. Você tem que usar std::string::nposlá.
Pavel Minaev
size_type não é sem sinal. Não tem assinatura em muitas plataformas, mas não em todas.
Alan,
12
Por que diabos isso não faz parte de std :: string? Existe alguma outra classe String séria no mundo da programação que não oferece uma operação de 'localizar e substituir'? Certamente é mais comum do que ter dois iteradores e querer substituir o texto entre eles ?? Às vezes, std :: string parece um carro com um pára-brisa de espectro ajustável, mas nenhuma maneira de abrir a janela do motorista.
Spike0xff
@ Spike0xff boost hasroll_down_window
ta.speot.is
1
@gustafr: Erro meu. Trabalhei em sistemas onde compiladores mais antigos definiram size_t incorretamente.
Alan
1
// Replace all occurrences of searchStr in str with replacer
// Each match is replaced only once to prevent an infinite loop
// The algorithm iterates once over the input and only concatenates 
// to the output, so it should be reasonably efficient
std::string replace(const std::string& str, const std::string& searchStr, 
    const std::string& replacer)
{
    // Prevent an infinite loop if the input is empty
    if (searchStr == "") {
        return str;
    }

    std::string result = "";
    size_t pos = 0;
    size_t pos2 = str.find(searchStr, pos);

    while (pos2 != std::string::npos) {
        result += str.substr(pos, pos2-pos) + replacer;
        pos = pos2 + searchStr.length();
        pos2 = str.find(searchStr, pos);
    }

    result += str.substr(pos, str.length()-pos);
    return result;
}
Björn Ganster
fonte
1
Precisamos apenas procurar por novas correspondências da última correspondência, é por isso que o algoritmo rastreia cuidadosamente a última correspondência na pos. pos2 sempre armazena a próxima correspondência, então concatenamos a string entre pos e pos2 para o resultado, então avançamos pos e pos2. Se nenhuma outra correspondência puder ser encontrada, concatenamos o restante da string para resultar.
Björn Ganster
1
#include <string>

using std::string;

void myReplace(string& str,
               const string& oldStr,
               const string& newStr) {
  if (oldStr.empty()) {
    return;
  }

  for (size_t pos = 0; (pos = str.find(oldStr, pos)) != string::npos;) {
    str.replace(pos, oldStr.length(), newStr);
    pos += newStr.length();
  }
}

A verificação de oldStr estar vazia é importante. Se por algum motivo esse parâmetro estiver vazio, você ficará preso em um loop infinito.

Mas sim, use o C ++ 11 experimentado e testado ou a solução Boost, se puder.

ericcurtina
fonte