Translate

24 mar 2013

Programación de listas con punteros.

Hoy voy a crear esta entrada con el objetivo de repasar programación avanzada en C, mediante punteros y direcciones de memoria, más que nada para entender el funcionamiento de todo esto y de cómo se opera con punteros.

Lo primero que hay que hacer es, obviamente, elaborar un nuevo proyecto en C, yo lo he llamado "ListasPunteros" con una única carpeta de código llamada "Main".

Lo primero a incluir serán las cabeceras (librería), por defecto ya me vienen stdio y stdlib, así que nada que añadir o modificar por el momento:

#include <stdio.h>  {Libre    }
#include <stdlib.h> {        ía}

Lo siguiente a realizar es una lista, debo crear primero la estructura de una lista, o elemento (por elemento se entiende que es una lista), la lista en principio solo contendrá un número, puesto que lo me interesa es practicar el uso y manejo de punteros y memoria dinámica, no de modificar los datos de la lista. Y obviamente, para que las listas se enlacen entre sí, en su propia estructura deben tener un puntero que "apunte" a la propia estructura, un puntero interno, éste será el encargado de enlazar listas. Esto queda así:

struct lista {
        int num;
        struct lista *sig;
};

Obviamente, eso de escribir "struct lista" cada vez que declaro un puntero es un coñazo, y algo tedioso, por lo que procederé a crear un tipo de dato que me simplifique trabajo:

typedef struct lista tLista;

O bien:

typedef struct lista {
       int num;
       struct lista *sig;
} tLista;

Lo siguiente que me atreveré a realizar es un puntero que marcará siempre el inicio de la lista, lo declararé en el main (función main) como: tLista *inicioLista=NULL;
He procurado en dejar el puntero inicializado a nulo para que no almacene basura o parásitos. Lo siguiente a llevar a cabo, es crear una lista, así de simple:

tLista* crearLista(int valor)
{
        tLista *p=NULL;
        p=(tLista*)malloc(sizeof(tLista));
        if(valor==NULL) {
                   printf("No ha introducido ningún valor, no se puede crear la lista.");
        else {
                   p->num=valor;
                   p->sig=NULL;
                   return(p);
}

¿Simple, verdad? Pues sí, bastante, la función "crearLista" me creará una sola lista cada vez que la llame, será la encargada de crear, nada más. Devolverá un puntero de tipo "tLista", es decir, una dirección de memoria en la que se aloja la estructura de una lista (un número y un puntero interno), dicha dirección de memoria que devolverá obviamente será la lista creada. La función recibirá como parámetro un número (int valor). Posteriormente declaro otro puntero (el destinado a apuntar la dirección de memoria creada), llamado "p" e igualado a nulo por las razones explicadas arriba con "*inicioLista", ese mismo puntero, p, reservará memoria para una estructura mediante el "malloc", y después la función comprobará si el valor introducido por parámetro será nulo o no, si es nulo (es decir, no tiene ningún valor), se mostrará en pantalla un aviso que dirá lo siguiente: "No ha introducido ningún valor, no se puede crear la lista.". Si no se cumple ese caso y el parámetro sí tiene valor numérico, el puntero "p" asignará al número de la lista reservada previamente (malloc), el valor del parámetro (p->num=valor), y asignará al puntero interno "sig" (siguiente) un valor nulo, ya que prefiero que primero la lista creada quede aislada. Finalmente devolverá la dirección de memoria en la que se aloja el puntero "p", con los datos de la lista.

Lo siguiente a realizar es la concatenación de listas, unir todas las listas que vaya a crear en un futuro, primero lo haré de manera "inicial", es decir, cada lista que cree se unirá a las que ya están creadas para formar así una especie de "fila" de listas unidas. Para eso voy a crear una función que se encargue de hacer dicha tarea, basada obviamente en la función previa "crearLista":


void insercionInicial(tLista **inicioLista, int valor)
{
    tLista *p=NULL;
    if(*inicioLista==NULL) {
p=crearLista(valor);
*inicioLista=p;
    }
    else {
p=crearLista(valor);
p->sig=*inicioLista;
*inicioLista=p;
    }
}

La función "inserciónInicial" simplemente no devolverá ningún valor, recibirá un doble puntero que esté marcando al inicio de la lista (doble puntero es la dirección de la dirección de memoria, por lo que el inicio de la lista quedará modificado cuando la función finalice), y un valor numérico. En primer lugar declararé un puntero a nulo, posteriormente preguntaré si el inicio de la lista apunta a nulo (la lista está vacía), si se da el caso, p apuntará a la dirección de memoria que se ha creado mediante el parámetro numérico y la función de crear, e inicioLista apuntará a ese elemento creado que es apuntado por el puntero local "p", creando así una lista de un solo elemento, este es el caso en el que no haya nada creado. Si no se da, crearé una estructura y p apuntará a su dirección de memoria, y gracias al puntero interno siguiente, apuntará a la dirección de memoria a la que apunta el puntero encargado de marcar el inicio de la lista, por tanto el elemento creado se ha insertado antes (va "detrás") del elemento inicial de la lista, y por último, el puntero que marca el inicio apuntará al nuevo elemento creado, siendo este el nuevo y primer elemento de la lista entera.

Para comprobar que todo ande como es debido, realizaré una función que me imprima en pantalla los elementos de las listas:


void imprimirLista(tLista *inicioLista)
{
    if(inicioLista==NULL) {
printf("La lista está vacía.");
    }
    else {
while(inicioLista!=NULL){
   printf("%d", inicioLista->num);
   if(inicioLista->sig!=NULL) {
printf("-->");
   }
   else {
printf("--//");
   }
   inicioLista=inicioLista->sig;
}
    }
}

Esta función se encargará de imprimirme una lista en pantalla. Recibirá como parámetro el inicio de la lista (el primer elemento), y comprobará si apunta a una dirección de memoria nula (donde no hay nada), o si en contraposición apuntará a una dirección de memoria en la que exista una estructura creada. Si apunta a la nada, dirá que la lista está vacía, si no, creará un bucle que se repetirá mientras la lista no finalice, esto se debe al algoritmo que he ideado en su interior:
Primero imprimirá el número de la estructura a la que apunta el inicio de la lista, si el puntero interno siguiente tiene una dirección de memoria (existe otro elemento, el segundo), mostrará en pantalla "-->" (esto), si no hay más elementos en la lista, mostrará esto "--//". Y por último, el puntero que apunta al inicio de la lista pasará a apuntar la dirección de memoria a la que apunta el puntero interno "siguiente", pasando de esta manera el segundo elemento y volviendo a repetir el bucle.

Otra manera de insertar elementos en la lista, será insertarlos en el final y no en el comienzo, para ello he ideado esta función:


void insercionFinal(tLista **inicioLista, int valor)
{
    tLista *p=NULL, *pAux=NULL;
    if(*inicioLista==NULL) {
p=crearLista(valor);
*inicioLista=p;
    }
    else {
pAux=*inicioLista;
p=crearLista(valor);
while((*inicioLista)->sig!=NULL) {
   *inicioLista=(*inicioLista)->sig;
}
(*inicioLista)->sig=p;
*inicioLista=pAux;
    }
}

La inserción Final (qNombre más chulo(?)). Recibirá como doble puntero el inicio de la lista, aunque realmente no sea estrictamente necesario hacerlo así, pero yo sí lo haré por comodidad, también se le pasará el valor de la nueva estructura a crear e insertar. Declararé dos punteros que apuntan a nulo y luego comprobaré si la lista está vacía o no, si lo está, creará una nueva estructura y esta pasará a ser el primer elemento de la lista, que no está vacía, pues el puntero Auxiliar (pAux), pasará a apuntar la dirección de memoria a la que apunta el inicio de la lista, luego p apuntará a un nuevo elemento creado y generaré un bucle, mientras el puntero interno "siguiente" no apunte a la nada (es decir, debe apuntar a otra dirección de memoria para que se repita el algoritmo), el puntero que marcaba al primer elemento pasará a apuntar al elemento al que apunta su puntero interno "siguiente", logrando con esto "moverse" a través de la fila de elementos hasta alcanzar el último. Una vez lo alcance, ese último elemento apuntará mediante el "siguiente" al elemento creado que hasta ahora estaba aislado, y el puntero destinado al inicio volverá a apuntar al primer elemento de la fila gracias al puntero Auxiliar que estaba allí.

De esta manera insertamos elementos al final de la lista.

Ahora voy a comprobar que todo lo que he hecho va perfectamente, así que haré varias modificaciones en la función "main", quedando esta así:


int main(int argc, char** argv)
{
    int array[]={5, 12, 1, 10, 32, 40}, i;
    const int MAXARRAY=6;
    tLista *inicioLista=NULL;
    for(i=0; i<MAXARRAY; i++) {
insercionInicial(&inicioLista, array[i]);
    }
    imprimirLista(inicioLista);
    printf("\n");
    insercionInicial(&inicioLista, 6);
    printf("\n");
    insercionFinal(&inicioLista, 100);
    imprimirLista(inicioLista);
 
    return (EXIT_SUCCESS);
}

Cuando se supera el susto inicial, uno se da cuenta de que no es para tanto, he creado una variable numérica   que hará de array, almacenando hasta 6 números que he puesto al azar. Luego declaro una constante que valdrá el mismo número de elementos que posee el array numérico, y además tengo el puntero que marca al inicio de la lista. Mediante un "for" llevaré a cabo un bucle hasta seis veces, y en cada pasada llamaré a la función de insertar por el inicio, utilizando el array numérico y el puntero de inicio. Luego llamo a la función de imprimir la lista creada (ha insertado los elementos en el bucle "for" previamente), vuelvo a insertar un número, me ha dado por insertar el 6, y luego llamo a la inserción final para insertar el número 100 por el final.

El resultado, lo que se debería mostrar en pantalla, vendría a ser algo así:



40-->32-->10-->1-->12-->5--//

6-->40-->32-->10-->1-->12-->5-->100--//

Primero muestro la lista de elementos insertados al principio, (array[]={5, 12, 1, 10, 32, 40}), la inserción inicial me insertó primero el 5, luego me insertó antes del cinco el 12, y así hasta el 40, siendo éste último el primer elemento de la lista. Inserto de nuevo al comienzo un seis, y luego al final inserto el número 100.

No hay comentarios: