4: Las cadenas a fondo

Tabla de contenidos

4.1. ¿Qué es un string?
4.2. Operaciones con cadenas
4.3. Buscar en cadenas
4.4. Una aplicación con cadenas
4.5. Resumen
4.6. Ejercicios

El procesamiento de cadenas de caracteres en C es una de las mayores pérdidas de tiempo. Las cadenas de caracteres requieren que el programador tenga en cuenta las diferencias entre cadenas estáticas y las cadenas creadas en la pila y en el montón, además del hecho que a veces pasa como argumento un char* y a veces hay que copiar el arreglo entero.

Precisamente porque la manipulación de cadenas es muy común, las cadenas de caracteres son una gran fuente de confusiones y errores. Es por ello que la creación de clases de cadenas sigue siendo desde hace años un ejercicio común para programadores novatos. La clase string de la biblioteca estándar de C++ resuelve el problema de la manipulación de caracteres de una vez por todas, gestionando la memoria incluso durante las asignaciones y las construcciones de copia. Simplemente no tiene que preocuparse por ello.

Este capítulo[1] examina la clase string del Estándar C++; empieza con un vistazo a la composición de las string de C++ y como la versión de C++ difiere del tradicional arreglo de caracteres de C. Aprenderá sobre las operaciones y la manipulación usando objetos string, y verá como éstas se FIXME[acomodan a la variación] de conjuntos de caracteres y conversión de datos.

Manipular texto es una de las aplicaciones más antiguas de la programación, por eso no resulta sorprendente que las string de C++ estén fuertemente inspiradas en las ideas y la terminología que ha usado continuamente en C y otros lenguajes. Conforme vaya aprendiendo sobre los string de C++, este hecho se debería ir viendo más claramente. Da igual el lenguaje de programación que escoja, hay tres cosas comunes que querrá hacer con las cadenas:

Verá como cada una de estas tareas se resuelve usando objetos string en C++.

4.1. ¿Qué es un string?

En C, una cadena es simplemente un arreglo de caracteres que siempre incluye un 0 binario (frecuentemente llamado terminador nulo) como elemento final del arreglo. Existen diferencias significativas entre los string de C++ y sus progenitoras en C. Primero, y más importante, los string de C++ esconden la implementación física de la secuencia de caracteres que contiene. No debe preocuparse de las dimensiones del arreglo o del terminador nulo. Un string también contiene cierta información para uso interno sobre el tamaño y la localización en memoria de los datos. Específicamente, un objeto string de C++ conoce su localización en memoria, su contenido, su longitud en caracteres, y la cantidad de caracteres que puede crecer antes de que el objeto string deba redimensionar su buffer interno de datos. Las string de C++, por tanto, reducen enormemente las probabilidades de cometer uno de los tres errores de programación en C más comunes y destructivos: sobrescribir los límites del arreglo, intentar acceder a un arreglo no inicializado o con valores de puntero incorrectos, y dejar punteros colgando después de que el arreglo deje de ocupar el espacio que estaba ocupando.

La implementación exacta del esquema en memoria para una clase string no esta definida en el estándar C++. Esta arquitectura esta pensada para ser suficientemente flexible para permitir diferentes implementaciones de los fabricantes de compiladores, garantizando igualmente un comportamiento predecible por los usuarios. En particular, las condiciones exactas de cómo situar el almacenamiento para alojar los datos para un objeto string no están definidas. FIXME: Las reglas de alojamiento de un string fueron formuladas para permitir, pero no requerir, una implementación con referencias múltiples, pero dependiendo de la implementación usar referencias múltiples sin variar la semántica. Por decirlo de otra manera, en C, todos los arreglos de char ocupan una única región física de memoria. En C++, los objetos string individuales pueden o no ocupar regiones físicas únicas de memoria, pero si su conjunto de referencias evita almacenar copias duplicadas de datos, los objetos individuales deben parecer y actuar como si tuvieran sus propias regiones únicas de almacenamiento.

//: C03:StringStorage.h
#ifndef STRINGSTORAGE_H
#define STRINGSTORAGE_H
#include <iostream>
#include <string>
#include "../TestSuite/Test.h"
using std::cout;
using std::endl;
using std::string;

class StringStorageTest : public TestSuite::Test {
public:
  void run() {
    string s1("12345");
    // This may copy the first to the second or
    // use reference counting to simulate a copy:
    string s2 = s1;
    test_(s1 == s2);
    // Either way, this statement must ONLY modify s1:
    s1[0] = '6';
    cout << "s1 = " << s1 << endl;  // 62345
    cout << "s2 = " << s2 << endl;  // 12345
    test_(s1 != s2);
  }
};
#endif // STRINGSTORAGE_H ///:~

Listado 4.1. C03/StringStorage.h


Decimos que cuando una implementación solo hace una sola copia al modificar el string usa una estrategia de copiar al escribir. Esta aproximación ahorra tiempo y espacio cuando usamos string como parámetros por valor o en otras situaciones de solo lectura.

El uso de referencias múltiples en la implementación de una librería debería ser transparente al usuario de la clase string. Desgraciadamente, esto no es siempre el caso. En programas multihilo, es prácticamente imposible usar implementaciones con múltiples referencias de forma segura[32].[2]



[1] Algunos materiales de este capítulo fueron creados originalmente por Nancy Nicolaisen

[2] Es dificil hacer implementaciones con multiples referencias para trabajar de manera segura en multihilo. (Ver [More Exceptional C++, pp.104-14]). Ver Capitulo 10 para más información sobre multiples hilos