Documentos de Académico
Documentos de Profesional
Documentos de Cultura
1 2
3 4
Asignacion de punteros (Aliasing) Puntero a NULL
I Consideremos este codigo: I Es un valor especial que indica que el puntero no
int* a = new int; esta apuntando a ninguna posicion de memoria.
*a = 10;
int* b;
I Se indica por la constante 0 (o una expresion que evalue 0).
b = a; // a y b apuntan a la misma direccion de memoria I Puede usarse la macro NULL que esta definida como el valor 0.
cout<<*b; // Muestra 10
*b = 5; int* a = new int;
cout<<*a //Muestra 5; ...
a = 0; // a no apunta a ninguna posicion de memoria
I Esto se debe a que las dos variables apuntan a la misma
posicion de memoria.
I Si se desreferencia el puntero nulo se produce un error en
tiempo de ejecucion (Segmentation fault).
I El mismo fenomeno que ocurre cuando utilizamos referencias.
int* a = new int;
...
a = NULL; // a no apunta a ninguna posicion de memoria
cout<<*a; //Error: Se desreferencia al puntero a NULL
5 6
7 8
Perdida de memoria (Memory Leaks) Referencias colgantes (Dangling references)
I Uso incorrecto de memoria I Luego de ejecutar delete a la memoria apuntada por a no
I Se asigno dinamicamente memoria que no puede accederse debera ser utilizada, sin embargo C++ no lo prohibe y es
mas, porque se pierden las referencias. responsabilidad del programador.
int* a = new int;
I El siguiente codigo no produce error, pero accede mal a la
a = 0; // se pierde la direccion de memoria asignada memoria.
... // int* a = new int;
*a = 5;
I La memoria asignada con new permanece inutilizable y debe delete a;
cout<<*a; //Mal uso de memoria: acceso a memoria que ha sido liberada
ser liberada
I Para ello usamos el operador delete. I Algunos programadores utilizan la siguiente convencion.
int* a = new int;
Asignar el puntero a NULL luego de liberar
... int* a = new int;
delete a; // la memoria apuntada por a es liberada *a = 5;
delete a;
a = 0;
I Para no perder memoria, cada porcion de memoria que se cout<<*a; //Error en tiempo de ejecucion
asigna con new, debe liberarse con delete.
I Se resuelve el problema?
I No, en presencia de aliasing.
9 10
Podemos pedir un arreglo de forma dinamica? Queremos una estructura para guardar datos, de largo variable.
tipo Lista<T> {
I S. Usando new[exp]. observador elems(l:Lista<T>): [T];
Pero la longitud queda fija para siempre. }
I Tenemos que ocuparnos de liberar la memoria usando problema agregarAdelante(l:Lista<T>, e: T) {
delete[] modifica l;
asegura elems(l)==e:elems(pre(l));
int n = 5;
int[] a = new a[n];
}
... problema agregarAtras(l:Lista<T>, e: T) {
delete a[]; modifica l;
asegura elems(l)==elems(pre(l))++[e];
}
problema quitarPrimero(l:Lista<T>) {
requiere |elems(l)| > 0;
modifica l;
asegura elems(l)==elems(pre(l))[1..|elems(pre(l)|)];
}
13 14
Queremos una estructura para guardar datos, de largo variable. I Por ejemplo, creamos tres nodos de la siguiente forma
I Idea: Pedir memoria para un elemento por vez y guardar una (asumiendo elemento es int):
referencia a este elemento. Nodo* nodo1 = new Nodo;
I Donde? nodo1 -> elemento = 1;
Nodo* nodo2 = new Nodo;
En un elemento anterior! nodo2 -> elemento = 2;
I Creamos una estructura de datos Nodo que guarda el Nodo* nodo3 = new Nodo;
nodo3 -> elemento = 3;
elemento que queremos almacenar y un puntero a otro Nodo:
struct Nodo { I Graficamente:
T elemento;
nodo1 nodo2 nodo3
Nodo* siguiente;
}
17 18
19 20
Constructores y Destructores Metodo imprimir
I Constructor: Permite definir el valor inicial de una lista recien I Para imprimir una lista, nos paramos en el primer elemento y
creada (estatica o dinamicamente). Garantiza que se cumpla vamos recorriendo la lista utilizando la referencia al siguiente:
el invariante de representacion! class Lista<T> {
public:
class Lista<T> {
void imprimir() {
Lista() {
cout<<"[";
primero = NULL;
Nodo* n = this -> primero;
}
while (n != NULL) {
private:
cout<<n -> elemento<<" ";
...
n = n -> siguiente;
Nodo* primero;
}
}
cout<<"]"<<endl;
}
I Destructor: Garantiza que cuando una instancia es destruida, }
se limpie tambien la memoria correspondiente a las instancias
creadas o que dependen de ella.
class Lista<T> {
~Lista() { ... // Luego volvemos a esto }
}
21 22
23 24
Esquemas de iteracion: lista Metodo largo
I En la lista, tenemos que crear una referencia a nodo, que se I Ejercicio: escribir un metodo int largo() que devuelve el largo
inicializa con el primer nodo de la lista y en cada paso se de la lista.
actualiza con el siguiente nodo: I int largo()
I Nodo* n = this -> primero; {
int l=0;
while (n != NULL)
Nodo* n = this->primero;
{
while (n != NULL)
// hacer algo con n.elemento
{
n = n -> siguiente
l++;
}
n = n -> siguiente;
}
I Este algoritmo puede alterarse para frenar en el ultimo return l;
elemento o en un elemento buscado. }
25 26
27 28
Metodo quitarPrimero Destructor
I Eliminar el primer elemento de la lista I El destructor se ocupa de limpiar lo hecho por la instrancia
l.imprimir(); //imprime [8 2 4 2 16 32] destruida.
l.quitarPrimero();
l.imprimir(); //imprime [2 4 2 15 32]
I En particular debe liberar la memoria ocupada por las
instancias que pudo haber creado.
I Tenemos que asegurarnos de liberar le memoria del elemento I en el caso de la Lista enlazada se debe ocupar la memoria
eliminado ocupada por cada nodo.
void quitarPrimero() { class Lista<T> {
Nodo * n = this -> primero; ~Lista() {
// El nuevo primero es el siguiente Nodo* n = this -> primero;
this -> primero = this -> primero -> siguiente; while(n!=NULL) {
// Libero la memoria Nodo* sig = n -> siguiente;
delete n; delete n;
} n=sig;
}
}
29 30
Listas enlazadas - Ampliando la clase (1) Listas enlazadas - Ampliando la clase (1)
I Que orden de complejidad tiene la funcion largo()? I Cambia el constructor ...
I Ampliamos la clase agregando una variable de instancia con la Lista()
{
cantidad de nodos de la lista: primero = NULL;
template <class T> nodos = 0;
class Lista<T> }
{
...
Nodo* primero;
int nodos;
}
31 32
Listas enlazadas - Ampliando la clase (1) Listas enlazadas - Ampliando la clase (1)
I Cambia el metodo agregarAdelante ... I Cual es la ventaja de haber agregado esta variable?
void agregarAdelante(int x) I Ahora el metodo largo es mas eficiente!
{
Nodo* nuevo = new Nodo; int largo()
nuevo -> elemento = x; {
nuevo -> siguiente = this -> primero; return nodos;
this -> primero = nuevo; }
++nodos;
} I En la version anterior, este metodo tena complejidad O(n), y
I ... y todos los metodos que alteren los nodos de la lista! la version actual tieme complejidad O(1).
I Mejoramos la eficiencia de uno de los metodos, sin empeorar el
orden del resto (solo agregamos una instruccion a cada uno).
33 34
Listas enlazadas - Ampliando la clase (2) Listas enlazadas - Ampliando la clase (2)
I Como es el algoritmo para agregar un elemento al final de la I Una instancia de ListaInt tiene ahora dos referencias:
lista? (lo llamamos agregarAtras).
I La representacion de listas con una referencia al primer
elemento genera una asimetra entre los metodos
agregarAdelante y agregarAtras.
I Agregar al principio es O(1).
I Agregar al final es O(n), porque tenemos que recorrer toda la
lista para localizar el ultimo nodo.
35 36
Listas doblemente enlazadas
I Cada nodo tiene una referencia al siguiente elemento, y
tambien una referencia al elemento anterior.
37