Saltar a contenido

Estructuras de datos

Arrays

Un array es un tipo de dato que nos permite almacenar y gestionar de forma conjunta multiples valores del mismo tipo. Los arrays pueden ser de una dimensión (vectores), de dos dimensiones (matrices) o de multiples dimensiones.

Vectores

Para facilitar el lenguaje nos referiremos a un array de una dimensión como un vector en este texto. En otros contextos podemos encontrar el término lista para referirse también a un array de una dimensión.

Para declarar un vector hay que especificar el tipo de dato que va a contener, el nombre que deseamos asignarle y el tamaño del mismo entre corchetes a continuación del nombre.

1
2
3
4
5
6
int v[2];

v[0] = 10;
v[1] = 20;

printf("%i %i", v[0], v[1]);

En el ejemplo declaramos en la línea 1 un vector llamado v que tiene un tamaño de 2 elementos. Para poder acceder a cada uno de los elementos del vector (para usarlos o para asignarles un valor) es necesario especificar el nombre del vector y la posición del elemento entre corchetes tal y como vemos en las líneas 3 y 4. El indice utilizado para acceder a un elemento empieza a contar en 0 de forma que si tenemos 2 elementos, sus índices serán 0 y 1.

Cuando declaramos un vector, podemos asignarle unos valores iniciales separados por comas entre llaves, en ese caso, es opcional especificar el tamaño del vector:

1
2
3
int v1[5] = {3, 8, 18, 23, 33};
float v2[3] = {7.5, 8.2, 7.0};
int v3[] = {6, 28, 496, 8128};

En el ejemplo anterior asignamos 5 valores enteros en la declaración del vector v1. Asignamos 3 valores en la declaración del vector v2 que en este caso es de tipo float. Y declaramos un vector v3 de tipo entero con cuatro valores iniciales omitiendo el tamaño entre corchetes (lo asume del número de datos que asignamos).

Es necesario asignar el tamaño en la declaración

Es necesario especificar el tamaño del vector en el momento de su declaración, ya sea explicitamente entre corchetes o bien mediante una lista de valores que le asignamos. En ocasiones puede suceder que no conozcamos el número de elementos que vamos a manejar en el momento de la declaración. Siempre cabe la posibilidad de declarar un vector de un tamaño muy grande y utilizar luego solo una parte del mismo. No es lo más recomendable pero es posible hacerlo.

Recorrer un vector

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:

1
2
3
4
5
6
7
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.

Uso en funciones

Pasar vectores completos

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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.

Pasar elementos de un vector

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#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.

Leer e imprimir vector

Dos algoritmos muy habituales son pedir los datos de un vector al usuario por consola e imprimir los datos de un vector por consola.

Pedir datos al usuario

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#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);
}

Imprimir en consola

Se plantea, a continuación, un ejemplo de función que imprime los datos de un vector en la consola.

Ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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]

Leer vector de un fichero

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.

Numero de datos conocido

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#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.

Numero de datos desconocido

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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);
}

Funciones para vectores

Función sizeof

La función sizeof devuelve el tamaño de memoria ocupado por una variable en bytes.

Ejemplo:

1
2
3
4
5
6
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).

Librería string.h

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.

Función memset

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#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.

Función memcpy

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#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.

Función memcmp

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#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.