Curso de CEl Rincón del C - www.elrincondelc.com

Strings (Cadenas de texto)

[Anterior] [Siguiente] [Contenido]

Contenido del Capítulo:

Introducción

Vamos a ver por fin cómo manejar texto con C, hasta ahora sólo sabíamos cómo mostrarlo por pantalla.

Para empezar diré que en C no existe un tipo string como en otros lenguajes. No existe un tipo de datos para almacenar texto, se utilizan arrays de chars. Funcionan igual que los demás arrays con la diferencia que ahora jugamos con letras en vez de con números.

Se les llama cadenas, strings o tiras de caracteres. A partir de ahora les llamaremos cadenas.

Para declarar una cadena se hace como un array:

    char texto[20];

Al igual que en los arrays no podemos meter más de 20 elementos en la cadena. Vamos a ver un ejemplo para mostrar el nombre del usuario en pantalla:

#include <stdio.h>

int main()
     {
     char nombre[20];

     printf( "Introduzca su nombre (20 letras máximo): " );
     scanf( "%s", nombre );
     printf( "\nEl nombre que ha escrito es: %s\n", nombre );
     }
Comprobado con DJGPP

Vemos cosas curiosas como por ejemplo que en el scanf no se usa el símbolo &. No hace falta porque es un array, y ya sabemos que escribir el nombre del array es equivalente a poner &nombre[0].

También puede llamar la atención la forma de imprimir el array. Con sólo usar %s ya se imprime todo el array. Ya veremos esto más adelante.

Si alguno viene de algún otro lenguaje esto es importante: en C no se puede hacer esto:

int main()
     {
     char texto[20];

     texto = "Hola";
     }
Comprobado con DJGPP

[Arriba]

Las cadenas por dentro

Es interesante saber cómo funciona una cadena por dentro, por eso vamos a ver primero cómo se inicializa una cadena.

#include <stdio.h>

int main()
     {
     char nombre[] = "Gorka";

     printf( "Texto: %s\n", nombre );
     printf( "Tamaño de la cadena: %i bytes\n", sizeof nombre );
     }
Comprobado con DJGPP

Resultado al ejecutar:

Texto: Gorka
Tamaño de la cadena: 6 bytes

!Que curioso! La cadena es "Gorka", sin embargo nos dice que ocupa 6 bytes. Como cada elemento (char) ocupa un byte eso quiere decir que la cadena tiene 6 elementos. !Pero si "Gorka" solo tiene 5! ?Por que? Muy sencillo, porque al final de una cadena se pone un simbolo '\0' que significa "Fin de cadena". De esta forma cuando queremos escribir la cadena basta con usar %s y el programa ya sabe cuántos elementos tiene que imprimir, hasta que encuentre '\0'.

El programa anterior sería equivalente a:

#include <stdio.h>

int main(int argc,char *argv[])
     {
     char nombre[] = { 'G', 'o', 'r', 'k', 'a', '\0' };

     printf( "Texto: %s\n", nombre );
     }
Comprobado con DJGPP

Aquí ya se ve que tenemos 6 elementos. Pero, ¿Qué pasaría si no pusiéramos '\0' al final?

#include <stdio.h>

int main()
     {
     char nombre[] = { 'G', 'o', 'r', 'k', 'a' };

     printf( "Texto: %s\n", nombre );
     }
Comprobado con DJGPP

En mi ordenador salía:

Texto: Gorka-
Tamaño de la cadena: 5 bytes

Pero en el tuyo después de "Gorka" puede aparecer cualquier cosa. Lo que aquí sucede es que no encuentra el símbolo '\0' y no sabe cuándo dejar de imprimir. Afortunadamente, cuando metemos una cadena se hace de la primera forma y el C se encarga de poner el dichoso símbolo al final.

Es importante no olvidar que la longitud de una cadena es la longitud del texto más el símbolo de fin de cadena. Por eso cuando definamos una cadena tenemos que reservarle un espacio adicional. Por ejemplo:

    char nombre[6] = "Gorka";

Si olvidamos esto podemos tener problemas.

[Arriba]

Funciones de manejo de cadenas

Existen unas cuantas funciones el la biblioteca estándar de C para el manejo de cadenas:

Para usar estas funciones hay que añadir la directiva:

    #include <string.h>

strlen

Esta función nos devuelve el número de caracteres que tiene la cadena (sin contar el '\0').

#include <stdio.h>
#include <string.h>

int main()
     {
     char texto[]="Gorka";
     int longitud;

     longitud = strlen(texto);
     printf( "La cadena \"%s\" tiene %i caracteres.\n", texto, longitud );
     }
Comprobado con DJGPP

Crea tus propias funciones: Vamos a ver cómo se haría esta función si no dispusiéramos de ella. Si no te enteras cómo funciona consulta Recorrer cadenas con punteros.

#include <stdio.h>
#include <string.h>

int main()
     {
     char texto[]="Gorka";
     char *p;
     int longitud=0;

     p = texto;
     while (*p!='\0')
          {
          longitud++;
          printf( "%c\n", *p ); /* Mostramos la letra actual */
          p++;                  /* Vamos a la siguiente letra */
          }
     printf( "La cadena \"%s\" tiene %i caracteres.\n", texto, longitud );
     }
Comprobado con DJGPP

Para medir la longitud de la cadena usamos un puntero para recorrerla (el puntero p). Hacemos que p apunte a texto. Luego entramos en un bucle while. La condición del bucle comprueba si se ha llegado al fin de cadena ('\0'). Si no es así suma 1 a longitud, muestra la letra por pantalla e incrementa el puntero en 1 (con esto pasamos a la siguiente letra).


strcpy

     #include <string.h>

     char *strcpy(char *cadena1, const char *cadena2);

Copia el contenido de cadena2 en cadena1. cadena2 puede ser una variable o una cadena directa (por ejemplo "hola"). Debemos tener cuidado de que la cadena destino (cadena1) tenga espacio suficiente para albergar a la cadena origen (cadena2).

#include <stdio.h>
#include <string.h>

int main()
     {
     char textocurso[] = "Este es un curso de C.";
     char destino[50];

     strcpy( destino, textocurso );
     printf( "Valor final: %s\n", destino );
     }
Comprobado con DJGPP

Vamos a ver otro ejemplo en el que la cadena destino es una cadena constante ("Este es un curso de C") y no una variable. Además en este ejemplo vemos que la cadena origen es sustituida por la cadena destino totalmete. Si la cadena origen es más larga que la destino, se eliminan las letras adicionales.

#include <stdio.h>
#include <string.h>

int main()
     {
     char destino[50] = "Esto no es un curso de HTML sino un curso de C.";

     printf( "%s\n", destino );
     strcpy( destino, "Este es un curso de C." );
     printf( "%s\n", destino );
     }
Comprobado con DJGPP

strcat

     #include <string.h>

     char *strcat(char *cadena1, const char *cadena2);

Copia la cadena2 al final de la cadena1.

#include <stdio.h>
#include <string.h>

int main()
     {
     char nombre_completo[50];
     char nombre[]="Gorka";
     char apellido[]="Urrutia";

     strcpy( nombre_completo, nombre );
     strcat( nombre_completo, " " );
     strcat( nombre_completo, apellido );
     printf( "El nombre completo es: %s.\n", nombre_completo );
     }
Comprobado con DJGPP

Como siempre tenemos que asegurarnos que la variable en la que metemos las demás cadenas tenga el tamaño suficiente. Con la primera línea metemos el nombre en nombre_completo. Usamos strcpy para asegurarnos de que queda borrado cualquier dato anterior. Luego usamos un strcat para añadir un espacio y finalmente metemos el apellido.


sprintf

     #include <stdio.h>

     int sprintf(char *destino, const char *format, ...);

Funciona de manera similar a printf, pero en vez de mostrar el texto en la pantalla lo guarda en una variable (destino). El valor que devuelve (int) es el número de caracteres guardados en la variable destino.

Con sprintf podemos repetir el ejemplo de strcat de manera más sencilla:

#include <stdio.h>
#include <string.h>

int main()
     {
     char nombre_completo[50];
     char nombre[]="Gorka";
     char apellido[]="Urrutia";

     sprintf( nombre_completo, "%s %s", nombre, apellido );
     printf( "El nombre completo es: %s.\n", nombre_completo );
     }
Comprobado con DJGPP

Se puede aplicar a sprintf todo lo que valía para printf.

[Arriba]


strcmp

     #include <string.h>

     int strcmp(const char *cadena1, const char *cadena2);

Compara cadena1 y cadena2. Si son iguales devuelve 0. Un número negativo si cadena1 va antes que cadena2 y un número positivo si es al revés:

#include <stdio.h>
#include <string.h>

int main()
     {
     char nombre1[]="Gorka";
     char nombre2[]="Pedro";

     printf( "%i", strcmp(nombre1,nombre2));
     }
Comprobado con DJGPP

[Arriba]


toupper() y tolower() - Convertir a mayúsculas y minúsculas

La funcion tolower() nos permite convertir una cadena a minusculas:

char *tolower( char *cadena );

toupper cumple la funcion contraria, convierte la cadena a mayusculas:

char *toupper( char *cadena );

[Arriba]

Entrada de cadenas por teclado (scanf y gets)


scanf

Hemos visto en capítulos anteriores el uso de scanf para números, ahora es el momento de ver su uso con cadenas.

Scanf almacena en memoria (en un buffer) lo que vamos escribiendo. Cuando pulsamos ENTER (o Intro o Return, como se llame en cada teclado) lo analiza, comprueba si el formato es correcto y por último lo mete en la variable que le indicamos.

#include <stdio.h>
#include <string.h>

int main()
     {
     char cadena[30];

     printf( "Escribe una palabra: " );
     fflush( stdout );
     scanf( "%s", cadena );
     printf( "He guardado: \"%s\" \n", cadena );
     }
Comprobado con DJGPP

Ejecutamos el programa e introducimos la palabra "hola". Esto es lo que tenemos:

Escribe una palabra: hola
He guardado: "hola"

Si ahora introducimos "hola amigos" esto es lo que tenemos:

Escribe una palabra: hola amigos
He guardado: "hola"

Sólo nos ha cogido la palabra "hola" y se ha olvidado de amigos. ¿Por qué? pues porque scanf toma una palabra como cadena. Usa los espacios para separar variables.

Es importante siempre asegurarse de que no vamos a almacenar en cadena más letras de las que caben. Para ello debemos limitar el número de letras que le va a introducir scanf. Si por ejemplo queremos un máximo de 5 caracteres usaremos %5s:

#include <stdio.h>
#include <string.h>

int main()
     {
     char cadena[6];

     printf( "Escribe una palabra: " );
     fflush( stdout );
     scanf( "%5s", cadena );
     printf( "He guardado: \"%s\" \n", cadena );
     }
Comprobado con DJGPP

Si metemos una palabra de 5 letras (no se cuenta '\0') o menos la recoge sin problemas y la guarda en cadena.

Escribe una palabra: Gorka
He guardado: "Gorka"

Si metemos más de 5 letras nos cortará la palabra y nos dejará sólo 5.

Escribe una palabra: Juanjo
He guardado: "Juanj"

Scanf tiene más posibilidades (consulta la ayuda de tu compilador), entre otras permite controlar qué caracteres entramos. Supongamos que sólo queremos coger las letras mayúsculas:

#include <stdio.h>
#include <string.h>

int main()
     {
     char cadena[30];

     printf( "Escribe una palabra: " );
     fflush( stdout );
     scanf( "%[A-Z]s", cadena );
     printf( "He guardado: \"%s\" \n", cadena );
     }
Comprobado con DJGPP

Guarda las letras mayúsculas en la variable hasta que encuentra una minúscula:

Escribe una palabra: Hola
He guardado: "H"
Escribe una palabra: HOLA
He guardado: "HOLA"
Escribe una palabra: AMigOS
He guardado: "AM"

[Arriba]


gets

Esta función nos permite introducir frases enteras, incluyendo espacios.

     #include <stdio.h>

     char *gets(char *buffer);

Almacena lo que vamos tecleando en la variable buffer hasta que pulsamos ENTER. Si se ha almacenado algún caracter en buffer le añade un '\0' al final y devuelve un puntero a su dirección. Si no se ha almacenado ninguno devuelve un puntero NULL.

#include <stdio.h>
#include <string.h>

int main()
     {
     char cadena[30];
     char *p;

     printf( "Escribe una palabra: " );
     fflush( stdout );
     p = gets( cadena );
     if (p) printf( "He guardado: \"%s\" \n", cadena );
     else printf( "No he guardado nada!\n" );
     }
Comprobado con DJGPP

Esta función es un poco peligrosa porque no comprueba si nos hemos pasado del espacio reservado (de 29 caracteres en este ejemplo: 29letras+'\0').

[Arriba]


Qué son los buffer y cómo funcionan

#include <stdio.h>

int main()
     {
     char ch;
     char nombre[20], apellido[20], telefono[10];
     printf( "Escribe tu nombre: " );
     scanf( "%[A-Z]s", nombre );
     printf( "Lo que recogemos del scanf es: %s\n", nombre );
     printf( "Lo que había quedado en el buffer: " );
     while( (ch=getchar())!='\n' )
            printf( "%c", ch );
     }
Comprobado con DJGPP
Escribe tu nombre: GORka
Lo que recogemos del scanf es: GOR
Lo que había quedado en el buffer: ka

Cuidado con scanf!!!

#include <stdio.h>

int main()
     {
     char nombre[20], apellido[20], telefono[10];
     printf( "Escribe tu nombre: " );
     scanf( "%s", nombre );
     printf( "Escribe tu apellido: " );
     gets( apellido );
     }
Comprobado con DJGPP

[Arriba]


getchar

[Arriba]


Recorrer cadenas con punteros

Las cadenas se pueden recorrer de igual forma que hacíamos con las matrices, usando punteros.

Vamos a ver un ejemplo: Este sencillo programa cuenta los espacios y las letras 'e' que hay en una cadena.

#include <stdio.h>
#include <string.h>

int main()
     {
     char cadena[]="Gorka es un tipo estupendo";
     char *p;
     int espacios=0, letras_e=0;

     p = cadena;
     while (*p!='\0')
           {
           if (*p==' ') espacios++;
           if (*p=='e') letras_e++;
           p++;
           }
     printf( "En la cadena \"%s\" hay:\n", cadena );
     printf( "  %i espacios\n", espacios );
     printf( "  %i letras e\n", letras_e );
     }
Comprobado con DJGPP

Para recorrer la cadena necesitamos un puntero p que sea de tipo char. Debemos hacer que p apunte a la cadena (p=cadena). Así p apunta a la dirección del primer elemento de la cadena. El valor de *p sería por tanto 'G'. Comenzamos el bucle. La condición comprueba que no se ha llegado al final de la cadena (*p!='\0'), recordemos que '\0' es quien marca el final de ésta. Entonces comprobamos si en la dirección a la que apunta p hay un espacio o una letra e. Si es así incrementamos las variables correspondientes. Una vez comprobado esto pasamos a la siguiente letra (p++).

Dos cosas muy importantes: primero no debemos olvidarnos nunca de inicializar un puntero, en este caso hacer que apunte a cadena. Segundo no debemos olvidarnos de incrementar el puntero dentro del bucle (p++), sino estaríamos en un bucle infinito siempre comprobando el primer elemento.

En la condición del bucle podíamos usar símplemente: while (!*p), que es equivalente a (*p!='\0').

En este otro ejemplo sustituímos los espacios por guiones:

#include <stdio.h>
#include <string.h>

int main()
     {
     char cadena[]="Gorka es un tipo estupendo";
     char *p;

     p = cadena;
     while (*p!='\0')
           {
           if (*p==' ') *p = '-';
           p++;
           }
     printf( "La cadena queda: \"%s\" \n", cadena );
     }
Comprobado con DJGPP

y se obtiene:

La cadena queda: "Gorka-es-un-tipo-estupendo"

[Arriba]


Arrays de cadenas

Un array de cadenas puede servirnos para agrupar una serie de mensajes. Por ejemplo todos los mensajes de error de un programa. Luego para acceder a cada mensaje basta con usar su número.

#include <stdio.h>
#include <string.h>

int error( int errnum )
    {
    char *errores[] = {
         "No se ha producido ningún error",
         "No hay suficiente memoria",
         "No hay espacio en disco",
         "Me he cansado de trabajar"
         };

    printf( "Error número %i: %s.\n", errnum, errores[errnum] );
    exit( -1 );
    }

int main()
     {
     error( 2 );
     }
Comprobado con DJGPP

El resultado será:

Error número 2: No hay espacio en disco.

Un array de cadenas es en realidad un array de punteros a cadenas. El primer elemento de la cadena ("No se ha producido ningún error") tiene un espacio reservado en memoria y errores[0] apunta a ese espacio.

[Arriba]


Ordenar un array de cadenas

Vamos a ver un sencillo ejemplo de ordenación de cadenas. En el ejemplo tenemos que ordenar una serie de dichos populares:

#include <stdio.h>
#include <string.h>

#define ELEMENTOS       5

int main()
     {
     char *dichos[ELEMENTOS] = {
         "La avaricia rompe el saco",
         "Más Vale pájaro en mano que ciento volando",
         "No por mucho madrugar amanece más temprano",
         "Año de nieves, año de bienes",
         "A caballo regalado no le mires el diente"
         };
     char *temp;
     int i, j;

     printf( "Lista desordenada:\n" );
     for( i=0; i<ELEMENTOS; i++ )
          printf( "  %s.\n", dichos[i] );
     for( i=0; i<ELEMENTOS-1; i++ )
          for( j=0; j<ELEMENTOS; j++ )
               if (strcmp(dichos[i], dichos[j])>0)
                  {
                  temp = dichos[i];
                  dichos[i] = dichos[j];
                  dichos[j] = temp;
                  }
     printf( "Lista ordenada:\n" );
     for( i=0; i<ELEMENTOS; i++ )
          printf( "  %s.\n", dichos[i] );
     }
Comprobado con DJGPP

Este método se conoce como el método de burbuja.

Cómo funciona el programa:

1.- Tomamos el primer elemento de la matriz. Lo comparamos con todos los siguientes. Si alguno es anterior los intercambiamos. Cuando acabe esta primera vuelta tendremos "A caballo regalado no le mires el diente" en primera posición.

2.- Tomamos el segundo elemento. Lo comparamos con el tercero y siguientes. Si alguno es anterior los intercambiamos. Al final de esta vuelta quedará "A caballo regalado no le mires el diente" en segunda posición.

Para mayor claridad (eso espero) voy a sustituir cada cadena por su primera letra (menos la de "Año de nieves..." que la sustituyo por Añ). Y así represento el proceso:

   0    1    2    3    3'   4    4'   5    6    6'   7    7'   8    8'   9    9'   10  10'
1  L    L L L   A
2  M    M    M    M    M    M    M    M M L L 
3  N    N    N    N    N    N    N    N    N    N    N    N    N M M L
4  Añ   Añ   Añ    L    L    L    L    L M    M    M    M N    N    N    N M
5  A    A    A    A    A    A    Añ   Añ   Añ    L    L    L    L M M N

[Arriba]


Ejercicios

Ejercicio 1: Crear un programa que tome una frase e imprima cada una de las palabras en una línea:

Introduzca una frase: La programación en C es divertida
Resultado:
La
programación
en
C
es
divertida

Solución:

#include <stdio.h>

int main() {
    char frase[100];
    int i = 0;
    
    printf( "Escriba una frase: " );
    gets( frase );
    printf( "Resultado:\n" );
    while ( frase[i]!='\0' ) {
        if ( frase[i]==' ' )
                printf( "\n" );
        else
                printf( "%c", frase[i] );
        i++;
    }
    system( "pause" );
    return 0;
}

Ejercicio 2: Escribe un programa que después de introducir una palabra convierta alternativamente las letras a mayúsculas y minúsculas:

Introduce una palabra: chocolate
Resultado: ChOcoLaTe

Solución:

#include <stdio.h>

int main() {
    char palabra[100];
    int i = 0, j = 0;
    
    printf( "Escribe una palabra: " );
    gets( palabra );
    printf( "Resultado:\n" );
    while ( palabra[i]!='\0' ) {
        if ( j==0 )
                printf( "%c", toupper( palabra[i] ) );
        else
                printf( "%c", tolower( palabra[i] ) );
        j = 1 - j;
        i++;
    }
    printf( "\n" );
    system( "pause" );
    return 0;
}

[Arriba]

[Anterior] [Siguiente] [Contenido]

© Gorka Urrutia

www.elrincondelc.com