Monografias.com > Sin categoría
Descargar Imprimir Comentar Ver trabajos relacionados

Manual de FreePascal (Parte 1) (página 4)




Enviado por rafaelfreites



Partes: 1, 2, 3, 4, 5, 6

Es posible emplear los mismos nombres en ámbitos
diferentes pero no es recomendable a menos que estemos muy
seguros de que
no nos vamos a confundir.

Sobrecarga de procedimientos y
funciones

Es posible definir más de una función o
procedimiento
con el mismo identificador pero con parámetros distintos.
Esta característica permite ahorrar trabajo al
programador ya que con un mismo nombre puede englobar más
de una función que realice operaciones
parecidas, por ejemplo.

Para sobrecargar una función o procedimiento
sólo hay que redeclararlo y tener en cuenta de que tienen
que cambiar los parámetros. Si no cambian los tipos de los
parámetros el compilador no sabrá distinguir las
dos funciones y presentará un error de
compilación.

Vamos a implementar tres funciones sobrecargadas que
compararan pares de datos de tipo
string, enteros y booleanos. Las funciones devolverán cero
cuando los dos elementos sean iguales, 1 cuando el primero sea
mayor y -1 cuando el segundo sea mayor que el primero.

function Comparar(a, b : integer) :
integer;

begin

if a > b then Comparar := 1;

if a < b then Comparar := -1;

if a = b then Comparar := 0;

end;

function Comparar(a, b : string) :
integer;

begin

if a > b then Comparar := 1;

if a < b then Comparar := -1;

if a = b then Comparar := 0;

end;

function Comparar(a, b : boolean) :
integer;

begin

if a > b then Comparar := 1;

if a < b then Comparar := -1;

if a = b then Comparar := 0;

end;

En este caso el código
de las tres funciones coincide pero no tiene porque ser
así en otros casos. Podemos llamar a las funciones
especificando dos enteros, dos strings o dos booleanos pero no
ninguna otra combinación que no hayamos especificado.
Incluso, podemos sobrecargar métodos
con distinto número de parámetros. Por
ejemplo :

function Comparar(a : integer) :
integer;

begin

Comparar := Comparar(a, 0);

end;

En este caso si no especificamos el segundo entero
entonces la comparación se hará con cero. Suele ser
habitual definir métodos sobrecargados con menos
parámetros cuando se trata de casos más habituales
o con parámetros por defecto.

La flexibilidad de la sobrecarga en FreePascal permite
incluso a admitir la mezcla de funciones y procedimientos.
Podemos definir otro procedimiento Comparar en el cual el
resultado se guarde en el parámetro c.

procedure Comparar(a, b : integer; var c :
integer);

begin

if a > b then c := 1;

if a < b then c := -1;

if a = b then c := 0;

end;

En este caso Comparar no devuelve nada pues es un
procedimiento y se empleará el parámetro c para
guardar el resultado.

Pasar parámetros de tipo open
array

FreePascal admite un tipo especial de parámetros
que reciben el nombre de array abierto, open array, que permite
especificar un array de mida variable y tipo determinado como
parámetro de una función. Para hacerlo hay que
emplear la sintaxis array of tipoDato al parámetro en
cuestión.

Vamos a implementar una función que escriba una
serie de enteros pasados como parámetros.

procedure EscribirEnteros(n : array of
Integer);

var

i : integer;

begin

for i := Low(n) to High(n) do

begin

Write(' ', n[i]);

end;

end;

Obsérvese que tenemos que emplear High y Low pues
no sabemos el tamaño del array pasado como
parámetro. Los datos que podemos pasar a estos
parámetros son literales, constantes y variables. Los
literales tienen una sintaxis parecida a los literales de
conjuntos.
Podemos llamar la función con el literal
siguiente :

EscribirEnteros([1, 2, 8, 3]);

Esto escribiría en la pantalla :

1 2 8 13

También podemos pasar una variable como
parámetro. En este caso tendrá que ser un array de
tamaño fijo.

var

prueba : array[1..3] of integer;

begin

prueba[1] := 4;

prueba[2] := 2;

prueba[3] := 6;

EscribirEnteros(prueba);

end.

No hay ningún problema por pasar
parámetros por referencia o constantes. Por ejemplo en el
caso siguiente se llena el array con el valor del
índice al cuadrado.

procedure Cuadrado(var n : array of
Integer);

var

i : integer;

begin

for i := Low(n) to High(n) do

begin

n[i] := i*i;

end;

end;

Al ser un parámetro por referencia no podremos
pasar literales pero si variables. Es muy recomendable emplear
parámetros constantes siempre que sea posible pues la
función o procedimiento tardará menos tiempo en
ejecutarse.

No hay que confundir los parámetros array
abiertos con los array cerrados. En el caso siguiente se puede
apreciar la diferencia.

program ArraysAbiertos;

type

TArray3 = array[1..3] of Integer;

procedure Cuadrado(var n : array of
Integer);

var

i : integer;

begin

for i := Low(n) to High(n) do

begin

n[i] := i*i;

end;

end;

procedure Cuadrado3(var n : TArray3);

var

i : integer;

begin

for i := Low(n) to High(n) do

begin

n[i] := i*i;

end;

end;

var

prueba : array[1..4] of integer;

begin

Cuadrado(prueba); // Correcto

Cuadrado3(prueba); // Incorrecto pues prueba no es del
tipo TArray3

end.

Si el array prueba hubiera sido de 3 elementos y de
índice [1..3] (pero no [0..2] u otros) el compilador nos
hubiera permitido pasarlo a Cuadrado3. En el caso de los arrays
fijos no es posible pasar como parámetro un literal con la
sintaxis de los arrays abiertos aunque el parámetro sea
constante o por valor. De hecho, en funciones y procedimientos
con parámetros de tipo array fijo sólo es posible
pasar variables.

Funciones declaradas a
posteriori

En algunos casos muy extraños podemos necesitar
que dos funciones se llamen la una a la otra de forma
recíproca. El problema se encuentra en que si declaramos A
antes que B entonces A no puede llamar a B y viceversa. Para
resolver este problema podemos posponer la implementación
de la función a un punto inferior de forma que al existir
la declaración sea sintácticamente correcto
llamarla y el compilador permita la llamada mutua de
funciones.

Para indicar al compilador de que una función
será declarada más abajo hay que emplear la palabra
forward después de su declaración Vamos a
implementar un par de procedimientos llamados Ping y Pong que se
irán
pasando un entero que decrecerá. El ejemplo carece de
utilidad
práctica alguna.

procedure Ping(n : integer); forward; // Vamos a
implementarlo más abajo

procedure Pong(n : integer);

begin

if n > 0 then

begin

Writeln('Pong ', n);

Ping(n-1); // Llamamos a Ping

end;

end;

procedure Ping(n : integer); // Implementamos
Ping

begin

if n > 0 then

begin

Write('Ping ', n, ' ');

Pong(n); // Llamamos a Pong

end;

end;

begin

Ping(5);

end.

El resultado del programa
sería :

Ping 5 Pong 5

Ping 4 Pong 4

Ping 3 Pong 3

Ping 2 Pong 2

Ping 1 Pong 1

 

Es importante implementar, en algún punto del
programa posterior a la declaración forward, la
función o procedimiento. En caso contrario el compilador
indicará un error sintáctico. Tampoco es posible
declarar una misma función doblemente forward aunque
tampoco tenga ningún sentido hacerlo.

La función exit

Muchas veces nos interesará salir de una
función antes de que ésta termine o que no ejecute
más instrucciones. Sobretodo cuando en un punto ya
conocemos el resultado y no hay que realizar nada más. En
estos casos podemos salir de la función o procedimiento
con la orden Exit. Obsérvese, que después de llamar
a Exit ya no se ejecuta ninguna instrucción más de
la función o del procedimiento. No hay que llamar a Exit
dentro del bloque principal puesto que se terminaría el
programa, lo cual no suele ser recomendable.

function EsPrimo(n : integer) :
boolean;

{ Devuelve true si n es primo, false en caso contrario
Esta función es poco eficiente y es un ejemplo del uso de
Exit }

var

i : integer;

begin

if n < 0 then n := Abs(n); // Trabajaremos con
positivos

if n = 1 then

begin

// En el número 1 no tiene sentido hablar de
primalidad

EsPrimo := false;

Exit; // Salimos de la función

end;

// Comprobamos si es primo

for i := 2 to (n-1) do

begin

if n mod i = 0 then

begin // Es divisible por lo que no es primo

EsPrimo := false;

Exit; // Salimos de la función

end;

end;

// Si llegamos aquí es porque no se ha dividido
con los anteriores

EsPrimo := true; // por tanto es primo

end;

Devolver al estilo de C/C++

Las funciones de C y C++ devuelven un valor mediante la
palabra reservada return seguida de una expresión. Esto
hace que la función termine y devuelva el valor de la
expresión. En Pascal, la
asignación al identificador de la función no hace
que ésta termine por lo que muchas veces necesitaremos
añadir un Exit después de la asignación.
FreePascal extiende la función Exit con un
parámetro que es el valor que devolverá la
función.

Por ejemplo, podemos simplificar las dos instrucciones
siguientes :

EsPrimo := false;

Exit; // Salimos de la función

en la siguiente :

Exit(false);

Esta sintaxis es mucho más parecida a la orden
return de C/C++ y facilita el trabajo a
la hora de portar funciones de C a Pascal.

Recursividad

Ventajas de la recursividad

Muchas operaciones requieren procesos
repetitivos y se pueden implementar con sentencias del tipo for,
while o repeat. Algunos casos, pero, no se pueden diseñar
con sentencias iterativas puesto que ya no es posible o es muy
complejo. Como alternativa disponemos de una herramienta muy
potente, a la par que arriesgada y compleja, como es la
recursividad.

Donde tiene más utilidad la recursividad es en
procesos que podríamos llamar inductivos. Dada una
propiedad
P(n), donde n pertenece a los enteros, que se cumple para n0 y
que si se cumple para un n cualquiera implica que también
se cumple para n+1, entonces es muy probable que se tenga que
codificar mediante recursividad.

Qué es recursividad ? Hablamos de
recursividad cuando una función se llama a si misma, con
parámetros distintos, para llegar a un caso en el cual no
haya que segur llamándose a si misma y se pueda resolver
la llamada inicial.

La ventaja de la recursividad es poder escribir
algoritmos muy
eficaces y rápidos con pocas líneas de
código.

Un ejemplo sencillo : el
factorial

Sabemos que la operación factorial n! equivale a
n! = n (n-1) (n-2)…2 1 y 0! = 1. Una forma de codificación sin emplear la recursividad es
la siguiente :

function Factorial(n : integer) :
Integer;

var

i, temp : integer;

begin

temp := 1;

for i := 1 to n do

begin

temp := i*temp;

end;

Factorial := temp;

end;

Si queremos codificar el factorial de forma recursiva
hay que tener en cuenta el caso sencillo cuando n = 0, 0! = 1, y
que n! = n (n-1)! El código siguiente es la forma
recursiva del factorial :

function Factorial(n : integer) :
Integer;

begin

if n = 0 then

begin

Factorial := 1

end

else

begin

Factorial := n*Factorial(n-1);

end;

end;

Como se ve, siempre llega algún momento en el
cual ya no hay más llamadas recursivas puesto que n se
vuelve cero en algún momento (siempre la n inicial sea
mayor que cero, claro).

Es importante que nuestro código termine en
algún momento las llamadas recursivas, pues de no hacerlo
la llamada a las funciones sería infinita y
gastaríamos toda la memoria
dedicada a las variables locales (memoria stack) y
se produciría un error runtime llamado Stack Overflow que
provocaría que nuestro programa finalizara
anormalmente.

Punteros y
memoria

Variables estáticas

Hasta ahora todas las variables que hemos empleado se
gestionan de forma totalmente automática por el
compilador. ..este se encarga de añadir el código
necesario para crearlas al iniciar el programa y liberarlas
cuando termina. Reciben el nombre de variables
estáticas.

En algunos casos necesitaremos acceder directamente a
las posiciones de memoria de nuestras variables, en otros
necesitaremos zonas de memoria con tamaños especiales,
etc. En estos casos tendremos que emplear los
punteros.

Estructura de la memoria

La memoria de un ordenador está formada por un
conjunto determinado de celdas de un byte a las cuales podemos
acceder mediante su dirección. Esta dirección es
única para cada posición de memoria y viene
expresada, en ordenadores superiores al 386, por variables
enteras de 32-bits. De esta forma, teóricamente, se
podrían direccionar 232 posiciones de memoria diferentes
lo que equivale 4 Gb
de memoria RAM. En la práctica no se llega
nunca a esta cifra debido a imposibilidades técnicas
evidentes.

Punteros con tipo

Un puntero es una variable que apunta a una
posición de memoria. Los punteros con tipo se declaran
añadiendo el símbolo ^ (circunflejo) antes de su
tipo.

var

PunteroEntero : ^Integer;

Antes de poder emplear un puntero con tipo habrá
que reservar memoria. Para esto tenemos la función New que
devolverá una dirección a la memoria alojada. New
se encarga de reservar suficiente memoria para nuestro puntero
con tipo.

New(PunteroEntero);

Cuando ya no necesitemos el puntero es necesario
liberarlo. De esta forma el sistema se da por
enterado de que esta posición de memoria está libre
y puede ser ocupada por otros datos. Si no lo liberásemos,
el sistema entendería que está ocupado con lo que
no intentaría alojar más datos en esta
posición y malgastaríamos la memoria. A partir de
este momento ya no será válido acceder al contenido
del puntero. Para hacerlo emplearemos el procedimiento
Dispose :

Dispose(PunteroEntero);

Ya que PunteroEntero sólo apunta a una
dirección de memoria será necesario poder acceder a
su contenido. Para esto hay que emplear el operador de
desreferenciación ^.

PunteroEntero^ := 10;

Obsérvese que es radicalmente diferente la
asignación PunteroEntero^ := 10; que la
asignación PunteroEntero := 10; En este último
caso hacemos que el puntero PunteroEntero apunte a la
dirección de memoria 10. Este es un error bastante
habitual ya que el compilador autoriza las asignaciones directas
al compilador. En este caso es más notable pues el puntero
es de tipo entero.

program PunterosConTipo;

var

PunteroEntero : ^Integer;

begin

New(PunteroEntero);

PunteroEntero^ := 5; // Asignamos 5 a la
posición apuntada por el puntero

{ PunteroEntero := 5; PELIGRO! El puntero
apuntaría a la posición n 5 }

Dispose(PunteroEntero);

end.

Hay que ir con mucho cuidado cuando hacemos asignaciones
a punteros sin desreferenciar pues es un error bastante habitual.
Además, si el puntero cambia de valor, entonces apunta a
otra dirección de la memoria y por tanto el valor de la
posición anterior se pierde inevitablemente con lo que no
podremos liberar la memoria que habíamos reservado
inicialmente pues intentaríamos liberar otra
posición y esto es un peligro potencial, pues puede ser
memoria que no tenga que ser liberada.

En este ejemplo hemos reservado memoria para un puntero
y la hemos liberado. Mediante el operador de
desreferenciación hemos modificado el contenido de la
memoria direccionada por el puntero.

Si queremos que un puntero no apunte en ningún
lugar empleamos la palabra reservada nil.

PunteroEntero := nil;

Puede resultar extraño en un primer momento que
un puntero no apunte en ningún lugar. Si intentamos
obtener algún valor del puntero mediante PunteroEntero^
obtendremos un error runtime de protección
general.

Es importante ver que hasta que no se reserva memoria
para un puntero no podemos acceder a su contenido ya que el valor
que contiene no apunta en ningún lugar, tiene el valor
nil. Podemos comprobar si un puntero no apunta en ningún
sitio si su valor es nil.

if PunteroEntero = nil then …

Los punteros del mismo tipo son compatibles por
asignación. Además las modificaciones que hagamos
en un puntero que apunta a una misma posición apuntada por
otro puntero se ven ambos punteros.

program PunterosConTipo;

var

PunteroEntero, PunteroModificar :
^Integer;

begin

New(PunteroEntero);

PunteroEntero^ := 5;

PunteroModificar := PunteroEntero; { Asignamos a
PunteroModificar la misma dirección que PunteroEntero
}

Writeln(PunteroEntero^); {5}

Writeln(PunteroModificar^); {5}

PunteroModificar^ := 12;

Writeln(PunteroEntero^); {12}

Writeln(PunteroModificar^); {12}

Dispose(PunteroEntero);

{ A partir de aquí PunteroEntero^ i
PunteroModificar^ ya no son válidos }

end.

Vemos que modificar el contenido de esta posición
mediante uno de los dos punteros produce el mismo resultado ya
que los dos punteros se refieren a la misma posición de
memoria y por tanto apuntan al mismo valor. Consíderese
también el hecho e que liberar una posición de
memoria invalida todos los punteros que apunta a ella por lo que
PunteroModificar y PunteroEntero ya no pueden ser
desreferenciados correctamente. Es importante ver que
PunteroModificar no se tiene que liberar con Dispose ya que
tampoco hemos reservado memoria para él.

El operador de dirección
@

Si empleamos el operador @ delante del nombre de una
variable estática
automáticamente obtendremos su dirección de
memoria. Por tanto podemos asignar este resultado a un puntero
del mismo tipo que la variable estática y realizar
modificaciones sobre la variable estática mediante el
puntero.

program PunterosConTipo;

var

PunteroEntero : ^Integer;

Entero : Integer;

begin

PunteroEntero := @Entero; { Obtenemos su
dirección y la guardamos en PunteroEntero }

Entero := 10;

Writeln(Entero); {10}

Writeln(PunteroEntero^); {10}

PunterEnter^ := 12;

Writeln(Entero); {12}

Writeln(PunterEntero^); {12}

end.

Aquí tampoco hay que liberar PunteroEntero ya que
no hemos reservado memoria para él. Simplemente lo hemos
asignado para que apunte a la dirección de la variable
Entero.

Copiar los contenidos de un puntero en
otro

Antes hemos visto que al asignar un puntero a otro
simplemente hacíamos que apuntase a la dirección
asignada pero no se transfería el contenido. Supongamos
hemos reservado memoria para los punteros PunteroEntero y
PunteroModificar, ambos de tipo Integer.

begin

New(PunteroEntero); // Reservamos memoria para
PunteroEntero

New(PunteroModificar); // Reservamos memoria para
PunteroModificar

PunteroEntero^ := 10;

PunteroModificar^ := PunteroEntero^; // Copiamos el
contenido

Writeln(PunteroModificar^); {10}

Dispose(PunteroEntero);

Dispose(PunteroModificar);

end.

Si en vez de hacer una asignación mediante el
operador de desreferenciación hubiésemos hecho la
asignación siguiente :

PunteroModificar := PunteroEntero;

No habríamos podido liberar la zona reservada
inicialmente para PunteroModificado ya que al hacer
Dispose(PunteroModificar) tendríamos un error al intentar
liberar una zona de memoria ya liberada. En cualquier otra
combinación :

PunteroModificar^ := PunteroEntero;

// o

PunteroModificar := PunteroEntero^;

El compilador no nos habría permitido realizar la
asignación pues los elementos no son compatibles por
asignación.

Aritmética e indexación de
punteros

FreePascal permite realizar aritmética de
punteros mediante las operaciones suma y resta y los
procedimientos Inc y Dec.

Dados dos punteros con tipo P1 y P2 podemos realizar
operaciones como las siguientes :

P1 := P1 + 1; {Ahora P1 apunta un byte más
adelante de la dirección inicial}

P1 := P1 – 1; {Ahora P1 vuelve apuntar a la
dirección inicial}

P1 := P2 + 2; {Ahora P1 apunta dos bytes más
adelante de P2}

P1 := P1 – 2; {Ahora P1 apunta a la
dirección de P2}

La función Inc incrementa la dirección del
puntero en n bytes donde n bytes es el tamaño del tipo de
puntero. Por ejemplo, dado el puntero P de tipo ^Longint
entonces :

Inc(P); { Equivale a P := P + 4 }

ya que un Longint tiene un tamaño de 4 bytes. La
función Dec hace lo mismo pero en vez de incrementar n
bytes los decrementa.

También podemos indexar un puntero como si de un
array se tratara. Obsérvese el ejemplo
siguiente :

program PunteroArray;

var

PunteroByte : ^Byte;

Entero : Word;

begin

Entero := 18236; { $403C }

PunteroByte := @Entero;

Writeln('Byte de menos peso ', PunteroByte[0]); { $3C =
60 }

Writeln('Byte de mayor peso ', PunteroByte[1]); { $40 =
71 }

end.

En este ejemplo hemos empleado un entero de tipo Word
que consta de dos bytes, el de mayor peso o superior, y el de
menor peso o inferior, y además no tiene signo. Ya que en
la plataforma Intel los bytes de menos peso se almacenan primero,
leemos PunteroByte[0] y después PunteroByte[1]. En otras
plataformas quizá hubiéramos encontrado primero el
byte superior y después el byte inferior. En este caso
concreto no es
necesario emplear el operador de desreferenciación pues se
sobreentiende con la sintaxis de array.

Punteros sin tipo

Es posible emplear un tipo de puntero que no va asociado
a un tipo en concreto. Este tipo que recibe el nombre de Pointer
permite apuntar a zonas de memoria genéricamente. Si
queremos reservar memoria con una variable de tipo Pointer
tendremos que emplear GetMem y FreeMem para liberarla. El uso de
punteros son tipo es aún más peligroso que con tipo
pues no podemos asignar valores al
contenido. Las únicas asignaciones posibles son entre
otros punteros, con o sin tipo.

program PunteroSinTipo;

var

Puntero : Pointer;

PunteroEntero : ^Integer;

begin

GetMem(Puntero, SizeOf(integer));

PunteroEntero := Puntero;

PunteroEntero^ := 10;

FreeMem(Puntero, SizeOf(integer));

end.

Las funciones FreeMem y GetMem exigen que se les
explicite el tamaño que hay que reservar y liberar. En
este ejemplo hemos reservado espacio para un entero y hemos
asignado su dirección a la variable PunteroEntero con el
cual podemos trabajar como si de un entero se tratara.
Nótese que se ha obtenido el tamaño del entero
mediante SizeOf(integer) y no SizeOf(^Integer) o
SizeOf(PunteroEntero) ya que cualquier puntero tiene un
tamaño de 4 bytes (32-bits) para poder apuntar a una
dirección de memoria.

El tipo AnsiString

El tipo AnsiString es el que se llama cadena larga. No
es una cadena propiamente Pascal pero permite superar la
limitación de los 255 caracteres impuestos por las
cadenas Pascal. Por otra parte tienen un comportamiento
especial pues se tratan como punteros por lo que si intentamos
escribir en un archivo un
AnsiString obtendremos que hemos copiado un entero de 4 bytes y
no la cadena en sí.

Cuando una cadena de tipo AnsiString se asigna a un
valor vacío () entonces esta toma el valor nil. Cuando
le asignamos una variable o un literal compatibles, entonces se
reserva suficiente memoria para alojar la cadena. Además,
las cadenas AnsiString tienen un contador de referencias interno.
Si hacemos la asignación siguiente :

S1 := S2;

Entonces el contador de S2 se reduce a cero para indicar
que no tiene un valor propio, y el contador de S1 se incrementa y
se copia la dirección de S1 en S2 con lo cual se acelera
la gestión
de cadenas. Al leer S2 estamos leyendo de hecho S1. En cambio, si
acto seguido hacemos :

S2 := S2 + '.';

Entonces el contador de S1 se reduce a 1, el contador de
S2 se incrementa a 1 y se copia el contenido de S1 en la
región reservada por S2 y se añade el punto. Todo
este proceso es
automático y es el compilador quien se encarga de
añadir el código necesario para estas operaciones
de forma que sea transparente para el programador.

Las cadenas largas son totalmente compatibles con las
cadenas de tipo String[n] o ShortString. Si combinamos cadenas
largas y cortas en una expresión, el resultado en general
es AnsiString pero si lo asignamos a un ShortString se transforma
automáticamente.

En el caso de los AnsiString no podemos saber el
tamaño máximo de la cadena con High pues son
cadenas dinámicas. Podemos especificar su tamaño
máximo con SetLength.

SetLength(S1, 5); // Ahora S1 ha reservado espacio para
5 caracteres

Pero si hacemos una asignación a la cadena que
exceda de su tamaño este no se trunca, como pasaría
con los string Pascal, sino que crece adecuadamente.

program CadenasAnsi;

var

S1 : AnsiString;

begin

SetLength(S1, 3);

Writeln(Length(S1)); {3}

S1 := 'Hola que tal !';

Writeln(Length(S1)); {14}

end.

Los string Pascal aparte de estar limitados a 255
caracteres no son compatibles con las cadenas finalizadas en nulo
que se emplean en C/C++ y por tanto tampoco lo son cuando
llamamos funciones escritas en este lenguaje. Las
cadenas AnsiString, a la contra, son más fáciles de
convertir a cadenas C. 10.5.1.La directiva de compilador
$H

La directiva de compilador $H permite determinar si el
tipo String (sin indicador de longitud) representa a una cadena
larga, AnsiString, o a una cadena corta, ShortString o
String[255]. Cuando $H está activada entonces el tipo
String representa un AnsiString. En caso contrario, representa un
ShortString. La forma larga de la directiva $H es $LONGSTRINGS.
Por defecto $H está activada. Esta directiva es local, de
forma que se puede intercalar dentro del código tal como
hacíamos con $I y sólo cambia si aparece una
directiva $H posterior.

{$H+} // String = AnsiString

{$H-} // String = ShortString

{$LONGSTRINGS on} // String = AnsiString

{$LONGSTRINGS OFF} // String = ShortString

Cadenas finalizadas en carácter nulo

Algunos lenguajes de
programación como C/C++ les falta un tipo especial
para las cadenas de caracteres. Como solución se emplean
arrays de caracteres sin índice que terminan cuando se
encuentra un carácter nulo (de valor
ASCII cero). Como hemos
comentado esta estructura es
incompatible con el tipo string de Pascal. Estas se basan en un
byte inicial que indica el nombre de caracteres de la cadena (el
valor que devuelve Length) y los bytes restantes son los
caracteres de la cadena. El máximo valor que puede
expresar el primer byte (de índice cero en la cadena) es
255 y por tanto las cadenas Pascal están limitadas a 255
caracteres como máximo.

Con este motivo existe el tipo PChar. Se puede entender
como la equivalencia en Pascal de las cadenas terminadas en nulo.
Formalmente el tipo PChar no es nada más que un puntero a
un carácter. Esto es así ya que en las funciones y
procedimientos en los que necesitemos pasar una cadena terminada
en nulo no se pasa nunca la cadena en si, sólo el puntero
al primer carácter de ésta. De esta forma la cadena
puede ser muy larga pero la información pasada a la función
sólo son los 4 bytes del puntero.

El asunto más complicado, por ahora, con las
cadenas PChar es trabajar con ellas. Especialmente cuando hay que
mezclarlas o convertir en cadenas AnsiString.

Un PChar lo podemos asignar directamente a un literal de
cadena. Podemos asignar el contenido de un PChar a otro y a
diferencia de los punteros habituales, la modificación de
éste último no implica la modificación del
primero.

program CadenesTerminadasNulo;

var

a, b : PChar;

begin

a := 'Hola';

b := a;

b := 'Adiós';

Writeln(a); { Hola }

Writeln(b); { Adiós }

end.

Muchas veces nos encontraremos en la tesitura de tener
que pasar una cadena PChar a una función pero solo
disponemos de un AnsiString o ShortString. En el caso de el
AnsiString podemos hacer un typecasting directamente a
PChar.

program CadenasTerminadasNulo;

procedure Escribir(Cadena : PChar);

begin

Writeln(Cadena);

end;

var

a : AnsiString;

begin

Escribir('Hola'); // Los literales son
correctos

a := 'Hola';

Escribir(a); // Esto es ilegal

Escribir(PChar(a)); // Esto es perfectamente
legal

end.

En el caso de un ShortString hay varias soluciones. La
más simple es asignarla a un AnsiString y después
aplicar el typecasting. Téngase en cuenta que en el caso
de parámetros por referencia los typecastings no
están permitidos y los parámetros tienen que ser
del mismo tipo idéntico.

Para transformar una cadena PChar a su equivalente
AnsiString o ShortString no hay que hacer ninguna
operación especial. El compilador se encarga de
transformar el PChar a String de forma
automática.

El tipo array[0..X] of Char

Antes de poder emplear una variable PChar es importante
inicializarla. Una forma poco elegante consiste en asignarla a un
literal de cadena. Otra forma es emplear las complicadas rutinas
de la unit Strings, que sólo operan con cadenas PChar,
para obtener un puntero válido del tamaño deseado
que posteriormente tendremos que liberar. Otra solución
más simple son los arrays de caracteres indexados en cero
que son totalmente compatibles con los PChar, se inicializan
automáticamente y pueden tener el tamaño que
queramos incluso más de 255 caracteres. El único
caso en el que no serían compatibles son en los
parámetros por referencia.

program CadenasTerminadasNulo;

procedure Escribir(Cadena : PChar);

begin

Writeln(Cadena);

end;

var

a : array [0..499] of Char; // El tamaño que
queramos o necesitemos

begin

a := '1234'; // Hasta quinientos
caracteres

Escribir(a);

end.

El tipo array of Char es compatible en asignación
con ShortString y AnsiString.

Es un error bastante habitual declarar un array of Char
demasiado corto lo que hace que los caracteres de más se
trunquen. Este hecho puede provocar errores difíciles de
detectar y por tanto es recomendable declarar arrays suficiente
grandes para nuestros datos.

El tipo
función/procedimiento

Es posible definir un tipo de dato que sea evaluable en
una expresión, función, o llamado desde una
instrucción mediante el que se llama tipo función,
y extensivamente, tipo procedural.

El tipo procedural tiene utilidad en algunos casos muy
concretos, por ejemplo en programación con DLL u otras
librerías de enlace dinámico y por tanto veremos
pocos ejemplos. Básicamente permite definir una variable
de tipo de procedimiento o función que apuntará a
otra función o procedimiento, una función de una
DLL en memoria por ejemplo, y que se podrá tratar como si
de un procedimiento o función se tratara. Vamos a ver un
caso sencillo para una función. Para los procedimientos es
muy parecido. Téngase en mente que vamos a emplear una
función ya definida. Obsérvese también que
es importante que los parámetros sean iguales, o al menos
el numero de bytes de los parámetros idéntico, para
evitar errores de desbordamiento de pila y de protección
general. El ejemplo es bastante inútil pero es
suficientemente ilustrativo.

Vamos a resolver una ecuación de segundo grado.
Recordemos que las ecuaciones de
segundo grado ax2 + bx + c = 0 se resuelven con la fórmula
siguiente :

No se pudo entender (Falta el
ejecutalbe de texvc; por favor, lee math/README para
configurarlo.): x=frac{-bpmsqrt{b^2-4ac}}{4ac}

En caso de que el discriminante = b2 – 4ac sea menor que
cero no habrá ninguna solución, en caso de que sea
igual a cero tendremos una solución x = -b/2a y en caso de
que sea positivo tendremos dos soluciones para los dos signos de la
raíz cuadrada.

const

NO_SOLUCION = 0;

UNA_SOLUCION = 1;

DOS_SOLUCIONES = 2;

function ResolverEq2nGrado(a, b, c : real; var x1,
x2 : real) : integer;

{ Resolución de ecuaciones de 2 grado en los
reales

La función devuelve

NO_SOLUCION si no hay solución

UNA_SOLUCION si sólo hay una
solución

DOS_SOLUCIONES si hay dos soluciones

}

var

discr : real; // El discriminante

begin

discr := b*b – 4*a*c;

if discr < 0 then

begin

ResolverEq2nGrado := NO_SOLUCION;

end

else

begin

if discr = 0 then

begin

ResolverEq2nGrado := UNA_SOLUCION;

x1 := -b / (2*a);

x1 := x2; // Dejamos constancia de q son
iguales

end

else // Sólo puede ser discr > 0

begin

ResolverEq2nGrado := DOS_SOLUCIONES;

x1 := (-b + Sqrt( discr )) / (2*a);

x2 := (-b – Sqrt( discr )) / (2*a);

end;

end;

end;

Una vez ya tenemos definida la función vamos a
ver como podemos llamarla mediante un tipo de función. Es
importante definir el tipo de función con los mismos
parámetros y especificando procedure o function
según convenga.

type

TFuncion = function(a, b, c : real; var x1,
x2 : real) : integer;

En este caso hemos copiado incluso el nombre de las
variables, lo único que realmente importa es que tengan el
mismo tamaño y mismo tipo de parámetro, por lo que
la declaración siguiente es igual a la
anterior :

type

TFuncion = function(A1, A2, A3 : real; var A4,
A5 : real) : integer;

Una vez tenemos declarado el tipo podemos declarar una
variable de este tipo.

var

Funcion : TFuncion;

Lo más interesante empieza ahora. Es necesario
asignar la dirección de memoria de Funcion a la
dirección de ResolverEq2nGrado. El código del
programa queda de la forma siguiente :

program TipoFuncion;

const

NO_SOLUCION = 0;

UNA_SOLUCION = 1;

DOS_SOLUCIONES = 2;

function ResolverEq2nGrado(a, b, c : real; var x1,
x2 : real) : integer;

begin

… // El código de la función está
listado más arriba

end;

type

TFuncion = function(a, b, c : real; var x1,
x2 : real) : integer;

var

Funcion : TFuncion;

x1, x2, a, b, c : Real;

begin

Write('A : '); Readln(a);

Write('B : '); Readln(b);

Write('C : '); Readln(c);

Funcion := @ResolverEq2nGrado; { Asignamos a
Funcion la dirección de ResolverEq2nGrado }

case Funcion(a, b, c, x1, x2) of

NO_SOLUCION : begin

Writeln('Esta ecuación no tiene solución
real');

end;

UNA_SOLUCION : begin

Writeln('Una solución : X=',
x1:5:2);

end;

DOS_SOLUCIONES : begin

Writeln('Dos soluciones');

Writeln('X1=', x1:5:2);

Writeln('X2=' , x2:5:2);

end;

end;

end.

Obsérvese que hemos asignado a Funcion la
dirección de ResolverEq2nGrado de forma que cuando
llamamos a Funcion con los parámetros adecuados en
realidad estamos llamando a ResolverEq2nGrado.

Para que los reales se vean con una cantidad adecuada de
cifras y decimales emplearemos el añadido :5:2. Esta
opción sólo está disponible en Writeln
establece 5 cifras en total 2 de las cuales serán
decimales del real. En el caso que el real tenga menos de 5
cifras, se escriben espacios en blanco hasta completarlas. Para
los enteros también se puede emplear pero sólo se
pueden especificar las cifras.

{ La constante Pi ya está declarada en Pascal
}

Writeln(Pi:2:0); {2 cifras, 0 decimales}

Writeln(Pi:0:5); {0 cifras, 5 decimales}

Writeln(Pi:7:3);

Writeln((Pi*10):7:3);

Las instrucciones anteriores escribirían en
pantalla :

83

3.14159

883.142

831.416

Dónde 8 representa un espacio en blanco.
Obsérvese que el punto no se cuenta como una cifra
decimal. Este valor que especifiquemos para cifras y decimales no
tiene porque ser un literal, puede ser una variable entera o una
constante entera.

Convenciones de llamada

No todos los lenguajes de programación llaman las
funciones y procedimientos de la misma forma. Por ejemplo, el
compilador Borland Delphi por
defecto pasa los parámetros de izquierda a derecha
mientras que FreePascal y los compiladores de C
los pasan de derecha a izquierda. En función de quien se
encarga de liberar la pila de la función y en el orden en
el que se pasan los parámetros tendremos una
convención de llamada u otra.

Para llamar una función que se encuentre en
memoria es conveniente emplear la convención adecuada.
Básicamente las convenciones más habituales son
cdecl y stdcall, esta última es la convención por
defecto de FreePascal.

Convención de
llamada

Paso de
parámetros

Liberación de
memoria

Parámetros en los
registros ?

por defecto

Derecha a izquierda

Función

No

cdecl

Derecha a izquierda

Quien hace la llamada

No

export

Derecha a izquierda

Quien hace la llamada

No

stdcall

Derecha a izquierda

Función

No

popstack

Derecha a izquierda

Quien hace la llamada

No

register^4 <#sdfootnote4sym>

Izquierda a derecha

Función

pascal

Izquierda a derecha

Función

No

 

Convenciones export, cdecl i popstack

Se emplean en funciones que tendrán que ser
llamadas por código en C o que han sido escritas con la
convención de llamada de C/C++. Popstack además
nombra la rutina con el nombre que el compilador FreePascal le
daría si fuera una función normal.

Convenciones pascal i register.

La convención register es ignorada por el
compilador que la sustituye por la convención stdcall. La
convención pascal tiene una mera finalidad de
compatibilidad hacia atrás.

Convención stdcall

La convención stdcall es la convención de
llamada por defecto de FreePascal. Además, es la
convención de llamada de las funciones de la API de
Win32.

Para indicar al compilador qué convención
de llamada tiene que emplear sólo hay que especificar la
convención de llamada después de la
declaración de la función. Téngase en cuenta
de que las convenciones de llamada son mutuamente excluyentes
entre si, por lo que, no podemos definir más de una.
Obsérvese el ejemplo siguiente :

procedure LlamadaStdCall; stdcall; // Convención
stdcall

begin

end;

procedure LlamadaCdeCl; cdecl; // Convención
cdecl

begin

end;

Vamos a ver un ejemplo práctico de las
diferencias entre convenciones a la hora de pasar
parámetros. Emplearemos la convención pascal y la
convención stdcall, por defecto, para pasar cuatro enteros
de 4 bytes sin signo, Cardinal. Después veremos que es
posible acceder a ellos y obtener su dirección en
memoria.

program ConvencionLlamada;

procedure IzquierdaDerecha(a, b, c, d : Cardinal);
pascal;

var

Puntero : ^Cardinal;

begin

Puntero := @a;

Writeln(Puntero^, ' (', Cardinal(Puntero), ')' );
{a}

Dec(puntero);

Writeln(Puntero^, ' (', Cardinal(Puntero), ')');
{b}

Dec(puntero);

Writeln(Puntero^, ' (', Cardinal(Puntero), ')');
{c}

Dec(puntero);

Writeln(Puntero^, ' (', Cardinal(Puntero), ')');
{d}

end;

procedure DerechaIzquierda(a, b, c, d : Cardinal);
stdcall;

var

Puntero : ^Cardinal;

begin

Puntero := @a;

Writeln(Puntero^, ' (', Cardinal(Puntero), ')');
{a}

Inc(puntero);

Writeln(Puntero^, ' (', Cardinal(Puntero), ')');
{b}

Inc(puntero);

Writeln(Puntero^, ' (', Cardinal(Puntero), ')');
{c}

Inc(puntero);

Writeln(Puntero^, ' (', Cardinal(Puntero), ')');
{d}

end;

begin

Writeln('Izquierda a derecha :');

IzquierdaDerecha(1, 2, 3, 4);

Writeln;

Writeln('Derecha a izquierda :');

IzquierdaDerecha (5, 6, 7, 8);

end.

Cuando ejecute el programa obtendrá un resultado
parecido al siguiente :

Izquierda a derecha :

1 (39321120)

2 (39321116)

3 (39321112)

4 (39321108)

Derecha a izquierda :

5 (39321108)

6 (39321112)

7 (39321116)

8 (39321120)

El programa nos muestra el valor
de las variables que hemos pasado a las dos funciones y entre
paréntesis nos indica la dirección en memoria del
parámetro en cuestión. Es legal un typecasting de
puntero a Cardinal pues un puntero no es nada más que un
entero de cuatro bytes para acceder a una posición de
memoria. En el código obtenemos la dirección del
parámetro a y después obtenemos los otros
parámetros mediante aritmética de punteros.
Recuerde que Inc y Dec incrementan o decrementan el puntero en
tantos bytes como el tamaño del tipo de puntero. Un
cardinal tiene un tamaño de cuatro bytes por tanto el
puntero apuntará cuatro bytes más adelante o
atrás después de un Inc o Dec,
respectivamente.

Los parámetros se pasan, en el primer caso, de
izquierda a derecha, o sea, del último al primero. De
forma que el último parámetro d es el que se
encuentra en las posiciones más bajas de la memoria y los
parámetros van pasando del último al primero, a, el
cual tendrá la posición más a la derecha, o
sea la posición de memoria más alta.

En el segundo caso los parámetros se pasan de
derecha a izquierda, o sea, del primero al último. En este
caso, el último parámetro d está en la
posición de memoria más alta mientras que el primer
parámetro a está en la posición más a
la derecha, o sea, en la posición más baja de la
memoria.

Izquierda a derecha

Posiciones de memoria

39321108

39321112

39321116

39321120

d

c

b

a

Derecha a izquierda*

a

b

c

d

Es importante emplear la convención adecuada cada
vez que llamamos a una función. Generalmente no tendremos
que preocuparnos pues el compilador sabe qué
convención se está empleando en las funciones o
procedimientos que declaremos en el código. En el caso de
que empleemos funciones definidas en librerías escritas en
otros lenguajes de programación habrá que tener
mucho cuidado y especificar la convención adecuada, en
otro caso emplearía la convención stdcall por
defecto. También es posible especificar una
convención a un tipo procedural :

type

TIzquierdaDerecha = procedure (a, b, c, d :
Cardinal); pascal;

Obtenido de ""

Manual de FreePascal (parte
final)

De Gleducar,
http://www.gleducar.org.ar

Units

La necesidad de las units

Hasta ahora hemos empleado algunas funciones que se
encontraban en algunas units estándar de FreePascal. Las
units permiten reunir funciones, procedimientos, variables y
tipos de datos
para disponerlos de forma más ordenada y poderlos
reutilizar en otras aplicaciones.

Una primera forma de reutilizar el código que
hemos visto ha sido mediante la declaración de funciones.
Estas funciones tenían que estar declaradas antes de que
las hiciéramos servir. Habitualmente, pero, emplearemos
funciones y procedimientos en varios programas y en
otras units y no será práctico copiar de nuevo la
implementación. Además, de esta forma
tendríamos que cambiar muchas veces el código si lo
optimizáramos o lo corrigiéramos, por ejemplo.
Empleando units conseguimos reutilizar mejor las funciones,
procedimientos pues estas se encontrarán en la unit y
cualquier cambio que necesitemos sólo se tendrá que
realizar en la unit misma. Cuando necesitemos llamar las
funciones y los procedimientos de la unit, o emplear variables de
ésta, sólo habrá que indicar al compilador
que las emplee.

Utilizar una unit

Para emplear una unit en nuestro programa ya hemos visto
que es tan simple como especificarla en la cláusula uses
del programa. Normalmente necesitaremos más de una, pues
las separamos entre comas.

uses Crt, Dos, SysUtils;

Hay que tener en cuenta de que la unit tendrá que
ser visible para el compilador. Normalmente el compilador
buscará el archivo que contenga la unit en el directorio
del programa más algunos directorios del compilador.
También podemos especificar la posición en el disco
de la unit, en caso de que no se encontrara en el directorio
actual, con el parámetro -Fudirectorio pasado al
compilador, donde directorio es el directorio donde el compilador
puede encontrar la unit. Otra forma es modificar el archivo
PPC386.CFG al directorio C:PPBINWIN32 y añadir la
línea -Fudirectorio. Si tenemos que especificar más
de un directorio pues añadiremos más líneas.
De esta forma es permanente cada vez que se ejecuta el
compilador. Finalmente podemos emplear la directiva de
compilador, dentro del código del programa o unit,
$UNITPATH separando los directorios con puntos y
comas. :

{$UNITPATH directorio1;..;directorion}

Por ejemplo:

{$UNITPATH ..graficos;c:units}

Crear una unit

Para crear una unit hay que tener en cuenta que el
archivo que vamos a crear, con extensión PAS o PP tiene
que tener el mismo nombre que le daremos a la unit. Por ejemplo
si nuestra unit se llama Utilidades (recuerde que las
mayúsculas son poco importantes en Pascal) el archivo
tendrá que ser UTILIDADES.PAS o bien UTILIDADES.PP. Es muy
recomendable poner el nombre del archivo de la unit en
minúsculas sobretodo en el caso de Linux donde los
archivos son
sensibles a mayúsculas y minúsculas.

El encabezado de una unit tiene que empezar con la
palabra reservada unit y el nombre de la unit, que tiene que
coincidir con el nombre del archivo. En nuestro caso
sería :

unit Utilidades;

Esta palabra reservada indica al compilador que vamos a
hacer una unit y no un programa. Además, a diferencia de
program, que era opcional, unit es necesaria para indicarle al
compilador que lo que se va a encontrar es una unit y no un
programa, como suele ser por defecto.

Estructura de una unit

Una unit está formada por tres partes bien
diferenciadas : una interfaz, una implementación y
una sección de inicialización.

En la interfaz se declaran todas las variables,
constantes, tipos y funciones o procedimientos que queremos que
estén disponibles cuando vayamos a emplear la unit. En lo
que los procedimientos y funciones concierne, los declararemos
como si se trataran de funciones forward pero sin la palabra
reservada forward.

En la implementación implementaremos las
funciones de la interfaz y podemos declarar variables, tipos,
constantes y otras funciones. A diferencia de los declarados en
la interfaz, los elementos declarados en la implementación
sólo están disponibles dentro de la misma unit y
tienen como finalidad ser elementos auxiliares en la
implementación.

Finalmente el código de inicialización se
ejecutará al cargase la unit. Este código se
ejecuta antes que se inicie el programa y tiene como finalidad
realizar tareas de inicialización de la propia unit. Como
es de esperar, la unit termina con un end.

La parte interface

La interfaz se implementa primero y se indica su inicio
con la palabra reservada interface. Justo después de
interface podemos especificar las units que empleará la
función aunque sólo es recomendable hacerlo si
alguno de los tipos de datos que vamos a emplear en interface se
encuentra en otra unit. Si sólo la necesitamos para
ciertas funciones ya la incluiremos dentro de la
implementación. Esto es así para evitar referencias
circulares donde A emplea B y B emplea A, ya que el compilador
emitirá un error. En la sección
implementación este error no se puede dar.

Vamos a implementar diversas funciones y procedimientos
simples en nuestra unit que nos faciliten un poco más el
trabajo a la hora de pedir datos al usuario. Concretamente vamos
a hacer diversas funciones sobrecargadas que emitirán un
mensaje y comprobarán que el dato introducido es correcto.
La unit empieza de la forma siguiente :

unit Utilidades;

interface

procedure PedirDato(Cadena : String; var
Dato : String);

procedure PedirDato(Cadena : String; var
Dato : Integer);

procedure PedirDato(Cadena : String; var
Dato : Real);

Sólo lo implementaremos para estos tres tipos
pues no se suelen pedir otros tipos de datos ni caracteres
sueltos (en este caso pediríamos la pulsación de
una tecla). Ahora sólo falta implementar las funciones en
la sección de implementación.

La parte implementation

Una vez declarados en interface tipos de datos,
constantes y variables no hay que hacer nada más. No es
así con las funciones y los procedimientos que hay que
implementar. La sección de implementación empieza
con la palabra reservada implementation. No importa mucho el
orden en el que las implementemos sino que realmente las
implementemos, de no hacerlo el compilador nos dará
error.

implementation

procedure PedirDato(Cadena : string; var
Dada : string);

begin

Write(Cadena); Readln(Dato);

end;

procedure PedirDato(Cadena : string; var
Dato : Integer);

begin

Write(Cadena); Readln(Dato);

end;

procedure PedirDato(Cadena : string; var
Dato : Real);

begin

Write(Cadena); Readln(Dato);

end;

end. // Fin de la unit

Curiosamente las implementaciones de las tres funciones
son idénticas pero como ya hemos comentado no tiene porque
ser así. Por ejemplo podíamos haber pedido un
string al usuario y haber añadido código de
comprobación para ver si es un entero o no, devolviendo
true o false en función de si la función ha tenido
éxito.
Esto es así ya que Readln falla si al pedir un entero, o
un real, el usuario introduce un dato que no es convertible a
entero o real.

Inicialización de una
unit

Supongamos que tenemos una unit que dispone de una orden
llamada NotificarLog(Cadena : String); que se encarga de
escribir en un archivo de logging (donde se anotará lo que
va haciendo el programa y así si falla saber dónde
ha fallado) la cadena que se especifica como parámetro. En
este caso nos interesa abrir un archivo de texto para
hacer las anotaciones y cerrarlo si el programa termina ya sea de
forma normal o anormal. Es por este motivo que las units
incorporan una sección de inicialización y
también de finalización. En la
especificación habitual de Pascal sólo había
sección de inicialización pero recientemente a
Delphi se le añadió la posibilidad de disponer
también de una sección de finalización y
FreePascal también la incorpora.

Inicialización "a la
antigua"

Es posible inicializar, sólo inicializar, si
antes del end. final añadimos un begin y en medio las
sentencias de inicialización. Por ejemplo podríamos
haber puesto unos créditos que se verían al
inicializarse la unit.

begin

Writeln('Unit de utilidades 1.0 – Roger Ferrer
Ibáñez');

end. // Fin de la unit

Este tipo de inicialización tiene la desventaja
de que no permite la finalización de la unit de forma
sencilla. Sólo nos será útil en algunos
casos. Cuando necesitemos inicializar y finalizar tendremos que
emplear otra sintaxis.

Inicialización y
finalización

Debido a que la mayoría de inicializaciones
requerirán una finalización es posible
añadir secciones de inicialización y
finalización en la parte final de la unit. Estas secciones
irán precedidas por las palabras reservadas initialization
y finalization. En este caso especial no es necesario rodear las
sentencias de begin ni de end. Tampoco es obligatoria la
presencia de los dos bloques : puede aparecer uno
sólo o bien los dos a la vez.

Ahora ya podemos implementar nuestra función de
logging.

unit Logging;

interface

procedure NotificarLog(Cadena : string);

implementation

var // Variable de la unit no accesible fuera de la
unit

Log : Text;

procedure NotificarLog(Cadena : string);

begin

Writeln(Log, Cadena);

end;

initialization

Assign(Log, 'LOGGING.LOG');

Rewrite(Log);

finalization

Close(Log);

end. // Fin de la unit

Orden de inicialización y
finalización

El orden de inicialización y finalización
de las units depende exclusivamente de su posición en la
cláusula uses. El orden es estrictamente el que aparece en
esta cláusula. Supongamos que tenemos la cláusula
siguiente en nuestro programa (o unit) :

uses Unit1, Unit2, Unit3;

Supongamos que todas tienen código de inicio y
final. Entonces la Unit1 se inicializaría primera,
después Unit2 y después Unit3.

El proceso de finalización es a la inversa.
Primero se finalizaría la Unit3, después Unit2 y
después Unit1. En general el proceso de inicio es
simétrico : las units que se han inicializado primero
también son las últimas a finalizarse.

En el caso de que una unit que empleamos necesite otra,
entonces el criterio del compilador es inicializar todas las
units que se encuentre más afuera primero y después
las de más adentro. Por ejemplo, si en uses sólo
hubiera Unit1 pero ésta empleara Unit2 y ésta
última Unit3 entonces primero se inicializaría
Unit3, después Unit2 y finalmente Unit1. El proceso de
finalización seria el inverso, tal como se ha
comentado.

A diferencia de C y de algunos lenguajes, incluir dos
veces una misma unit en el código (por ejemplo que Unit1 y
Unit2 empleen Unit3) no comporta ningún problema y la
inicialización y finalización sólo se lleva
a cabo una vez siguiendo el orden antes indicado.

ÿmbito de una unit

Se llama ámbito a aquel conjunto de partes del
código que tienen acceso a un identificador, ya sea una
constante, variable, tipo o
función/procedimiento.

Interface

Si el identificador se declara en la sección
interface entonces el ámbito de este
será :

  • Toda la propia unit del identificador incluyendo las
    secciones interface, implementation, initialization y
    finalization.
  • Todas las units y programas que tengan esta unit
    dentro de la cláusula uses.

Implementation

Si el identificador se declara en la sección
implementation entonces su ámbito es :

  • Toda la propia unit a excepción de la
    sección interface. Esto quiere decir, por ejemplo, que
    no podremos emplear tipos de datos declarados en implementation
    dentro de los parámetros de una función en
    interface, puesto que no sería posible para el
    programador pasar parámetros a esta función fuera
    de la propia unit pues no conocería el tipo de
    dato.
  • Fuera de la unit estos identificadores no son
    accesibles.

Incluir units dentro de
interface

Es posible especificar una cláusula uses dentro
de la sección interface. Esta posibilidad sólo se
tendría que emplear en caso de que algún
identificador declarado en interface precise de algún otro
tipo declarado en otra unit.

Situando la cláusula uses en interface permitimos
que toda la unit, incluidas implementation, initialization y
finalization, acceda a esta otra unit.

Ahora bien, esta posibilidad tiene el riesgo de que
nuestra unit también se encuentre en la sección
interface de la otra unit lo que resultaría en una
referencia circular que es ilegal.

Incluir units dentro de
implementation

En este caso sólo las secciones implementation,
initialization, finalization tienen acceso a los identificadores
declarados en la unit en uses. Es posible que dos units se
incluyan mutuamente siempre que no se incluyan en interface a la
vez, ya sea las dos en implementation o una de ellas en interface
y la otra en implementation. Siempre que sea posible, es
recomendable incluir las units en la cláusula de
implementation.

[editar]

Librerías

Librerías vs units

Hasta ahora hemos visto que todos los elementos que
conformaban nuestro programa se resolvían en tiempo de
compilación. Es lo que se llama enlace estático.
Una vez se ha compilado el programa, el propio EXE dispone de
todo lo necesario para ejecutarse.

En el caso de las units escritas compiladas con
FreePascal, la compilación nos dará un archivo PPW
que contiene la información que en tiempo de
compilación se enlazará adecuadamente al EXE
final.

Algunas veces, pero, el código no habrá
sido generado por nosotros sino que con otros compiladores. Para
poder emplear este código dentro de nuestro programa
tendremos que enlazarlo. En algunos casos realizaremos enlaces
estáticos, resueltos en tiempo de compilación, pero
en otros tendremos que enlazar en tiempo de ejecución,
especialmente en el caso de las librerías de enlace
dinámico (DLL, Dynamic Library Linking) de Windows.

Archivos objeto

FreePascal permite el enlace estático con
archivos llamados archivos objeto (que suelen llevar
extensión O, o OW en Win32) que hayan sido compilados con
los compiladores GNU
Pascal, GNU
C/C++ o algún otro compilador que compile este
formato de archivos objeto. Los compiladores Borland, Microsoft e
Intel generan un tipo de archivos objeto que en general no son
directamente compatibles con los archivos objeto que vamos a
emplear.

Importación de rutinas en
archivos objeto

Supongamos que queremos enlazar una función muy
sencilla escrita en C que tiene el código
siguiente :

int incrementador(int a)

{

return a+1;

}

Esta función toma como parámetro un entero
de 32 bits (el parámetro a) y devuelve otro entero que es
el valor del parámetro incrementado en uno. En Pascal esta
función sería la siguiente :

function incrementador(a : integer) :
integer;

begin

incrementador := a+1;

end;

Como se puede ver C y Pascal tienen un cierto parecido
sintáctico. Supongamos que la rutina en C se encuentra
almacenada en un archivo llamado incr.c. Ahora hay que compilarlo
para obtener un archivo objeto. Como que no es un programa
completo, pues no tiene bloque principal, indicaremos al
compilador que sólo lo compile y no llame al enlazador,
que nos daría un error. El compilador de C que emplearemos
en los ejemplos en C es el conocido GNU CC, o gcc. Para compilar nuestro
código ejecutamos el compilador desde la línea de
órdenes habitual:

Partes: 1, 2, 3, 4, 5, 6
 Página anterior Volver al principio del trabajoPágina siguiente 

Nota al lector: es posible que esta página no contenga todos los componentes del trabajo original (pies de página, avanzadas formulas matemáticas, esquemas o tablas complejas, etc.). Recuerde que para ver el trabajo en su versión original completa, puede descargarlo desde el menú superior.

Todos los documentos disponibles en este sitio expresan los puntos de vista de sus respectivos autores y no de Monografias.com. El objetivo de Monografias.com es poner el conocimiento a disposición de toda su comunidad. Queda bajo la responsabilidad de cada lector el eventual uso que se le de a esta información. Asimismo, es obligatoria la cita del autor del contenido y de Monografias.com como fuentes de información.

Categorias
Newsletter