Cadenas de caracteres: `std::string`
La clase std::string forma parte de la Biblioteca Estándar de C++ y proporciona una forma moderna, segura y flexible de trabajar con cadenas de caracteres. A diferencia de las cadenas de C tradicionales (arreglos de caracteres terminados en '\0'), std::string:
- Gestiona automáticamente la memoria necesaria para almacenar el texto.
- Permite redimensionar la cadena dinámicamente.
- Ofrece numerosos métodos que facilitan la manipulación de texto.
- Proporciona seguridad adicional, evitando errores comunes como desbordamientos de búfer.
En C++ moderno, salvo casos muy específicos de bajo nivel o interoperabilidad con C, se recomienda siempre utilizar std::string para trabajar con texto.
Métodos más utilizados de std::string
A continuación se presenta una lista de los métodos más frecuentes y útiles que proporciona la clase std::string:
length()osize(): Devuelve el número de caracteres de la cadena. No devuelve unintdevuelve un tipo llamadostd::size_tque es un entero sin signo y que se suele utilizar para representar tamaños, conteos o índices de arrays, contenedores y estructuras de datos.empty(): Indica si la cadena está vacía.at(pos): Devuelve el carácter en la posiciónposcon comprobación de límites.front(): Devuelve el primer carácter de la cadena.back(): Devuelve el último carácter de la cadena.substr(pos, n): Devuelve una subcadena que comienza en la posiciónposy tiene longitudn.find(cadena): Devuelve la posición donde se encuentra la primera aparición decadena, ostd::string::npossi no se encuentra.rfind(cadena): Devuelve la posición de la última aparición decadena.insert(pos, cadena): Insertacadenaen la posiciónpos.erase(pos, n): Eliminancaracteres a partir de la posiciónpos.replace(pos, n, cadena): Reemplaza losncaracteres a partir deposporcadena.append(cadena): Añadecadenaal final.compare(cadena): Compara dos cadenas, devolviendo0si son iguales,< 0si la cadena que llama al método es menor y> 0si la cadena que llama al método es mayor.
Veamos un ejemplo:
#include <iostream>
#include <string>
int main() {
std::string texto {"Hola Mundo"};
// Longitud de la cadena
std::cout << "Longitud: " << texto.length() << std::endl;
// Acceso seguro al primer y último carácter
std::cout << "Primer carácter: " << texto.front() << std::endl;
std::cout << "Último carácter: " << texto.back() << std::endl;
// Subcadena
std::string subcadena = texto.substr(0, 4);
std::cout << "Subcadena: " << subcadena << std::endl;
// Búsqueda de una palabra
size_t posicion = texto.find("Mundo");
if (posicion != std::string::npos) {
std::cout << "\"Mundo\" encontrado en la posición: " << posicion << std::endl;
}
// Reemplazo de texto
texto.replace(5, 5, "ChatGPT");
std::cout << "Texto modificado: " << texto << std::endl;
// Concatenación con append
texto.append("!");
std::cout << "Texto final: " << texto << std::endl;
// Comparación
std::string otroTexto {"Hola ChatGPT!"};
if (texto.compare(otroTexto) == 0) {
std::cout << "Las cadenas son iguales." << std::endl;
} else {
std::cout << "Las cadenas son diferentes." << std::endl;
}
return 0;
}
Recorrido de un std::string
Una operación habitual al trabajar con cadenas es recorrer sus caracteres, ya sea para mostrarlos, analizarlos o modificarlos. Existen varias formas de hacerlo, y es importante utilizar el tipo de dato correcto para los índices, especialmente en C++ moderno.
Bucle clásico con índice
#include <iostream>
#include <string>
int main() {
std::string texto {"Hola Mundo"};
for (std::size_t i = 0; i < texto.size(); i++) {
std::cout << texto[i] << ' ';
}
std::cout << std::endl;
return 0;
}
Ten en cuenta que hemos usado std::size_t para declarar el índice i y hemos usado indexación para acceder a cada uno de los elementos utilizando [ ].
Si queremos tener un acceso seguro no utilizamos indexación, usamos el método at:
#include <iostream>
#include <string>
int main() {
std::string texto {"Hola Mundo"};
for (std::size_t i = 0; i < texto.size(); i++) {
std::cout << texto.at(i) << ' ';
}
std::cout << std::endl;
return 0;
}
texto.at(i)lanza una excepciónstd::out_of_rangesi el índice es inválido, haciendo el acceso más seguro.
Bucle range-based for loop (bucle basado en rangos)
#include <iostream>
#include <string>
int main() {
std::string texto {"Hola Mundo"};
for (char c : texto) {
std::cout << c << ' ';
}
std::cout << std::endl;
return 0;
}
Esta forma es más sencilla y legible, y no tenemos la necesidad de usar índices. Si queremos modificar la cadena tenemos que hacer un recorrido con referencias:
#include <iostream>
#include <string>
#include <cctype>
int main() {
std::string texto {"Hola Mundo"};
for (char& c : texto) {
c = std::toupper(c); // Convierte cada carácter a mayúscula
}
std::cout << texto << std::endl; // Salida: "HOLA MUNDO"
return 0;
}
Recorrido con iteradores
Un iterador en C++ moderno es un objeto que permite recorrer los elementos de un contenedor (como un std::vector, std::list, std::map, etc.) de manera similar a un puntero. Los iteradores abstraen el acceso a los elementos del contenedor, proporcionando una interfaz común para iterar sobre diferentes tipos de contenedores, sin necesidad de conocer su implementación interna.
#include <iostream>
#include <string>
int main() {
std::string texto {"Hola Mundo"};
for (std::string::iterator it = texto.begin(); it != texto.end(); ++it) {
std::cout << *it << ' ';
}
std::cout << std::endl;
return 0;
}
En este ejemplo:
texto.begin()devuelve un iterador que apunta al primer carácter de la cadena.texto.end()devuelve un iterador que apunta a la posición después del último elemento.++itavanza el iterador al siguiente elemento, y*itaccede al valor al que apunta.
std::string como parámetro en funciones
Paso por valor
Cuando pasamos un std::string por valor, se realiza una copia completa del objeto. Esto significa que la cadena original no se ve afectada por modificaciones dentro de la función, pero puede ser menos eficiente si la cadena es muy grande.
#include <iostream>
#include <string>
void imprimirCadena(std::string texto) {
std::cout << "Texto dentro de la función: " << texto << std::endl;
texto = "Texto modificado";
std::cout << "Texto modificado dentro de la función: " << texto << std::endl;
}
int main() {
std::string original = "Hola Mundo";
imprimirCadena(original); // Se pasa por valor
std::cout << "Texto después de la función: " << original << std::endl; // No ha cambiado
return 0;
}
Paso por referencia
Cuando pasamos un std::string por referencia (sin const), la función puede modificar directamente el objeto original. Esto es útil cuando se necesita cambiar el contenido de la cadena en la función y no se desea realizar una copia.
#include <iostream>
#include <string>
void modificarCadena(std::string& texto) {
std::cout << "Texto antes de la modificación: " << texto << std::endl;
texto = "Texto modificado";
std::cout << "Texto después de la modificación: " << texto << std::endl;
}
int main() {
std::string original = "Hola Mundo";
modificarCadena(original); // Se pasa por referencia
std::cout << "Texto después de la función: " << original << std::endl; // Ha cambiado
return 0;
}
Paso por referencia constante
Si no necesitas modificar la cadena dentro de la función, pero aún deseas evitar hacer una copia innecesaria, puedes pasar el std::string por referencia constante. Esto mejora la eficiencia, ya que no se realiza una copia, y también garantiza que la cadena no sea modificada dentro de la función.
#include <iostream>
#include <string>
void imprimirCadena(const std::string& texto) {
std::cout << "Texto dentro de la función: " << texto << std::endl;
}
int main() {
std::string original = "Hola Mundo";
imprimirCadena(original); // Se pasa por referencia constante
return 0;
}
Paso por puntero
Aunque no es tan común en C++ moderno, también es posible pasar un std::string por puntero. Este enfoque se utiliza con menos frecuencia, ya que la referencia es más segura y conveniente. Sin embargo, es útil cuando se quiere pasar un puntero nulo (es decir, cuando se desea tener la posibilidad de que el argumento sea opcional).
#include <iostream>
#include <string>
void modificarCadena(std::string* texto) {
if (texto != nullptr) {
std::cout << "Texto antes de la modificación: " << *texto << std::endl;
*texto = "Texto modificado";
std::cout << "Texto después de la modificación: " << *texto << std::endl;
}
}
int main() {
std::string original = "Hola Mundo";
modificarCadena(&original); // Se pasa por puntero
std::cout << "Texto después de la función: " << original << std::endl; // Ha cambiado
return 0;
}