6. Cadenas de texto (1: al estilo de C)
C++
6. Cadenas de texto
6.1. Cadenas de texto al estilo de C
6.1.1. Definición y lectura desde teclado
C++ tiene un tipo de datos específico para almacenar cadenas de texto, pero vamos a comenzar por conocer el el estándar marcado por C, en el que las cadenas de texto, se crean como “arrays” de caracteres. Están formadas por una sucesión de caracteres terminada con un carácter nulo (\0), de modo que tendremos que reservar una letra más de las que necesitamos. Por ejemplo, para guardar el texto “Hola” usaríamos “char saludo[5]”.
Este carácter nulo lo utilizarán todas las órdenes estándar que tienen que ver con manejo de cadenas: las que las muestran en pantalla, las que comparan cadenas, las que dan a una cadena un cierto valor, etc. Por tanto, si no queremos usar esas funciones y sólo vamos a acceder letra a letra (como hemos hecho con los números en los últimos ejemplos) nos bastaría con “char saludo[4]”, pero si queremos usar cualquiera de esta posibilidades (que será lo habitual), deberemos tener la prudencia de reservar una letra más de las “necesarias”, para ese carácter nulo, que indica el final de la cadena, y que todas esas órdenes utilizan para saber cuando deben terminar de manipular la cadena.
Un primer ejemplo que nos pidiese nuestro nombre y nos saludase sería:
// Introducción a C++, Nacho Cabanes
// Ejemplo 06.01:
// Primer ejemplo de cadenas de texto al estilo C
#include <iostream>
using namespace std;
int main()
{
char texto[40]; // Para guardar hasta 39 letras
cout << "Introduce tu nombre: ";
cin >> texto;
cout << "Hola " << texto << endl;
return 0;
}
Ejercicios propuestos:
- (6.1.1.1) Un programa que te pida tu nombre y una cifra numérica, y escriba tu nombre tantas veces como indique esa cifra numérica.
Si la cadena contiene espacios, se lee sólo hasta el primer espacio. Esto se puede considerar una ventaja o un inconveniente, según el uso que se le quiera dar. En cualquier caso, dentro de muy poco veremos cómo evitarlo si queremos.
6.1.2. Entrada de cadenas al estilo de C
Un primer ejemplo que nos pidiese nuestro nombre y nos saludase sería:
// Introducción a C++, Nacho Cabanes
// Ejemplo 06.01b:
// Cadenas de texto al estilo C... con funciones de C
#include <cstdio>
int main()
{
char texto[40]; // Para guardar hasta 39 letras
printf("Introduce tu nombre: ");
scanf("%s", texto);
printf("Hola, %s\n", texto);
return 0;
}
Si se trata de algún otro tipo de datos, la idea es la misma, con dos diferencias:
- Habrá que indicar el tipo de datos correspondiente en la cadena de formato. Por ejemplo, se usa "%d" para un número entero (int) y "%f" para un número real (float).
- "scanf" necesitará un símbolo de "ampersand" (&) antes del nombre de la variable cuando ésta no sea una cadena de texto (nuevamente, los motivos son un poco avanzados, así que los aplazamos para más adelante).
De modo que podríamos pedir un entero y un real así:
// Introducción a C++, Nacho Cabanes
// Ejemplo 06.01c:
// Entrada/salida de enteros al estilo C
#include <cstdio>
int main()
{
int n;
float x;
printf("Dime un entero: ");
scanf("%d", &n);
printf("Y un real: ");
scanf("%f", &x);
printf("El entero es %d y el real es %f\n", n,x);
return 0;
}
En lo que sigue de este apartado, aunque usemos cadenas al estilo de C, emplearemos "cin" y "cout" para leer sus datos y mostrarlos.
Ejercicios propuestos:
- (6.1.2.1) Un programa que te pida tu nombre y una cifra numérica, y escriba tu nombre tantas veces como indique esa cifra numérica, usando "scanf" para pedir los datos.
6.1.3. Cómo acceder a las letras que forman una cadena
Podemos leer (o modificar) una de las letras de una cadena de igual forma que leemos o modificamos los elementos de cualquier tabla: el primer elemento será texto[0], el segundo será texto[1] y así sucesivamente:
// Introducción a C++, Nacho Cabanes
// Ejemplo 06.02:
// Cadenas de texto: acceder letra a letra
#include <iostream>
using namespace std;
int main()
{
char texto[40];
cout << "Introduce tu nombre: ";
cin >> texto;
cout << "Hola, " << texto << ". Tu inicial es "
<< texto[0] << endl;
return 0;
}
Ejercicio propuesto:
- (6.1.3.1) Un programa que pida al usuario que introduzca una palabra, cambie su primera letra por una "A" y muestre la palabra resultante.
6.1.4. Longitud de la cadena.
En una cadena que definamos como “char texto[40]” lo habitual es que realmente no ocupemos las 39 letras que podríamos llegar a usar. Si guardamos 9 letras (y el carácter nulo que marca el final), tendremos 30 posiciones que no hemos usado. Pero estas 30 posiciones generalmente contendrán “basura”, lo que existiera previamente en esas posiciones de memoria, porque el compilador las reserva para nosotros pero no las “limpia”. Si queremos saber cual es la longitud real de nuestra cadena tenemos dos opciones:
- Podemos leer la cadena, carácter a carácter, desde el principio hasta que encontremos el carácter nulo (\0) que marca el final.
- Hay una orden predefinida que lo hace por nosotros, y que nos dice cuantas letras hemos usado realmente en nuestra cadena. Es “strlen”, que se usa así:
// Introducción a C++, Nacho Cabanes
// Ejemplo 06.03:
// Longitud de una cadena
#include <cstring>
#include <iostream>
using namespace std;
int main()
{
char texto[40];
cout << "Introduce una palabra: ";
cin >> texto;
cout << "Has tecleado " << strlen(texto)
<< " letras." << endl;
return 0;
}
Como es de esperar, si escribimos “Hola”, esta orden nos dirá que hemos tecleado 4 letras (no cuenta el \0 que se añade automáticamente al final).
Si empleamos “strlen”, o alguna de las otras órdenes relacionadas con cadenas de texto que veremos en este tema, debemos incluir
Ejercicios propuestos:
- (6.1.4.1) Un programa que te pida tu nombre y lo muestre en pantalla separando cada letra de la siguiente con un espacio. Por ejemplo, si tu nombre es “Juan”, debería aparecer en pantalla “J u a n”.
- (6.1.4.2) Un programa que te pida tu nombre y lo muestre en pantalla separando al revés. Por ejemplo, si tu nombre es “Juan”, debería aparecer en pantalla “nauJ”.
6.1.5. Asignando a una cadena el valor de otra: strcpy, strncpy; strcat
Cuando queremos dar a una variable el valor de otra, normalmente usamos construcciones como a =2, o como a = b. Pero en el caso de las cadenas de texto, esta NO es la forma correcta, no podemos hacer algo como saludo="hola" ni algo como texto1=texto2. Si hacemos algo así, haremos que las dos cadenas estén en la misma posición de memoria, y que los cambios que hagamos a una de ellas se reflejen también en la otra. La forma correcta de guardar en una cadena de texto un cierto valor es:
strcpy (destino, origen);
Es decir, debemos usar una función llamada “strcpy” (string copy, copiar cadena), que se encuentra también en “string.h”. Vamos a ver dos ejemplos de su uso:
strcpy (saludo, "hola");
strcpy (textoDefinitivo, textoProvisional);
Es nuestra responsabilidad que en la cadena de destino haya suficiente espacio reservado para copiar lo que queremos. Si no es así, estaremos sobreescribiendo direcciones de memoria en las que no sabemos qué hay.
Para evitar este problema, tenemos una forma de indicar que queremos copiar sólo los primeros n bytes de origen, usando la función “strncpy”, así:
strncpy (destino, origen, n);
Vamos a ver un ejemplo, que nos pida que tecleemos una frase y guarde en otra variable sólo las 4 primeras letras:
// Introducción a C++, Nacho Cabanes
// Ejemplo 06.04:
// Tomar 4 letras de una cadena
#include <cstring>
#include <iostream>
using namespace std;
int main()
{
char texto1[40], texto2[40], texto3[10];
cout << "Introduce un frase: ";
cin >> texto1;
strcpy(texto2, texto1);
cout << "Una copia de tu texto es " << texto2 << endl;
strncpy(texto3, texto1, 4);
cout << "Y sus 4 primeras letras son " << texto3 << endl;
return 0;
}
Finalmente, existe otra orden relacionada con estas dos: podemos añadir una cadena al final de otra (concatenarla), con
strcat (destino, origen);
Vamos a ver un ejemplo de su uso, que nos pida nuestro nombre, nuestro apellido y cree una nueva cadena de texto que contenga los dos, separados por un espacio:
// Introducción a C++, Nacho Cabanes
// Ejemplo 06.05:
// Concatenar dos cadenas
#include <cstring>
#include <iostream>
using namespace std;
int main()
{
char texto1[40], texto2[40];
cout << "Introduce tu nombre: ";
cin >> texto1;
cout << "Introduce tu apellido: ";
cin >> texto2;
strcat(texto1, " "); // Añado un espacio al nombre
strcat(texto1, texto2); // Y luego el apellido
cout << "Te llamas " << texto1 << endl;
return 0;
}
Ejercicio propuesto:
- (6.1.5.1) Un programa que te pida una palabra, y la almacene en la variable llamada “texto”. Luego deberá pedir una segunda palabra, y añadirla al final de “texto” (y mostrar el resultado). Finalmente, deberá pedir una tercera palabra, y guardarla en la variable “texto” y en otra variable llamada “texto2” (y mostrar ambas variables).
6.1.6. Comparando cadenas: strcmp
Para comparar dos cadenas alfabéticamente (para ver si son iguales o para poder ordenarlas, por ejemplo), usamos
strcmp (cad1, cad2);
Esta función devuelve un número entero, que será:
- 0 si ambas cadenas son iguales.
- Un número negativo, si cadena1 < cadena2.
- Un número positivo, si cad1 > cad2.
Hay que tener cuidado, porque las cadenas se comparan como en un diccionario, pero hay que tener en cuenta ciertas cosas:
- Al igual que en un diccionario, todas las palabras que empiecen por B se consideran “mayores” que las que empiezan por A.
- Si dos cadenas empiezan por la misma letra (o las mismas letras), se ordenan basándose en la primera letra diferente, también al igual que en el diccionario.
- La primera diferencia está que en que se distingue entre mayúsculas y minúsculas. Para más detalles, en el código ASCII las mayúsculas aparecen antes que las minúsculas, así que las palabras escritas en mayúsculas se consideran “menores” que las palabras escritas en minúsculas. Por ejemplo, “ala” es menor que “hola”, porque una empieza por “a” y la otra empieza por “h”, pero “Hola” es menor que “ala” porque la primera empieza con una letra en mayúsculas y la segunda con una letra en minúsculas.
- La segunda diferencia es que el código ASCII estándar no incluye eñe, vocales acentuadas ni caracteres internacionales, así que estos caracteres “extraños” aparecen después de los caracteres “normales”, de modo que “adiós” se considera “mayor” que “adiposo”, porque la o acentuada está después de todas las letras del alfabeto inglés.
Vamos a ver un primer ejemplo que nos pida dos palabras y diga si hemos tecleado la misma las dos veces:
// Introducción a C++, Nacho Cabanes
// Ejemplo 06.06:
// Comparar dos cadenas
#include <cstring>
#include <iostream>
using namespace std;
int main()
{
char texto1[40], texto2[40];
cout << "Introduce una palabra: ";
cin >> texto1;
cout << "Introduce otra palabra: ";
cin >> texto2;
if (strcmp(texto1, texto2)==0)
cout << "Son iguales" << endl;
else
cout << "Son distintas" << endl;
return 0;
}
Podemos mejorarlo ligeramente para que nos diga qué palabra es “menor” de las dos:
// Introducción a C++, Nacho Cabanes
// Ejemplo 06.06:
// Comparar dos cadenas
#include <cstring>
#include <iostream>
using namespace std;
int main()
{
char texto1[40], texto2[40];
cout << "Introduce una palabra: ";
cin >> texto1;
cout << "Introduce otra palabra: ";
cin >> texto2;
int comparacion = strcmp(texto1, texto2);
if (comparacion==0)
cout << "Son iguales" << endl;
else
if (comparacion>0)
cout << "La primera palabra es mayor" << endl;
else
cout << "La segunda palabra es mayor" << endl;
return 0;
}
Ejercicio propuesto:
- (6.1.6.1) Un programa que te pida una clave de acceso, y no te deje "seguir" hasta que aciertes la contraseña correcta (que estará prefijada en el programa, y será "clave").
6.1.7. Buscando en cadenas
Podemos ver si una cadena contiene un cierto texto, usando "strstr", que devuelve 0 si no la contiene, o un valor distinto en caso de que sí la contenga, así:
// Introducción a C++, Nacho Cabanes
// Ejemplo 06.07:
// Buscar una subcadena dentro de otra cadena
#include <cstring>
#include <iostream>
using namespace std;
int main()
{
char texto1[40], texto2[40];
cout << "Introduce una palabra: ";
cin >> texto1;
cout << "Introduce otra palabra a buscar en ella: ";
cin >> texto2;
if (strstr(texto1, texto2)==0)
cout << "La primera NO contiene a la segunda" << endl;
else
cout << "La primera contiene a la segunda" << endl;
return 0;
}
Ejercicio propuesto:
- (6.1.7.1) Un programa que te pida una frase y luego varias palabras. Deberá decir si esas palabaras son parte de la frase o no. Terminará cuando se pulse Intro sin introducir ninguna palabra (la palabra introducida será una cadena vacía, de longitud cero).
6.1.8. Array de cadenas
Crear un array de cadenas de texto es tan sencillo como crear un array bidimensional de "char", así:
// Introducción a C++, Nacho Cabanes
// Ejemplo 06.08:
// Array de cadenas
#include <iostream>
using namespace std;
int main()
{
char mensajeError[5][80] = {
"Fichero no encontrado",
"El fichero no se puede abrir para escritura",
"El fichero está vacío",
"El fichero contiene datos de tipo incorrecto"
"El fichero está siendo usado"
};
cout << "El segundo mensaje de error es: "
<< mensajeError[1] << endl;
cout << "La primera letra del tercer mensaje de error es: "
<< mensajeError[2][0] << endl;
return 0;
}
Ejercicio propuesto:
- (6.1.8.1) Un programa que te pida 5 frases y luego varias las muestre en el orden contrario al que se introdujeron.
6.1.9. Valor inicial de una cadena de texto
Podemos dar un valor inicial a una cadena de texto, usando dos formatos distintos:
El formato “clásico” para dar valores a tablas:
char nombre[50]= {'J','u','a','n'};
O bien un formato más compacto:
char nombre[50]="Juan";
Pero cuidado con este último formato: hay que recordar que sólo se puede usar cuando se declara la variable, al principio del programa. Si ya estamos dentro del programa, deberemos usar necesariamente la orden “strcpy” para dar un valor a una cadena de texto.