La representación de una pila, como una secuencia de nodos, se muestra
en la siguiente figura:
Abstracción de una pila como una secuencia de nodos. |
Los detalles de su implementación se
desarrollarán a continuación en dos secciones: Pila primitiva y Pila genérica.
Pila primitiva.
Esta sección describe la implementación de una pila primitiva. Se ha
denominado de esta forma debido a que implementa una estructura de datos
que almacena un tipo de dato primitivo: int.
La definición de la clase fundamental para la implementación de la estructura de datos se muestra en la clase Nodo_p del Ejemplo pila_primitiva. Los detalles de este tipo de implementación de clases autorreferidas se han discutido con anterioridad en la entrada Abstracción de estructuras de datos
y no se repetirán aquí, por lo que se sugiere al lector que la revise
nuevamente y se tome el tiempo necesario para comparar la clase Nodo_p y su respectiva definición con el Ejemplo Nodo antes de continuar.
La implementación de la estructura de datos pila se muestra en la clase pila_primitiva (líneas 36-48 del archivo pila_primitiva.h), y su explicación se centrará únicamente en la definición de los métodos push, pop, esta_vacia e imprime del archivo pila_primitiva.cpp.
El método esta_vacia (líneas 76-78) realiza una verificación bastante simple: si el tope de la pila es igual a nullptr, regresa verdadero (está vacía), si no, regresa falso (existe al menos un elemento).
Por otra parte, el método push (líneas 55-60), recibe como argumento el elemento a insertar en la pila (elemento), y se apoya del método esta_vacia y de los constructores para realizar la inserción de la siguiente manera:
- Si la pila está vacía (línea 56), se crea un nuevo nodo con el elemento correspondiente (línea 57) para el atributo dato, y nullptr en su atributo siguiente (vea la clase Nodo_p).
- Si la pila no está vacía (línea 58), se crea un nuevo nodo con el elemento correspondiente para el atributo dato y con el tope actual de la pila en su atributo siguiente (línea 59). Así mismo, note que el tope es actualizado para hacer referencia al nodo recién creado, dando el efecto de corrimiento a la derecho o hundimiento del tope anterior.
La excepción excepcion_ED_vacia está definida en la línea 41 del archivo pila_primitiva.h. Esta excepción es la que será utilizada en todas las estructuras de datos del blog que se implementan en C++.
Por último, en el método imprime (líneas 81-94), si la estructura de datos está vacía (línea 82), se reporta (línea 83); en caso contrario, se realiza un recorrido por todos los nodos de la estructura para imprimir su contenido (líneas 84-93).
El programa principal para la pila primitiva se presenta en el archivo main.cpp del Ejemplo pila_primitiva. Observe cómo en la línea 19 se crea la pila y se utiliza el constructor por omisión (constructor sin argumentos).
Las líneas 22-25 realizan la inserción en la pila de los números del cero al nueve; por cada inserción se imprime todo el contenido de la pila tal y como se muestra a continuación:
La Pila de enteros primitivos es: 0
La Pila de enteros primitivos es: 1 0
La Pila de enteros primitivos es: 2 1 0
La Pila de enteros primitivos es: 3 2 1 0
La Pila de enteros primitivos es: 4 3 2 1 0
La Pila de enteros primitivos es: 5 4 3 2 1 0
La Pila de enteros primitivos es: 6 5 4 3 2 1 0
La Pila de enteros primitivos es: 7 6 5 4 3 2 1 0
La Pila de enteros primitivos es: 8 7 6 5 4 3 2 1 0
La Pila de enteros primitivos es: 9 8 7 6 5 4 3 2 1 0
Por otro lado, las líneas 28-36 realizan la eliminación de los
elementos de la pila (uno a uno). Dicho fragmento de código intenta eliminar once
elementos (líneas 30-35) de la pila (recuerde que sólo fueron insertados
diez elementos, por lo que al intentar eliminar el décimo primero, se
generará la excepción excepcion_ED_vacia), y dado que el método pop puede lanzar una excepción, el código involucrado en la eliminación debería estar dentro de una cláusula try-catch, la cual permite atrapar (cachar) las excepciones que un método pudiera lanzar y, si así se desea, realizar la gestión que más convenga.
Si se genera un excepción excepcion_ED_vacia, ésta es atrapada y el flujo de control se envía al ámbito de la cláusula catch en
donde se realiza el correspondiente tratamiento de la excepción (líneas 36-38).
La salida correspondiente a la eliminación de elementos de la pila primitiva, se muestra a continuación:
Elemento eliminado de la pila: 9
La Pila de enteros primitivos es: 8 7 6 5 4 3 2 1 0
Elemento eliminado de la pila: 8
La Pila de enteros primitivos es: 7 6 5 4 3 2 1 0
Elemento eliminado de la pila: 7
La Pila de enteros primitivos es: 6 5 4 3 2 1 0
Elemento eliminado de la pila: 6
La Pila de enteros primitivos es: 5 4 3 2 1 0
Elemento eliminado de la pila: 5
La Pila de enteros primitivos es: 4 3 2 1 0
Elemento eliminado de la pila: 4
La Pila de enteros primitivos es: 3 2 1 0
Elemento eliminado de la pila: 3
La Pila de enteros primitivos es: 2 1 0
Elemento eliminado de la pila: 2
La Pila de enteros primitivos es: 1 0
Elemento eliminado de la pila: 1
La Pila de enteros primitivos es: 0
Elemento eliminado de la pila: 0
Vacia: Pila de enteros primitivos
terminate called after throwing an instance of 'pila::pila_primitiva::excepcion_ED_vacia'
Abortado
- En el ejemplo pila_primitiva se utiliza una clase únicamente como tipo de dato para lanzar una excepción.
- La cláusula throw acepta un parámetro y en principio puede ser cualquiera de los tipos de C++, incluyendo primitivos como int, por lo que en este sentido, es posible lanzar como excepciones enteros y objetos de cualquier tipo, y las clases definen objetos.
- La excepción lanzada empatará con el manejador de excepción (expresiones catch) que coincida con el tipo correspondiente de la excepción.
- Se pueden utilizar puntos suspensivos (...) como parámetro de un catch para atrapar cualquier tipo de excepción sin importar el tipo; es decir, funciona como un comodín de excepciones.
- En el ejemplo pila_primitiva2 se ha utilizado una excepción estándar (exception) y se ha derivado una clase de ella (excepcion_ED_vacia). exception es una clase base específicamente diseñada para declarar objetos que puedan ser lanzados como excepción. Dicha clase tiene una función miembro virtual llamada what que regresa una secuencia de caracteres. Esta función puede ser sobre escrita (override) en clases derivadas (eso significar ser virtual) para proporcionar algún tipo de descripción o detalle de la excepción.
- El manejador de esta excepción (línea 36 del archivo main.cpp) atrapa objetos de excepción por referencia (note el ampersand (&) después del tipo), por lo que atrapará también clases derivadas de exception (como excepcion_ED_vacia).
Como primer paso, es preciso que el lector se tome el tiempo que considere necesario para comparar la clase Nodo_p descrita en la sección anterior, con la clase Nodo (líneas 17-29) del Ejemplo pila_genérica. Es preciso notar que, aunque distintos, ambos ejemplos son esencialmente equivalentes.
Observe cómo ahora los constructores y los métodos de tipo set reciben como argumentos objetos genéricos T y referencias a objetos que a su vez almacenan objetos genéricos (líneas 23-26); mientras que los métodos de tipo get regresan genéricos (línea 27), o referencias a objetos que almacenan objetos genéricos (línea 28).
Ahora bien, las consideraciones hechas para la clase Nodo respecto a los genéricos, son las mismas que se deben tomar en cuenta para la clase pila_generica (líneas 33-44), por lo que una vez más se pide encarecidamente al lector que compare esta clase con la clase del Ejemplo pila_primitiva discutido en la sección anterior, tomando en consideración lo explicado hasta este momento para los genéricos.
Observe que en ninguna parte de la clase pila_generica se hace explícita una clase o tipo específico para el genérico T, por lo que también aquí se hace una gestión de genéricos con base en una plantilla en directa y estrecha relación con las plantillas y los genéricos explicados para la clase Nodo.
Finalmente, la función main (líneas 164-190) muestra la prueba para la pila genérica. La línea más importante es la línea 166, ya que es en ella en donde finalmente se define la clase de objetos que contendrá (almacenará) la pila: string. En este ejemplo se ha sustituido "\n" por endl porque en general es más conveniente y el resultado es el mismo (líneas 174, 182 y 186) y se deja como ejercicio para el lector probar el mismo ejemplo con enteros primitivos int.
La Pila genérica es: 0
La Pila genérica es: 1 0
La Pila genérica es: 2 1 0
La Pila genérica es: 3 2 1 0
La Pila genérica es: 4 3 2 1 0
La Pila genérica es: 5 4 3 2 1 0
La Pila genérica es: 6 5 4 3 2 1 0
La Pila genérica es: 7 6 5 4 3 2 1 0
La Pila genérica es: 8 7 6 5 4 3 2 1 0
La Pila genérica es: 9 8 7 6 5 4 3 2 1 0
La Pila genérica es: 8 7 6 5 4 3 2 1 0
Elemento eliminado de la pila: 8
La Pila genérica es: 7 6 5 4 3 2 1 0
Elemento eliminado de la pila: 7
La Pila genérica es: 6 5 4 3 2 1 0
Elemento eliminado de la pila: 6
La Pila genérica es: 5 4 3 2 1 0
Elemento eliminado de la pila: 5
La Pila genérica es: 4 3 2 1 0
Elemento eliminado de la pila: 4
La Pila genérica es: 3 2 1 0
Elemento eliminado de la pila: 3
La Pila genérica es: 2 1 0
Elemento eliminado de la pila: 2
La Pila genérica es: 1 0
Elemento eliminado de la pila: 1
La Pila genérica es: 0
Elemento eliminado de la pila: 0
Vacia: Pila genérica
terminate called after throwing an instance of 'excepcion_ED_vacia'
what(): Estructura de datos vacía
Abortado
Ahora bien, el Ejemplo pila_generica2 muestra una característica adicional importante, que es preciso tomar en cuenta, y que se utilizará también en implementaciones posteriores de estructuras de datos: el uso de los destructores. Un constructor será llamado siempre que se cree un objeto de una clase, y habitualmente considera también la inicialización adecuada del estado del objeto. Por otro lado, si se necesita realizar alguna acción de limpieza cuando un objeto de una clase sale fuera de su ámbito (y en consecuencia se destruirá o eliminará), se puede (y en general se debe) declarar un complemento del constructor denominado destructor (líneas 40 y 109-120) que se encargue de esa labor. Note que para este ejemplo se han insertado un cierto número de elementos en la pila (líneas 183-186) pero no han sido explícitamente eliminados de la estructura de datos, por lo que el destructor se encarga de hacerlo. Asegúrese de entender esto antes de continuar.
No hay comentarios.:
Publicar un comentario