ESTRUCTURA DE DATOS I UFT
HECHO POR: MAYRA RODRIGUEZ Y MARIA FERNANDA MANRIQUE
Esta revista esta creada para todo aquel interesado en saber algo de programación principalmente esta basada en la memoria dinámica y en los punteros, su utilización, los componentes necesarios para crearlos y algunos ejemplos de punteros y de la memoria dinámica. La memoria dinámica, más que un tipo de memoria es una manera de utilizarla. Consiste en declarar variables y eliminarlas cuando ya no sean necesarias, mediante subprogramas. La memoria dinámica es un espacio de almacenamiento que se puede solicitar en tiempo de ejecución.
Hasta ahora, hemos realizado una administración de memoria de tipo estático. Esto significa que los vectores y matrices creados tienen un tamaño fijo que el compilador conoce en tiempo de compilación. Sin embargo, es posible que el tamaño de dichos vectores no se conozca a-priori y por lo tanto necesitemos solicitar memoria al sistema en tiempo de ejecución. La administración de memoria desde este punto de vista recibe el nombre de memoria dinámica. La memoria dinámica, más que un tipo de memoria es una en declarar variables y eliminarlas cuando ya no sean necesarias, mediante subprogramas. La memoria dinámica es un espacio de almacenamiento que se puede solicitar en tiempo de ejecución. Además de solicitar espacios de almacenamiento, también podemos liberarlos (en tiempo de ejecución) cuando dejemos de necesitarlos. Manejo de la memoria dinámica
La memoria dinámica es un espacio de almacenamiento que manera de utilizarla. Consiste se solicita en tiempo de ejecución. De esa manera, a medida que el proceso va necesitando espacio para más líneas, va solicitando más memoria al sistema operativo para guardarlas. El medio para manejar la memoria que otorga el sistema operativo, es el puntero, puesto que no podemos saber en tiempo de compilación dónde nos dará huecos el sistema operativo (en la memoria de nuestro PC).
Además de solicitar espacios de almacenamiento, también podemos liberarlos (en tiempo de ejecución) cuando dejemos de necesitarlos. Para realizar esta administración de la memoria dinámica, C++ cuenta con dos operadores new y delete. Antes de utilizarlos, debemos incluir el encabezado <new>. El operador new reserva memoria dinámica de cualquier tipo, esto es: • tipos primitivos (int, double, etc) • tipos definidos por el usuario (clases o estructuras). Usando instrucciones que nos permitan asignar datos y liberar la memoria en tiempo de ejecución. Esto depende del lenguaje que se utilice. En Pascal, por ejemplo con new creamos un objeto de tipo puntero y con dispose se destruye. El tratamiento de memoria dinámica sigue tres pasos fundamentales: 1) Petición de memoria (función malloc) 2) Utilización de dicha memoria para nuestro propósito
3) Liberación de memoria (función free)
Las variables de tipo puntero son las que nos permiten referenciar datos dinámicos. Un puntero es una dirección de memoria. Para definir una variable puntero de un determinado tipo se sigue una sintaxis parecida a la usada para definir variables normales sólo que al nombre del tipo se le postpone un símbolo de asterisco (*) O sea, un puntero se define así: <tipo> * <nombrePuntero>; Por ejemplo, una variable puntero llamada a que pueda almacenar referencias a posiciones de memoria donde se almacenen objetos de tipo int se declara así: int * a; En caso de quererse declarar una tabla de punteros, entonces el asterisco hay que incluirlo tras el nombre del tipo pero antes de los corchetes. Por ejemplo, una tabla de nombre t que pueda almacenar punteros a objetos de tipo int se declara así: int*[] t; Hay un tipo especial de puntero que es capaz de almacenar referencias a objetos de cualquier tipo. Éstos punteros se declaran indicando void como <tipo>. Por ejemplo: void * punteroACualquierCosa; Hay que tener en cuenta que en realidad lo que indica el tipo que se dé a un puntero es cuál es el tipo de objetos que se ha de considerar que se almacenan en la dirección de memoria almacenada por el puntero. Si se le da el valor void lo que se está diciendo es que no se desea que se considere que el puntero apunta a ningún tipo específico de objeto. Es decir, no se está dando información sobre el tipo apuntado.
Se pueden declarar múltiples variables
punteros de tipos que se almacenen en
locales de tipo puntero en una misma
memoria dinámica o contengan miembros
línea. En ese caso el asterisco sólo hay que
que se almacenen en memoria dinámica,
incluirlo antes del nombre de la primera.
ya que entonces podría ocurrir que un
Por ejemplo:
objeto sólo referenciado a través de
int * a, b; // a y b son de tipo int * No
punteros sea destruido por considerar el
sería válido haberlas definido como int *a,
recolector que nadie le referenciaba. Por
*b;
ello, sólo es válido definir punteros de Hay que tener en cuenta que esta
tipos cuyos objetos se puedan almacenar
sintaxis especial para definir en una misma
completamente en pila, pues la vida de
definición varios punteros de un mismo
estos objetos no está controlada por el
tipo sólo es válida en definiciones de
recolector de basura sino que se destruyen
variables locales. Al definir campos no
cuando se abandona el ámbito donde
sirve y hay que dar para cada campo una
fueron definidos.
definición independiente.
En concreto, los únicos punteros válidos
El recolector de basura no tiene en
son aquellos que apunten a tipos valor
cuenta los datos a los que se referencie
básicos, enumeraciones o estructuras que
con punteros, pues ha de conocer cuál es
no contengan campos de tipos referencias.
el objeto al referenciado por cada variable
También pueden definirse punteros a tipos
y un puntero en realidad no tiene porqué
puntero, como se muestra en el siguiente
almacenar referencias a objetos de ningún
ejemplo de declaración de un puntero a
tipo en concreto. Por ejemplo, pueden
punteros
tenerse punteros int * que en realidad
punteroApunteros:
apunten a objeto char, o punteros void *
int ** punteroApunteros;
que no almacenen información sobre el
de
tipo
int
llamando
Obviamente la anidación puede hacerse
tipo de objeto al que debería considerarse
a
que apuntan, o punteros que apunte a
pudiéndose definir punteros a punteros a
direcciones donde no hayan objetos, etc.
punteros, o punteros a punteros a
Como el recolector de basura no trabaja con punteros, no es posible definir
cualquier
nivel
de
punteros a punteros, etc.
profundidad,
La función malloc posee la siguiente sintaxis: [direcc_inicio] malloc([tamaño en bytes]);
[ Para especificar la cantidad de memoria que necesitamos se suele utilizar la función sizeof. [tamaño en bytes] sizeof( [tipo definido] );
Combinando estas funciones podremos reservar memoria para vectores, matrices, etc… Para liberar memoria asignada utilizaremos la función free, especificando la dirección de inicio de la misma. Su sintaxis es la siguiente: free( [direcc_inicio] );
A partir de esta instrucción, la memoria que habíamos reservado podrá ser utilizada por otros procesos del sistema. Veamos las siguientes líneas:
int *ptrEntero; ptrEntero = new int; /*(al puntero ptrEntero le asignamos dinamicamente espacio para contener un valor int)*/
int *ptrEnteroA; ptrEnteroA = new int(5); /*(igual que al anterior pero de paso lo inicializamos en 5)*/ También podríamos hacerlo con un arreglo, para esto: int *arreglo = new int[45]; /* (creamos un arreglo dinámico, ésta vez lo hacemos en la misma línea en la que declaramos el puntero) */ Supongamos ahora que poseemos una clase Punto, podríamos crear objetos dinámicos a partir de esta clase. Punto *ptrQ; ptrQ = new Punto(4,5); /* (en la segunda línea le pasamos además dos parámetros separados por coma al constructor de Punto) */