E.T.S. de Ingeniería Informática (Ing. Informática) Dpto de Lenguajes y C. de la Computación II.1. El tipo Puntero El tipo puntero permite manipular direcciones de memoria. En este curso se utilizará UNIVERSIDAD DE MALAGA para trabajar con la memoria dinámica, prescindiendo de cualquier otro uso que permita el lenguaje. DPTO. DE LENGUAJES Y C. DE LA COMPUTACION E.T.S. DE INGENIERIA INFORMATICA INGENIERIA INFORMATICA LABORATORIO DE PROGRAMACIÓN (CURSO 2006-2007) TEMA II La memoria dinámica es una zona de memoria que se consume y libera en función de las necesidades que se presenten durante la ejecución del programa. Puesto que a priori no es posible saber cuanta memoria dinámica se necesitará, y donde se encuentra localizada, es necesario disponer de algún mecanismo para ello. El tipo puntero representa direcciones de memoria, con lo que permite hacer referencia a determinadas zonas de memoria. En concreto, nosotros lo utilizaremos para hacer referencia a la memoria que se ha ido solicitando dinámicamente. Aunque el lenguaje permite definir directamente variables de tipo puntero, GESTIÓN DINÁMICA DE MEMORIA II.1. El tipo Puntero. II.2. Operaciones con punteros. II.3. Operaciones básicas sobre listas enlazadas II.4. Arrays de memoria dinámica. II.5. Punteros como parámetros a subprogramas. Bibliografía: [SAVI00], [JOYA00] recomendamos hacer uso de los punteros a través de la definición de un tipo explÍcito. Para ello se seguirá el siguiente formato: typedef tipo_vble_dinámica* tipo_puntero; Por ejemplo typedef int* Punt_Entero; Ahora se podrán definir variables que sean puntero a números enteros. Punt_Entero vble_punt1; El tipo puntero es realmente útil cuando se usa para hacer referencia a tipos estructurados. Por ejemplo typedef struct Persona* PuntPersona; struct Persona { string nombre; string tfno; int edad; }; De esta forma, según veremos más adelante, será posible construir estructuras de datos dinámicas tales como listas enlazadas. En determinadas situaciones nos interesa utilizar punteros que no apuntan a ninguna variable dinámica. Para ello usaremos el puntero NULL Laboratorio de Programación _______________________________________ Tema II Gestión Dinámica de Memoria 1 E.T.S. de Ingeniería Informática (Ing. Informática) Dpto de Lenguajes y C. de la Computación E.T.S. de Ingeniería Informática (Ing. Informática) Dpto de Lenguajes y C. de la Computación II.3. Operaciones básicas sobre listas enlazadas II.2. Operaciones con punteros El tipo puntero admite las siguientes operaciones: Petición de memoria. Se solicita memoria para una entidad y se obtiene el puntero a la memoria obtenida. Para ello, se usa el operador new, seguido del tipo de la entidad a crear. Ejemplo: PuntPersona pers = new Persona; Liberación de memoria. Se devuelve memoria que previamente fue pedida mediante new y que sabemos que a partir de ahora no volverá a ser útil. Para ello se usa el operador delete seguido del puntero que apunta a la entidad que deseamos liberar. El tipo puntero puede ser utilizado para construir listas enlazadas dinámicas. Aunque la sintaxis del lenguaje C++ admite múltiples variantes para efectuar la definición de una lista enlazada, recomendamos utilizar el siguiente estilo de declaración: typedef struct nodo* PuntNodo; struct nodo { tipoCampo1 campo_1; ... ... tipoCampoN campo_n; Puntnodo sig; }; Como se puede observar, primero hemos definido un tipo PunteroNodo que cuyos valores apuntan a registros (struct) de tipo nodo. A continuación se define el tipo registro Ejemplo: apuntado, que entre otros, contiene un campo para enlazar con el siguiente nodo de la lista. delete pers; Desreferenciación. Acceso a la entidad apuntada por un puntero. Para ello, se utiliza el operador *, aunque hay algunas abreviaturas para situaciones frecuentes. Ejemplos. Para poder trabajar con listas es necesario disponer de la posibilidad de localizar el último nodo de la lista. Ello se hará detectando un puntero especial NULL, cuyo valor no apunta a ninguna entidad real de la memoria dinámica. Las operaciones de manipulación de listas siguen los mismos criterios estudiados en la asignatura Metodología de la Programación. *vble_punt1 = 2; // almacena un 2 en la entidad de tipo entero // apuntada desde vble_punt1. int num = *vble_punt1; // obtiene la entidad apuntada por vble_punt1 // (un 2) y lo almacena en la vble num II.4. Arrays de memoria dinámica (*pers).edad++; // suma 1 al campo edad de la estructura // apuntada por pers El tipo puntero puede ser utilizado para definir arrays dinámicos. Éstos son arrays cuyo tamaño se especifica en tiempo de ejecución, en el momento de efectuar una petición de (*pers).nombre = “Juan”; memoria para el mismo. Debido a que es frecuente el uso de puntero para acceder a entidades de tipo registro, el acceso a los campos de un registro apuntado se puede especificar además mediante el operador -> pers->edad++; // suma 1 al campo edad de la estructura // apuntada por pers pers->nombre = “Juan”; Asignación. Se puede almacenar el valor de un puntero en una variable de tipo puntero. Para definir un array dinamico de tipo base T_Base se utiliza la misma sintaxis que para definir un puntero a un elemento de tipo T_Base. typedef struct Persona* ArrayDinamicoPersonas; Será en el momento de la petición de memoria dinámica cuando se indique que se está pidiendo memoria para un array dinámico. Para ello, bastará con especificar el número de elementos del array entre corchetes. const int TAM = 20; ArrayDinamicoPersonas Pers = new Persona[TAM]; // TAM puede ser constante o vble Comparaciones de igualdad y desigualdad. Para comprobar si dos punteros están apuntando a la misma entidad (son la misma dirección) o no. Una vez efectuada la petición de memoria para un array dinámico podremos trabajar con él como con cualquier otro array, usando la sintaxis típica de éstos. Pers[i].edad++; Laboratorio de Programación _______________________________________ Tema II Gestión Dinámica de Memoria 2 Laboratorio de Programación // suma uno a la edad de la persona “i” del array “Pers” _______________________________________ Tema II Gestión Dinámica de Memoria 3 E.T.S. de Ingeniería Informática (Ing. Informática) Dpto de Lenguajes y C. de la Computación Para liberar la memoria utilizada por un array dinámico se insertan corchetes entre delete y el nombre del array. delete [] Pers; Notas: E.T.S. de Ingeniería Informática (Ing. Informática) Dpto de Lenguajes y C. de la Computación II.5. Punteros como parámetros a subprogramas El paso de valores de tipo puntero a subprogramas no se diferencia del paso de valores de cualquier otro tipo como por ejemplo int. Así pues, si el parámetro es de entrada usaremos paso por valor y si es de salida o de entrada/salida, usaremos paso por referencia. Al acceder a una posición del arrray dinámico no se comprueba que el valor del índice se refiera a una posición válida del array. Al liberar un array dinámico no se comprueba que la nemoria del mismo fuera solicitada correctamente. Sin embargo, sabemos que usamos punteros para apuntar a variables dinámicas. Dichas variablas apuntadas no se ven afectadas por el mecanismo de paso por valor / paso por referencia. Como consecuencia, aunque pasemos un puntero por valor, la variable dinámica apuntada por éste no se copia. Si desreferenciamos un puntero en un subprograma, accedemos a la misma variable dinámica que si desreferenciamos el parámetro Arrays dinámicos como parámetros a subprogramas. Se realizará utilizando un array abierto en el parámetro formal correspondiente. Además, para poder trabajar con el array dentro del subprograma, deberá incluirse un parámetro que informe del número de elementos del array que está siendo pasado como parámetro. Ejemplo: correspondiente en la llamada, y por tanto, si modificamos la variable dinámica dentro del subprograma, dicha modificación permanece al finalizar el subprograma, tanto si el parámetro puntero fue pasado por valor como si fue pasado por referencia. Las funciones pueden devolver valores de tipo puntero. De nuevo, debemos tener en cuenta que únicamente se devuelve el puntero, sin que ello afecte a la variable dinámica apuntada por éste. typedef int* AD_Enteros; Ejemplos: void leer(int n, int numeros[]) { cout << ”Introduce “ << n << “ elementos” << endl; for (int i = 0; i < n ; i++) { cin >> numeros[i]; } } float media(int n, const int numeros[]) { float suma = 0.0; for (int i = 0; i < n; i++) { suma += numeros[i]; } return suma / float(n); } /* * Dada una lista que puede contener elementos repetidos * elimina los duplicados que aparezcan en la misma. */ void purgar_lista(T_Lista& lista) /* * Devuelve una lista nueva que contenga aquellos elementos * de “l” mayores que “num” */ T_Lista copiar_lista(T_Lista l, int num) int main() { int n_elem: AD_Enteros numeros; cout << “Introduce el número de elementos”; cin >> n_elem; numeros = new int[n_elem]; leer(n_elem, numeros); cout << “Media: ” << media(n_elem, numeros) << endl; delete [] numeros; return 0; } Laboratorio de Programación _______________________________________ Tema II Gestión Dinámica de Memoria 4 Laboratorio de Programación _______________________________________ Tema II Gestión Dinámica de Memoria 5