Para las cadenas de texto, la situación se complica un poco: 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 (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:
/*---------------------------*/
/* Ejemplo en C nº 40: */
/* C040.C */
/* */
/* Primer ejemplo de */
/* cadenas de texto */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include
int main()
{
char texto[40]; /* Para guardar hasta 39 letras */
printf("Introduce tu nombre: ");
scanf("%s", &texto);
printf("Hola, %s\n", texto);
return 0;
}
Dos comentarios:
/*---------------------------*/
/* Ejemplo en C nº 41: */
/* C041.C */
/* */
/* Segundo ejemplo de */
/* cadenas de texto: scanf */
/* sin & */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include
int main()
{
char texto[40]; /* Para guardar hasta 39 letras */
printf("Introduce tu nombre: ");
scanf("%s", texto);
printf("Hola, %s\n", texto);
return 0;
}
Ejercicio propuesto:
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:
/*---------------------------*/
/* Ejemplo en C nº 42: */
/* C042.C */
/* */
/* Tercer ejemplo de */
/* cadenas de texto: */
/* acceder letra a letra */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include
int main()
{
char texto[40]; /* Para guardar hasta 39 letras */
printf("Introduce tu nombre: ");
scanf("%s", texto);
printf("Hola, %s. Tu inicial es %c\n", texto, texto[0]);
return 0;
}
Ejercicio propuesto:
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 hubiera 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 por 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í:
/*---------------------------*/
/* Ejemplo en C nº 43: */
/* C043.C */
/* */
/* Longitud de una cadena */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include
#include
int main()
{
char texto[40];
printf("Introduce una palabra: ");
scanf("%s", texto);
printf("Has tecleado %d letras", strlen(texto));
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 <string.h> , que es donde se definen todas ellas.
Ejercicios propuestos:
Hemos visto que si leemos una cadena de texto con “scanf”, se paraba en el primer espacio en blanco y no seguía leyendo a partir de ese punto. Existen otras órdenes que están diseñadas específicamente para manejar cadenas de texto, y que nos podrán servir en casos como éste.
Para leer una cadena de texto (completa, sin parar en el primer espacio), usaríamos la orden “gets”, así:
gets(texto);
De igual modo, para escribir un texto en pantalla podemos usar “puts”, que muestra la cadena de texto y avanza a la línea siguiente:
puts(texto);
Sería equivalente a esta otra orden:
printf("%s\n", texto);
Ejercicio propuesto:
Existe un posible problema cuando se mezcla el uso de "gets" y el de "scanf": si primero leemos un número, al usar "scanf("%d", ...", la variable numérica guardará el número... pero el Intro que pulsamos en el teclado después de introducir ese número queda esperando en el buffer (la memoria intermedia del teclado). Si a continuación leemos un segundo número, no hay problema, porque se omite ese Intro, pero si leemos una cadena de texto, ese Intro es aceptable, porque representaría una cadena vacía. Por eso, cuando primero leemos un número y luego una cadena usando "gets", tendremos que "absorber" el Intro, o de lo contrario el texto no se leería correctamente. Una forma de hacerlo sería usando "getchar":
scanf("%d", &numero);
getchar();
gets(texto);
Además, existe un problema adicional: muchos compiladores que sigan el estándar C99 pueden dar el aviso de que "gets es una orden no segura y no debería utilizarse". En compiladores que sigan el estándar C11 (propuesto en diciembre de 2011), es posible que esta orden ni siquiera esté disponible. Se debe a que "gets" no comprueba que haya espacio suficiente para los datos que introduzca el usuario, lo que puede dar lugar a un error de desbordamiento y a que el programa se comporte de forma imprevisible. En un fuente de práctica, creado por un principiante, no es peligroso, pero sí en caso de un programa en C "profesional" que esté dando servicio a una página Web, donde el uso de "gets" sí podría suponer una vulnerabilidad. Por eso, en el estándar C11 se propone una orden "gets_s", en la que se indica el nombre de la variable pero también el tamaño máximo permitido. En el próximo tema (ficheros, tema 6) veremos una alternativa segura a "gets" que sí se puede utilizar en cualquier compilador.
Ejercicios propuestos:
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:
/*---------------------------*/
/* Ejemplo en C nº 44: */
/* C044.C */
/* */
/* Tomar 4 letras de una */
/* cadena */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include
#include
int main()
{
char texto1[40], texto2[40], texto3[10];
printf("Introduce un frase: ");
gets(texto1);
strcpy(texto2, texto1);
printf("Una copia de tu texto es %s\n", texto2);
strncpy(texto3, texto1, 4);
printf("Y sus 4 primeras letras son %s\n", texto3);
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:
/*---------------------------*/
/* Ejemplo en C nº 45: */
/* C045.C */
/* */
/* Concatenar dos cadenas */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include
#include
int main()
{
char texto1[40], texto2[40], texto3[40];
printf("Introduce tu nombre: ");
gets(texto1);
printf("Introduce tu apellido: ");
gets(texto2);
strcat(texto1, " "); /* Añado un espacio al nombre */
strcat(texto1, texto2); /* Y luego el apellido */
printf("Te llamas %s\n", texto1);
return 0;
}
Ejercicio propuesto:
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á:
Hay que tener cuidado, porque las cadenas se comparan como en un diccionario, pero hay que tener en cuenta ciertas cosas:
Vamos a ver un primer ejemplo que nos pida dos palabras y diga si hemos tecleado la misma las dos veces:
/*---------------------------*/
/* Ejemplo en C nº 46: */
/* C046.C */
/* */
/* Comparar dos cadenas */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include
#include
int main()
{
char texto1[40], texto2[40];
printf("Introduce una palabra: ");
gets(texto1);
printf("Introduce otra palabra: ");
gets(texto2);
if (strcmp(texto1, texto2)==0)
printf("Son iguales\n");
else
printf("Son distintas\n");
return 0;
}
Podemos mejorarlo ligeramente para que nos diga qué palabra es “menor” de las dos:
/*---------------------------*/
/* Ejemplo en C nº 47: */
/* C047.C */
/* */
/* Comparar dos cadenas (2) */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include
#include
int main()
{
char texto1[40], texto2[40];
int comparacion;
printf("Introduce una palabra: ");
gets(texto1);
printf("Introduce otra palabra: ");
gets(texto2);
comparacion = strcmp(texto1, texto2);
if (comparacion==0)
printf("Son iguales\n");
else if (comparacion>0)
printf("La primera palabra es mayor\n");
else
printf("La segunda palabra es mayor\n");
return 0;
}
Ejercicio propuesto:
Hay dos posibilidades más de las cadenas de texto que merece la pena comentar. Son las que nos ofrecen las funciones “sprintf” y “sscanf”:
La funcion “sprintf” crea una cadena de texto a partir de una especificación de formato y unos ciertos parámetros, al igual que hace “printf”, pero la diferencia está en que “printf” manda su salida a la pantalla, mientras que “sprintf” la deja guardada en una cadena de texto.
Por ejemplo, si escribimos
printf(”El número %d multiplicado por 2 vale %d\n”, 50, 50*2);
En pantalla aparecerá escrito
El número 50 multiplicado por 2 vale 100
Pues bien, si tenemos una cadena de texto que hayamos definido (por ejemplo) como char cadena[100] y escribimos
sprintf(cadena,”El número %d multiplicado por 2 vale %d\n”, 50, 50*2);
Esta vez en pantalla no aparece nada escrito, sino que “cadena” pasa a contener el texto que antes habíamos mostrado. Ahora ya podríamos escribir este texto con:
puts(cadena);
o bien con
printf(”%s”, cadena);
¿Qué utilidad tiene esta orden? Nos puede resultar cómoda cuando queramos formatear texto que no vaya a aparecer directamente en pantalla de texto, sino que lo vayamos a enviar a un fichero, o que queramos mostrar en pantalla gráfica, o enviar a través de una red mediante “sockets”, por ejemplo.
Por otra parte “sscanf” es similar a “scanf”, con la diferencia de que los valores para las variables no se leen desde el teclado, sino desde una cadena de texto
strcpy(cadena, "20 30");
sscanf(cadena, "%d %d", &primerNum, &segundoNum);
Nota: sscanf devuelve el número de valores que realmente se han detectado, de modo que podemos comprobar si ha tomado todos los que esperábamos o alguno menos (porque el usuario haya tecleado menos de los que esperábamos o porque alguno esté tecleado incorrectamente).
if (sscanf(cadena, "%d %d", &primerNum, &segundoNum)<2)
printf("Debia teclear dos numeros");
Ejercicio propuesto: Un programa que pida tu nombre, tu día de nacimiento y tu mes de nacimiento y lo junte todo en una cadena, separando el nombre de la fecha por una coma y el día del mes por una barra inclinada, así: “Juan, nacido el 31/12”.
Una tercera orden que puede resultar útil más de una vez es “strstr”. Permite comprobar si una cadena contiene un cierto texto. Devuelve NULL (un valor especial, que nos encontraremos cada vez más a partir de ahora) si no la contiene, y otro valor (no daremos más detalles por ahora sobre qué tipo de valor ni por qué) en casi de que sí la contenga:
if (strstr (frase, "Hola ") == NULL)
printf("No has dicho la palabra Hola ");
Nota: estas no son todas las posibilidades que tenemos para manipular cadenas, pero posiblemente sí son las más habituales. Hay otras que nos permiten buscar una letra dentro de una cadena (strchr), una cadena dentro de otra cadena (strstr), “dar la vuelta” a una cadena (strrev), etc. Según el compilador que usemos, podemos tener incluso funciones ya preparadas para convertir una cadena a mayúsculas (strupr) o a minúsculas (strlwr).
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.