Falta incluir imágenes, añadir colores,
completar el contenido... incluso releer ;-)
5. Arrays y estructuras
5.1. Conceptos básicos sobre tablas5.1.1 Definición de una tabla y acceso a los datos
Una tabla o array (que algunos autores traducen por
“arreglo”) es un conjunto de elementos, todos los cuales
son del mismo tipo. Estos elementos tendrán todos el mismo
nombre, y ocuparán un espacio contiguo en la memoria.
Por ejemplo, si queremos definir un grupo de 4 números enteros, usaríamos
int ejemplo[4];
Podemos acceder a cada uno de los valores individuales indicando su
nombre (ejemplo) y el número de elemento que nos interesa, pero
con una precaución: se empieza a numerar desde 0, así que
en el caso anterior tendríamos 4 elementos, que serían
ejemplo[0], ejemplo[1], ejemplo[2], ejemplo[3].
Como ejemplo, vamos a definir un grupo de 5 números enteros y hallar su suma:
/*---------------------------*/
/* Ejemplo en C nº 38: */
/* C038.C */
/*
*/
/* Primer ejemplo de tablas */
/*---------------------------*/
#include <stdio.h>
int numero[5]; /* Un array de 5 números enteros */
int
suma;
/* Un entero que será la suma */
main()
{
numero[0] = 200; /* Les damos valores */
numero[1] = 150;
numero[2] = 100;
numero[3] = -50;
numero[4] = 300;
suma = numero[0] + /* Y hallamos la suma */
numero[1] + numero[2] + numero[3] + numero[4];
printf("Su suma es %d", suma);
/* Nota: esta es la forma más ineficiente e incómoda */
/* Ya lo iremos mejorando */
}
Ejercicio propuesto: Un programa que pida al usuario 4 números,
los memorice (utilizando una tabla), calcule su media aritmética
y la muestre en pantalla.
5.1.2. Valor inicial de una tabla
Al igual que ocurría con las variables “normales”,
podemos dar valor a los elementos de una tabla al principio del
programa. Será más cómodo que dar los valores uno
por uno, como hemos hecho antes. Esta vez los indicaremos todos entre
llaves, separados por comas:
/*---------------------------*/
/* Ejemplo en C nº 39: */
/* C039.C */
/*
*/
/* Segundo ejemplo de */
/* tablas */
/*---------------------------*/
#include <stdio.h>
int numero[5] = /* Un array de 5 números enteros */
{200, 150, 100, -50, 300};
int
suma;
/* Un entero que será la suma */
main()
{
suma = numero[0] + /* Y hallamos la suma */
numero[1] + numero[2] + numero[3] + numero[4];
printf("Su suma es %d", suma);
/* Nota: esta forma es algo menos engorrosa, pero todavía no */
/* está bien hecho. Lo seguiremos mejorando */
}
Ejercicio propuesto: Un programa almacene en una tabla el número
de días que tiene cada mes (supondremos que es un año no
bisiesto), pida al usuario que le indique un mes (1=enero,
12=diciembre) y muestre en pantalla el número de días que
tiene ese mes.
5.1.3. Recorriendo los elementos de una tabla
Es de esperar que exista una forma más cómoda de acceder
a varios elementos de un array, sin tener siempre que repetirlos todos,
como hemos hecho en
suma = numero[0] + numero[1] + numero[2] + numero[3] + numero[4];
El “truco” consistirá en emplear cualquiera de las
estructuras repetitivas que ya hemos visto (while, do..while, for), por
ejemplo así:
suma = 0; /* Valor inicial */
for (i=0; i<=4; i++)
suma += numero[i];
En este caso, que sólo sumábamos 5 números, no
hemos escrito mucho menos, pero si trabajásemos con 100, 500 o
1000 números, la ganancia en comodidad sí que está
clara.
Ejercicio propuesto: A partir del programa anterior, que almacenaba en
una tabla el número de días que tiene cada mes, crear
otro que pida al usuario que le indique la fecha, detallando el
día (1 al 31) y el mes (1=enero, 12=diciembre), como respuesta
muestre en pantalla el número de días que quedan hasta
final de año.
5.2. Cadenas de caracteres
5.2.1. Definición. Lectura desde teclado
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 hecho 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 */
/*---------------------------*/
#include <stdio.h>
char texto[40]; /* Para guardar hasta 39 letras */
main()
{
printf("Introduce tu nombre: ");
scanf("%s", &texto);
printf("Hola, %s\n", texto);
}
Dos comentarios:
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.
Siendo estrictos, no hace falta el
“&” en “scanf” cuando estamos leyendo
cadenas de texto. Los motivos exactos los veremos más adelante,
cuando hablemos de direcciones de memoria y de punteros. Pero este
programa se podría haber escrito así:
/*---------------------------*/
/* Ejemplo en C nº 41: */
/* C041.C */
/*
*/
/* Segundo ejemplo de */
/* cadenas de texto: scanf */
/* sin
&
*/
/*---------------------------*/
#include <stdio.h>
char texto[40]; /* Para guardar hasta 39 letras */
main()
{
printf("Introduce tu nombre: ");
scanf("%s", texto);
printf("Hola, %s\n", texto);
}
5.2.2. 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:
/*---------------------------*/
/* Ejemplo en C nº 42: */
/* C042.C */
/*
*/
/* Segundo ejemplo de */
/* cadenas de texto: scanf */
/* sin
&
*/
/*---------------------------*/
#include <stdio.h>
char texto[40]; /* Para guardar hasta 39 letras */
main()
{
printf("Introduce tu nombre: ");
scanf("%s", texto);
printf("Hola, %s. Tu inicial es %c\n", texto, texto[0]);
}
5.2.3. 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 hubiera previamente en esas posiciones de
memoria, porque el compilador las reerva 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 */
/*---------------------------*/
#include <stdio.h>
#include <string.h>
char texto[40];
main()
{
printf("Introduce una palabra: ");
scanf("%s", texto);
printf("Has tecleado %d letras", strlen(texto));
}
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 esta orden, 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.
Ejercicio propuesto: Un programa 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”.
5.2.4. Entrada/salida para cadenas: gets, puts
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);
5.2.5. Asignando a una cadena el valor de otra; strcpy, strncpy
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 */
/*---------------------------*/
#include <stdio.h>
#include <string.h>
char texto1[40], texto2[40], texto3[10];
main()
{
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);
}
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 */
/*---------------------------*/
#include <stdio.h>
#include <string.h>
char texto1[40], texto2[40], texto3[40];
main()
{
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);
}
5.2.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
“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:
/*---------------------------*/
/* Ejemplo en C nº 46: */
/* C046.C */
/*
*/
/* Comparar dos cadenas */
/*---------------------------*/
#include <stdio.h>
#include <string.h>
char texto1[40], texto2[40];
main()
{
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");
}
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) */
/*---------------------------*/
#include <stdio.h>
#include <string.h>
char texto1[40], texto2[40];
int comparacion;
main()
{
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");
}
5.2.7. Otras funciones de cadenas: sprintf
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 a través de una red mediante “sockets”, o
que queramos mostrar en pantalla gráfica, 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 te pida tu nombre, tu dia de
nacimiento y tu mes de nacimiento y lo junto todo en una cadena,
separando el nombre de la fecha por una coma y el dia del mes por una
barra inclinada, así: “Juan, nacido el 31/12”.
Nota: estas no son todas las posiblidades que tenemos para manipular
cadenas, pero posiblemente sí son las más habituales. Hay
oitras 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).
5.2.8. 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”.
5.3. Tablas bidimensionales
Podemos declarar tablas de dos o más dimensiones. Por ejemplo,
si queremos guardar datos de dos grupos de alumnos, cada uno de los
cuales tiene 20 alumnos, tenemos dos opciones:
Podemos usar int
datosAlumnos[40] y entonces debemos recordar que los 20
primeros datos corresponden realmente a un grupo de alumnos y los 20
siguientes a otro grupo.
O bien podemos emplear int
datosAlumnos[2][20] y entonces sabemos que los datos de la
forma datosAlumnos[0][i] son los del primer grupo, y los
datosAlumnos[1][i] son los del segundo.
En cualquier caso, si queremos indicar valores iniciales, lo haremos
entre llaves, igual que si fuera una tabla de una única
dimensión. Vamos a verlo con un ejemplo de su uso:
/*---------------------------*/
/* Ejemplo en C nº 48: */
/* C048.C */
/*
*/
/* Array de dos dimensiones */
/*---------------------------*/
#include <stdio.h>
int notas[2][10] =
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
main() {
printf("La nota del tercer alumno del grupos 1 es %d",
notas[0][2]);
}
Este tipo de tablas son las que se usan también para guardar
matrices, cuando hay que resolver problemas matemáticos
más complejos.
También podemos usar arrays de dos dimensiones si queremos
guardar una lista de cadenas de texto, como en este ejemplo:
/*---------------------------*/
/* Ejemplo en C nº 49: */
/* C049.C */
/*
*/
/* Array de cadenas */
/*---------------------------*/
#include <stdio.h>
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"
};
main() {
printf("El segundo mensaje de error es: %s",
mensajeError[1]);
}
5.4. Arrays indeterminados.
Si damos un valor inicial a un array, no será necesario que
indiquemos su tamaño, porque el compilador lo puede saber
contando cuantos valores hemos detallado, así:
int punto[] = {10, 0, -10};
char saludo[ ] = "hola";
char mensajes[][80] = {"Bienvenido", "Hasta otra"};