Pages - Menu

sábado, 11 de mayo de 2013

Curso básico de C 6/10: Arrays y registros

A la hora de desarrollar aplicaciones necesitamos almacenar una gran cantidad de información, necesitamos también estructuras que recojan varios tipos de datos a la vez. C cuenta con varias herramientas para poder desarrollar todas estas funcionalidades. En éste capítulo veremos los Arrays simples y multidimensionales para poder almacenar una cantidad determinada de datos de un mismo tipo, acceder a ellos en cualquier momento y operar, también veremos los registros para poder trabajar con conjuntos de variables.


Programación
Foto tomada de freedigitalphotos.net

Arrays

Un array, vector, tabla o arreglo es una cantidad de valores de un determinado tipo colocados de manera secuencial e indexados, cuya forma de acceder a ellos es especificando la posición (o índice) donde se encuentre dentro del array.

Declaración.

Para declarar un array hay que seguir la siguiente estructura:

Tipo_dato nombre_variable [longitud_array];

El tipo de datos puede ser cualquier de los vistos anteriormente, por ejemplo, un array de 20 enteros sería así:

int num[20];

La declaración es exactamente igual que cuando declaramos variables normales, salvo que hay que añadir la longitud del array en cuestión.

Una cosa importante que hay que tener en cuenta a la hora de trabajar con arrays en C es que la primera celda del array tiene el índice 0, por lo que el array anterior tendría las siguientes posiciones:

num[0], num[1], num[2], num[3], num[4], num[5], num[6] … num[19].

En una misma fila de declaración se pueden declarar tanto varios arrays a la vez como variables normales.

int num1,num2,num3[10],num4,num5[5];

También es posible declarar arrays prescindiendo del uso de números para definir su tamaño, aunque es necesario que tengamos en cuenta ciertos factores:

1 – Para declarar un array usando identificadores sólo se podrá usar constantes definidas mediante #define.

#define TAM 50

int main(void)
{
            int a[TAM];
}

2 – No es correcto definir el tamaño de un array con variables.

int main(void)
{
            int tam=50;
            int a[tam];
}

3 – No es correcto definir el tamaño de un array con constantes declaradas mediante const. Esto es debido a que, a efectos del compilador, una constante declarada mediante const es una variable de sólo lectura.

int main(void)
{
            const int tam=50;
            int a[tam];
}

Asignación de valores e iniciación de un array.

Cuando se declara un array las celdas de éste contienen valores completamente aleatorios, es necesario que le asignemos los valores con los que necesitamos trabajar, para esto hay varias posibilidades.

Asignación directa de un valor mediante el operador de asignación =, hay que especificar mediante su índice la celda concreta donde se va a almacenar. En el array anterior sería tal que así:

a[2] = 1;

Por lo general cuando comenzamos un programa se debe inicializar los valores de todo el array, es decir, tendremos que asignar valores en todos sus índices, para ello lo mejor es recorrer el array completo mediante un bucle.

El bucle for es perfecto para trabajar con arrays puesto que al tener controlados todos los elementos esenciales de un bucle podemos centrarnos en trabajar con el array directamente.

Un ejemplo de inicialización de un array es el siguiente:

#include <stdio.h>

#define TAM 20

int main(void)
{
int x, a[TAM];

for (x = 0; x < TAM; x++)
{
a[x]=0;
            }

return 0;
}

También es posible inicializar un array en su propia declaración, para ello debemos seguir la siguiente estructura:

Tipo_dato nombre_variable [longitud_array] = {valor0, valor1... valor_longitud_array};

Un ejemplo práctico sería el siguiente:

int num[10] = {0,0,0,0,0,0,0,0,0,0}:

así nos aseguramos de que tenemos el array con valores definidos por nosotros desde el principio del programa.

Recorrer un array.

Como hemos podido comprobar en el anterior apartado, un array se recorre mediante un bucle.

Se toma como primer valor el cero puesto que la primera celda tiene índice cero, por lo tanto la condición para salir del bucle es el tamaño menos uno. Si recorremos el array y nos pasamos, o bien hacemos referencia a una celda fuera del rango del array el programa dará errores.

Veamos un ejemplo donde recorremos un array y mostramos su valor por pantalla:

#include <stdio.h>

#define TAM 5

int main(void)
{
int x,
int a[TAM] = {0,0,0,0,0};

for (x = 0; x < TAM; x++)
{
printf(“%d”,a[x]);
            }

return 0;
}

Cadenas de texto o array de caracteres.

El tipo de dato String o Cadena no existe en C como tal, en lugar de eso lo que tenemos a nuestra disposición es el array de caracteres, la forma de tratarlo es exactamente igual que con los arrays de otros tipos de datos a excepción de muchas instrucciones, entre ellas printf y scanf.

Consideraciones del array de caracteres

Definir un array de caracteres es sencillo, exactamente igual que con los otros tipos de datos, pero debemos tener en consideración el término de la cadena.

En las variables de tipo entero todas las celdas del array se rellenan por defecto con valores aleatorios, en los arrays de caracteres pasa igual. Al introducir o rellenar un array con caracteres no es necesario que lo rellenos al completo, no obstante hay que indicar cuando termina la cadena.

Para eso en C disponemos del carácter especial \0, con éste le diremos al programa que tenga en cuenta dentro del array todos los caracteres hasta llegar al carácter \0.

El \0 cuenta como un carácter más dentro del array y como tal ocupa una celda, es por eso que si definimos un array de 50 caracteres en realidad sólo podremos rellenar los primeros 49, ya que siempre el carácter final es el \0.

H
O
L
A
\0

H
A
Y
\0


El carácter \0 no tenemos por qué introducirlo nosotros, el programa puede encargarse, con que sepamos qué significa y cómo utilizarlo y controlarlo es suficiente.

Un truco para poder almacenar un número de caracteres en un array con el tamaño de ese número es definir el array con un tamaño igual y sumar una posición:

#define TAM 5

int main(void)
{
char a[TAM+1};
return 0;
}

Cadenas y printf

Para poder escribir un array de caracteres utilizamos la sentencia printf y la marca de formato %s

#include <stdio.h>
#define TAM 50

int main(void)
{
char saludo[TAM +1] = "hola";

printf ("%s.\n", saludo);

return 0;
}

No es necesario utilizar los bucles y recorrer celda a celda, aunque se puede, resulta trabajoso ya que a veces los arrays pueden ser bastante grandes.

Cadenas y scanf.

La instrucción scanf cumple con su cometido de leer una palabra, pero se presenta un problema y es el que acabo de decir, sólo lee una palabra, no una frase completa.

Pero a eso le daremos solución en el siguiente apartado, ahora veamos cómo utilizar la instrucción scanf:

#include <stdio.h>
#define TAM 50

int main(void)
{
char saludo[TAM +1];

scanf(“%s”,saludo);

printf ("%s.\n", saludo);

return 0;
}

Como se puede observar no es necesario de nuevo recorrer el array, la instrucción hace posible asignar los caracteres directamente en sus celdas.

También si se observa el código, en la instrucción scanf no he puesto un “&” delante del array, esto es debido a que hasta ahora hemos usado el scanf con “&” porque lo que scanf necesita es una dirección de memoria donde alojar el valor, cosa que hacíamos con “&”, como los arrays son en si mismos direcciones de memoria indexados no es necesario colocar el “&”.

Prueba el código introduciendo la siguiente cadena “Hola buenos días” y observa el resultado.

Cadenas y gets.

Para solucionar el problema del scanf con el array de caracteres tenemos dentro de la librería stdio.h la instrucción gets

Con gets no es necesario utilizar una marca de formato, tan sólo con especificar el array es suficiente, veamos un ejemplo:

#include <stdio.h>
#define TAM 50

int main(void)
{
char saludo[TAM +1];

gets(saludo);

printf ("%s.\n", saludo);

return 0;
}

Prueba ahora el código introduciendo de nuevo la cadena “Hola buenos días” y observa el resultado.

Copiar cadenas.

El caso de necesitar hacer una copia de cadenas es una cosa que nos podemos encontrar perfectamente en un programa, hasta ahora todo parece que se puede realizar perfectamente mediante sentencias, por lo que copiar cadenas no debe ser muy distinto, veamos el siguiente ejemplo:

#include <stdio.h>
#define TAM 50

int main(void)
{
char saludo[TAM +1] = "hola";
char despedida[TAM +1];

despedida=saludo;

printf ("%s.\n", despedida);

return 0;
}

Esto es incorrecto, el compilador dará error y el programa no llegará ni a ejecutarse, esto es debido a que, recordemos, las cadenas son arrays, y los arrays son direcciones de memoria que una vez declarados no se pueden alterar, por lo que en el programa anterior lo que estaríamos intentando hacer es asignar la dirección de memoria de otra dirección de memoria.

La copia de cadenas debe realizarse carácter a carácter, asignando los valores uno a uno en todas las posiciones implicadas, veamos el mismo ejemplo pero correctamente:

#include <stdio.h>
#define TAM 50

int main(void)
{
char saludo[TAM +1] = "hola";
char despedida[TAM +1];

for (x=0;x<TAM;x++)
{
despedida[x]=saludo[x];
}

printf ("%s.\n", despedida);

return 0;
}

Esta es la forma correcta de copiar cadenas, no obstante se puede refinar un poco dado que tenemos dos cadenas de 50 caracteres y realmente sólo tenemos que copiar cuatro, para ello introduciremos un condicional if para detectar el \0 y salir del bucle, veamos el resultado final:

#include <stdio.h>
#define TAM 50

int main(void)
{
char saludo[TAM +1] = "hola";
char despedida[TAM +1];

for (x=0;x<TAM;x++)
{
despedida[x]=saludo[x];

if (saludo[x]==’\0’)
{
break;
}
}

printf ("%s.\n", despedida);

return 0;
}

Esto se puede plantear de múltiples formas, podemos refinarlo de diferentes maneras, controlando en algunos sitios o bien modificando las condiciones del bucle.

Arrays Multidimensionales.

Hasta ahora lo que hemos visto como array es una tabla de una columna y un número determinado de filas, es posible ampliar todo esto bastante más, para ello se utilizan los arrays multidimensionales. De esta forma podemos trabajar con una tabla de filas y columnas a determinar, pero eso no es todo, también podemos darle más dimensiones al array.

Declaración

Los arrays multidimensionales se declaran de la misma forma que los arrays normales, pero por cada dimensión que queramos añadir debemos especificar igualmente su longitud.

int numeros [10][2];
int numeros [10][2][5];

Recorrer un array multidimensional.

Un array multidimensional se recorre de la misma forma que un array normal, mediante el bucle for, pero al igual que en la declaración debemos agregar tantos bucles como sean necesarios y anidaarlos entre ellos.

Para los arrays anteriores sería así:

for (x=0;x<10,x++)
{
for (y=0;y<2,y++)
{
}
}

De ésta forma recorremos de la siguiente forma: Primera fila, primera columna, segunda columna, ... , segunda fila, primera columna, segunda columna, ... etc.

for (x=0;x<10,x++)
{
for (y=0;y<2,y++)
{
for (z=0;z<5,z++)
{
}
}
}

Aquí recorremos así: Primera fila, primera columna, primera celda, segunda celda, ... , primera fila, segunda columna, primera celda, segunda celda, ... etc.

Todo lo que hemos visto en el apartado de arrays es completamente válido para los arrays multimensionales, no necesitan de mayores retoques.

Registros.

El último punto de éste capítulo es bastante interesante, imaginemos que necesitamos gestionar los datos de una tienda de discos, sería necesario conocer varios datos como por ejemplo el nombre del disco, el año en que se puso a la venta o el género al que pertenece. Esto se puede realizar perfectamente usando variables y arrays, pero hay ocasiones en las que es mejor agrupar estos datos con el fin de poder acceder a todos ellos de una sola vez.

Imaginemos que queremos gestionar los datos de 100 discos de música, es sencillo porque utilizaremos tres arrays, uno para el nombre del disco, otro para el año de publicación y otro para el género. Todo perfecto.

Ahora imaginemos que necesitamos buscar un disco en concreto y mostrar la información completa del disco, del que sólo sabemos el nombre. Buscamos en el primer array y lo localizamos, tenemos el nombre, ahora busquemos en el segundo array el año en que se publicó, deberemos saber en qué posición del array se encuentra, después busquemos en el siguiente array el género al que pertenece ¿No es un poco engorroso esto?

Ahora un caso perfectamente posible, imaginemos que deseamos eliminar un disco, nos vamos al array del nombre y lo eliminamos, después al array de año y luego al array de género, nuevamente engorroso.

Ahora lo último, imaginemos que nos hemos equivocado al programar y resulta que por un error al eliminar el nombre del disco no hemos podido eliminar el año y el género. El caos está servido.

En C disponemos de la posibilidad de declarar registros, esto son agrupaciones de datos de cualquier tipo, accesibles mediante identificadores.

Usando un registro podemos agrupar los tres arrays anteriores en uno sólo, y si queremos realizar cualquier operación la estaremos haciendo con un solo array, un array de registros.

La representación de los tres arrays podría ser así:

Disco1

2000

Pop
Disco2

1997

Rock
Disco3

1999

Pop

La representación de un array de registros podría ser así:

Disco1
2000
Pop
Disco2
1997
Rock
Disco3
1999
Pop

Así lo tenemos mucho mejor ¿verdad? La representación de los datos mediante un array de registros nos facilita operar con los datos y evitar mantenimientos incómodos y que de resultar erróneos podría ocasionar errores de datos muy graves.

Para definir un registro usaremos el siguiente esquema:

struct NombreRegistro {
variable1;
variable2;
variable3;
...
variableN;
};

Una vez lo hemos definido procedemos a declarar las variables basándonos en la estructura siguiendo el siguiente esquema:

Struct NombreRegistro nombre_variable;

El registro lo definimos antes del programa principal (main), concretamente después del #define, y por consiguiente después del #include, la declaración de variables basadas en el registro la podemos hacer en cualquier momento.

Tanto en las variables de la definición como en la declaración podemos usar arrays de cualquier dimensión.

Veamos el caso del programa para gestionar discos:

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

#define TAM 100
#define TAMEST 50

struct Discos {
char nombre[TAMEST+1];
int anno;
char genero[TAMEST+1];
};

int main(void)
{
struct Discos misdiscos[TAM];

return 0;
}

Con esto habremos contemplado el caso que planteábamos, ahora tenemos un array de 100 registros donde gestionaremos nuestra colección de discos.

Para poder hacer referencia a una variable del registro tan sólo debemos indicar su nombre de la siguiente forma:

Discos.nombre
Discos.anno
Discos.genero

El tratamiento con respecto a los operadores es el mismo que con las variables, podemos asignar valores, realizar operaciones, comparar, etc.

En el caso que nos ocupa, el de los discos, al tratarse de un array lo recorremos como ya hemos visto y trabajaremos con las variables así:

Discos[x].nombre
Discos[x].anno
Discos[x].genero

Donde X es el índice del array.

Con esto hemos terminado el sexto capítulo del curso básico de C. En éste capítulo hemos aprendido los conceptos de arrays simples y multidimensionales, hemos visto como recorrerlos y como trabajar con sus datos, también hemos visto las cadenas o arrays de caracteres y la nueva instrucción gets, por último hemos definido los registros, hemos visto como construirlos y como operar con ellos.

En el siguiente capítulo veremos dos conceptos importantes dentro de la programación, los procedimientos y las funciones, así como el paso de parámetros y formas de utilización.

¿A qué se os asemejan los registros? ¿Veis complicado el tratamientos de arrays?