Array Genérico en C con Memoria Dinámica

COMPARTIR EN REDES SOCIALES

En esta entrada vamos a crear un Array Genérico en C con memoria dinámica que servirá para almacenar cualquier tipo de datos en su interior. Preparaos para una buena dosis de punteros!!!

La clave para poder realizar esta estructura de datos es tratar a los elementos de nuestro array como si fuesen un puntero de tipo void*, es decir, una simple dirección de memoria, y realizar los siguientes casteos en las operaciones:

  • Al introducir un nuevo elemento castear nuestro nuevo elemento a puntero de tipo void*
  • Al obtener el elemento realizar el casteo al tipo de datos correcto para poder acceder a su valor o propiedades.

El código de nuestro Array Genérico en C con Memoria Dinámica

Veamos el código y como siempre después expliquémoslo:

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

#define CAPACIDAD_INICIAL 20
#define TRUE 1
#define FALSE 0

struct ArrayGenerico
{
    void** elementos;
    int numeroElementos;
    int capacidad;
};

struct Persona
{
    int edad;
    char* nombre, * apellidos;
};

struct Empleado {
    int numEmpleado, edad;
    char* nombre, * apellidos;
};

int initializeArray(struct ArrayGenerico* array)
{
    array->elementos = (void**)malloc(CAPACIDAD_INICIAL * sizeof(void*));

    if (array->elementos != NULL)
    {
        array->numeroElementos = 0;
        array->capacidad = CAPACIDAD_INICIAL;
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

void releaseMemory(struct ArrayGenerico* array)
{
    free(array->elementos);
    free(array);
}

void releaseMemoryPersona(struct Persona* persona) {
    free(persona->nombre);
    free(persona->apellidos);
    free(persona);
}

void releaseMemoryEmpleado(struct Empleado* empleado) {
    free(empleado->nombre);
    free(empleado->apellidos);
    free(empleado);
}

struct Persona* reserveMemoryPersona() {
    //Reservamos memoria para el struct
    struct Persona* persona = (struct Persona*)malloc(sizeof(struct Persona));
    if (persona != NULL) {
        //Reservamos memoria para los atributos nombre y apellidos
        persona->nombre = (char*)malloc(30);
        if (persona->nombre != NULL) {
            persona->apellidos = (char*)malloc(50);
            if (persona->apellidos == NULL) {
                return NULL;
            }
        }
        else {
            return NULL;
        }

    }
    else {
        return NULL;
    }

    return persona;
}

struct Empleado* reserveMemoryEmpleado() {
    //Reservamos memoria para el struct
    struct Empleado* empleado = (struct Empleado*)malloc(sizeof(struct Empleado));

    if (empleado != NULL) {
        //Reservamos memoria para los atributos nombre y apellidos
        empleado->nombre = (char*)malloc(30);
        if (empleado->nombre != NULL) {
            empleado->apellidos = (char*)malloc(50);
            if (empleado->apellidos == NULL) {
                return NULL;
            }
        }
        else {
            return  NULL;
        }

    }
    else {
        return NULL;
    }


    return empleado;
}

int removeItem(struct ArrayGenerico* array, int position)
{

    if (array->numeroElementos > 1)
    {
        if (position == array->numeroElementos - 1)
        {
            //Si se trata de la última posición
            array->elementos[position] = NULL;
        }
        else
        {
            //Si no se trata de la última posición, lo intercambiamos por el último y borramos el último
            array->elementos[position] = array->elementos[array->numeroElementos - 1];
            array->elementos[array->numeroElementos - 1] = NULL;
        }

        array->numeroElementos--;
    }
    else
    {
        if (array->numeroElementos == 1)
        {
            // Si solo tenemos un elemento
            array->elementos[0] = NULL;
            array->numeroElementos--;
        }
        else
        {
            return FALSE;
        }
    }

    return TRUE;
}

int addElement(struct ArrayGenerico* array, void* elemento)
{
    if (array->numeroElementos == array->capacidad)
    {
        // Duplicamos la memoria
        void** elementos = (void**)malloc((array->capacidad * 2) * sizeof(void*));

        if (elementos == NULL)
        {
            // No hemos podido reservar memoria
            return FALSE;
        }

        int i;
        // Copiamos los elementos en el nuevo array
        for (i = 0; i < array->numeroElementos; i++)
        {
            elementos[i] = array->elementos[i];
        }
        // Liberamos el antiguo array
        free(array->elementos);
        // Asignamos el nuevo array al struct
        array->elementos = elementos;
        // Incrementamos la capacidad, duplicandola
        array->capacidad = array->capacidad * 2;
    }

    // Insertamoos el elemento
    array->elementos[array->numeroElementos] = elemento;
    array->numeroElementos++;

    return TRUE;
}


int main(void)
{
    struct ArrayGenerico* array = (struct ArrayGenerico*)malloc(sizeof(struct ArrayGenerico));

    if (array != NULL) {
        //Hemos podido reservar memoria
        if (initializeArray(array) == TRUE) {
            //Ya podemos añadir elementos
            //Reservamos memoria para una persona
            struct Persona* persona1 = reserveMemoryPersona();
            persona1->edad = 32;
            strncpy_s(persona1->nombre, 30, "Ivan\0", strlen("Ivan") + 1);
            strncpy_s(persona1->apellidos,50, "Ramon Moran\0", strlen("Ramon Moran") + 1);

            //Añadimos el elemento
            addElement(array, (void*)persona1);

            //Reservamos memoria para un empleado
            struct Empleado* empleado1 = reserveMemoryEmpleado();
            empleado1->edad = 44;
            empleado1->numEmpleado = 1;
            strncpy_s(empleado1->nombre,30, "Pepe\0", strlen("Pepe") + 1);
            strncpy_s(empleado1->apellidos,50, "Fernandez Martinez\0", strlen("Fernandez Martinez") + 1);

            //Añadimos el elemento
            addElement(array, (void*)empleado1);

            //Vamos ahora a añadir un numero simplemente
            int* numero = (int*)calloc(1, sizeof(int));
            *numero = 12;
            addElement(array, (void*)numero);

            //Añadamos ahora un double
            double* numero2 = (double*)malloc(sizeof(double));
            *numero2 = 5.4;
            addElement(array, (void*)numero2);


            //Obtenemos el numero de elementos de nuestro vector
            printf("Hay %d elementos\n", array->numeroElementos);
            //Obtenemos la capacidad de nuestro vector
            printf("La capacidad del array es de: %d\n", array->capacidad);

            //Obtenemos los elementos e imprimimos sus propiedades a través del Array
            printf("Nombre: %s\n", ((struct Persona*)array->elementos[0])->nombre);
            printf("Apellidos: %s\n", ((struct Persona*)array->elementos[0])->apellidos);
            printf("Edad: %d\n", ((struct Persona*)array->elementos[0])->edad);

            printf("Nombre: %s\n", ((struct Empleado*)array->elementos[1])->nombre);
            printf("Apellidos: %s\n", ((struct Empleado*)array->elementos[1])->apellidos);
            printf("Edad: %d\n", ((struct Empleado*)array->elementos[1])->edad);
            printf("Numero Empleado: %d\n", ((struct Empleado*)array->elementos[1])->numEmpleado);

            printf("Un simple numero: %d\n", *((int *)(array->elementos[2])));
            printf("Un simple numero: %lf\n", *((double *)(array->elementos[3])));

            //Borramos el primer elemento
            removeItem(array, 0);
            //Borramos el último elemento
            removeItem(array, 2);

            //Obtenemos los elementos con los nuevos indices
            printf("Nombre: %s\n", ((struct Empleado*)array->elementos[1])->nombre);
            printf("Apellidos: %s\n", ((struct Empleado*)array->elementos[1])->apellidos);
            printf("Edad: %d\n", ((struct Empleado*)array->elementos[1])->edad);
            printf("Numero Empleado: %d\n", ((struct Empleado*)array->elementos[1])->numEmpleado);
            //Obtenemos el número entero
            printf("Un simple numero: %lf\n", *((double*)(array->elementos[0])));

            //Obtenemos el numero de elementos de nuestro vector
            printf("Hay %d elementos\n", array->numeroElementos);
            //Obtenemos la capacidad de nuestro vector
            printf("La capacidad del array es de: %d\n", array->capacidad);
           
            //Liberamos la memoria
            releaseMemoryPersona(persona1);
            releaseMemoryEmpleado(empleado1);
            free(numero);
            free(numero2);
            releaseMemory(array);
        }
        else {
            printf("No se ha podido inicializar el ARRAY. Quizás no hay memoria disponible\n");
        }
    }
    else {
        //No hemos podido reservar memoria
        printf("No se ha podido asignar memoria para el array. Quizás no hay memoria disponible\n");
    }
}

La explicación del código

El struct que define nuestro Array Genérico en C

struct ArrayGenerico
{
    void** elementos;
    int numeroElementos;
    int capacidad;
};

Tenemos 3 variables:

  1. «void** elementos»: Se trata del array que almacenará nuestros elementos. Fijaos que es un puntero de tipo void, le ponemos doble puntero para especificar que se trata de un array de punteros void.
  2. «numeroElementos»: contendrá el número de elementos que tiene nuestro Array Genérico.
  3. «capacidad»: Contendrá la capacidad actual de nuestro Array. Nuestro Array se redimensiona automáticamente cuando se llega al máximo de la capacidad, siempre siguiendo la siguiente formula: nuevaCapacidad = capacidadActual * 2.

¿ Cómo se inicializa nuestro Array Genérico ?

int initializeArray(struct ArrayGenerico* array)
{
    array->elementos = (void**)malloc(CAPACIDAD_INICIAL * sizeof(void*));

    if (array->elementos != NULL)
    {
        array->numeroElementos = 0;
        array->capacidad = CAPACIDAD_INICIAL;
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

Esta función recibe por parámetro de entrada un puntero de tipo «struct ArrayGenerico*), sobre este reserva memoria para almacenar los punteros de tipo «void*» y sí ha podido reservar memoria inicializa el número de elementos a 0 y la capacidad a la constante CAPACIDAD_INICIAL, que es igual a 20.

Con esto ya tenemos memoria para poder añadir elementos.

¿ Cómo se añaden elementos ?

Añadimos elementos con la siguiente función:

int addElement(struct ArrayGenerico* array, void* elemento)
{
    if (array->numeroElementos == array->capacidad)
    {
        // Duplicamos la memoria
        void** elementos = (void**)malloc((array->capacidad * 2) * sizeof(void*));

        if (elementos == NULL)
        {
            // No hemos podido reservar memoria
            return FALSE;
        }

        int i;
        // Copiamos los elementos en el nuevo array
        for (i = 0; i < array->numeroElementos; i++)
        {
            elementos[i] = array->elementos[i];
        }
        // Liberamos el antiguo array
        free(array->elementos);
        // Asignamos el nuevo array al struct
        array->elementos = elementos;
        // Incrementamos la capacidad, duplicandola
        array->capacidad = array->capacidad * 2;
    }

    // Insertamos el elemento
    array->elementos[array->numeroElementos] = elemento;
    array->numeroElementos++;

    return TRUE;
}

Básicamente, si hemos llegado al máximo número de elementos que podemos albergar, reservamos memoria para almacenar el doble de elementos y copiamos todos los punteros sobre la nueva asignación de memoria. Dupliquemos o no la memoria al final siempre asignamos igual el elemento:

// Insertamos el elemento
array->elementos[array->numeroElementos] = elemento;
array->numeroElementos++;

Fijaos que por parámetro de entrada a la función nos llega tanto el Array como el elemento que queremos insertar.

¿ Cómo eliminamos elementos de nuestro Array Genérico ?

Para borrar elementos utilizamos la siguiente función:

int removeItem(struct ArrayGenerico* array, int position)
{

    if (array->numeroElementos > 1)
    {
        if (position == array->numeroElementos - 1)
        {
            //Si se trata de la última posición
            array->elementos[position] = NULL;
        }
        else
        {
            //Si no se trata de la última posición, lo intercambiamos por el último y borramos el último
            array->elementos[position] = array->elementos[array->numeroElementos - 1];
            array->elementos[array->numeroElementos - 1] = NULL;
        }

        array->numeroElementos--;
    }
    else
    {
        if (array->numeroElementos == 1)
        {
            // Si solo tenemos un elemento
            array->elementos[0] = NULL;
            array->numeroElementos--;
        }
        else
        {
            return FALSE;
        }
    }

    return TRUE;
}

Esta función recibe por parámetros de entrada el Array y la posición del elemento que queremos borrar, se pueden cumplir los siguientes casos de uso:

  1. Que el número de elementos sea mayor que 1:
    1. Que el elemento que queremos borrar sea el último. Ponemos el valor del último puntero ocupado a NULL.
    2. Que no sea el último, trasladamos el valor del último puntero del array a la posición que queremos borrar y asignamos NULL a el último puntero del Array.
  2. Qué solo haya un elemento, por lo tanto, borramos este elemento asignando su puntero a NULL. Si solo hay un elemento este estará en la posición 0.

¿ Cómo liberamos la memoria ?

Para liberar la memoria utilizamos la siguiente función:

void releaseMemory(struct ArrayGenerico* array)
{
    free(array->elementos);
    free(array);
}

Simplemente eliminamos la memoria asignada a nuestros elementos y al propio struct. Es importante destacar que será responsabilidad del programador liberar la memoria de las direcciones de memoria a la que apunten nuestros punteros, es decir, si creamos un struct Persona con punteros char *, tendremos que liberar la memoria nosotros porque no depende de nuestro Array Genérico.

Los structs que hemos creado y funciones auxiliares para probar nuestra estructura de datos.

Para poder probar nuestra estructura de datos hemos creado diferentes structs:

struct Persona
{
    int edad;
    char* nombre, * apellidos;
};
struct Empleado {
    int numEmpleado, edad;
    char* nombre, * apellidos;
};

Y las funciones auxiliares de reserva y liberación de memoria para cada struct:

struct Persona* reserveMemoryPersona() {
    //Reservamos memoria para el struct
    struct Persona* persona = (struct Persona*)malloc(sizeof(struct Persona));
    if (persona != NULL) {
        //Reservamos memoria para los atributos nombre y apellidos
        persona->nombre = (char*)malloc(30);
        if (persona->nombre != NULL) {
            persona->apellidos = (char*)malloc(50);
            if (persona->apellidos == NULL) {
                return NULL;
            }
        }
        else {
            return NULL;
        }

    }
    else {
        return NULL;
    }

    return persona;
}
void releaseMemoryPersona(struct Persona* persona) {
    free(persona->nombre);
    free(persona->apellidos);
    free(persona);
}
struct Empleado* reserveMemoryEmpleado() {
    //Reservamos memoria para el struct
    struct Empleado* empleado = (struct Empleado*)malloc(sizeof(struct Empleado));

    if (empleado != NULL) {
        //Reservamos memoria para los atributos nombre y apellidos
        empleado->nombre = (char*)malloc(30);
        if (empleado->nombre != NULL) {
            empleado->apellidos = (char*)malloc(50);
            if (empleado->apellidos == NULL) {
                return NULL;
            }
        }
        else {
            return  NULL;
        }

    }
    else {
        return NULL;
    }


    return empleado;
}
void releaseMemoryEmpleado(struct Empleado* empleado) {
    free(empleado->nombre);
    free(empleado->apellidos);
    free(empleado);
}

¿ Cómo se utiliza nuestro Array Genérico con memoria dinámica en C ?

Veamos el código para luego explicarlo:

struct ArrayGenerico* array = (struct ArrayGenerico*)malloc(sizeof(struct ArrayGenerico));

    if (array != NULL) {
        //Hemos podido reservar memoria
        if (initializeArray(array) == TRUE) {
            //Ya podemos añadir elementos
            //Reservamos memoria para una persona
            struct Persona* persona1 = reserveMemoryPersona();
            persona1->edad = 32;
            strncpy_s(persona1->nombre, 30, "Ivan\0", strlen("Ivan") + 1);
            strncpy_s(persona1->apellidos,50, "Ramon Moran\0", strlen("Ramon Moran") + 1);

            //Añadimos el elemento
            addElement(array, (void*)persona1);

            //Reservamos memoria para un empleado
            struct Empleado* empleado1 = reserveMemoryEmpleado();
            empleado1->edad = 44;
            empleado1->numEmpleado = 1;
            strncpy_s(empleado1->nombre,30, "Pepe\0", strlen("Pepe") + 1);
            strncpy_s(empleado1->apellidos,50, "Fernandez Martinez\0", strlen("Fernandez Martinez") + 1);

            //Añadimos el elemento
            addElement(array, (void*)empleado1);

            //Vamos ahora a añadir un numero simplemente
            int* numero = (int*)calloc(1, sizeof(int));
            *numero = 12;
            addElement(array, (void*)numero);

            //Añadamos ahora un double
            double* numero2 = (double*)malloc(sizeof(double));
            *numero2 = 5.4;
            addElement(array, (void*)numero2);


            //Obtenemos el numero de elementos de nuestro vector
            printf("Hay %d elementos\n", array->numeroElementos);
            //Obtenemos la capacidad de nuestro vector
            printf("La capacidad del array es de: %d\n", array->capacidad);

            //Obtenemos los elementos e imprimimos sus propiedades a través del Array
            printf("Nombre: %s\n", ((struct Persona*)array->elementos[0])->nombre);
            printf("Apellidos: %s\n", ((struct Persona*)array->elementos[0])->apellidos);
            printf("Edad: %d\n", ((struct Persona*)array->elementos[0])->edad);

            printf("Nombre: %s\n", ((struct Empleado*)array->elementos[1])->nombre);
            printf("Apellidos: %s\n", ((struct Empleado*)array->elementos[1])->apellidos);
            printf("Edad: %d\n", ((struct Empleado*)array->elementos[1])->edad);
            printf("Numero Empleado: %d\n", ((struct Empleado*)array->elementos[1])->numEmpleado);

            printf("Un simple numero: %d\n", *((int *)(array->elementos[2])));
            printf("Un simple numero: %lf\n", *((double *)(array->elementos[3])));

            //Borramos el primer elemento
            removeItem(array, 0);
            //Borramos el último elemento
            removeItem(array, 2);

            //Obtenemos los elementos con los nuevos indices
            printf("Nombre: %s\n", ((struct Empleado*)array->elementos[1])->nombre);
            printf("Apellidos: %s\n", ((struct Empleado*)array->elementos[1])->apellidos);
            printf("Edad: %d\n", ((struct Empleado*)array->elementos[1])->edad);
            printf("Numero Empleado: %d\n", ((struct Empleado*)array->elementos[1])->numEmpleado);
            //Obtenemos el número entero
            printf("Un simple numero: %lf\n", *((double*)(array->elementos[0])));

            //Obtenemos el numero de elementos de nuestro vector
            printf("Hay %d elementos\n", array->numeroElementos);
            //Obtenemos la capacidad de nuestro vector
            printf("La capacidad del array es de: %d\n", array->capacidad);
           
            //Liberamos la memoria
            releaseMemoryPersona(persona1);
            releaseMemoryEmpleado(empleado1);
            free(numero);
            free(numero2);
            releaseMemory(array);
        }
        else {
            printf("No se ha podido inicializar el ARRAY. Quizás no hay memoria disponible\n");
        }
    }
    else {
        //No hemos podido reservar memoria
        printf("No se ha podido asignar memoria para el array. Quizás no hay memoria disponible\n");
    }

Lo primero que realizamos el reservar memoria para nuestro struct ArrayGenérico:

struct ArrayGenerico* array = (struct ArrayGenerico*)malloc(sizeof(struct ArrayGenerico));

Si hemos podido reservar memoria, inicializamos el Array:

if (array != NULL) {
        //Hemos podido reservar memoria
        if (initializeArray(array) == TRUE) {
            //Ya podemos añadir elementos

Ahora ya podemos añadir elementos, por lo que creamos diferentes elementos y los añadimos:

 //Reservamos memoria para una persona
struct Persona* persona1 = reserveMemoryPersona();
persona1->edad = 32;
strncpy_s(persona1->nombre, 30, "Ivan\0", strlen("Ivan") + 1);
strncpy_s(persona1->apellidos,50, "Ramon Moran\0", strlen("Ramon Moran") + 1);

//Añadimos el elemento
addElement(array, (void*)persona1);

//Reservamos memoria para un empleado
struct Empleado* empleado1 = reserveMemoryEmpleado();
empleado1->edad = 44;
empleado1->numEmpleado = 1;
strncpy_s(empleado1->nombre,30, "Pepe\0", strlen("Pepe") + 1);
strncpy_s(empleado1->apellidos,50, "Fernandez Martinez\0", strlen("Fernandez Martinez") + 1);

//Añadimos el elemento
addElement(array, (void*)empleado1);

//Vamos ahora a añadir un numero simplemente
int* numero = (int*)calloc(1, sizeof(int));
*numero = 12;
addElement(array, (void*)numero);

//Añadamos ahora un double
double* numero2 = (double*)malloc(sizeof(double));
*numero2 = 5.4;
addElement(array, (void*)numero2);

Por último solo nos queda mostrar los elementos:

//Obtenemos el numero de elementos de nuestro vector
printf("Hay %d elementos\n", array->numeroElementos);
//Obtenemos la capacidad de nuestro vector
printf("La capacidad del array es de: %d\n", array->capacidad);

//Obtenemos los elementos e imprimimos sus propiedades a través del Array
printf("Nombre: %s\n", ((struct Persona*)array->elementos[0])->nombre);
printf("Apellidos: %s\n", ((struct Persona*)array->elementos[0])->apellidos);
printf("Edad: %d\n", ((struct Persona*)array->elementos[0])->edad);

printf("Nombre: %s\n", ((struct Empleado*)array->elementos[1])->nombre);
printf("Apellidos: %s\n", ((struct Empleado*)array->elementos[1])->apellidos);
printf("Edad: %d\n", ((struct Empleado*)array->elementos[1])->edad);
printf("Numero Empleado: %d\n", ((struct Empleado*)array->elementos[1])->numEmpleado);

printf("Un simple numero: %d\n", *((int *)(array->elementos[2])));
printf("Un simple numero: %lf\n", *((double *)(array->elementos[3])));

Probamos a borrar algunos elementos y mostrar aquellos que quedan:

//Borramos el primer elemento
removeItem(array, 0);
//Borramos el último elemento
removeItem(array, 2);

//Obtenemos los elementos con los nuevos indices
printf("Nombre: %s\n", ((struct Empleado*)array->elementos[1])->nombre);
printf("Apellidos: %s\n", ((struct Empleado*)array->elementos[1])->apellidos);
printf("Edad: %d\n", ((struct Empleado*)array->elementos[1])->edad);
printf("Numero Empleado: %d\n", ((struct Empleado*)array->elementos[1])->numEmpleado);
//Obtenemos el número entero
printf("Un simple numero: %lf\n", *((double*)(array->elementos[0])));

//Obtenemos el numero de elementos de nuestro vector
printf("Hay %d elementos\n", array->numeroElementos);
//Obtenemos la capacidad de nuestro vector
printf("La capacidad del array es de: %d\n", array->capacidad);

Finalmente solo nos queda liberar toda la memoria que hemos utilizado:

//Liberamos la memoria
releaseMemoryPersona(persona1);
releaseMemoryEmpleado(empleado1);
free(numero);
free(numero2);
releaseMemory(array);

La salida de nuestro programa

Hay 4 elementos
La capacidad del array es de: 20
Nombre: Ivan
Apellidos: Ramon Moran
Edad: 32
Nombre: Pepe
Apellidos: Fernandez Martinez
Edad: 44
Numero Empleado: 1
Un simple numero: 12
Un simple numero: 5.400000
Nombre: Pepe
Apellidos: Fernandez Martinez
Edad: 44
Numero Empleado: 1
Un simple numero: 5.400000
Hay 2 elementos
La capacidad del array es de: 20

Y esto ha sido nuestra entrada sobre como crear un Array Genérico en C con memoria dinámica, ya os había avisado de que iba a haber sobredosis de punteros, jajaja.

Espero que hayáis disfrutado la entrada y si así ha sido compartidla en redes sociales, suscribiros al blog para que os lleguen notificaciones sobre nuevas entradas (es gratis) y dejad algún comentario.

Podéis revisar nuestra entrada sobre punteros en C aquí.

Un saludo programadores


COMPARTIR EN REDES SOCIALES

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *