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

Arrays (Matrices)

[Anterior] [Siguiente] [Contenido]

Contenido del Capítulo:

¿Qué es un array?

Nota: algunas personas conocen a los arrays como arreglos, matrices o vectores. Sin embargo, en este curso, vamos a usar el termino array ya que es, segun creo, el mas extendido en la bibliografia sobre el tema.

La definición sería algo así:

Un array es un conjunto de variables del mismo tipo que tienen el mismo nombre y se diferencian en el índice.

Pero ¿qué quiere decir esto y para qué lo queremos?. Pues bien, supongamos que somos un metereólogo y queremos guardar en el ordenador la temperatura que ha hecho cada hora del dia. Para darle cierta utilidad al final calcularemos la media de las temperaturas. Con lo que sabemos hasta ahora sería algo así (que nadie se moleste ni en probarlo):

#include <stdio.h>

int main()
     {
     /* Declaramos 24 variables, una para cada hora del dia */
     int temp1,  temp2,  temp3,  temp4,  temp5,  temp6,  temp7,  temp8;
     int temp9,  temp10, temp11, temp12, temp13, temp14, temp15, temp16;
     int temp17, temp18, temp19, temp20, temp21, temp22, temp23, temp0;
     int media;

     /* Ahora tenemos que dar el valor de cada una */
     printf( "Temperatura de las 0: " );
     scanf( "%i", &temp0 );
     printf( "Temperatura de las 1: " );
     scanf( "%i", &temp1 );
     printf( "Temperatura de las 2: " );
     scanf( "%i", &temp2 );
     ...
     printf( "Temperatura de las 23: " );
     scanf( "%i", &temp23 );
	
     media = ( temp0 + temp1 + temp2 + temp3 + temp4 + ... + temp23 ) / 24;
     printf( "\nLa temperatura media es %i\n", media );
     }

NOTA: Los puntos suspensivos los he puesto para no tener que escribir todo y que no ocupe tanto, no se pueden usar en un programa.

Para acortar un poco el programa podríamos hacer algo así:

#include <stdio.h>

int main()
     {
     /* Declaramos 24 variables, una para cada hora del dia */
     int temp1,  temp2,  temp3,  temp4,  temp5,  temp6,  temp7,  temp8;
     int temp9,  temp10, temp11, temp12, temp13, temp14, temp15, temp16;
     int temp17, temp18, temp19, temp20, temp21, temp22, temp23, temp0;
     int media;

     /* Ahora tenemos que dar el valor de cada una */
     printf( "Introduzca las temperaturas desde las 0 hasta las 23 separadas por un espacion: " );
     scanf( "%i %i %i ... %i", &temp0, &temp1, &temp2, ... &temp23 );
	
     media = ( temp0 + temp1 + temp2 + temp3 + temp4 + ... + temp23 ) / 24;
     printf( "\nLa temperatura media es %i\n", media );
     }

Lo que no deja de ser un peñazo. Y esto con un ejemplo que tiene tan sólo 24 variables, ¡¡imagínate si son más!!

Y precisamente aquí es donde nos vienen de perlas los arrays. Vamos a hacer el programa con un array. Usaremos nuestros conocimientos de bucles for y de scanf.

#include <stdio.h>

int main()
     {
     int temp[24]; /* Con esto ya tenemos declaradas las 24 variables */
     float media = 0;
     int hora;

     /* Ahora tenemos que dar el valor de cada una */
     for( hora=0; hora<24; hora++ )
          {
          printf( "Temperatura de las %i: ", hora );
          scanf( "%i", &temp[hora] );
          media += temp[hora];
          }
     media = media / 24;

     printf( "\nLa temperatura media es %f\n", media );
     }

Como ves es un programa más rápido de escribir (y es menos aburrido hacerlo), y más cómodo para el usuario que el anterior.

Como ya hemos comentado cuando declaramos una variable lo que estamos haciendo es reservar una zona de la memoria para ella. Cuando declaramos un array lo que hacemos (en este ejemplo) es reservar espacio en memoria para 24 variables de tipo int. El tamaño del array (24) lo indicamos entre corchetes al definirlo. Esta es la parte de la definición que dice: Un array es un conjunto de variables del mismo tipo que tienen el mismo nombre.

La parte final de la definición dice: y se diferencian en el índice. En ejemplo recorremos la matriz mediante un bucle for y vamos dando valores a los distintos elementos de la matriz. Para indicar a qué elemento nos referimos usamos un número entre corchetes (en este caso la variable hora), este número es lo que se llama Indice. El primer elemento de la matriz en C tiene el índice 0, El segundo tiene el 1 y así sucesivamente. De modo que si queremos dar un valor al elemento 4 (índice 3) haremos:

	temp[ 3 ] = 20;

NOTA: No hay que confundirse. En la declaración del array el número entre corchetes es el número de elementos, en cambio cuando ya usamos la matriz el número entre corchetes es el índice.

[Arriba]

Declaración de un Array

La forma general de declarar un array es la siguiente:

	tipo_de_dato nombre_del_array[ dimensión ];

El tipo_de_dato es uno de los tipos de datos conocidos (int, char, float...) o de los definidos por el usuario con typdef. En el ejemplo era int.

El nombre_del_array es el nombre que damos al array, en el ejemplo era temp.

La dimensión es el número de elementos que tiene el array.

Como he indicado antes, al declarar un array reservamos en memoria tantas variables del tipo_de_dato como las indicada en dimensión.

[Arriba]

Sobre la dimensión de un Array

Hemos visto en el ejemplo que tenemos que indicar en varios sitios el tamaño del array: en la declaración, en el bucle for y al calcular la media. Este es un programa pequeño, en un programa mayor probablemente habrá que escribirlo muchas más veces. Si en un momento dado queremos cambiar la dimensión del array tendremos que cambiar todos. Si nos equivocamos al escribir el tamaño (ponemos 25 en vez de 24) cometeremos un error y puede que no nos demos cuenta. Por eso es mejor usar una constante con nombre, por ejemplo ELEMENTOS.

#include <stdio.h>

#define ELEMENTOS	24
int main()
     {
     int temp[ELEMENTOS]; /* Con esto ya tenemos declaradas las 24 variables */
     float media = 0;
     int hora;

     /* Ahora tenemos que dar el valor de cada una */
     for( hora=0; hora<ELEMENTOS; hora++ )
          {
          printf( "Temperatura de las %i: ", hora );
          scanf( "%i", &temp[hora] );
          media += temp[hora];
          }
     media = media / ELEMENTOS;

     printf( "\nLa temperatura media es %f\n", media );
     }
Comprobado con DJGPP

Ahora con sólo cambiar el valor de elementos una vez lo estaremos haciendo en todo el programa.

[Arriba]

Inicializar un array

En C se pueden inicializar los arrays al declararlos igual que hacíamos con las variables. Recordemos que se podía hacer:

	int hojas = 34;

Pues con arrays se puede hacer:

	int temperaturas[24] = { 15, 18, 20, 23, 22, 24, 22, 25, 26, 25, 24, 22,
                            21, 20, 18, 17, 16, 17, 15, 14, 14, 14, 13, 12 };

Ahora el elemento 0 (que será el primero), es decir temperaturas[0] valdrá 15. El elemento 1 (el segundo) valdrá 18 y así con todos. Vamos a ver un ejemplo:

#include <stdio.h>

int main()
     {
     int hora;
     int temperaturas[24] = { 15, 18, 20, 23, 22, 24, 22, 25, 26, 25, 24,
                                  22, 21, 20, 18, 17, 16, 17, 15, 14, 14, 14, 13, 12 };

     for (hora=0 ; hora<24 ; hora++ )
         {
         printf( "La temperatura a las %i era de %i grados.\n", hora, temperaturas[hora] );
         }
     }
Comprobado con DJGPP

Pero a ver quién es el habilidoso que no se equivoca al meter los datos, no es difícil olvidarse alguno. Hemos indicado al compilador que nos reserve memoria para un array de 24 elementos de tipo int. ¿Qué ocurre si metemos menos de los reservados? Pues no pasa nada, sólo que los elementos que falten valdrán cero.

#include <stdio.h>

int main()
     {
     int hora;
     /* Faltan los tres últimos elementos */
     int temperaturas[24] = { 15, 18, 20, 23, 22, 24, 22, 25, 26, 25,
                           24, 22, 21, 20, 18, 17, 16, 17, 15, 14, 14 };

     for (hora=0 ; hora<24 ; hora++ )
         {
         printf( "La temperatura a las %i era de %i grados.\n", hora, temperaturas[hora] );
         }
     }
Comprobado con DJGPP

El resultado será:

La temperatura a las 0 era de 15 grados.
La temperatura a las 1 era de 18 grados.
La temperatura a las 2 era de 20 grados.
La temperatura a las 3 era de 23 grados.
...
La temperatura a las 17 era de 17 grados.
La temperatura a las 18 era de 15 grados.
La temperatura a las 19 era de 14 grados.
La temperatura a las 20 era de 14 grados.
La temperatura a las 21 era de 0 grados.
La temperatura a las 22 era de 0 grados.
La temperatura a las 23 era de 0 grados.

Vemos que los últimos 3 elementos son nulos, que son aquellos a los que no hemos dado valores. El compilador no nos avisa que hemos metido menos datos de los reservados.

NOTA: Fíjate que para recorrer del elemento 0 al 23 (24 elementos) hacemos: for(hora=0; hora<24; hora++). La condición es que hora sea menos de 24. También podíamos haber hecho que hora!=24.

Ahora vamos a ver el caso contrario, metemos más datos de los reservados. Vamos a meter 25 en vez de 24. Si hacemos esto dependiendo del compilador obtendremos un error o al menos un warning (aviso). En unos compiladores el programa se creará y en otros no, pero aún así nos avisa del fallo. Debe indicarse que estamos intentando guardar un dato de más, no hemos reservado memoria para él.

Si la matriz debe tener una longitud determinada usamos este método de definir el número de elementos. En nuestro caso era conveniente, porque los dias siempre tienen 24 horas. Es conveniente definir el tamaño de la matriz para que nos avise si metemos más elementos de los necesarios.

En los demás casos podemos usar un método alternativo, dejar al ordenador que cuente los elementos que hemos metido y nos reserve espacio para ellos:

#include <stdio.h>

int main()
     {
     int hora;
     /* Faltan los tres últimos elementos */
     int temperaturas[] = { 15, 18, 20, 23, 22, 24, 22, 25, 26, 25, 24,
                         22, 21, 20, 18, 17, 16, 17, 15, 14, 14 };

     for ( hora=0 ; hora<24 ; hora++ )
         {
         printf( "La temperatura a las %i era de %i grados.\n", hora, temperaturas[hora] );
         }
     }
Comprobado con DJGPP

Vemos que no hemos especificado la dimensión del array temperaturas. Hemos dejado los corchetes en blanco. El ordenador contará los elementos que hemos puesto entre llaves y reservará espacio para ellos. De esta forma siempre habrá el espacio necesario, ni más ni menos. La pega es que si ponemos más de los que queríamos no nos daremos cuenta.

Para saber en este caso cuantos elementos tiene la matriz podemos usar el operador sizeof. Dividimos el tamaño de la matriz entre el tamaño de sus elementos y tenemos el número de elementos.

#include <stdio.h>

int main()
     {
     int hora;
     int elementos;
     int temperaturas[] = { 15, 18, 20, 23, 22, 24, 22, 25, 26, 25,
                    24, 22, 21, 20, 18, 17, 16, 17, 15, 14, 14 };

     elementos = sizeof temperaturas / sizeof(int);
     for ( hora=0 ; hora<elementos ; hora++ )
          {
          printf( "La temperatura a las %i era de %i grados.\n", hora, temperas[hora] );
          }
     printf( "Han sido %i elementos.\n" , elementos );
     }
Comprobado con DJGPP

[Arriba]

Recorrer un array

En el apartado anterior veíamos un ejemplo que mostraba todos los datos de un array. Veíamos también lo que pasaba si metíamos más o menos elementos al inicializar la matriz. Ahora vamos a ver qué pasa si intentamos imprimir más elementos de los que hay en la matriz, en este caso intentamos imprimir 28 elementos cuando sólo hay 24:

#include <stdio.h>

int main()
     {
     int hora;
     /* Faltan los tres últimos elementos */
     int temperaturas[24] = { 15, 18, 20, 23, 22, 24, 22, 25, 26, 25, 24,
                            22, 21, 20, 18, 17, 16, 17, 15, 14, 14, 13, 13, 12 };

     for (hora=0 ; hora<28 ; hora++ )
         {
         printf( "La temperatura a las %i era de %i grados.\n", hora, temperaturas[hora] );
         }
     }
Comprobado con DJGPP

Lo que se obtiene en mi ordenador es:

La temperatura a las 22 era de 15 grados.
...
La temperatura a las 23 era de 12 grados.
La temperatura a las 24 era de 24 grados.
La temperatura a las 25 era de 3424248 grados.
La temperatura a las 26 era de 7042 grados.
La temperatura a las 27 era de 1 grados.

Vemos que a partir del elemento 24 (incluído) tenemos resultados extraños. Esto es porque nos hemos salido de los límites del array e intenta acceder al elemento temperaturas[25] y sucesivos que no existen. Así que nos muestra el contenido de la memoria que está justo detrás de temperaturas[23] (esto es lo más probable) que puede ser cualquiera. Al contrario que otros lenguajes C no comprueba los límites de los array, nos deja saltárnoslos a la torera. Este programa no da error al compilar ni al ejecutar, tan sólo devuelve resultados extraños. Tampoco bloqueará el sistema porque no estamos escribiendo en la memoria sino leyendo de ella.

Otra cosa muy diferente es meter datos en elementos que no existen. Veamos un ejemplo (ni se te ocurra ejecutarlo):

#include <stdio.h>

int main()
     {
     int temp[24];
     float media = 0;
     int hora;

     for( hora=0; hora<28; hora++ )
          {
          printf( "Temperatura de las %i: ", hora );
          scanf( "%i", &temp[hora] );
          media += temp[hora];
          }
     media = media / 24;

     printf( "\nLa temperatura media es %f\n", media );
     }

Lo que sospechaba, lo he probado en mi ordenador y se ha bloqueado. He tenido que apagarlo. El problema ahora es que estamos intentando escribir en el elemento temp[24] que no existe y puede ser un lugar cualquiera de la memoria. Como consecuencia de esto podemos estar cambiando algún programa o dato de la memoria que no debemos y el sistema hace pluf. Así que mucho cuidado con esto.

[Arriba]

Punteros a arrays

Aquí tenemos otro de los importantes usos de los punteros, los punteros a arrays. Estos están íntimamente relacionados.

Para que un puntero apunte a un array se puede hacer de dos formas, una es apuntando al primer elemento del array:

	int *puntero;
	int temperaturas[24];

	puntero = &temperaturas[0];

El puntero apunta a la dirección del primer elemento. Otra forma equivalente, pero mucho más usada es:

	puntero = temperaturas;

Con esto también apuntamos al primer elemento del array. Fijaos que el puntero tiene que ser del mismo tipo que el array (en este caso int).

Ahora vamos a ver cómo acceder al resto de los elementos. Para ello empezamos por cómo funciona un array: Un array se guarda en posiciones seguidas en memoria, de tal forma que el segundo elemento va inmediatamente después del primero en la memoria. En un ordenador en el que el tamaño del tipo int es de 32 bits (4 bytes) cada elemento del array ocupará 4 bytes. Veamos un ejemplo:

#include <stdio.h>

int main()
     {
     int i;
     int temp[24];

     for( i=0; i<24; i++ )
          {
          printf( "La dirección del elemento %i es %p.\n", i, &temp[i] );
          }
     }

NOTA: Recuerda que %p sirve para imprimir en pantalla la dirección de una variable en hexadecimal.

El resultado es (en mi ordenador):

La dirección del elemento 0 es 4c430.
La dirección del elemento 1 es 4c434.
La dirección del elemento 2 es 4c438.
La dirección del elemento 3 es 4c43c.
...
La dirección del elemento 21 es 4c484.
La dirección del elemento 22 es 4c488.
La dirección del elemento 23 es 4c48c.

(Las direcciones están en hexadecimal). Vemos aquí que efectivamente ocupan posiciones consecutivas y que cada una ocupa 4 bytes. Si lo representamos en una tabla:

4C4304C4344C4384C43C
temp[0]temp[1]temp[2]temp[3]

Ya hemos visto cómo funcionas los arrays por dentro, ahora vamos a verlo con punteros. Voy a poner un ejemplo:

#include <stdio.h>

int main()
     {
     int i;
     int temp[24];
     int *punt;

     punt = temp;

     for( i=0; i<24; i++ )
          {
          printf( "La dirección de temp[%i] es %p y la de punt es %p.\n",
                  i, &temp[i], punt );
          punt++;
          }
     }
Comprobado con DJGPP

Cuyo resultado es:

La dirección de temp[0] es 4c430 y la de punt es 4c430.
La dirección de temp[1] es 4c434 y la de punt es 4c434.
La dirección de temp[2] es 4c438 y la de punt es 4c438.
...
La dirección de temp[21] es 4c484 y la de punt es 4c484.
La dirección de temp[22] es 4c488 y la de punt es 4c488.
La dirección de temp[23] es 4c48c y la de punt es 4c48c.

En este ejemplo hay dos líneas importantes (en negrita). La primera es punt = temp. Con esta hacemos que el punt apunte al primer elemento de la matriz. Si no hacemos esto punt apunta a un sitio cualquiera de la memoria y debemos recordar que no es conveniente dejar los punteros así, puede ser desastroso.

La segunda línea importante es punt++. Con esto incrementamos el valor de punt, pero curiosamente aunque incrementamos una unidad (punt++ equivale a punt=punt+1) el valor aumenta en 4. Aquí se muestra una de las características especiales de los punteros. Recordemos que en un puntero se guarda una dirección. También sabemos que un puntero apunta a un tipo de datos determinado (en este caso int). Cuando sumamos 1 a un puntero sumamos el tamaño del tipo al que apunta. En el ejemplo el puntero apunta a una variable de tipo int que es de 4 bytes, entonces al sumar 1 lo que hacemos es sumar 4 bytes. Con esto lo que se consigue es apuntar a la siguiente posición int de la memoria, en este caso es el siguiente elemento de la matriz.

OperaciónEquivalenteValor de punt
punt = temp;punt = &temp[0];4c430
punt++;sumar 4 al contenido de punt (4c430+4)4c434
punt++;sumar 4 al contenido de punt (4c434+4)4c438

Cuando hemos acabado estamos en temp[24] que no existe. Si queremos recuperar el elemento 1 podemos hacer punt = temp otra vez o restar 24 a punt:

	punt -= 24;

con esto hemos restado 24 posiciones a punt (24 posiciones int*4 bytes por cada int= 96 posiciones).

Vamos a ver ahora un ejemplo de cómo recorrer la matriz entera con punteros y cómo imprimirla:

#include <stdio.h>

int main(int argc,char *argv[])
     {
     int temperaturas[24] = { 15, 18, 20, 23, 22, 24, 22, 25, 26, 25, 24,
                                 22, 21, 20, 18, 17, 16, 17, 15, 14, 14, 13, 12, 12 };

     int *punt;
     int i;

     punt = temperaturas;

     for( i=0 ; i<24; i++ )
          {
          printf( "Elemento %i: %i\n", i, *punt );
          punt++;
          }
     }
Comprobado con DJGPP

Cuando acabamos punt apunta a temperaturas[24], y no al primer elemento, si queremos volver a recorrer la matriz debemos volver como antes al comienzo. Para evitar perder la referencia al primer elemento de la matriz (temperaturas[0]) se puede usar otra forma de recorrer la matriz con punteros:

#include <stdio.h>

int main(int argc,char *argv[])
     {
     int temperaturas[24] = { 15, 18, 20, 23, 22, 24, 22, 25, 26, 25, 24,
                                 22, 21, 20, 18, 17, 16, 17, 15, 14, 14, 13, 12, 12 };

     int *punt;
     int i;

     punt = temperaturas;

     for( i=0 ; i<24; i++ )
          {
          printf( "Elemento %i: %i\n", i, *(punt+i) );
          }
     }
Comprobado con DJGPP

Con *(punt+i)lo que hacemos es tomar la dirección a la que apunta punt (la dirección del primer elemento de la matriz) y le sumamos i posiciones. De esta forma tenemos la dirección del elemento i. No estamos sumando un valor a punt, para sumarle un valor habría que hacer punt++ o punt+=algo, así que punt siempre apunta al principio de la matriz.

Se podría hacer este programa sin usar punt. Sustituyendolo por temperaturas y dejar *(temperaturas+i). Lo que no se puede hacer es: temperaturas++;.

Importante: Como final debo comentar que el uso de índices es una forma de maquillar el uso de punteros. El ordenador convierte los índices a punteros. Cuando al ordenador le decimos temp[5] en realidad le estamos diciendo *(temp+5). Así que usar índices es equivalente a usar punteros de una forma más cómoda.

[Arriba]

Paso de un array a una función

En C no podemos pasar un array entero a una función. Lo que tenemos que hacer es pasar un puntero al array. Con este puntero podemos recorrer el array:

#include <stdio.h>

int sumar( int *m )
     {
     int suma, i;

     suma = 0;
     for( i=0; i<10; i++ )
          {
          suma += m[i];
          }
     return suma;
     }

int main()
     {
     int contador;
     int matriz[10] = { 10, 11, 13, 10, 14, 9, 10, 18, 10, 10 };

     for( contador=0; contador<10; contador++ )
          printf( "   %3i\n", matriz[contador] );
     printf( "+ -----\n" );
     printf( "   %3i", sumar( matriz ) );
     }
Comprobado con DJGPP

Este programa tiene alguna cosilla adicional como que muestra toda la matriz en una columna. Además se usa para imprimir los números %3i. El 3 indica que se tienen que alinear los números a la derecha, así queda más elegante.

Como he indicado no se pasa el array, sino un puntero a ese array. Si probamos el truquillo de más arriba de usar sizeof para calcular el número de elementos no funcionará aquí. Dentro de la función suma añadimos la línea:

    printf( "Tamaño del array: %i Kb, %i bits\n", sizeof m, (sizeof m)*8 );

Devolverá 4 (depende del compilador) que serán los bytes que ocupa el int (m es un puntero a int). ¿Cómo sabemos entonces cual es el tamaño del array dentro de la función? En este caso lo hemos puesto nosotros mismos, 10. Pero creo que lo mejor es utilizar constantes como en el apartado Sobre la dimensión de un array.

He dicho que el array no se puede pasar a una función, que se debe usar un puntero. Pero vemos que luego estamos usando m[i], esto lo podemos hacer porque como se ha mencionado antes el uso de índices en una forma que nos ofrece C de manejar punteros con matrices. Ya se ha visto que m[i] es equivalente a *(m+i).

Otras declaraciones equivalentes serían:

    int sumar( int m[] )
ó
    int sumar( int m[10] )

En realidad esta última no se suele usar, no es necesario indicar el número de elementos a la matriz.

int m[] e int *m son equivalentes.

[Arriba]

Ejercicios

Ejercicio 1:

Solución:

[Arriba]

[Anterior] [Siguiente] [Contenido]

© Gorka Urrutia

www.elrincondelc.com