-
#1 Lección 23: Memoria dinámica - Punteros.
Lección 23: Memoria Dinámica - El tipo Puntero
Introducción:
Hemos llegado al fin al tema más importante de este tutorial, el que he venido nombrando desde hace tiempo y el que tal vez les haga quemar bastante más las neuronas, sí, más. Estoy hablando nada más y nada menos que de los Punteros. Antes que nada déjenme aclarar que el tipo Puntero no tiene nada que ver con el puntero del ratón.... jeje... :-) Bueno, pongámonos serios... ejem...
Como dije por ahí en alguna parte de este curso, todos los tipos que hemos visto hasta ahora son estáticos, o sea que una vez declaradas las variables de cierto tipo, incluso tipos declarados por nosotros mismos, se asignará en tiempo de compilación la memoria RAM necesaria para guardar el mayor valor de dicho tipo y mantendremos siempre esa memoria reservada para nuestro uso, la utilicemos o no. Un ejemplo bien sencillo son los arreglos con tope. Declarado un arreglo de N cantidad de celdas, al compilar el programa se dirá que reservemos en memoria el espacio suficiente para guardar datos en todas ellas. Supongan que N es igual a 1000 en un arreglo con tope para guardar quién sabe qué cosa; al compilar el programa se asignará memoria para mil celdas aunque usemos solo una o dos, lo cual es un desperdicio. Tengan en cuenta que un programa normalmente mantiene registros de muchos miles de datos, o simplemente disponemos de poca memoria disponible, por ejemplo, un celular de hoy en día.
Es aquí donde entra en juego el tipo Punteros, ya que nos permitirá pedir memoria en tiempo de ejecución, tanta como necesitemos, y devolverla cuando queramos. Por eso llamamos a este tema Memoria Dinámica.
Utilizando la estructura de los punteros, por ejemplo, podemos hacer que nuestro programa de productos de almacén pida memoria para guardar los datos de un producto cada vez que el usuario ingresa uno nuevo, y liberar dicha memoria cuando se eliminan los productos, siempre ocupando el espacio en memoria únicamente de lo que estamos guardando, o sea, nada se desperdicia. Ese tipo de tareas las haremos con una estructura llamada Listas Encadenadas, pero las veremos en la lección que sigue a esta, así que vallamos allá primero con los punteros.
-----------------------------------------------------------------------------
El tipo Puntero:
Un puntero es un tipo, al igual que un arreglo o un registro, pero es un tipo muy especial ya que hace referencia directa a un lugar específico de la memoria RAM pero no es parte de ese lugar, o sea, una variable del tipo puntero es entonces un apuntador a un lugar de memoria, una dirección, una flecha o como ustedes quieran visualizarla mejor. Todas las variables de los tipos estáticos (todos los que hemos visto hasta el momento) nos referencian el lugar de la memoria en el que están, por esto son llamadas variables referenciadas y por lo tanto cambiando el valor de la variable cambiamos el valor del lugar de la memoria, sin embargo con un puntero no es así, ya que su valor es la dirección de memoria a la que está mirando y por más que cambiemos el valor de ese lugar el puntero seguirá mirándolo a él, o sea que un puntero no apunta al lugar de la memoria en que está, sino a otro totalmente distinto.
¿Qué cornos estoy diciendo? Pues, un puntero es una variable y como tal él tiene su lugar de memoria asignado para guardar allí una dirección de otro lugar de memoria con el cual podremos trabajar. Una variable referenciada "apunta" al mismo lugar en que está.
Tal vez leyendo eso ustedes dirán: Pero ya hemos leído de flechas y lugares de memoria a los que apuntan, y yo les diré que tienen razón, pero la diferencia ahora es que podemos controlar a esas flechas y además al lugar al que apuntan. Ya comprenderán esto un poco mejor a medida que avancemos.
Declaración:
Veamos entonces en un ejemplo como definir el tipo puntero hacia enteros:
Código:
TYPE
MiPuntero= ^integer;
Como pueden observar, al tratarse de un tipo debe ir declarado debajo de la palabra reservada TYPE, luego le damos un nombre seguido del signo de igual tal como si fuera un registro, un arreglo u otro tipo definido por nosotros mismos. Luego escribimos el acento circunflejo (^) seguido inmediatamente del tipo al que apuntaremos sin separar por espacios. En este caso hemos definido un tipo puntero llamado MiPuntero que apuntará a datos del tipo integer, o sea, apuntaremos a números enteros, pero podría ser cualquier otro, sea primitivo de Pascal o declarado por nosotros mismos. En este último caso dicho tipo debe estar declarado antes que el puntero, de lo contrario el compilador no lo reconocerá y nos dará un error.
Como en cualquier tipo en pascal, para el tipo puntero también debemos definir luego una variable de ese tipo para poder trabajar con él. En este ejemplo:
Código:
VAR
puntero1: MiPuntero;
Hasta este punto solo tenemos una variable del tipo puntero, o sea una dirección hacia un lugar de memoria en el que guardaremos un número, pero no está inicializada por lo cual no apunta a nada, tiene basura y con ella no se puede trabajar. Cualquier intento de trabajo con un puntero no inicializado terminará probablemente (dependerá del compilador) con un error en tiempo de ejecución lo cual no es para nada recomendable, por lo tanto SIEMPRE INICIALIZEN SUS PUNTEROS, o mejor dicho SIEMPRE INICIALIZEN SUS VARIABLES. Se que ya dije eso antes, pero nunca está de más recordarlo, aunque pueden verlo en todos y cada uno de los ejemplos de programas en este manual.
Entonces lo primero que debemos hacer es inicializar nuestro puntero, y para esto tenemos dos maneras:
- Lo inicializamos de la nada: o sea, pedimos una celda vacía de memoria a la que apuntar para luego poder asignarle valores a esta y trabajar con ella como con cualquier variable.
- Le asignamos la dirección de otro puntero ya inicializado.
A diferencia de cualquier tipo, a un puntero no podemos asignarle valores, o sea, no podemos dar una dirección específica de una celda y decirle apuntá para allí, excepto si le pasamos la dirección de otro puntero o si le damos el valor de una constante especial, pero esto lo veremos más adelante. Ahora veamos como inicializar un puntero desde la nada.
Otro modo de declarar un puntero es, al igual que con arreglos, de forma anónima:
Código:
VAR puntero1: ^integer;
-----------------------------------------------------------------
El procedimiento NEW y el operador ^:
Bien, un puntero no se inicializa como cualquier variable porque como ya dije no podemos ir y asignar un valor específico tal y como lo hacemos con los enteros (podríamos pero no nos concierne ahora), los caracteres y demás. Simplemente pediremos una celda de memoria y esta se asignará automáticamente en función de lo que exista disponible; nuestro puntero se asignará a sí mismo la dirección de esta nueva celda. Si se diera el caso en el que no haya suficiente memoria RAM tendríamos algún problema, nuestro programa se caería, pero esto no sucederá para nosotros ya que nuestros programas no usarán casi nada. Para darle un valor inicial a un puntero tenemos que utilizar el procedimiento NEW, el cual simplemente llevará entre paréntesis el nombre de nuestra variable puntero. En nuestro ejemplo sería:
con lo cual ahora tenemos una celda de memoria asignada para nuestro trabajo. Nuestro puntero tiene una dirección fija, o sea, tiene un valor, pero el lugar de la celda al que apunta contiene basura, tal como sucede con las variables normales. Si definimos una variable del tipo integer y no la inicializamos esta quedará con un valor que dependerá de nuestro compilador, lo cual no es para nada seguro. Gráficamente tenemos lo siguiente:
![]()
Nuestra variable puntero1 apunta hacia un lugar preciso pero no sabemos qué hay allí. Ahora bien, si un puntero contiene la dirección de la memoria pero no su valor ¿cómo modificamos lo que existe allí?
En una variable común y corriente como las que conocemos, simplemente al escribir su nombre estamos referenciando al lugar de la memoria que ocupa y su contenido, con lo cual podemos acceder a él directamente y modificarlo, mostrarlo en pantalla, asignarle un valor leído de la entrada estándar o hacer cualquier operación. Con un puntero podemos hacer lo mismo ya que PASCAL nos da la estructura para trabajar con el valor de la memoria a la que apunta como si fuera una variable referenciada:
De este modo estamos asignando el valor 10 a la celda de memoria, tal como si fuera una variable entera común. Podemos entonces decir que puntero1^ es el identificador de la variable en esa celda. Lo que intento decir es que del mismo modo que en una declaración del tipo
utilizamos al identificador X para trabajar con los valores de la memoria, en una declaración del tipo
Código:
VAR
X: UnTipoPuntero;
utilizamos al identificador X^ para trabajar con el valor de la memoria. De este modo podemos hace con X^ todo lo que se puede hacer con las variables comunes, sea READ, READLN, WRITE, WRITELN, así como también podemos utilizarlos en asignaciones, operaciones, comparaciones y demás.
Entonces, hay algo que tiene que quedar muy claro. Dado un tipo puntero y una variable X del tipo puntero, una cosa es nombrar a X (el puntero en sí) y otra es nombrar a X^ (el lugar al que apunta X). Diremos que X contiene la dirección en la que está X^. Si lo vemos gráficamente sería así:
![]()
En esta figura pueden ver el código PASCAL completo de lo que hemos visto hasta ahora sobre punteros. Creamos el tipo MiPuntero como puntero a enteros, luego declaramos la variable puntero1 de ese tipo, pedimos una celda de memoria utilizando el procedimiento NEW, para luego poder asignarle el valor entero 10 con lo cual termina nuestro programa.
Algo muy pero muy importante es que no pueden hacer referencia a puntero1^ si antes no han solicitado una celda de memoria ya que si nuestro puntero no apunta a ningún lado ¿qué es entonces puntero1^?
Como dije antes, trabajar con un puntero no inicializado nos dará un error en tiempo de ejecución, y no queremos eso jamás. Las variables del tipo puntero, a pesar de contener una dirección de memoria, son variables y por tanto su espacio de memoria se asigna en tiempo de compilación.
ATENCIÓN a esto último: El espacio que se asigna en tiempo de compilación es el del puntero , o sea, el espacio en el que se guardará la dirección de memoria; lo que se pide y devuelve de manera dinámica es la memoria de los datos A LOS QUE APUNTARÁ EL PUNTERO.
¿De que sirven entonces? No es lo mismo mantener estático el volumen de memoria necesario para almacenar a varios punteros que para almacenar los tipos a los que estos apuntarán. Veamos un ejemplo para que comprendan esto. Supongan la siguiente declaración:
Código:
TYPE
RegistroPersona= Record
Nombre: String;
Edad: Integer;
Peso: Real;
End;
Persona= ^RegistroPersona; //Un puntero al registro.
VAR
pers1: Persona; //Variable del tipo puntero a un registro. La variable pers1 es un puntero a un registro, o sea, contendrá la dirección de memoria en la que guardaré un registro con tres datos, sin embargo pers1 solo guarda una dirección, lo cual es estático para cualquier tipo de puntero y es infinitamente menor en tamaño que lo que se necesita para almacenar un registro. Por lo tanto es mejor mantener un volumen de memoria estático para almacenar punteros que para almacenar estructuras de datos complejas. Si tengo que almacenar tres registros, es mejor ocupar el espacio de tres punteros que soliciten la memoria cuando haga falta y la liberen luego, que mantener siempre ocupado el espacio para tres registros, los cuales pueden ser enormes. Imaginen un arreglo de registros tal y como usamos en nuestro programa de productos; dicho arreglo es una estructura muy compleja y conlleva mucha memoria ¿no sería mejora administrarla de otra manera? Pues sí, y pronto veremos como hacerlo.
Ahora vean el siguiente código e intenten darse cuenta de qué saldrá en pantalla si ptr1 y ptr2 son punteros a enteros:
Código:
new(ptr1);
new(ptr2);
ptr1^ := 12;
ptr2^ := ptr1^ + 4;
readln(ptr1^); (* suponga que se ingresa 3 *)
writeln(ptr2^ * ptr1^) (* ¿qué despliega? *)
-------------------------------------------------------------------
Alias de variables:
Veamos lo siguiente paso a paso:
Código:
TYPE
puntero= ^integer;
VAR
p1, p2: puntero;
Tenemos hasta este momento lo siguiente:
![]()
donde ninguna de nuestras variables está inicializada y por lo tanto no podemos hacer nada con ellas. Sigamos entonces y veamos lo que sucede:
![]()
Como pueden ver hemos solicitado un lugar de memoria para p1, o sea lo inicializamos de la nada mediante NEW y le damos su espacio para almacenar un entero; dicho espacio contiene algún valor desconocido (basura). La variable p2 sigue sin inicializar por lo que contiene una dirección que no sirve para nada y que solo nos traerá problemas hasta que le demos un valor.
Ahora hacemos lo siguiente: con lo cual obtenemos el siguiente resultado:
![]()
Hemos asignado a p2 el valor de p1, o sea, le hemos dicho a p2 que apunte hacia la misma celda que p1. Cuando dos o más punteros apuntan a una misma celda decimos que son alias. Esto puede ser muy útil, pero a la vez es muy peligroso ya que al modificar la celda de uno modificamos la del otro, tal como sucede con el pasaje de parámetros por referencia.
El pasaje de parámetros por referencia es justamente esto, trabajar con alias de variables, por tanto lo que Pascal hace en este tipo de pasaje d eparámetros es crear punteros que apunten a lo mismo. Por este motivo existen lenguajes que no tienen incorporado el pasaje por referencia ya que los punteros pueden cumplir esto perfectamente. Un ejemplo de lenguaje que no maneja el pasaje de parámetros por referencia es Java, ni tampoco los punteros, al menos no del modo en que los usamos en pascal, C o C++.
Ahora veamos una asignación similar pero a su vez muy diferente:
Sean p1 y p2 punteros a caracteres (char), dado el siguiente fragmento de código tenemos la siguiente situación:
Código:
New(p1);
New(p2);
p1^:= 'c';
p2^:= 'j';
![]()
Noten ahora la diferencia entre estas dos asignaciones suponiendo que las mismas son escritas luego del código anterior:
![]()
![]()
Como podrán observar, en el primer caso hemos copiado el contenido de la celda de p2 en la de p1 con lo cual ambas variables (p1^ y p2^) valen lo mismo, pero los punteros p1 y p2 son distintos. Esto es igual a hacer una asignación del tipo X:= Y siendo X e Y variables de un mismo tipo.
Ahora vean el segundo caso donde en vez de usar p1^ y p2^ usamos p1 y p2 directamente. Como podrán observar p1 pasa a apuntar al mismo lugar que p2 transformándose ambos en alias, sin embargo la celda a la que apuntaba p1 originalmente queda sin nadie que la apunte por lo cual ya no es posible acceder a ella (nadie la referencia), pero sigue conteniendo su valor guardado y por tanto sigue ocupada, no ha sido liberada, pero no podemos ya hacer nada con ella. La forma correcta de hacer esto habría sido liberar primero la celda de p1 de modo que este quede indefinido y luego hacer la asignación
.
Este tipo de errores es muy común y pronto veremos alguno más. El trabajo con la memoria dinámica requiere muchísima atención para hacerlo bien y controlar todo correctamente. Por eso muchos programadores sostienen que es mejor tener un sistema que se encargue de liberar la memoria automáticamente cada cierto tiempo como lo hace Java y otros no. Yo me inclino por los que no, pero es una simple opinión personal.
-------------------------------------------------------------------
Un uso incorrecto de NEW:
Si hacemos NEW de un puntero ya inicializado estaremos pidiendo un nuevo espacio de memoria para él, con lo cual apuntaremos al nuevo lugar y dejaremos el otro en la nada, algo parecido al caso anterior. Sea p un puntero a caracteres:
Código:
NEW(p); p^:= 'a';
![]()
Ahora, luego de estas instrucciones hacemos ![]()
Como verán, al hacer NEW de un puntero obtenemos un nuevo lugar en la memoria sin importar lo que hubiera antes. De este modo si el puntero estaba indefinido lo definimos, y si ya estaba definido lo redefinimos sin importar lo que había antes. De este modo, si ya teníamos un lugar de la memoria asignado, obtenemos uno nuevo a costa de abandonar el otro pero dejándolo ahí, con o sin valor asignado, pero no liberado y sin posibilidad de volver a acceder a él.
Tengan esto siempre en mente: NEW nos da un nuevo espacio de memoria y nos apunta hacia él, no importa lo que haya sucedido hasta el momento, NEW define nuevamente de cero al puntero y ya. Si hacemos treinta NEW seguidos de un mismo puntero habremos creado treinta lugares de memoria pero solo apuntaremos al último creado y los otros veintinueve quedarán en nuestra memoria, ocupando lugar, pero sin posibilidad de ser accedidos o usados.
Por suerte los sistemas operativos se encargan de liberar la memoria mal usada, pero un programa que hace esto se vuelve inestable y sobretodo muy ineficiente, más aún cuando los recursos del sistema son escasos; corremos con el riesgo de que nuestro programa se caiga de pronto y no se puedan guardar los datos trabajados hasta el momento.
------------------------------------------------------------------------
El procedimiento DISPOSE:
Así como se nos hace necesario solicitar memoria cuando haga falta, se nos hará necesario liberarla cuando ya no la necesitemos. Hemos visto que, dado un puntero a algún tipo determinado, el procedimiento NEW nos asigna una porción de memoria y su dirección para que podamos acceder a ella. Por ejemplo, dado un puntero p a cualquier tipo, si hacemos NEW(p) obtenemos nuestra porción de memoria a la cual llamaremos p^ y su dirección quedará almacenada en p. Así como es de fácil solicitar memoria lo es el liberarla. Teniendo un puntero ya inicializado, utilizamos el procedimiento DISPOSE para liberar su porción de memoria, por ejemplo, sea el mismo puntero p a algún tipo específico y estando p ya inicializado utilizamos:
De este modo la porción de memoria se libera para que el sistema pueda utilizarla y p queda nuevamente indefinido, o sea, queda como si nunca lo hubiéramos inicializado.
Vimos anteriormente que si hacemos sucesivos NEW de un mismo puntero vamos solicitando memoria en cada uno, pero vamos dejando perdidas las porciones de memoria de los NEW anteriores quedándonos solo con la del último, lo cual es un desperdicio porque el sistema cree que las porciones no referenciadas están igualmente siendo utilizadas y eso no es así; en su momento el sistema eliminará las porciones de memoria no referenciadas por nadie. Ahora ¿qué pasa si hacemos sucesivos DISPOSE de un mismo puntero? Copien este código en su compilador y observen el resultado:
Código:
PROGRAM SucesivosDispose;
TYPE
TipoPuntero= ^Char;
VAR
puntero: TipoPuntero;
BEGIN
writeln('Presione ENTER para solicitar memoria al sistema');
readln;
new(puntero);
writeln('Hemos solicitado memoria al sistema.');
writeln('Presione ENTER para liberar la memoria solicitada.');
readln;
dispose(puntero);
writeln('Hemos liberado la memoria solicitada.');
writeln('Presione ENTER para liberar nuevamente.');
readln;
dispose(puntero);
writeln('Todo ha salido bien, presione ENTER para salir.');
readln;
END. Como verán, primero solicitamos memoria inicializando puntero mediante NEW con lo cual obtenemos nuestro espacio para almacenar un carácter así como la dirección de ese espacio. Luego liberamos ese espacio mediante DISPOSE con lo cual la información contenida en él se pierde, o sea, deja de existir puntero^ y puntero queda indefinido, o sea, su dirección es cualquier cosa. Luego nuevamente intentamos liberar memoria, sin embargo puntero no apunta a nada específico, contiene una dirección errónea porque está indefinido, por lo tanto no podemos liberar memoria de un lugar al que no podemos llegar lo cual genera un error en tiempo de ejecución y nuestro programa se cerrará abruptamente (si no es así depende del compilador, sin embargo dejar eso a suerte es un error en la metodología de programación). Como ven, utilizar DISPOSE con punteros no inicializados es un error grave y hay que tener mucho cuidado a la hora de utilizar este procedimiento. Por eso es complicado administrar bien la memoria que utiliza el programa, más aún cuando la cosa se vuelve compleja.
Ahora que saben liberar memoria pueden hacer de forma correcta lo que señalé como error anteriormente, o sea, cuando tenemos dos punteros inicializados pero queremos transformarlos en alias, o sea, que ambos apunten a lo mismo, debemos primero liberar la memoria de uno de ellos y luego cambiar su dirección, de este modo no habrá porciones de memoria que se queden perdidas en el espacio:
![]()
![]()
Nota: Sea un puntero p hacia cualquier tipo específico, si p no está inicializado no podemos referenciar a p^ porque tal lugar de memoria no existe. Hacerlo causa un error en tiempo de ejecución. De este modo, referenciar al lugar que apunta un puntero luego de un procedimiento DISPOSE es un error lógico.
Seguiremos con un último concepto para luego ver algunos ejercicios antes de continuar. El siguiente tema luego de esto será Listas Encadenadas, lo cual está basado en el mero uso de punteros.
---------------------------------------------------------------
UN ERROR COMÚN: Alias y DISPOSE:
Supongan que tenemos dos punteros, p y q, apuntando a una misma celda, o sea, p y q son alias:
![]()
Si hacemos DISPOSE(p) ¿qué pasa?
![]()
Ahora q quedó también indefinida porque la porción de memoria a la que apuntaba ya no existe. Tengan mucho cuidado con este tipo de situaciones.
-------------------------------------------------------------
El puntero nulo, la constante NIL:
Hemos visto que una variable de un tipo puntero es una variable que contiene una dirección hacia una porción de memoria en la que se almacenará un tipo de dato especificado. Vimos también que no es posible asignar valores específicos a un puntero excepto si le pasamos el valor de otro puntero ya inicializado anteriormente. Existe, sin embargo, una constante general para cualquier tipo de puntero la cual contiene el valor nulo y que podemos asignarle a cualquiera de nuestros punteros; esta constante es llamada NIL (en otros lenguajes se conoce como NULL). Sea p un puntero a un tipo cualquiera, podemos hacer:
en cualquier momento. NIL es la forma correcta de decir que un puntero no está apuntando a nada, que es nulo, lo cual no es lo mismo que decir que no está inicializado. Un puntero que apunta a NIL no tiene memoria asignada, pero tiene un valor correcto, asimismo, como no tiene celda asignada no podemos acceder a ella mediante el operador ^, o sea, para nuestro p, si p=NIL no podemos acceder a p^ porque no existe, si lo hacemos tendremos un error en tiempo de ejecución, por eso siempre que estemos trabajando en situaciones donde un puntero pueda valer NIL debemos preguntarlo primero. Esto es posible porque NIL admite comparaciones, o sea, si tenemos un puntero p podemos hacer por ejemplo:
o por ejemplo
Código:
IF p<>NIL THEN...
o en cualquier cosa ya que podemos utilizarlo como condición:
Código:
WHILE p<>NIL DO...
Código:
REPEAT ...UNTIL p=NIL
etc.
Asignar el valor NIL a un puntero ya inicializado sin antes hacer DISPOSE de él es un error ya que cambiaremos la dirección del puntero sin antes liberar la memoria con lo cual quedaría esta perdida en la nada. Es el mismo caso que asignar a un puntero ya inicializado el valor de otro puntero sin antes liberar sus memoria.
IMPORTANTE: Siempre que vayan a asignar a un puntero ya inicializado algún valor deben liberar la memoria asignada para él.
Hacer DISPOSE de un puntero NIL es un error de programación, puede no causar problemas, pero no debe hacerse jamás.
¿Para qué sirve el valor NIL? ¿De qué sirve decir que un puntero no apunta a nada? Pues lo veremos dentro de poco, primero realicen estos ejercicios simples antes de continuar.
---------------------------------------------------------------------------
Ejercicio1:
a) Determinen cuáles de las siguientes proposiciones son válidas:
Código:
new(apun1)
new(apun1^)
apun1 := apun3
apun2^ := apun2^ + apun1^
apun1 := NIL
apun4^ := NIL
writeln(apun2, apun3)
read(apun1^, apun4^)
b) Determinen cuáles de las siguientes proposiciones son válidas:
Código:
1.apun1 := NIL
2.apun2 := new(apun1)
3.dispose(apun3)
4.apun3^ := NIL
5.apun3 := apun4 AND (apun3 = NIL)
6.apun4^ := NIL
---------------------------------------------------------------
Ejercicio 2:
1. Consideren las declaraciones que se hicieron en el Ejercicio 1. Determinen la salida que produce el siguiente código:
Código:
new(apun1);
new(apun2);
new(apun3);
apun2 := apun1;
apun1^ := 2;
apun2^ := 3;
apun3^ := A;
writeln(apun1^, apun2^, apun3^)
2. ¿Tiene algún error el siguiente código? Supongan que apun1 es una variable de apuntador que hace referencia a una variable entera.
Código:
new(apun1);
read(apun1^);
writeln(apun1^);
dispose(apun1);
writeln(apun1^)
---------------------------------------------------------------
Ejercicio 3: Consideren las declaraciones realizadas en el Ejercicio 1, determinen la salida del siguiente código:
Código:
new(apun3);
new(apun1);
apun3^ := Z;
apun2 := NIL;
apun4 := NIL;
IF (apun3 <> NIL) AND (apun2 = NIL) THEN
writeln (Código A);
IF apun3^ = Z THEN
writeln (Código Z)
ELSE
writeln (Código X)
---------------------------------------------------------------
Ejercicio 4: Dado el siguiente código en Pascal
Código:
Type
TipoVehiculo = (barco, camion);
Transporte = record
capacidad : INTEGER;
case vehiculo : TipoVehiculo of
barco : (habitaciones : INTEGER);
camion : ();
end;
var a, b, c : ^Transporte;
begin
new(a);
a^.capacidad := 30;
a^.vehiculo := barco;
a^.habitaciones := 4;
new(b);
b^.capacidad := 4;
b^.vehiculo := camion;
new(c);
c^.capacidad := 5;
c^.vehiculo := camion;
end. 1- ¿Cuál de las siguientes figuras representa el estado de la memoria? (tengan en cuenta que se omiten los nombres y valores de algunos campos para no recargar el dibujo).
![]()
2- Dada la instrucción: 2.1 ¿Cuál de las siguientes figuras representa el estado de la memoria posterior a la instrucción? (tengan en cuenta que en las figuras se omiten los nombres y valores de algunos campos para no recargar el dibujo).
![]()
2.2 ¿Sería correcto hacer dispose(b) antes de la instrucción? Justifiquen.
3- Dada la instrucción 3.1 Dibujen el estado de la memoria posterior a la instrucción.
3.2 ¿Qué valor tiene b^.capacidad?
3.3 Teniendo en cuenta que se quiere mantener las referencias a los componentes referenciados ¿sería correcto hacer dispose(c ) después de la instrucción? Justifiquen.
Realicen todos estos ejercicios antes de continuar. Pregúntenme acerca de todo aquello que les provoque dudas, les responderé a la brevedad. Si no logran comprender estos conceptos no podrán trabajar con lo que viene a continuación, y mucho menos pensar en aprender Java o cualquier lenguaje de objetos. ¡¡¡Adelante!!!
¿Este tema te pareció interesante? Compártelo!
¿No es lo que buscabas? Intenta buscar un tema similar
0 comentarios
/ 344 Visitas