4.2.2. Reemplazar caracteres en cadenas

La función insert() es particularmente útil por que te evita el tener que estar seguro de que la inserción de caracteres en un string no sobrepasa el espacio reservado o sobrescribe los caracteres que inmediatamente siguientes al punto de inserción. El espacio crece y los caracteres existentes se mueven graciosamente para acomodar a los nuevos elementos. A veces, puede que no sea esto exactamente lo que quiere. Si quiere que el tamaño del string permanezca sin cambios, use la función replace() para sobrescribir los caracteres. Existe un número de versiones sobrecargadas de replace(), pero la más simple toma tres argumentos: un entero indicando donde empezar en el string, un entero indicando cuantos caracteres para eliminar del string original, y el string con el que reemplazaremos (que puede ser diferente en numero de caracteres que la cantidad eliminada). Aquí un ejemplo simple:

//: C03:StringReplace.cpp
// Simple find-and-replace in strings.
#include <cassert>
#include <string>
using namespace std;

int main() {
  string s("A piece of text");
  string tag("$tag$");
  s.insert(8, tag + ' ');
  assert(s == "A piece $tag$ of text");
  int start = s.find(tag);
  assert(start == 8);
  assert(tag.size() == 5);
  s.replace(start, tag.size(), "hello there");
  assert(s == "A piece hello there of text");
} ///:~

Listado 4.3. C03/StringReplace.cpp


Tag es insertada en s (notese que la inserción ocurre antes de que el valor indicando el punto de inserción y de que el espacio extra haya sido añadido despues de Tag), y entonces es encontrada y reemplazada.

Debería cerciorarse de que ha encontrado algo antes de realizar el replace(). En los ejemplos anteriores se reemplaza con un char*, pero existe una versión sobrecargada que reemplaza con un string. Aqui hay un ejempl más completo de demostración de replace():

//: C03:Replace.cpp
#include <cassert>
#include <cstddef>  // For size_t
#include <string>
using namespace std;

void replaceChars(string& modifyMe,
  const string& findMe, const string& newChars) {
  // Look in modifyMe for the "find string"
  // starting at position 0:
  size_t i = modifyMe.find(findMe, 0);
  // Did we find the string to replace?
  if(i != string::npos)
    // Replace the find string with newChars:
    modifyMe.replace(i, findMe.size(), newChars);
}

int main() {
  string bigNews = "I thought I saw Elvis in a UFO. "
                   "I have been working too hard.";
  string replacement("wig");
  string findMe("UFO");
  // Find "UFO" in bigNews and overwrite it:
  replaceChars(bigNews, findMe, replacement);
  assert(bigNews == "I thought I saw Elvis in a "
         "wig. I have been working too hard.");
} ///:~

Listado 4.4. C03/Replace.cpp


Si replace() no encuentra la cadena buscada, retorna un string::npos. El dato miembro npos es una constante estatica de la clase string que representa una posición de carácter que no existe[33]. [3]

A diferencia de insert(), replace() no aumentará el espacio de alamcenamiento de string si copia nuevos caracteres en el medio de una serie de elementos de array existentes. Sin embargo, sí que cerecerá su espacio si es necesario, por ejemplo, cuando hace un "reemplazamiento" que pueda expandir el string más allá del final de la memoria reservada actual. Aquí un ejemplo:

//: C03:ReplaceAndGrow.cpp
#include <cassert>
#include <string>
using namespace std;

int main() {
  string bigNews("I have been working the grave.");
  string replacement("yard shift.");
  // The first argument says "replace chars
  // beyond the end of the existing string":
  bigNews.replace(bigNews.size() - 1,
    replacement.size(), replacement);
  assert(bigNews == "I have been working the "
         "graveyard shift.");
} ///:~

Listado 4.5. C03/ReplaceAndGrow.cpp


La llamada a replace() empieza "reemplazando" más allá del final del array existente, que es equivalente a la operación append(). Nótese que en este ejemplo replace() expande el array coherentemente.

Puede que haya estado buscando a través del capítulo; intentando hacer algo relativamente fácil como reemplazar todas las ocurrencias de un carácter con diferentes caracteres. Al buscar el material previo sobre reemplazar, puede que haya encontrado la respuesta, pero entonces ha empezaro viendo grupos de caracteres y contadores y otras cosas que parecen un poco demasiado complejas. ¿No tiene string una manera para reemplazar un carácter con otro simplemente?

Puede escribir fácilmente cada funcin usando las funciones miembro find() y replace() como se muestra acontinuacion.

//: C03:ReplaceAll.h
#ifndef REPLACEALL_H
#define REPLACEALL_H
#include <string>

std::string& replaceAll(std::string& context,
  const std::string& from, const std::string& to);
#endif // REPLACEALL_H ///:~

Listado 4.6. C03/ReplaceAll.h


//: C03:ReplaceAll.cpp {O}
#include <cstddef>
#include "ReplaceAll.h"
using namespace std;

string& replaceAll(string& context, const string& from,
  const string& to) {
  size_t lookHere = 0;
  size_t foundHere;
  while((foundHere = context.find(from, lookHere))
    != string::npos) {
    context.replace(foundHere, from.size(), to);
    lookHere = foundHere + to.size();
  }
  return context;
} ///:~

Listado 4.7. C03/ReplaceAll.cpp


La versión de find() usada aquí toma como segundo argumento la posición donde empezar a buscar y retorna string::npos si no lo encuentra. Es importante avanzar en la posición contenida por la variable lookHere pasada como subcadena, en caso de que from es una subcadena de to. El siguiente programa comprueba la funcion replaceAll():

//: C03:ReplaceAllTest.cpp
//{L} ReplaceAll
#include <cassert>
#include <iostream>
#include <string>
#include "ReplaceAll.h"
using namespace std;

int main() {
  string text = "a man, a plan, a canal, Panama";
  replaceAll(text, "an", "XXX");
  assert(text == "a mXXX, a plXXX, a cXXXal, PXXXama");
} ///:~

Listado 4.8. C03/ReplaceAllTest.cpp


Como puede comprobar, la clase string por ella sola no resuelve todos los posibles problemas. Muchas soluciones se han dejado en los algoritmos de la librería estándar[4] por que la clase string puede parece justamente como una secuencia STL(gracias a los iteradores descritos antes). Todos los algoritmos genéricos funcionan en un "rango" de elementos dentro de un contenedor. Generalmente este rango es justamente desde el principio del contenedor hasta el final. Un objeto string se parece a un contenedor de caracteres: para obtener el principio de este rango use string::begin(), y para obtener el final del rango use string::end(). El siguiente ejemplomuestra el uso del algoritmo replace() para reemplazar todas las instancias de un determinado carácter "X" con "Y"

//: C03:StringCharReplace.cpp
#include <algorithm>
#include <cassert>
#include <string>
using namespace std;

int main() {
  string s("aaaXaaaXXaaXXXaXXXXaaa");
  replace(s.begin(), s.end(), 'X', 'Y');
  assert(s == "aaaYaaaYYaaYYYaYYYYaaa");
} ///:~

Listado 4.9. C03/StringCharReplace.cpp


Nótese que esta función replace() no es llamada como función miembro de string. Además, a diferencia de la función string::replace(), que solo realiza un reemplazo, el algoritmo replace() reemplaza todas las instancias de un carácter con otro.

El algoritmo replace() solo funciona con objetos individuales (en este caso, objetos char) y no reemplazará arreglos constantes o objetos string. Desde que un string se copmporta como una secuencia STL, un conjunto de algoritmos pueden serle aplicados, que resolverán otros problemas que las funciones miembro de string no resuelven.



[3] Es una abrviación de "no position", y su valor más alto puede ser representado por el ubicador de string size_type (std::size_t por defecto).

[4] Descrito en profundidad en el Capítulo 6.