LOS PUNTEROS CAPITULO 8

10:55:00 Unknown 0 Comments

Lo primero: hay que entender muy bien lo que es un puntero, porque al principio puede se un poco complicado. Un puntero es un tipo de datos (como un INTEGER, por ejemplo) que lo que almacena no es ni un número ni una letra sino una dirección de memoria. Una posición de memoria es una "cosa" del tipo 0xxxxh:0xxxxh que identifica una posición de la memoria

CONVENCIONAL del ordenador. (Recordemos que el TP 7 sólo sabe hacer programas para 286, o sea: para modo real, por lo tanto sólo podemos usar la memoria convencional [esto en realidad es falso, se puede usar memoria XMS y EMS desde Pascal, pero eso ya es otro tema])
Se dice que un puntero APUNTA a la dirección de memoria.
Para declararlo tenemos el tipo POINTER, con lo que lo declaramos igual que cualquier otro tipo de datos:

Var
Puntero: Pointer;

¿Y esto para qué nos sirve? Pues así, con lo que sabemos ahora, de nada. Pero lo curioso de los punteros es que podemos decirle qué es lo que hay en la posición de memoria a la que está apuntando.

Me explico: el puntero "señala" a un lugar de la memoria de nuestro PC. Pues ese lugar podemos tratarlo como un BYTE, o como un WORD, como un STRING, como una estructura de datos o incluso como otro puntero!
En este caso la declaración cambia. En este caso se declara igual que si declarásemos una variable de un tipo concreto sólo que precedemos el nombre del tipo por el símbolo ^. (ALT+94)
Así para declarar un puntero de manera que la memoria a la que apunta sea tratada como un byte haríamos esto:

Var
Puntero: ^Byte;
Y lo mismo para una estructura:
Type
Tipo = record
Nombre : String;
DNI : Longint;
NIF : Char;
Calle : String;
Edad : Word;
End;
Var
Puntero : ^Tipo;

Este tipo de punteros se denominan de una manera especial, son punteros A el tipo de datos. Es decir, este último ejemplo sería un puntero A una estructura (o a la estructura Tipo), el anterior sería un puntero a un byte, etc... Y también pueden hacerse punteros a punteros, punteros a arrays de punteros, etc... pero eso es más complicado y vendrá más adelante.

Pero... ¿a dónde apunta un puntero? Eeeeiiinngg? ;) Me refiero: un puntero, nada más empezar el programa ¿apunta a alguna parte? ¿a dónde apunta? Pues lo más normal es que apunte a la dirección 0000:0000, ya que las variables suelen inicializarse a cero. Por lo tanto antes de usar el puntero tenemos que hacer que apunte al sitio que nosotros queremos. ¿Qué pasa si no lo hacemos? Pues si modificamos un puntero que apunta a 0000:0000... en esa zona de la memoria está la tabla de los vectores de interrupción, si lo modificamos nos cepillaremos esa tabla, con lo cual, en cuanto se genere una interrupción del temporizador (una cada 55 milisegundos) nuestro programa nos brindará un precioso cuelgue.

Este tipo de punteros puede ser más útil, porque... Ya que un puntero puede apuntar a cualquier parte de la memoria convencional... podemos modificar cualquier posición de memoria, tanto si pertenece a nuestro programa como si no. Por ejemplo, el WORD de la posición 040h:02h contiene la direción del puerto base para el COM2, para leerla sólo tenemos que hacer que un puntero a un word apunte a esa direción (040h:02h) y después leer lo que hay allí. Para hacer que un puntero apunte a un sitio determinado tenemos la función Ptr. La función Ptr acepta como parámetros el segmento y el offset de la direción de memoria y nos devuelve como resultado un puntero a esa direción. Así, es programa que lee la direción del COM2 quedaría así:

Var
Puntero_a_COM2 : ^Word;
Begin
Puntero_a_COM2 := Ptr($40,2);
WriteLn('La direción del COM2 es ',Puntero_a_COM2^);
End.

Una cosa importante a tener en cuenta es que no es lo mismo la direción de memoria que lo que hay en esa direción. El puntero sólo contiene la direción, no contiene lo que hay en ella. Es decir: el puntero sólo señala al WORD donde está la direción de COM2. El puntero NO almacena la posición del COM2. Por eso estas dos lieas son bien diferentes:

Puntero := 8;
Puntero^ := 8;

La primera intenta asignar el valor 8 como direción de memoria a la cual tiene que apuntar el puntero. Mientras que la segunda lo que hace es asignar el valor 8 a la direción de memoria a la que apunta el puntero. Es muy importante que esto quede claro.

Podríamos decir que Puntero^ es el elemento y Puntero es la direción del elemento.
Pero la utilidad de los punteros no acaba aquí ni mucho menos.
Creo que ya antes había dicho que las variables de un programa sólo podían ocupar 64 Kbs como mucho. Y... ¿qué ha pasado con en resto de los 640 Kbs de memoria? Pues esa memoria se puede usar mediante punteros.

Para pedir memoria tenemos la función Getmem y para liberarla una vez ya no la necesitamos tenemos Freemem. A ambas hay que pasarles un puntero y el tamaño de memoria que queremos reservar. Por ejemplo:
Getmem(Puntero, 30000);
Esto lo que haría sería reservar 30000 bytes de memoria y hacer que el puntero apunte al inicio de esos 30000 bytes. ¿A qué tipo de datos tiene que apuntar este puntero? A cualquiera. Si hemos declarado Puntero como un puntero a un byte podremos usar
"Puntero^ := valor;" para modificar el primer byte del bloque de memoria que hemos pedido.
¿Y qué pasa con el resto? Pues muy sencillo. Podemos "mover" el puntero para que apunte al segundo byte del bloque, o al tercero... Para eso tenemos las funciones INC y DEC. Con INC(PUNTERO); hacemos apuntar el puntero a el siguiente elemento.
PERO OJO!!! El siguiente elemento no siempre será el siguiente byte. Si hemos definido el puntero como un puntero a words cada vez que usemos INC avanzaremos 2 bytes (que es lo que ocupa un
WORD). Y DEC se usa igual y tiene la función inversa.
La pega que tiene este método es que nadie nos impide hacer 600.000 INCs y poner el puntero apuntando a una parte de la memoria que no hemos reservado. Y es que para bloques de memoria hay maneras más prácticas de manejarlos.
Para esto son muy útiles los punteros a arrays. Podemos definir un así:

Type
P = Array [0..29999] of Byte;
Var
Puntero : ^P;
Begin
Getmem(Puntero, 30000);
End.

De esta manera definimos un array que empieza en la direción a la que apunta el puntero. Esta direción, después del Getmem, será la direción del bloque de memoria. Y a partir de esa direción tenemos 29999 elementos más en el array. Cada elemento es un Byte, por lo tanto, a cada posición del array le corresponde un byte del bloque de memoria que hemos reservado.
Ahora, para leer/escribir en ese bloque sólo tenemos que hacer cosas como esta:
Puntero^[8] := Puntero^[15003] - 80;
Fácil ¿no? Pero... ¿qué pasa si queremos poner todo el bloque a cero? Podríamos hacer un bucle FOR que fuese poniendo las posiciones a cero una por una, pero hay una manera mejor. Tenemos la instrución FillChar. Esta instrución llena con un valor dado el número de posiciones que le digamos a partir de la posición que le demos. Se usaría así:
FillChar(Puntero^, 30000, 0);
Atención a que ponemos "Puntero^" y no "Puntero". ¿Por qué? Pues porque lo que queremos llenar de ceros no es el puntero sino la memoria a la que apunta ese puntero. (Ya vereis que cuelgues más majos os salen cuando se os olvide esto ;)
Y... ¿qué pasa si tenemos 2 bloques de memoria y queremos copiar todo lo que haya en uno al otro? Pues podríamos hacer un FOR, pero hay una función específica para eso: Move. Move se usaría así:
Move(Puntero_Fuente^, Puntero_Destino^, Tamaño);
Cuando ya hayamos hecho todo lo que queríamos con un bloque de memoria debemos liberarlo. Para eso usamos Freemem igual que si fuese GetMem: Freemem(Puntero, Tamaño). Hay que tener en cuenta que el puntero debe apuntar exactamente al mismo sitio que despuésde hacer el GetMem. Es decir: si hemos usado algún INC o DEC con él debemos asegurarnos que lo dejamos tal y como estaba antes de llamar a Freemem.
Ahora supongamos que tenemos un puntero que apunta a una estructura y queremos reservar la memoria justa para que quepa esa estructura. Podemos contar los bytes que ocupa esa estructura (no estaría mal para practicar ;) o podemos usar New en lugar de Getmem. A New sólo hay que pasarle el puntero: New(Puntero);
Y el equivalente a Freemem entonces será Dispose, que usaremos igual que New.
Y ahora el punto más interesante. Antes de empezarlo os presento a NIL. NIL es una constante, pero no es una constante numérica sino un puntero. Y ¿a dónde apunta este puntero? En la ayuda dice que no apunta a ninguna parte. Y... aunque un puntero SIEMPRE apunta a alguna parte haremos como si fuese verdad que no apunta a nada.
Así podemos decir que NIL es el puntero nulo. Ahora empecemos: declaramos un puntero así:

Type
PEsctructura = ^Estructura;
Estructura = record
Nombre : String;
Siguiente : PEstructura;
End;
Var
Puntero : PEstructura;
Bien... y reservamos memoria para el puntero:
New(Puntero);

En este momento ya tenemos un puntero apuntando a una estructura que consta de una cadena de caracteres y de otro puntero.
Rellenamos el primer campo:
Puntero^.Nombre := 'Pepito';
Y pedimos memoria para el segundo (ya que un puntero no podemos usarlo si no hemos pedido memoria para él o le hemos hecho apuntar a alguna parte).
New(Puntero^.Siguiente);
Ahora el puntero Siguiente apunta a una estructura de datos que consta de 2 campos: una cadena de caracteres y un puntero. ¿os suena de algo? ;)
Podemos volver a pedir memoria para el puntero de esta segunda estructura y luego pedir memoria para el puntero de la tercera, etc, etc...
¿Y qué ventaja tiene esto? Pues que podemos irlo repitiendo todas las veces que queramos hasta que se nos acabe la memoria. En un array hay que definir el número de elementos a la hora de escribir el programa, pero de esta manera podemos ir llenanado más y más elementos sin tener un límite prefijado.
La pega ahora es... ¿Cómo se trabaja con esto? Pues bien, para dejarlo lo más claro posible os pongo un ejemplo sencillo:

Type
P2 = ^Estruc;
Estruc = record
Cadena : String;
Siguiente : P2;
End;
Var
Puntero, PAux : P2;
Cadena : String;
Begin
WriteLn('Memoria libre: ',maxavail);
WriteLn('Escribe nombres. (una linea en blanco para terminar)');
New(Puntero);
PAux := Puntero;
PAux^.Siguiente := Nil;
Repeat
ReadLn(Cadena);
PAux^.Cadena := Cadena;
if Cadena <> '' Then
Begin
New(PAux^.Siguiente);
PAux := PAux^.Siguiente;
PAux^.Siguiente := Nil;
End;
Until Cadena = '';
WriteLn;
WriteLn('Has escrito:');
PAux := Puntero;
While PAux^.Siguiente <> Nil do
Begin
WriteLn(Paux^.Cadena);
Puntero := Paux;
PAux := Paux^.Siguiente;
Dispose(Puntero);
End;
Dispose(Paux);
WriteLn('Memoria libre: ',maxavail);
End.


Y esta ha sido la última entrega del curso, espero que les aya servido, pueden seguir con el siguiente curso que es Metodología de la Programación Pascal

0 comentarios: