4.2. Operaciones con cadenas

Si ha programado en C, estará acostumbrado a la familia de funciones que leen, escriben, modifican y copian cadenas. Existen dos aspectos poco afortunados en la funciones de la librería estándar de C para manipular cadenas. Primero, hay dos familias pobremente organizadas: el grupo plano, y aquellos que requieren que se les suministre el número de caracteres para ser consideradas en la operación a mano. La lista de funciones en la librería de cadenas de C sorprende al usuario desprevenido con una larga lista de nombres crípticos y mayoritariamente impronunciables. Aunque el tipo y número de argumentos es algo consistente, para usarlas adecuadamente debe estar atento a los detalles de nombres de la función y a los parámetros que le pasas.

La segunda trampa inherente a las herramientas para cadenas del estándar de C es que todas ellas explícitamente confían en la asunción de que cada cadena incluye un terminador nulo. Si por confusión o error el terminador nulo es omitido o sobrescrito, poco se puede hacer para impedir que las funciones de cadena de C manipulen la memoria más allá de los límites del espacio de alojamiento, a veces con resultados desastrosos.

C++ aporta una vasta mejora en cuanto a conveniencia y seguridad de los objetos string. Para los propósitos de las actuales operaciones de manipulación, existe el mismo número de funciones que la librería de C, pero gracias a la sobrecarga, la funcionalidad es mucho mayor. Además, con una nomenclatura más sensata y un acertado uso de los argumentos por defecto, estas características se combinan para hacer de la clase string mucho más fácil de usar que la biblioteca de funciones de cadena de C.

4.2.1. Añadiendo, insertando y concatenando cadenas

Uno de los aspectos más valiosos y convenientes de los string en C++ es que crecen cuando lo necesitan, sin intervención por parte del programador. No solo hace el código de manejo del string sea inherentemente mas confiable, además elimina por completo las tediosas funciones "caseras" para controlar los limites del almacenamiento en donde nuestra cadena reside. Por ejemplo, si crea un objeto string e inicializa este string con 50 copias de "X", y después copia en el 50 copias de "Zowie", el objeto, por sí mismo, readecua suficiente almacenamiento para acomodar el crecimiento de los datos. Quizás en ningún otro lugar es más apreciada esta propiedad que cuando las cadenas manipuladas por su código cambian de tamaño y no sabe cuan grande puede ser este cambio. La función miembro append() e insert() de string reubican de manera transparente el almacenamiento cuando un string crece:

//: C03:StrSize.cpp
#include <string>
#include <iostream>
using namespace std;

int main() {
  string bigNews("I saw Elvis in a UFO. ");
  cout << bigNews << endl;
  // How much data have we actually got?
  cout << "Size = " << bigNews.size() << endl;
  // How much can we store without reallocating?
  cout << "Capacity = " << bigNews.capacity() << endl;
  // Insert this string in bigNews immediately
  // before bigNews[1]:
  bigNews.insert(1, " thought I");
  cout << bigNews << endl;
  cout << "Size = " << bigNews.size() << endl;
  cout << "Capacity = " << bigNews.capacity() << endl;
  // Make sure that there will be this much space
  bigNews.reserve(500);
  // Add this to the end of the string:
  bigNews.append("I've been working too hard.");
  cout << bigNews << endl;
  cout << "Size = " << bigNews.size() << endl;
  cout << "Capacity = " << bigNews.capacity() << endl;
} ///:~

Listado 4.2. C03/StrSize.cpp


Aquí la salida desde un compilador cualquiera:

I saw Elvis in a UFO.
Size = 22
Capacity = 31
I thought I saw Elvis in a UFO.
Size = 32
Capacity = 47
I thought I saw Elvis in a UFO. I've been
working too hard.
Size = 59
Capacity = 511

Este ejemplo demuestra que aunque puede ignorar con seguridad muchas de las responsabilidades de reserva y gestión de la memoria que tus string ocupan, C++ provee a los string con varias herramientas para monitorizar y gestionar su tamaño. Nótese la facilidad con la que hemos cambiado el tamaño de la memoria reservada para los string. La función size() retorna el numero de caracteres actualmente almacenados en el string y es idéntico a la función miembro lenght(). La función capacity() retorna el tamaño de la memoria subyacente actual, es decir, el número de caracteres que el string puede almacenar sin tener que reservar más memoria. La función reserve() es una optimización del mecanismo que indica su intención de especificar cierta cantidad de memoria para un futuro uso; capacity() siempre retorna un valor al menos tan largo como la ultima llamada a reserve(). La función resize() añade espacio si el nuevo tamaño es mayor que el tamaño actual del string; sino trunca el string. (Una sobrecarga de resize() puede especificar una adición diferente de caracteres).

La manera exacta en que las funciones miembro de string reservan espacio para sus datos depende de la implementación de la librería. Cuando testeamos una implementación con el ejemplo anterior, parece que se hacia una reserva de una palabra de memoria (esto es, un entero) dejando un byte en blanco entre cada una de ellas. Los arquitectos de la clase string se esforzaron para poder mezclar el uso de las cadenas de caracteres de C y los objetos string, por lo que es probable por lo que se puede observar en StrSize.cpp, en esta implementación en particular, el byte esté añadido para acomodar fácilmente la inserción de un terminador nulo.