Anda di halaman 1dari 35

Apuntes sobre punteros y listas enlazadas Computacin III

APUNTES SOBRE MEMORIA DINAMICA Y PUNTEROS


Una estructura se dice dinmica si su tamao cambia en tiempo de ejecucin
del programa. De los tipos de datos vistos: struct,int, char , bool, float, double,
no permiten construir estructuras dinmicas. El espacio de memoria utilizado
se reserva cuando comienza la ejecucin del bloque donde estn declaradas y
este espacio no vara en toda la ejecucin del bloque.

El tipo puntero
Un puntero es una variable que apunta o referencia a una ubicacin de
memoria en la cual hay datos. El contenido del puntero es la direccin de esa
ubicacin. Para entender qu es un puntero veremos primero cmo se
almacenan los datos en un computador. La memoria de un computador est
compuesta por unidades bsicas llamadas bits. Cada bit slo puede tomar dos
valores, normalmente denominados alto y bajo, 1 y 0. Pero trabajar con bits
no es prctico, y por eso se agrupan. Cada grupo de 8 bits forma un byte u
octeto. En realidad el microprocesador, y por lo tanto nuestro programa, slo
puede manejar directamente bytes o grupos de dos o cuatro bytes. Para
acceder a los bits hay que acceder antes a los bytes.
Cada byte de la memoria de un computador tiene una direccin, llamada
direccin de memoria.
Los microprocesadores trabajan con una unidad bsica de informacin, a la
que se denomina palabra (en ingls Word). Dependiendo del tipo de
microprocesador una palabra puede estar compuesta por uno, dos, cuatro,
ocho o diecisis bytes. Hablaremos en estos casos de plataformas de 8, 16, 32,
64 128 bits. Se habla indistintamente de direcciones de memoria, aunque las
palabras sean de distinta longitud. Cada direccin de memoria contiene
siempre un byte. Lo que suceder cuando las palabras sean, por ejemplo, de
32 bits es que accederemos a posiciones de memoria que sern mltiplos de 4.
Por otra parte, la mayor parte de los objetos que usamos en nuestros
programas no caben en una direccin de memoria. La solucin utilizada para
manejar objetos que ocupen ms de un byte es usar posiciones de memoria
correlativas. De este modo, la direccin de un objeto es la direccin de
memoria de la primera posicin que contiene ese objeto. Dicho de otro modo,
si para almacenar un objeto se precisan cuatro bytes, y la direccin de
memoria de la primera posicin es n, el objeto ocupar las posiciones desde n
a n+3, y la direccin del objeto ser, tambin, n.
Todo esto sucede en el interior de la mquina, y nos importa relativamente
poco. Pero podemos saber qu tipo de plataforma estamos usando
averiguando el tamao del tipo int, y para ello hay que usar el operador
sizeof, por ejemplo:
cout << "Plataforma de " << 8*sizeof(int) << " bits";

AERS..Pg. 1

Apuntes sobre punteros y listas enlazadas Computacin III


Ahora veamos cmo funcionan los punteros. Un puntero es un tipo especial de
objeto que contiene, ni ms ni menos que, la direccin de memoria de un
objeto. Por supuesto, almacenada a partir de esa direccin de memoria puede
haber cualquier clase de objeto: un char, un int, un float, un array, una
estructura, una funcin u otro puntero. Seremos nosotros los responsables de
decidir ese contenido, al declarar el puntero.
De hecho, podramos decir que existen tantos tipos diferentes de punteros
como tipos de objetos puedan ser referenciados mediante punteros. Si
tenemos esto en cuenta, los punteros que apunten a tipos de objetos distintos,
sern tipos diferentes. Por ejemplo, no podemos asignar a un puntero a char
el valor de un puntero a int.
Intentemos ver con mayor claridad el funcionamiento de los punteros.
Podemos considerar la memoria del ordenador como un gran array, de modo
que podemos acceder a cada celda de memoria a travs de un ndice. Podemos
considerar que la primera posicin del array es la 0 celda[0].

array = ARREGLO

Si usamos una variable para almacenar el ndice, por ejemplo, indice=0,


entonces celda[0] == celda[indice]. Finalmente, si prescindimos de la notacin
de los arrays, podemos ver que el ndice se comporta exactamente igual que
un puntero.
El puntero indice podra tener por ejemplo, el valor 3, en ese caso, el valor
apuntado por indice tendra el valor 'val3'. Las celdas de memoria tienen una
existencia
fsica, es
decir
son
algo
real y
existirn
siempre,

AERS..Pg. 2

Apuntes sobre punteros y listas enlazadas Computacin III


independientemente del valor del puntero. Existirn incluso si no existe el
puntero.
De forma recproca, la existencia o no existencia de un puntero no implica la
existencia o la inexistencia del objeto. De la misma forma que el hecho de no
sealar a un rbol, no implica la inexistencia del rbol. Algo ms oscuro es si
tenemos un puntero para rboles, que no est sealando a un rbol. Un
puntero de ese tipo no tendra uso si estamos en medio del mar: tener ese
puntero no crea rboles de forma automtica cuando sealemos con l. Es un
puntero, no una varita mgica.
Del mismo modo, el valor de la direccin que contiene un puntero no implica
que esa direccin sea vlida, en el sentido de que no tiene por qu contener la
direccin de un objeto del tipo especificado por el puntero.
Supongamos que tenemos un mapa en la pared, y supongamos tambin que
existen diferentes tipos de punteros lser para sealar diferentes tipos de
puntos en el mapa (ya s que esto suena raro, pero usemos la imaginacin).
Creamos un puntero para sealar ciudades. Nada ms crearlo (o encenderlo),
el puntero sealar a cualquier sitio, podra sealar incluso a un punto fuera
del mapa. En general, daremos por sentado que una vez creado, el puntero no
tiene por qu apuntar a una ciudad, y aunque apunte al mapa, podra estar
sealando a un mar o a un ro.
Con los punteros en C++ ocurre lo mismo. El valor inicial del puntero, cuando
se declara, podra estar fuera del mapa, es decir, contener direcciones de
memoria que no existen. Pero, incluso sealando a un punto de la memoria, es
muy probable que no seale a un objeto del tipo adecuado. Debemos
considerar esto como el caso ms probable, y no usar jams un puntero que
no haya sido inicializado correctamente.
Dentro del array de celdas de memoria existirn zonas que contendrn
programas y datos, tanto del usuario como del propio sistema operativo o de
otros programas, el sistema operativo se encarga de gestionar esa memoria,
prohibiendo o protegiendo determinadas zonas.
Pero el propio puntero, como objeto que es, tambin se almacenar en
memoria, y por lo tanto, tambin tendr una direccin de memoria. Cuando
declaramos un puntero estaremos reservando la memoria necesaria para
almacenarlo, aunque, como pasa con el resto del los objetos, el contenido de
esa memoria contendr basura.
En principio, debemos asignar a un puntero, o bien la direccin de un objeto
existente, o bien la de uno creado explcitamente durante la ejecucin del
programa o un valor conocido que indique que no seala a ningn objeto, es
decir el valor 0. El sistema operativo, cuanto ms avanzado es, mejor suele
controlar la memoria. Ese control se traduce en impedir el acceso a
determinadas direcciones reservadas por el sistema.

AERS..Pg. 3

Apuntes sobre punteros y listas enlazadas Computacin III


Declaracin de punteros:
Los punteros se declaran igual que el resto de los objetos, pero precediendo el
identificador con un asterisco (*).
Sintaxis:
<tipo> *<identificador>;

Ejemplos:
int *pEntero;
char *pCaracter;
struct stPunto *pPunto;

Los punteros slo pueden apuntar a objetos de un tipo determinado, en el


ejemplo, pEntero slo puede apuntar a un objeto de tipo int.
La forma:
<tipo>* <identificador>;

Con el (*) junto al tipo, en lugar de junto al identificador del objeto, tambin
est permitida. De hecho, tambin es legal la forma:
<tipo> * <identificador>;

Veamos algunos matices. Tomemos el primer ejemplo:


int *pEntero;

Equivale a:
int* pEntero;

Otro detalle importante es que, aunque las tres formas de situar el asterisco en
la declaracin son equivalentes, algunas de ellas pueden inducirnos a error,
sobre todo si se declaran varios objetos en la misma lnea:
int* x, y;

En este caso, x es un puntero a int, pero y no es ms que un objeto de tipo


int. Colocar el asterisco junto al tipo puede que indique ms claramente que
estamos declarando un puntero, pero hay que tener en cuenta que slo afecta
al primer objeto declarado, si quisiramos declarar ambos objetos como
punteros a int tendremos que hacerlo de otro modo:
int* x, *y;
// O:

AERS..Pg. 4

Apuntes sobre punteros y listas enlazadas Computacin III


int *x, *y;
// O:
int* x;
int* y;

Los punteros apuntan a objetos, por lo tanto, lo primero que tenemos que
saber hacer con nuestros punteros es asignarles direcciones de memoria
vlidas de objetos. Para averiguar la direccin de memoria de cualquier objeto
usaremos el operador de direccin (&), que leeremos como "direccin de".
Por supuesto, los tipos tienen que ser "compatibles", no podemos almacenar la
direccin de un objeto de tipo char en un puntero de tipo int.

Por ejemplo:
int A;
int *pA;
pA = &A;

Segn este ejemplo, pA es un puntero a int que apunta a la direccin donde se


almacena el valor del entero A.
Objeto apuntado por un puntero
La operacin contraria es obtener el objeto referenciado por un puntero, con el
fin de manipularlo, ya sea modificando su valor u obteniendo el valor actual.
Para manipular el objeto apuntado por un puntero usaremos el operador de
indireccin, que es un asterisco (*).
En C++ es muy habitual que el mismo smbolo se use para varias cosas
diferentes, este es el caso del asterisco, que se usa como operador de
multiplicacin, para la declaracin de punteros y, como vemos ahora, como
operador de indireccin. Como operador de indireccin slo est permitido
usarlo con punteros, y podemos leerlo como "objeto apuntado por".
Por ejemplo:
int *pEntero;
int x = 10;
int y;
pEntero = &y;
*pEntero = x; // (1)

En (1) asignamos al objeto apuntado por pEntero en valor del objeto x. Como
pEntero apunta al objeto y, esta sentencia equivale (segn la secuencia del
programa), a asignar a y el valor de x.

AERS..Pg. 5

Apuntes sobre punteros y listas enlazadas Computacin III


Diferencia entre punteros y otros objetos
Debemos tener muy claro, en el ejemplo anterior, que pEntero es un objeto del
tipo "puntero a int", pero que *pEntero NO es un objeto de tipo int, sino una
expresin.
Por qu decimos esto?
Pues porque, como pasa con todos los objetos en C++, cuando se declaran
slo se reserva espacio para almacenarlos, pero no se asigna ningn valor
inicial, (recuerda que nuestro puntero para rboles no crea rbol cada vez que
sealemos con l). El contenido del objeto permanecer sin cambios, de modo
que el valor inicial del puntero ser aleatorio e indeterminado. Debemos
suponer que contiene una direccin no vlida.
Si pEntero apunta a un objeto de tipo int, *pEntero ser el contenido de ese
objeto, pero no olvides que *pEntero es un operador aplicado a un objeto de
tipo "puntero a int". Es decir, *pEntero es una expresin, no un objeto.
Declarar un puntero no crear un objeto del tipo al que apunta. Por ejemplo:
int *pEntero; no crea un objeto de tipo int en memoria. Lo que crea es un
objeto que puede contener la direccin de memoria de un entero.
Podemos decir que existe fsicamente un objeto pEntero, y tambin que ese
objeto puede (aunque esto no es siempre cierto) contener la direccin de un
objeto de tipo int.
Como todos los objetos, los punteros tambin contienen "basura" cuando son
declarados. Es costumbre dar valores iniciales nulos a los punteros que no
apuntan a ningn sitio concreto:
int *pEntero = 0; // Tambin podemos asignar el valor NULL
char *pCaracter = 0;

NULL es una constante, que est definida como cero en varios ficheros de
cabecera, como "cstdio" o "iostream", y normalmente vale 0L. Sin embargo,
hay muchos textos que recomiendan usar el valor 0 para asignar a punteros
nulos, al menos en C++.
Correspondencia entre arrays y punteros
En muchos aspectos, existe una equivalencia entre arrays y punteros. De
hecho, cuando declaramos un array estamos haciendo varias cosas a la vez:

Declaramos un puntero del mismo tipo que los elementos del array.

AERS..Pg. 6

Apuntes sobre punteros y listas enlazadas Computacin III

Reservamos memoria para todos los elementos del array. Los elementos
de un array se almacenan internamente en la memoria del ordenador en
posiciones consecutivas.

Se inicializa el puntero de modo que apunte al primer elemento del


array.

Las diferencias entre un array y un puntero son dos:

Que el identificador de un array se comporta como un puntero


constante, es decir, no podemos hacer que apunte a otra direccin de
memoria.

Que el compilador asocia, de forma automtica, una zona de memoria


para los elementos del array, cosa que no hace para los elementos
apuntados por un puntero corriente.

Ejemplo:
int vector[10];
int *puntero;
puntero = vector; /* Equivale a puntero = &vector[0]; (1)
esto se lee como "direccin del primer elemento de
vector" */
(*puntero)++;
/* Equivale a vector[0]++; (2) */
puntero++;
// puntero equivale a asignar a puntero=puntero+1 (3)

En (1) se asigna a puntero la direccin del array, o ms exactamente, la


direccin del primer elemento del array vector.
En (2) se incrementa el contenido de la memoria apuntada por puntero, que es
vector[0].
En (3) se incrementa el puntero, esto significa que apuntar a la posicin de
memoria del siguiente elemento int, y no a la siguiente posicin de memoria.
Es decir, el puntero no se incrementar en una unidad, como tal vez sera
lgico esperar, sino en la longitud de un int, ya que puntero apunta a un
objeto de tipo int.
Anlogamente, la operacin:
puntero = puntero + 7;

No incrementar la direccin de memoria almacenada en puntero en siete


posiciones, sino en 7*sizeof(int).
De forma anloga, el siguiente ejemplo tambin es vlido:
int vector[10];

AERS..Pg. 7

Apuntes sobre punteros y listas enlazadas Computacin III


int *puntero;
puntero = &vector[5];
puntero[-2] = puntero[2] = 100;

Evidentemente, nunca podremos usar un ndice negativo con un array, ya que


estaramos accediendo a una zona de memoria que no pertenece al array, pero
eso no tiene por qu ser cierto con punteros.
En este ltimo ejemplo, puntero apunta al sexto elemento de vector, de modo
que puntero[-2] apunta al cuarto, es decir, vector[3] y puntero[2] apunta al
octavo, es decir, vector[7].
Operaciones con punteros
La aritmtica de punteros es limitada, pero en muchos aspectos muy
interesante; y aunque no son muchas las operaciones que se pueden hacer con
los punteros, cada una tiene sus peculiaridades.
Asignacin
Ya hemos visto cmo asignar a un puntero la direccin de una variable.
Tambin podemos asignar un puntero a otro, esto har que los dos apunten a
la misma direccin de memoria:
int *q, *p;
int a;
q = &a; /* q apunta a la direccin de a */
p = q; /* p apunta al mismo sitio, es decir,
a la direccin de a */

Slo hay un caso especial en la asignacin de punteros, y es cuando se asigna


el valor cero. Este es el nico valor que se puede asignar a cualquier puntero,
independientemente del tipo de objeto al que apunte.
Operaciones aritmticas
Podemos distinguir dos tipos de operaciones aritmticas con punteros. En uno
de los tipos uno de los operando es un puntero, y el otro un entero. En el otro
tipo, ambos operando son punteros.
Ya hemos visto ejemplos del primer caso. Cada unidad entera que se suma o
resta al puntero hace que este apunte a la direccin del siguiente objeto o al
anterior, respectivamente, del mismo tipo.
El valor del entero, por lo tanto, no se interpreta como posiciones de memoria
fsica, sino como posiciones de objetos del tipo al que apunta el puntero.

AERS..Pg. 8

Apuntes sobre punteros y listas enlazadas Computacin III


Por ejemplo, si sumamos el valor 2 a un puntero a int, y el tipo int ocupa
cuatro bytes, el puntero apuntar a la direccin ocho bytes mayor a la original.
Si se tratase de un puntero a char, el puntero avanzar dos posiciones de
memoria.
Las restas con enteros operan de modo anlogo.
Con este tipo de operaciones podemos usar los operadores de suma, resta,
preincremento, postincremento, predecremento y postdecremento. Adems,
podemos combinar los operadores de suma y resta con los de asignacin: +=
y -=.
En cuanto al otro tipo de operaciones aritmticas, slo est permitida la resta,
ya que la suma de punteros no tiene sentido. Si la suma o resta de un puntero
y un entero da como resultado un puntero, la resta de dos punteros dar como
resultado, lgicamente, un entero. Veamos un ejemplo:
int vector[10];
int *p, *q;
p = vector; /* Equivale a p = &vector[0]; */
q = &vector[4]; /* apuntamos al 5 elemento */
cout << q-p << endl;

El resultado ser 4, que es la "distancia" entre ambos punteros.


Generalmente, este tipo de operaciones slo tendr sentido entre punteros que
apunten a objetos del mismo tipo, y ms frecuentemente, entre punteros que
apunten a elementos del mismo array.
Comparacin entre punteros
Comparar punteros puede tener sentido en la misma situacin en la que lo
tiene restar punteros, es decir, averiguar posiciones relativas entre punteros
que apunten a elementos del mismo array. Podemos usar los operadores <,
<=, >= o > para averiguar posiciones relativas entre objetos del mismo tipo
apuntados por punteros.
Existe otra comparacin que se realiza muy frecuente con los punteros. Para
averiguar si estamos usando un puntero nulo es corriente hacer la
comparacin:
if(NULL != p)

Punteros genricos
Es posible declarar punteros sin especificar a qu tipo de objeto apuntan:
void *<identificador>;

AERS..Pg. 9

Apuntes sobre punteros y listas enlazadas Computacin III


Usaremos estos punteros en situaciones donde podemos referirnos a distintos
tipos de objetos, ya que podemos hacer que apunten a objetos de cualquier
tipo.
Por supuesto, para eso tendremos que hacer un casting con punteros, sintaxis:
(<tipo> *)<variable puntero>

Por ejemplo:
#include <iostream>
using namespace std;
int main() {
char cadena[10] = "Hola";
char *c;
int *n;
void *v;
c = cadena; // c apunta a cadena
n = (int *)cadena; // n tambin apunta a cadena
v = (void *)cadena; // v tambin
cout << "carcter: " << *c << endl;
cout << "entero:
" << *n << endl;
cout << "float:
" << *(float *)v << endl;
return 0;
}

El resultado ser:
carcter: H
entero:
1634496328
float:
2.72591e+20

Vemos que tanto cadena como los punteros n, c y v apuntan a la misma


direccin, pero cada puntero tratar la informacin que encuentre all de modo
diferente, para c es un carcter y para n un entero. Para v no tiene tipo
definido, pero podemos hacer casting con el tipo que queramos, en este
ejemplo con float.
Punteros a estructuras
Los punteros tambin pueden apuntar a estructuras. En este caso, para
referirse a cada elemento de la estructura se usa el operador (), en lugar del
(*).
Ejemplo:
#include <iostream>
using namespace std;

AERS..Pg. 10

Apuntes sobre punteros y listas enlazadas Computacin III


struct stEstructura {
int a, b;
} estructura, *e;
int main() {
estructura.a = 10;
estructura.b = 32;
e = &estructura;
cout
cout
cout
cout
cout
cout

<<
<<
<<
<<
<<
<<

"puntero" << endl;


e->a << endl;
e->b << endl;
"objeto" << endl;
estructura.a << endl;
estructura.b << endl;

return 0;
}

Ejemplos
Veamos algunos ejemplos de cmo trabajan los punteros.
Primero un ejemplo que ilustra la diferencia entre un array y un puntero:
#include <iostream>
using namespace std;
int main() {
char cadena1[] = "Cadena 1";
char *cadena2 = "Cadena 2";
cout << cadena1 << endl;
cout << cadena2 << endl;
//cadena1++; // Ilegal, cadena1 es constante
cadena2++; // Legal, cadena2 es un puntero
cout << cadena1 << endl;
cout << cadena2 << endl;
cout << cadena1[1] << endl;
cout << cadena2[0] << endl;
cout << cadena1 + 2 << endl;
cout << cadena2 + 1 << endl;
cout << *(cadena1 + 2) << endl;
cout << *(cadena2 + 1) << endl;
return 0;
}

AERS..Pg. 11

Apuntes sobre punteros y listas enlazadas Computacin III


Aparentemente, y en la mayora de los casos, cadena1 y cadena2 son
equivalentes, sin embargo hay operaciones que estn prohibidas con los
arrays, ya que son punteros constantes.
Apuntadores y Funciones:
Cuando C pasa argumentos a funciones, los pasa por valor, es decir, si el
parmetro es modificado dentro de la funcin, una vez que termina la funcin
el valor pasado de la variable permanece inalterado.
Hay muchos casos que se quiere alterar el argumento pasado a la funcin y
recibir el nuevo valor una vez que la funcin ha terminado. Para hacer lo
anterior se debe usar una llamada por referencia, en C se puede simular
pasando un puntero al argumento. Con esto se provoca que la computadora
pase la direccin del argumento a la funcin.
Para entender mejor lo anterior consideremos la funcin swap() que
intercambia el valor de dos argumentos enteros:
void swap(int *px, int *py);
main()
{
int x, y;
x = 10;
y = 20;
printf("x=%d\ty=%d\n",x,y);
swap(&x, &y);
printf("x=%d\ty=%d\n",x,y);
}
void swap(int *px, int *py)
{
int temp;
temp = *px; /* guarda el valor de la direccin x */
*px = *py; /* pone y en x */
*py = temp; /* pone x en y */
}

AERS..Pg. 12

Apuntes sobre punteros y listas enlazadas Computacin III


Objetos dinmicos
El uso principal y ms potente de los punteros es el manejo de la memoria
dinmica.
La memoria se clasifica en muchas categoras, por ahora nos centraremos en
algunas de ellas. Cuando se ejecuta un programa, el sistema operativo reserva
una zona de memoria para el cdigo o instrucciones del programa y otra para
los objetos que se usan durante la ejecucin. A menudo estas zonas son la
misma, y componen lo que se denomina memoria local. Tambin hay otras
zonas de memoria, como la pila, que se usa, entre otras cosas, para
intercambiar datos entre las funciones. El resto, la memoria que no se usa por
ningn programa es lo que se conoce como almacenamiento libre o free store.
Nuestro programa puede hacer uso de esa memoria durante la ejecucin, de
modo que la cantidad de espacio de memoria usado por el programa no est
limitada por el diseo ni por las declaraciones de objetos realizadas en el
cdigo fuente. Por eso se denomina a este tipo, memoria dinmica, ya que
tanto la cantidad de memoria como su uso se deciden durante la ejecucin, y
en general, cambia a lo largo del tiempo, de forma dinmica. Para ello,
normalmente se usar memoria del montn, y no se llama as porque sea de
peor calidad, sino porque suele haber un buen montn de memoria de este
tipo.
C++ dispone de dos operadores para manejar (reservar y liberar) la memoria
dinmica, son new y delete.
Reserva de memoria
new devuelve un puntero de tipo void *, lo que significa que se
puede asignar a cualquier tipo de puntero. Convierte automticamente el
puntero devuelto al tipo adecuado, por lo que el programador no tiene que
preocuparse de hacer la conversin de forma explcita.
char *p;
p = new char; // reserva espacio para un carcter

El operador new tambin se encarga de averiguar cul es el tamao en bytes


del tipo de datos para el cual se desea reservar memoria (recordemos que la
cardinalidad de un tipo nos permite saber la memoria necesaria para su
almacenamiento). Observar en el ejemplo anterior que se indica el que se
quiere reservar memoria para un dato de tipo char y no cual es el tamao en
bytes que se quiere reservar.
Tras una llamada con xito, new devuelve un puntero al primer byte
de la regin de memoria dispuesta del montn. Si no hay suficiente memoria
libre para satisfacer la peticin, se produce un fallo de asignacin y new
devuelve un NULL.

AERS..Pg. 13

Apuntes sobre punteros y listas enlazadas Computacin III


Nota: cuando se reserva memoria para una variable de ese tipo se indica el
tipo de datos de la variable annima (a la que apunta el puntero) y no el
nombre del tipo puntero.
typedef char *TPChar;
TPChar p;
p = new char; //Correcto
p = new TPChar; //INCORRECTO

Liberacin de memoria

El operador delete es complementario de new. Una vez que la memoria


ha sido liberada, puede ser reutilizada en una posterior llamada a new.
El prototipo de delete es:
delete <variable_puntero>;

Aqu, p es un puntero a memoria que ha sido previamente asignado con new.


Por ejemplo:
char *p;
p = new char; // reserva espacio para un carcter
delete p; //libera la memoria previamente reservada
NOTA: Es muy importante no llamar NUNCA a delete con un argumento no vlido; se
daar el Sistema de asignacin. El operador delete p NO asigna el valor nulo al
puntero p despus de liberar la memoria a la que ste apuntaba.

Hay una regla de oro cuando se usa memoria dinmica: toda la memoria que
se reserve durante el programa hay que liberarla antes de salir del programa.
No seguir esta regla es una actitud muy irresponsable, y en la mayor parte de
los casos tiene consecuencias desastrosas. No confes de lo que diga el
compilador, de que estas variables se liberan solas al terminar el programa, no
siempre es verdad.
Ejemplo:
int main()
{
int *a;
a = new int; // variable dinmica
*a = 10;
a = new int; // nueva variable dinmica,
// se pierde el puntero a la anterior
*a = 20;

AERS..Pg. 14

Apuntes sobre punteros y listas enlazadas Computacin III


delete a;
return 0;

// slo liberamos la ltima reservada

En este ejemplo vemos cmo es imposible liberar la primera reserva de


memoria dinmica. Lo correcto, si no la necesitbamos habra sido liberarla
antes de reservar un nuevo bloque usando el mismo puntero, y si la
necesitamos, habra que guardar su direccin, por ejemplo usando otro
puntero.
Arreglos Dinmicos
El manejo de arreglos de forma esttica conlleva muchas veces a desperdicio
de la memoria cuando se declara un arreglo muy grande para cantidad de
datos que se va a manejar o en caso contrario quedarse corto en la declaracin
y no contar con espacio para almacenar la data completa. Es por ello que los
arreglos dinmicos vienen a ofrecer una posible solucin a este problema.
Para manejar un arreglo de forma dinmica en C++ se requiere declarar e
inicializar un puntero al tipo de dato que deseo utilizar, luego proceder a
solicitar como entrada de datos en tiempo de ejecucin, el tamao del arreglo
para reservar el espacio correspondiente. La manipulacin posterior es similar
a la de un arreglo que se ha declarado de forma esttica.
En el siguiente ejemplo se maneja de forma dinmica un vector de nmeros
aleatorios:
int*datos;//puntero a int
cout <<"Cuantos nmeros deseas generar:";
cin >> Num;
datos =new int[Num]; //Reserva de memoria para el arreglo
if(datos != NULL)
{for(inti=0;i<Num;i++)
{ datos[i] = rand() * 100 / RAND_MAX;
cout << i <<":"<< datos[i] <<endl;}
delete [] datos; //libera todo el bloque de memoria

}
De la misma forma que manejamos vectores tambin se pueden
manejar tablas o matrices:
Veamos la declaracin de un puntero a puntero:
int **tabla;
Tabla es un puntero que apunta a un objeto que a su vez es puntero de un
entero.

AERS..Pg. 15

Apuntes sobre punteros y listas enlazadas Computacin III


Sabemos que un arreglo se comporta muy parecido a un puntero, por lo tanto
Tabla apunta al primer elemento de un arreglo de punteros:
int n = 134;
tabla = new int*[n];

Cada elemento del arreglo anterior puede ser a su vez un puntero al primer
elemento de otro arreglo:
int m = 231;
for(int i = 0; i < n; i++)
tabla[i] = new int[m];

Ahora tabla apunta a un arreglo de dimensiones nxm, podemos acceder a cada


elemento de la misma forma que accedemos a arreglos normales:
tabla[21][33] = 123;

Una de las grandes diferencias con los arreglos normales es que antes de
finalizar el programa es necesario liberar la memoria dinmica usada, primero
la asociada a cada elemento de Tabla[i]:
for(int i = 0; i < n; i++) delete[] tabla[i];

y despus la del arreglo de punteros a int Tabla:


delete[] tabla;

El cdigo completo queda de la siguiente forma:


#include <iostream>
void main() {
int **tabla;
int n = 134;
int m = 231;
int i;
// Array de punteros a int:
tabla = new int*[n];
// n arrays de m ints
for(i = 0; i < n; i++)

AERS..Pg. 16

Apuntes sobre punteros y listas enlazadas Computacin III

tabla[i] = new int[m];


tabla[21][33] = 123;
cout << tabla[21][33] << endl;
// Liberar memoria:
for(i = 0; i < n; i++) delete[] tabla[i];
delete[] tabla;
return 0;
}

Redimensionar Arreglos en C++


Uno de los problemas (por decirlo un problema) en C++ con los arreglos o
vectores es que no existe un operador o funcin que permita redimensionar, es
necesario crear nuestro propio procedimiento que se encargue de realizar la
tarea. Por ejemplo si ya tenemos un arreglo con cierta cantidad de datos y se
le quiere ampliar la capacidad, un ejemplo de cdigo podra ser:

int size = 10, nsize=14;


int* arr = new int[size];
void redimensionar(int * arr, int & size,int nsize)
{
int* Temp_arr = new int[nsize];
for(int i = 0; i < size; i++)
Temp_arr[i] = arr[i];
size=nsize;
delete[] arr;// libera las celdas viejas
arr = Temp_arr;
}

El cdigo lo que hace en realidad es crear un nuevo arreglo, y volcar los


valores del anterior arreglo al nuevo que tiene un tamao mayor. Deben tener
en cuenta que el cdigo slo sirve para expandir el tamao, no para reducirlo.
Para reducir la cantidad de celdas sera necesario considerar dos casos:

Cuando se necesita eliminar una celda en particular, de tal forma que al


momento de transferir la informacin para el nuevo arreglo dicha celda
o celdas nos sean transferidas.
Cuando simplemente se redimensiona a un tamao menor

Ejemplo: cuando se desea eliminar una celda en particular


void redimensionar(int * arr, int & size,int pos)
{
int i;
int* Temp_arr = new int[size-1];

AERS..Pg. 17

Apuntes sobre punteros y listas enlazadas Computacin III


for(i = 0; i < pos; i++)
Temp_arr[i] = arr[i];
for(i = pos; i < size-1; i++)
Temp_arr[i] = arr[i+1];
size-=1;
delete[] arr;// libera las celdas viejas
arr = Temp_arr;
}

Ejemplo: cuando se redimensiona solo indicando la cantidad de celdas que se


quiere tenga el nuevo arreglo
void redimensionar(int * datos, int & size,int n)
if (n!=size)
{
if (n!=0)
{
int * nuevos_datos;
nuevos_datos= new int[n];
if (size>0)
{
int minimo;
if (size<n)
minimo=size;
else minimo=n;
for (int i= 0; i<minimo;++i)
nuevos_datos[i]= datos[i];
delete[] datos;
}
size= n;
datos= nuevos_datos;
}
}
}

Listas Enlazadas
Una de las aplicaciones ms interesantes y potentes de la memoria dinmica y
de los punteros son, sin duda, las estructuras dinmicas de datos. Las
estructuras bsicas disponibles en C y C++ (structs y arrays) tienen una
importante limitacin: no pueden cambiar de tamao durante la ejecucin. Los
arrays estn compuestos por un determinado nmero de elementos, nmero
que se decide en la fase de diseo, antes de que el programa ejecutable sea
creado.

AERS..Pg. 18

Apuntes sobre punteros y listas enlazadas Computacin III


En muchas ocasiones se necesitan estructuras que puedan cambiar de tamao
durante la ejecucin del programa. Por supuesto, podemos crear arrays
dinmicos, pero una vez creados, su tamao tambin ser fijo, y para hacer
que crezcan o disminuyan de tamao, deberemos reconstruirlos desde el
principio. Las estructuras dinmicas nos permiten crear estructuras de datos
que se adapten a las necesidades reales a las que suelen enfrentarse nuestros
programas. Pero no slo eso, como veremos, tambin nos permitir crear
estructuras de datos muy flexibles, ya sea en cuanto al orden, la estructura
interna o las relaciones entre los elementos que las componen.
Una lista enlazadas estn compuestas de otras pequeas estructuras a las que
llamaremos nodos o elementos, que agrupan los datos con los que trabajar
nuestro programa y adems uno o ms punteros autoreferenciales, es decir,
punteros a objetos del mismo tipo nodo.
Una estructura bsica de un nodo para crear listas de datos seria:
struct nodo {
int dato;
struct nodo *otronodo;
};
El campo "otronodo" puede apuntar a un objeto del tipo nodo. De este modo,
cada nodo puede usarse como un ladrillo para construir listas de datos, y cada
uno mantendr ciertas relaciones con otros nodos.
Para acceder a un nodo de la estructura slo necesitaremos un puntero a un
nodo.

Definicin:
La forma ms simple de estructura dinmica es la lista abierta. En esta
forma los nodos se organizan de modo que cada uno apunta al siguiente,
y el ltimo no apunta a nada, es decir, el puntero del nodo siguiente vale
NULL.
En las listas abiertas existe un nodo especial: el primero. Normalmente
diremos que nuestra lista es un puntero a ese primer nodo y llamaremos
a ese nodo la cabecera de la lista. Eso es porque mediante ese nico
puntero podemos acceder a toda la lista.

AERS..Pg. 19

Apuntes sobre punteros y listas enlazadas Computacin III


Cuando el puntero que usamos para acceder a la lista vale NULL, diremos
que la lista est vaca.

El nodo tpico para construir listas tiene esta forma:


struct nodo {
int dato;
struct nodo *siguiente;
};

En el ejemplo, cada elemento de la lista slo contiene un dato de tipo


entero, pero en la prctica no hay lmite en cuanto a la complejidad de
los datos a almacenar.

Declaraciones de tipos para manejar listas


Normalmente se definen varios tipos que facilitan el manejo de las listas, en C,
la declaracin de tipos puede tener una forma parecida a esta:
typedef struct _nodo {
int dato;
struct _nodo *siguiente;
} tipoNodo;
typedef tipoNodo *pNodo;
typedef tipoNodo *Lista;

tipoNodo es el tipo para declarar nodos, evidentemente.


pNodo es el tipo para declarar punteros a un nodo.
Lista es el tipo para declarar listas, como puede verse, un puntero a un nodo y
una lista son la misma cosa. En realidad, cualquier puntero a un nodo es una
lista, cuyo primer elemento es el nodo apuntado.

Es muy importante que nuestro programa nunca pierda el valor del puntero
al primer elemento, ya que si no existe ninguna copia de ese valor, y se
pierde, ser imposible acceder al nodo y no podremos liberar el espacio de
memoria que ocupa.

AERS..Pg. 20

Apuntes sobre punteros y listas enlazadas Computacin III


Operaciones bsicas con listas
Con las listas tendremos un pequeo repertorio de operaciones bsicas que se
pueden realizar:

Creacin de una lista enlazada


Aadir o insertar elementos.
Buscar o localizar elementos.
Borrar elementos.
Moverse a travs de una lista, anterior, siguiente, primero.

Cada una de estas operaciones tendr varios casos especiales, por ejemplo, no
ser lo mismo insertar un nodo en una lista vaca, o al principio de una lista no
vaca, o la final, o en una posicin intermedia.
Creacin de una lista enlazada
El procedimiento de creacin de una lista enlazada es muy simple e inicializa
un puntero del tipo TLista a NULL. Por ejemplo podra ser:

Insertar elementos en una lista abierta


Veremos primero los casos sencillos y finalmente construiremos un algoritmo
genrico para la insercin de elementos en una lista.
1. Insertar un nodo al comienzo de una lista
2. Insertar un nodo al final de la lista
3. Insertar un nodo despus de un determinado nodo existente (por
ejemplo, para mantener una lista ordenada).

Insertar un elemento en una lista vaca


Este es, evidentemente, el caso ms sencillo. Partiremos de que ya tenemos el
nodo a insertar y, por supuesto un puntero que apunte a l, adems el puntero
a la lista valdr NULL:

AERS..Pg. 21

Apuntes sobre punteros y listas enlazadas Computacin III

El proceso es muy simple, bastar con que:


1. nodo->siguiente apunte a NULL.
2. Lista apunte a nodo.
Insertar un elemento en la primera posicin de una lista
Podemos considerar el caso anterior como un caso particular de ste, la nica
diferencia es que en el caso anterior la lista es una lista vaca, pero siempre
podemos, y debemos considerar una lista vaca como una lista.

De nuevo partiremos de un nodo a insertar, con un puntero que apunte a l, y


de una lista, en este caso no vaca:

El proceso sera el siguiente:


1. Hacemos que nodo->siguiente apunte a Lista.
2. Hacemos que Lista apunte a nodo.
El algoritmo es el siguiente:

ALGORITMO InsertarAlPrincipio(ES TpLista lista; E Elem Tdato)


VARIABLES
TpLista ptr // Puntero auxiliar
INICIO
ASIGNAR(ptr) // Nuevo nodo
ptr->Dato = Elem
ptr->sig = lista
lista = ptr
FIN InsertarAlPrincipio
*************************************************************
void INSERTAR_INI(pNodo &Lista, DATO Elem)
{
pNodo NUEVO;
NUEVO=new(tipoNodo);

AERS..Pg. 22

Apuntes sobre punteros y listas enlazadas Computacin III


if (NUEVO)
{ NUEVO->Dato=Elem;
NUEVO->Sig=NULL;
if(Lista==NULL) Lista=NUEVO; // Lista Vacia
else
{ NUEVO->Sig=Lista;
Lista=NUEVO; } }
else
cout<<No hay memoria suficiente;
}

Insertar un elemento en la ltima posicin de una lista


Este es otro caso especial. Para este caso partiremos de una lista no vaca:

El proceso es el siguiente:
1. Necesitamos un puntero que seale al ltimo elemento de la lista. La
manera de conseguirlo es empezar por el primero y avanzar hasta que
el nodo que tenga como siguiente el valor NULL.
2. Hacer que nodo->siguiente sea NULL.
3. Hacer que ultimo->siguiente sea nodo.

El Algoritmo es el siguiente:

ALGORITMO InsertarAlFinal(ES TpLista lista; E Elem Tdato)


VARIABLES
TpLista ptr, nuevonodo // Puntero auxiliar
INICIO
ASIGNAR(nuevoNodo) // Nuevo nodo
nuevoNodo->Dato = Elemen
nuevoNodo->sig = NULO
ptr=lista
MIENTRAS (ptr->sig != NULO) HACER
ptr = ptr->sig
FINMIENTRAS
ptr->sig = nuevoNodo
AERS..Pg. 23

Apuntes sobre punteros y listas enlazadas Computacin III

FIN InsertarAlFinal

***********************************
void INSERTAR_FIN(pNodo &Lista, DATO Elem)
{
pNodo A = Lista, NUEVO;
NUEVO = new (tipoNodo);
NUEVO->Dato = Elem;
NUEVO->Sig = NULL;
if(Lista == NULL)
Lista = NUEVO;
else
{while (A->Sig != NULL) A=A->Sig;
A->Sig = NUEVO;
}
}

Insertar un elemento a continuacin de un nodo existente en la lista


De nuevo podemos considerar el caso anterior como un caso particular de
este. Ahora el nodo "anterior" ser aquel a continuacin del cual insertaremos
el nuevo nodo:

Suponemos que ya disponemos del nuevo nodo a insertar, apuntado por nodo,
y un puntero al nodo a continuacin del que lo insertaremos.
El proceso a seguir ser:
1. Hacer que nodo->siguiente seale a anterior->siguiente.
2. Hacer que anterior->siguiente seale a nodo.

Ejemplo1: Insertar despus de un nodo dado su Posicin en la lista

void INSERTAR_NODO(pNodo &Lista, DATO X, int POS)


{
int i;
pNodo A= Lista, NUEVO;
NUEVO = new( tipoNodo);
if NUEVO

AERS..Pg. 24

Apuntes sobre punteros y listas enlazadas Computacin III


{ NUEVO ->Info = X;
NUEVO ->Sig = NULL;
if(POS==1 || Lista==NULL)
{
NUEVO->Sig = Lista;
Lista = NUEVO;
}
else
{
i=1;
while(i<POS-1 && A->Sig != NULL)
{
i++;
A=A->Sig;}
if(A->Sig != NULL)
{
NUEVO->Sig = A->Sig;
A->Sig= NUEVO;
}
else A->Sig=NUEVO;
} }

Ejemplo2: Insertar Ordenadamente

ALGORITMO InsertarOrdenada(ES TpLista lista; E Elem Tdato)


VARIABLES
TpLista nuevoNodo, ptr
INICIO
SI lista == NULO O lista->Dato >= Elem ENTONCES
InsertarAlPrincipio(lista, Elem)
SINO
ASIGNAR(nuevoNodo)
nuevoNodo->Dato = Elem
nuevoNodo->sig = NULO
ptr = lista
MIENTRAS ptr->sig != NULO Y (Elem > ptr->sig->Dato ) HACER
ptr = ptr->sig
FINMIENTRAS
nuevoNodo->sig = ptr->sig
ptr->sig = nuevoNodo
FINSI
FIN InsertarOrdenada
************************************************
void Insertar(Lista &lista, int v)
{
pNodo nuevo, anterior;
nuevo = new tipoNodo; //Crear un nodo nuevo
nuevo->valor = v;
if(ListaVacia( lista) || (lista->valor > v) //Si la lista est vaca
{ nuevo->Sig = lista;
lista = nuevo;
}

AERS..Pg. 25

Apuntes sobre punteros y listas enlazadas Computacin III


else
{anterior = lista; //Buscar el nodo de valor menor a v
while(anterior->Sig && anterior->Sig->valor <= v)
anterior = anterior->Sig;
nuevo->Sig = anterior->Sig;
anterior->Sig = nuevo;
}
}

Localizar elementos en una lista abierta


Muy a menudo necesitaremos recorrer una lista, ya sea buscando un valor
particular o un nodo concreto. Las listas abiertas slo pueden recorrerse en un
sentido, ya que cada nodo apunta al siguiente, pero no se puede obtener, por
ejemplo, un puntero al nodo anterior desde un nodo cualquiera si no se
empieza desde el principio.
Para recorrer una lista procederemos siempre del mismo modo, usaremos un
puntero auxiliar como ndice:
1. Asignamos al puntero ndice el valor de Lista.
2. Abriremos un bucle que al menos debe tener una condicin, que el
ndice no sea NULL.
3. Dentro del bucle asignaremos al ndice el valor del nodo siguiente al
ndice actual.
Procedimiento que permite localizar un nodo y retornar su direccin:
pNodo BUSCAR_DIR(pNodo Lista, DATO elem)
{

pNodo A=Lista;
if(Lista!=NULL)
{

while(A->Dato!=elem && A->Sig!=NULL)


A=A->Sig;
if(A->Dato==elem) return A;
else return NULL;

}
else
{

cout<<"Lista Vacia "<<endl;


return NULL;

Por ejemplo, para mostrar todos los valores de los nodos de una lista,
podemos usar el siguiente bucle en C:
typedef struct _nodo {
int dato;

AERS..Pg. 26

Apuntes sobre punteros y listas enlazadas Computacin III


struct _nodo *siguiente;
} tipoNodo;
typedef tipoNodo *pNodo;
typedef tipoNodo *Lista;
...
pNodo indice;
...
indice = Lista;
while(indice) {
cout<< indice->dato <<"\n";
indice = indice->siguiente;
}
...

Supongamos que slo queremos mostrar los valores hasta que encontremos
uno que sea mayor que 100, podemos sustituir el bucle por:
indice = Lista;
while(indice && indice->dato <= 100) {
cout<< indice->dato<< "\n";
indice = indice->siguiente;
}

Si analizamos la condicin del bucle, tal vez encontremos un posible error:


Qu pasara si ningn valor es mayor que 100, y alcancemos el final de la
lista?. Podra pensarse que cuando ndice sea NULL, si intentamos acceder a
indice->dato se producir un error.
En general eso ser cierto, no puede accederse a punteros nulos. Pero en este
caso, ese acceso est dentro de una condicin y forma parte de una expresin
"and". Recordemos que cuando se evala una expresin "and", se comienza
por la izquierda, y la evaluacin se abandona cuando una de las expresiones
resulta falsa, de modo que la expresin "indice->dato <= 100" nunca se
evaluar si indice es NULL.
Si hubiramos escrito la condicin al revs, el programa nunca funcionara
bien. Esto es algo muy importante cuando se trabaja con punteros.
Eliminar elementos en una lista abierta
De nuevo podemos encontrarnos con varios casos, segn la posicin del nodo
a eliminar.
1) Borrar el primer nodo de la lista enlazada
2) Borrar un determinado nodo de la lista enlazada (por ejemplo,
para mantener la lista ordenada).

AERS..Pg. 27

Apuntes sobre punteros y listas enlazadas Computacin III


Eliminar el primer nodo de una lista abierta

Es el caso ms simple. Partiremos de una lista con uno o ms nodos, y


usaremos un puntero auxiliar, nodo:
1. Hacemos que nodo apunte al primer elemento de la lista, es decir a
Lista.
2. Asignamos a Lista la direccin del segundo nodo de la lista:
Listasiguiente.
3. Liberamos la memoria asignada al primer nodo, el que queremos
eliminar.
Si no guardamos el puntero al primer nodo antes de actualizar Lista, despus
nos resultara imposible liberar la memoria que ocupa. Si liberamos la memoria
antes de actualizar Lista, perderemos el puntero al segundo nodo.

Si la lista slo tiene un nodo, el proceso es tambin vlido, ya que el valor de


Listasiguiente es NULL, y despus de eliminar el primer nodo la lista quedar
vaca, y el valor de Lista ser NULL.
De hecho, el proceso que se suele usar para borrar listas completas es eliminar
el primer nodo hasta que la lista est vaca.
Algoritmo para borrar el primer nodo de una Lista

ALGORITMO EliminarPrimero(ES TpLista lista)


VARIABLES
TpLista ptr
INICIO
SI lista != NULO ENTONCES // Si no, no hay que eliminar
ptr = lista
lista = lista->sig
LIBERAR(ptr)
FINSI
FIN EliminarPrimero

AERS..Pg. 28

Apuntes sobre punteros y listas enlazadas Computacin III

**************************************
void Eiminarprimero(Lista &lista, Tdato & Elem )
{

pNodo
if

nodo;

lista!=NULL

{nodo = lista;
lista = lista->Sig;
Elem=nodo->Dato;
delete nodo;

Eliminar un nodo cualquiera de una lista abierta


En todos los dems casos, eliminar un nodo se puede hacer siempre del mismo
modo. Supongamos que tenemos una lista con al menos dos elementos, y un
puntero al nodo anterior al que queremos eliminar. Y un puntero auxiliar nodo.

El proceso es parecido al del caso anterior:


1. Hacemos que nodo apunte al nodo que queremos borrar.
2. Ahora, asignamos como nodo siguiente del nodo anterior, el siguiente al
que queremos eliminar: anterior->siguiente = nodo->siguiente.
3. Eliminamos la memoria asociada al nodo que queremos eliminar.

Si el nodo a eliminar es el ltimo, es procedimiento es igualmente vlido, ya


que anterior pasar a ser el ltimo, y anterior->siguiente valdr NULL.
Algoritmo para Borrar un nodo cualquiera

ALGORITMO Borrar(ES TpLista lista; E Elem Tdato)


VARIABLES
TpLista ptr, ant=NULO
AERS..Pg. 29

Apuntes sobre punteros y listas enlazadas Computacin III

INICIO
SI lista != NULO ENTONCES // Sino, no hay que hacer nada
ptr = lista
MIENTRAS ptr != NULO Y ptr->Dato != Elem HACER
ant = ptr
ptr = ptr->sig
FINMIENTRAS
SI ptr != NULO ENTONCES // Encontrado
SI ant == NULO ENTONCES // Es el primer elemento
lista = lista->sig
SINO
ant->sig = ptr->sig
FINSI
LIBERAR(ptr)
FINSI
FINSI
FIN Borrar
*************************************************************
void Borrar(Lista &lista, int v)
{
pNodo anterior, nodo;
nodo = lista;
anterior = NULL;
while(nodo && nodo->dato != v)
{ anterior = nodo;
nodo = nodo->Sig;
}
if(!nodo || nodo->dato != v)
return;
else {
if(!anterior) // Primer elemento
lista = nodo->Sig;
else // un elemento cualquiera
anterior->Sig = nodo->Sig;
delete nodo;
}
}

Moverse a travs de una lista abierta


Slo hay un modo de moverse a travs de una lista abierta, hacia delante.
An as, a veces necesitaremos acceder a determinados elementos de una lista
abierta. Veremos ahora como acceder a los ms corrientes: el primero, el
ltimo, el siguiente y el anterior.
Mostrar la lista

AERS..Pg. 30

Apuntes sobre punteros y listas enlazadas Computacin III


void MostrarLista(pNodo lista)
{
pNodo nodo = lista;
if(ListaVacia(lista))
cout<< "Lista vaca<< endl;
else
{ while(nodo)
{ cout<< " -> << nodo->dato;
nodo = nodo->Sig;
}
cout<< endl;
}
}

Primer elemento de una lista


El primer elemento es el ms accesible, ya que es a ese a que apunta el
puntero que define la lista. Para obtener un puntero al primer elemento
bastar con copiar el puntero Lista.
Elemento siguiente a uno cualquiera
Supongamos que tenemos un puntero nodo que seala a un elemento de una
lista. Para obtener un puntero al siguiente bastar con asignarle el campo
"siguiente" del nodo, nodosiguiente.
pNodo SIGUIENTE(pNodo Lista, pNodo P)
{
pNodo nodo=Lista;
if(Lista==NULL || P==NULL || Lista==P) return NULL;
else
{while(nodo!=NULL && nodo!=P) nodo=nodo->Sig;
if(nodo==P) return nodo->Sig;
else return NULL;
}
}

Elemento anterior a uno cualquiera


Ya hemos dicho que no es posible retroceder en una lista, de modo que para
obtener un puntero al nodo anterior a uno dado tendremos que partir del
primero, e ir avanzando hasta que el nodo siguiente sea precisamente nuestro
nodo.
pNodo ANTERIOR(pNodo Lista, pNodo P)
{
pNodo A=Lista;
if(Lista==NULL || P==NULL || Lista==P) return NULL;
else
{
while(A->Sig!=P && A->Sig!=NULL) A=A->Sig;
if(A->Sig==P) return A;

AERS..Pg. 31

Apuntes sobre punteros y listas enlazadas Computacin III


else

return NULL;

}
}

ltimo elemento de una lista


Para obtener un puntero al ltimo elemento de una lista partiremos de un nodo
cualquiera, por ejemplo el primero, y avanzaremos hasta que su nodo
siguiente sea NULL.
pNodo ULTIMO (pNodo Lista)
{

pNodo A=Lista;
if(Lista==NULL )

return NULL;

else
{while(A->Sig!=NULL) A=A->Sig;
return A;}}

Saber si una lista est vaca


Basta con comparar el puntero Lista con NULL, si Lista vale NULL la lista est
vaca.
int ListaVacia(Lista lista)
{

return (lista == NULL);}

Borrar una lista completa


El algoritmo genrico para borrar una lista completa consiste simplemente en
borrar el primer elemento sucesivamente mientras la lista no est vaca.

ALGORITMO BorrarLista(ES TpLista lista)


VARIABLES
TpLista ptr
INICIO
MIENTRAS lista != NULO HACER
ptr = lista
lista = lista->sig
LIBERAR(ptr)
FINMIENTRAS
FIN BorrarLista
*********************************************
void BorrarLista(Lista &lista)

AERS..Pg. 32

Apuntes sobre punteros y listas enlazadas Computacin III


{

pNodo nodo;
while(lista)
{
nodo = lista;
lista = lista->Sig;
delete nodo;
}
}

Ejercicio de aplicacin.
Se crea una lista de n elementos y la llena con valores aleatorios usando una
funcin random(). Posteriormente entra en un ciclo donde se insertan
elementos en posiciones especficas de la lista, mostrando en cada paso el
resultado de cada operacin.
En el programa se define una funcin que permite insertar un nodo en una
posicin especfica de la lista. La funcin (INSERTAR_NODO) recibe como
parmetros la direccin de la lista, el elemento a insertar y la posicin donde
se debe insertar.
El programa no verifica si una posicin especifica esta fuera del alcance de la
lista, por ejemplo, si se tiene una lista de 5 elementos y se intenta un
elemento en la posicin 8.
#include<iostream.h>
#include<iomanip.h>
#include<conio.h>
#include<stdlib.h>
typedef int tipo_info;
struct t_nodo
{ tipo_info Info;
t_nodo * Sig;
};
typedef struct t_nodo tipo_nodo;
typedef tipo_nodo* pt_nodo;
void INSERTAR_INI(pt_nodo &L,
tipo_info Elem)
{ pt_nodo Aux;
Aux=new(tipo_nodo);
Aux->Info=Elem;
Aux->Sig=NULL;
if(L==NULL) L=Aux;
else
{ Aux->Sig=L;
L=Aux;
}
}
void INSERTAR_NODO(pt_nodo &Lista,
tipo_info Elem, int ind)
{ int I;
pt_nodo Act;
Act=Lista;
if(ind==1)
INSERTAR_INI(Lista,Elem);

void MOSTRAR_LISTA(pt_nodo Lis)


{ pt_nodo P;
P=Lis;
while(P!=NULL)
{ cout<<setw(4)<<P->Info;
P=P->Sig; }
cout<<endl;
}
void main(void)
{ int I, n, Elem, Pos;
pt_nodo Lista=NULL;
cout<<Numero Elementos: ;
cin>>n;
for(i=1;i<=n;i++)
{
INSERTAR_INI(Lista,random(20));
}
MOSTRAR_LISTA(Lista);
getch();
char RESP=S;
while(RESP==S||RESP==s)
{ cout<<Valor a insertar: ;
cin>>Elem;
cout<<posicin: ;
cin>>Pos;
INSERTAR_NODO(Lista,Elem,Pos);
MOSTRAR_LISTA(Lista);
getch();

AERS..Pg. 33

Apuntes sobre punteros y listas enlazadas Computacin III


else
{ for (i=1;i<ind-1;i++)
Act=Act->Sig;
INSERTAR_INI((Act->Sig),Elem);
}

cout<<Otro elemento S/N ;


cin>>RESP;
}
}

EJERCICIOS PROPUESTOS
1. Escribir una funcin que cuente (CONTAR(Lista, valor)) el nmero de veces que
aparece un valor en una lista, modificando la funcin que determina si un dato
existe o no en la lista.
2. Desarrolle una funcin que permita intercambiar dos nodos de una lista
simplemente enlazada dadas las posiciones de los mismos.
3. Escribir una funcin que permita eliminar el penltimo Nodo de una Lista Enlazada.
4. Escribir una funcin que permita detectar si una lista tiene valores repetidos.
5. Escribir una funcin que recibe dos listas L1 y L2, remueve el nodo frontal de la
segunda lista y lo inserta al inicio de la primera lista.
6. Escribir una funcin DivAlterna() que divida una lista en dos sub-listas, pero las
listas pequeas deben ser construidas tomando en forma alterna los nodos de la
lista original. Si la lista original fuera {7,10,12,8,17}, se obtienen dos listas
{7,12,17} y {10,8}.
7. Escribir un algoritmo que permita sumar dos polinomios P1 y P2 almacenados en
listas simples enlazadas.
8. Desarrolle un programa que lleve el registro de todos los estudiantes de la
asignatura computacin III, utilizando listas enlazadas. Los estudiantes aprobados
deben insertarse por el principio de la lista y los reprobados por el final. Los datos
requeridos por cada estudiante son: cedula, nombre y nota definitiva. El programa
debe permitir realizar las operaciones de: agregar estudiante, buscar estudiante
por cedula, eliminar estudiante, total estudiantes aprobados, total estudiantes
reprobados.
9. Disear un programa que maneje N cantidad de datos de tipo entero en dos listas
con insercin por el principio. Debe existir un ciclo de carga para la primera lista y
otro para segunda. Una vez cargados los datos en ambas listas, el programa debe
comparar las dos listas para verificar si ambas listas son iguales en tamao y
contenido. Una vez realizada la verificacin, el programa debe mostrar: si las listas
son iguales en tamao y contenido, si las listas son iguales en tamao pero; no en
contenido, o no tienen ni el mismo tamao ni el mismo contenido.
10. Crear una funcin que almacene N cantidad de datos de tipo real en una lista. Una
vez cargados los datos en la lista, la funcin debe calcular el promedio de todos los
datos. Posteriormente, debe cargar los datos menores o igual al promedio en una
segunda lista, y los mayores en una tercera lista. Al finalizar este proceso, la
funcin debe mostrar lo siguiente: a) Los datos cargados en la lista principal, b) El
promedio, c) Los datos cuyo valor sea igual o menor al promedio d) los datos que
sean mayores al promedio.

AERS..Pg. 34

Apuntes sobre punteros y listas enlazadas Computacin III


11. Se tiene un arreglo de 12 listas ( 1 x mes) que guarda la temperatura promedio/dia
de una ciudad durante un ao. Escriba una funcin que recibe el arreglo, busca y
devuelve la temperatura promedio mxima y el da y mes que ocurri.

12. Disear un programa que permita: pasar los datos de un archivo lista_mus2.txt
a una lista enlazada, mostrar los datos cargados en la lista y permitir buscar los
datos de un intrprete en particular. Donde el campo Datos de cada nodo estara
conformado por los siguientes campos: fecha, hora, tamao del archivo (mp3),
intrprete y titulo.

13. Un sistema de control de


depsitos de un banco tiene
la
estructura
que
se
muestra.

Disee un algoritmo, usando


listas
de registros, que
permita consultar el saldo de
las cuentas de un cliente
utilizando la cedula

CLIENTES
CUENTA CEDULA
100
7777
101
1234
102
9876
200
7777
:

OPERACIONES
CUENTA COD MONTO
100
D
20000
100
R
15000
200
R
12000
101
D
30000
100
R
10000
:

Para el diseo se debe tener en cuenta:


La operaciones puede ser D/R Deposito/Retiro, que se representan en el
cdigo de la operacin (COD). El saldo de la cuenta es SALDO = Depsitos
- Retiros.
Un cliente puede tener Varias cuentas y una cuenta puede tener varias
operaciones de Deposito/Retiro.

AERS..Pg. 35

Anda mungkin juga menyukai