3.1.1. Definición de una tabla y acceso a los datos
Una tabla, vector, matriz 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 main()
{
int numero[5]; /* Un array de 5 números enteros */
int suma; /* Un entero que será la suma */
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#93; + 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 */
return 0;
}
Ejercicios propuestos:
Un programa que pida al usuario 4 números, los memorice (utilizando una tabla), calcule su media aritmética y después muestre en pantalla la media y los datos tecleados.
Un programa que pida al usuario 5 números reales y luego los muestre en el orden contrario al que se introdujeron.
3.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 main()
{
int numero[5] = /* Un array de 5 números enteros */
{200, 150, 100, -50, 300};
int suma; /* Un entero que será la suma */
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 */
return 0;
}
Ejercicios propuestos:
• Un programa que 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.
• Un programa que almacene en una tabla el número de días que tiene cada mes (año no bisiesto), pida al usuario que le indique un mes (ej. 2 para febrero) y un día (ej. el día 15) y diga qué número de día es dentro del año (por ejemplo, el 15 de febrero sería el día número 46, el 31 de diciembre sería el día 365).
3.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.
Una operación muy frecuente es recorrer los elementos de un vector, bien para asignarles un valor o bien para leer su valor y operar con él. Para ello utilizaremos un bucle con una estructura de control y una variable que actue como contador y que nos sirva de índice para acceder a cada elemento del vector.
Ejemplo:
int vector[5] = {3, 8, 18, 23, 33};
int i;
for(i = 0; i < 5; i++)
{
printf("%i ", vector[i]);
}
Resultado:
3 8 18 23 33
En el ejemplo anterior se utiliza un for para recorrer el vector. En la variable i se van generando los valores desde 0 hasta 4, que son los indices de los valores almacenados en el vector (5 datos). El resultado del ejemplo es que se muestran por consola todos los datos contenidos en el vector.
Para enviar un vector como argumento en la llamada a una función bastará con poner su nombre, ya sea que la función vaya a modificar su contenido o en el caso de que solo sea un dato de entrada.
Cuando definimos una función que acepta un vector como parámetro, en la definición del parámetro pondremos el equivalente a una declaración normal del vector. No es obligatorio indicar el tamaño del vector (entre corchetes), pero si es recomendable hacerlo (y pasar también una variable que represente ese tamaño) ya que cuando manejemos arrays de múltiples dimensiones si deberemos hacerlo.
Ejemplo:
24
#include <stdio.h>
void copiar(int size, int origen[size], int destino[size])
{
int i;
for(i = 0; i < size; i++)
{
destino[i] = origen[i];
}
}
void main()
{
int v1[5] = {3, 8, 18, 23, 33};
int v2[5];
copiar(5, v1, v2);
int i;
for(i = 0; i < 5; i++)
{
printf("%i ", v2[i]);
}
}
Resultado:
3 8 18 23 33
Para copiar los datos de un vector en otro vector no podemos realizar una asignación directa del tipo v1 = v2. En el ejemplo, la función copiar se utiliza para copiar los datos de un vector origen en otro vector destino. Esto hay que realizarlo elemento a elemento y para ello utilizamos la sentencia for de la línea 6 que recorre todos los índices del vector.
En la definición de la función de la línea 3 vemos como se declaran ambos parámetros de tipo vector de enteros (origen y destino).
En la llamada a la función de la línea 18 vemos como se pasan ambos vectores como argumentos. Se pasan los dos de la misma forma independientemente de que uno de ellos se va a modificar en la función y el otro no.
A la hora de pasar a una función elementos individuales de un vector, se aplican las mismas normas que cuando pasamos variables. Se pueden pasar por valor cuando son datos de entrada o por referencia (el puntero) cuando son datos de salida. En este último caso utilizando el operador & en la llamada a la función y el operador * en la declaración del parámetro.
Ejemplo:
#include <stdio.h>
void cambiar(int *n1, int *n2)
{
int aux = *n1;
*n1 = *n2;
*n2 = aux;
}
void main()
{
int v[5] = {3, 8, 18, 23, 33};
cambiar(&v[0], &v[1]);
int i;
for(i = 0; i < 5; i++)
{
printf("%i ", v[i]);
}
}
Resultado:
8 3 18 23 33
En el ejemplo se ha definido una función llamada cambiar que intercambia el valor de dos datos enteros que recibe como parámetros. Invocamos a esa función pasando los dos primeros elementos del vector (por referencia) para que la función los intercambie. Vemos que se realiza igual que si fueran dos variables enteras normales.
Dos algoritmos muy habituales son pedir los datos de un vector al usuario por consola e imprimir los datos de un vector por consola.
Se plantea, a continuación, un ejemplo de función que pide los datos de un vector al usuario desde la consola y los almacena en un vector.
Ejemplo:
#include <stdio.h>
void pedir_datos(int n, int v[n])
{
int i;
for(i = 0; i < n; i++)
{
printf("Dame v[%i]: ", i);
scanf("%i", &v[i]);
}
}
void main()
{
int v[4];
pedir_datos(4, v);
}
Se plantea, a continuación, un ejemplo de función que imprime los datos de un vector en la consola.
Ejemplo:#include <stdio.h>
void imprimir_datos(int n, float v[n])
{
int i;
printf("v = [");
for(i = 0; i < n; i++)
{
if(i > 0)
{
printf(", ");
}
printf("%.2f", v[i]);
}
printf("]");
}
void main()
{
float v[4] = {4.56, 2.32, 1.25, 6.35};
imprimir_datos(4, v);
}
Resultado:
v = [4.56, 2.32, 1.25, 6.35]
Podemos cargar los datos de un vector de un fichero. Podemos contemplar dos escenarios: cuando conocemos a priori cuantos datos hay en el fichero o cuando es necesario leer todos los datos del fichero sin saber cuantos datos contiene.
Veamos un ejemplo en el cúal existe un fichero que contiene, en su primera línea, un número entero indicando el número de datos que contiene y en las siguientes líneas, los datos numéricos con decimales, uno en cada línea. El código para cargar esos datos en un vector sería el siguiente:
Fichero datos.txt:
4
1243.52
3221.87
1250.25
8900.75
Ejemplo:
#include <stdio.h>
void main()
{
FILE *f;
int n, i;
f = fopen("datos.txt", "r");
fscanf(f, "%i", &n);
float v[n];
for(i = 0; i < n; i++)
{
fscanf(f, "%f", &v[i]);
}
fclose(f);
}
En la línea 8 se lee el primer dato del fichero que es el número de datos numéricos que contiene. En la línea 9 se declara un vector con ese tamaño y mediante una sentencia for se va iterando con la variable i para generar los índices de los elementos del vector que vamos leyendo del fichero en la línea 12.
Si deseamos cargar todos los datos de un fichero en un vector y no conocemos cuantos datos contiene podemos utilizar dos estrategias:
Hacer un primer recorrido del fichero para contar cuantos datos contiene, declarar el vector de ese tamaño y dar una segunda pasada para cargar los datos.
Declarar un vector lo suficientemente grande para almacenar el posible máximo volumen de datos presentes en el fichero y recorrer el fichero hasta el final cargando los datos y contando cuantos hay (quedará una parte del vector sin utilizar).
A continuación, se desarrolla un ejemplo con la primera estrategia:
Fichero datos.txt:
1243.52
3221.87
1250.25
8900.75
Ejemplo:
#include <stdio.h>
void main()
{
FILE *f;
float d;
int n = 0, i;
f = fopen("datos.txt", "r");
while( fscanf(f, "%f", &d) == 1)
{
n++;
}
fclose(f);
f = fopen("datos.txt", "r");
float v[n];
for(i = 0; i < n; i++)
{
fscanf(f, "%f", &v[i]);
}
fclose(f);
}
La función sizeof devuelve el tamaño de memoria ocupado por una variable en bytes.
Ejemplo:
7
int v1[5] = {3, 8, 18, 23, 33};
float v2[3] = {7.5, 8.2, 7.0};
int s1 = sizeof(v1[0]);
int s2 = sizeof(v1);
int s3 = sizeof(v2[0]);
int s4 = sizeof(v2);
printf("%i %i %i %i", s1, s2, s3, s4);
Resultado:
4 20 4 12
En el ejemplo almacenamos en s1 el tamaño del primer elemento del vector v1 que es de tipo entero. Un entero ocupa 4 bytes. En s2 almacenamos el tamaño del vector v1 completo que es 20, dado que es de enteros y tiene tamaño 5. En s3 almacenamos el tamaño de un float que es también 4. Por último en s4 almacenamos el tamaño del vector s4 que es de 12 (3 elementos float multiplicado por el tamaño de un float).
En la librería string.h existen algunas funciones que son útiles para inicializar vectores (memset), para copiar vectores (memcpy) y para comparar vectores entre sí (memcmp). Para utilizarlas hemos de incluir la directiva #include <string.h> en nuestro código.
Esta función inicializa un vector con el valor que deseemos. Cuando declaramos un vector, el contenido de este son valores aleatorios que existen en la memoria del ordenador en el espacio que le es asignado. Es conveniente dar un valor inicial a todos los elementos del vector y una forma de hacerlo es con la función memset.
A la función memset tenemos que pasarle un primer argumento que es el nombre del vector, un segundo argumento que es el valor con el cual queremos rellenarle y un tercer argumento que es el tamaño en bytes que queremos rellenar. Para obtener este tamaño podemos multiplicar el tamaño de un elemento del vector por el número de elementos o bien preguntarle al vector su tamaño directamente.
Ejemplo:
#include <stdio.h>
#include <string.h>
void main()
{
int n = 10;
int v[n];
memset(v, 0, sizeof(v));
for(int i = 0; i < n; i++)
{
printf("%i ", v[i]);
}
}
Resultado:
0 0 0 0 0 0 0 0 0 0
En el ejemplo anterior inicializamos un vector de enteros de tamaño 10 con ceros.
Esta función copia los elementos de un vector en otro vector.
A la función memcpy tenemos que pasarle un primer argumento que es el vector destino de la copia, un segundo argumento que es el vector origen y un tercer argumento que es el tamaño en bytes que queremos copiar. Para obtener este tamaño podemos multiplicar el tamaño de un elemento del vector por el número de elementos o bien preguntarle al vector su tamaño directamente.
Ejemplo:
#include <stdio.h>
#include <string.h>
void main()
{
int v1[] = {3, 8, 18, 23, 33};
int v2[5];
memcpy(v2, v1, sizeof(v1));
int i;
for(i = 0; i < 5; i++)
{
printf("%i ", v2[i]);
}
}
Resultado:
3 8 18 23 33
En el ejemplo anterior copiamos los elementos del vector v1 al vector v2 y finalmente comprobamos que la copia ha funcionado imprimiendolos.
Esta función compara los valores de dos vectores y devuelve 0 si ambos son iguales.
A la función memcmp tenemos que pasarle como argumentos los dos vectores que deseamos comparar y un tercer argumento que es el tamaño en bytes de elementos que queremos comparar. Para obtener este tamaño podemos multiplicar el tamaño de un elemento del vector por el número de elementos o bien preguntarle al vector su tamaño directamente.
Ejemplo:
#include <stdio.h>
#include <string.h>
void main()
{
int v1[] = {3, 8, 18, 23, 33};
int v2[] = {3, 8, 18, 23, 33};
if( memcmp(v1, v2, sizeof(v1)) == 0 )
{
printf("v1 y v2 tienen los mismos datos");
}
}
Resultado:
v1 y v2 tienen los mismos datos
En el ejemplo anterior comparamos los datos de los vectores v1 y v2. Como son iguales memcmp devuelve 0 y muestra el mensaje.