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

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




Enviado por rafaelfreites



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

Para implementar el ejemplo que hemos propuesto no hay
más remedio que emplear una unit. Esto es así
porque emplearemos una variable que no queremos que accesible
desde fuera de la clase y
tampoco desde fuera de la unit.

En la sección interface declaramos la clase :

unit EjemploClase;

{$MODE OBJFPC}

interface

type

TContarInst = class (TObject)

public

constructor Create;

destructor Destroy; override;

class function ContarInstancias : Integer; // Método de
clase

end;

En la sección implementation declararemos la que
hará las veces de variable de clase, aunque no lo sea.

implementation

var

NumInstancias : Integer; // Hará las veces de
"variable de clase"

Ya podemos implementar el constructor y el destructor.
También inicializaremos la variable NumInstancias a cero,
en la sección inicialización de la unit.

constructor TContarInst.Create;

begin

inherited Create;

NumInstancias := NumInstancias + 1;

end;

destructor TContarInst.Destroy;

begin

NumInstancias := NumInstancias – 1;

inherited Destroy;

end;

function TContarInst.ContarInstancias : Integer;

begin

ContarInstancias := NumInstancias; // Devolvemos la
"variable de clase"

end;

initialization

NumInstancias := 0;

end.

Llegados aquí hay que hacer un comentario sobre
inherited. Es posible que la clase ascendente implemente otros
métodos
con el mismo nombre y distintos parámetros, métodos
sobrecargados. Para llamar al método adecuado es
importante pasar bien los parámetros como si de una
llamada normal se tratara pero precedida de inherited. En este
caso no lo hemos hecho porque no lleva parámetros, incluso
podríamos haber escrito simplemente inherited; pues el
compilador ya entiende que el método que queremos llamar
es el mismo que estamos implementando pero de la clase superior
(Create y Destroy).

Vamos a ver el funcionamiento de esta clase que cuenta sus
instancias.

program MetodosDeClase;

{$MODE OBJFPC}

uses EjemploClase;

var

C1, C2, C3 : TContarInst;

begin

Writeln('Instancias al empezar el programa :
',

TContarInst.ContarInstancias);

C1 := TContarInst.Create; // Creamos una
instancia

Writeln('Instancias actuales : ',
TContarInst.ContarInstancias);

C2 := TContarInst.Create;

Writeln('Instancias actuales : ',
TContarInst.ContarInstancias);

C3 :=
TContarInst.Create;

Writeln('Instancias actuales : ',
TContarInst.ContarInstancias);

C3.Free;

Writeln('Instancias actuales : ',
TContarInst.ContarInstancias);

C2.Free;

Writeln('Instancias actuales : ',
TContarInst.ContarInstancias);

C1.Free;

Writeln('Instancias al terminar el programa :
',

TContarInst.ContarInstancias);

end.

Tal como es de esperar el resultado del programa
es :

Instancias al empezar el programa : 0

Instancias actuales : 1

Instancias actuales : 2

Instancias actuales : 3

Instancias actuales : 2

Instancias actuales : 1

Instancias al terminar el programa : 0

Invocación de métodos de
clase

Tal como hemos visto, podemos llamar a un método
de clase a través de el nombre de la clase, pero
también mediante una instancia. Dentro de los
métodos convencionales también podemos llamar a los
métodos de clase.

Punteros a métodos

Es posible emplear un tipo especial de tipo procedimiento/función
que podríamos llamar tipo de método. La
declaración es idéntica a la de un tipo de
procedimiento/función pero añadiendo las palabras
of object al final de la declaración de tipo.

type

TPunteroMetodo = procedure (Num : integer) of
object;

Este tipo de método se puede emplear dentro de
las clases como haríamos con los tipos
procedimiento/función normalmente. Es posible, como hemos
visto, mediante tipos de procedimiento/función llamar
diferentes funciones bajo un
mismo nombre ya que las podemos asignar a funciones existentes.
Los tipos de método tienen un comportamiento
parecido. Declaramos la clase siguiente :

TImplementadorA = class (TObject)

public

procedure Ejecutar(Num : integer);

end;

TImplementadorB = class (TObject)

public

procedure Ejecutar(Num : integer);

end;

Y una tercera clase donde emplearemos el puntero a
método.

TEjemplo = class (TObject)

public

Operacion : TPunteroMetodo;

end;

La implementación de las clases TImplementadorA y
TImplementadorB es la siguiente :

procedure TImplementadorA.Ejecutar(Num :
integer);

begin

Writeln(Num*7);

end;

procedure TImplementadorB.Ejecutar(Num :
integer);

begin

Writeln(Num*Num);

end;

Ahora es posible, dado que las declaraciones de Ejecutar
ambas clases TImplementador es compatible con el tipo
TPunteroMetodo asignar a Operacion los métodos Ejecutar de
las clases TImplementadorA y TImplementadorB. Obsérvese el
programa siguiente :

var

ImplA : TImplementadorA;

ImplB : TImplementadorB;

Ejemplo : TEjemplo;

begin

ImplA := TImplementadorA.Create;

ImplB := TImplementadorB.Create;

Ejemplo := TEjemplo.Create;

Ejemplo.Operacion := @ImplA.Ejecutar;

Ejemplo.Operacion(6); {42}

Ejemplo.Operacion := @ImplB.Ejecutar;

Ejemplo.Operacion(6); {36}

Ejemplo.Free;

ImplB.Free;

ImplA.Free;

end.

Esta asignación no habría sido
válida si TPunteroMetodo no fuera un tipo de método
o también llamado puntero a método.

La utilidad de los
punteros a método sirve cuando queremos modificar el
comportamiento de un método. Téngase en cuenta que
si el puntero a método no está asignado a
algún otro método entonces al ejecutarlo se
producirá un error de ejecución.

Paso de parámetros de tipo
objeto

Podemos pasar parámetros de tipo objeto a
funciones y procedimientos.
En este caso habrá que ir con algo más de cuidado
pues una clase no es una variable como las
demás.

Las clases son siempre parámetros
por referencia

Las clases se pasan siempre por referencia. Esto es
así ya que un objeto, o sea, una instancia de clase no es
nada más que un puntero y por tanto no tiene sentido pasar
una clase por valor. Esto
implica que las modificaciones en la clase que hagamos en la
función permanecerán después de la
llamada.

Si la clase se pasa como un parámetro constante
entonces no se podrán modificar variables
directamente (mediante una asignación) pero aún es
posible llamar métodos que modifiquen los campos del
objeto. En caso que el parámetro sea var es lo mismo que
si hubiéramos pasado un parámetro
normal.

Las clases conviene que estén
instanciadas

Es posible pasar una clase no instanciada e instanciarla
dentro de la función pero no es muy recomendable pues
nadie nos asegura de que ya ha sido instanciada. A la inversa
tampoco, no libere clases dentro de funciones que hayan sido
pasadas como parámetros.

Téngase en mente que los objetos
son punteros

Por lo que no asigne un objeto a otro porqué no
se copiará. Sólo se copiará la referencia de
uno a otro y cuando modifique uno se verá también
en el otro.

Referencias de clase

Hasta ahora cuando empleábamos el tipo clase era
para declarar una variable del tipo de la clase. Ahora vamos a
declarar variables que sean auténticas clases y no tipos
de la clase. Por ejemplo esto es una variable de tipo
TObject :

var

Objeto : TObject

Pero también podemos tener lo que se llaman
referencias de clase. O sea, "sinónimos" de nombres de
clase. Para definir que una variable es una referencia de clase
empleamos las palabras class of.

var

TClase : class of TObject;

Qué es en realidad TClase ? Pues TClase es
un tipo de clase estrictamente hablando, una referencia de clase.
Por lo que es posible asignar a TClase otra clase pero no un tipo
de clase (u objeto).

TClase := TObject; // Correcto

TClase := TEjemplo;

TClase := Objeto; // Incorrecto! Objeto es un
objeto mientras que TClase es una clase

En realidad el conjunto de clases que podemos asignar a
una referencia de clase es el conjunto de clases que desciendan
de la referencia de la clase. En el caso anterior cualquier
objeto se puede asignar a TClase ya que en Pascal cualquier
clase desciende de TObject. Pero con el ejemplo del
capítulo 14 si hacemos :

var

TRefVehiculo : class of TVehiculo;

Sólo podremos asignar a TRefVehiculo :
TVehiculo, TMoto y TCoche. Las referencias de clase llaman a los
métodos que convenga según la asignación y
si el método es virtual o no. No hay ningún
problema para que el constructor de una clase también
pueda ser virtual.

Los operadores de RTTI is y
as

RTTI, son las siglas de Run-Time type Information y
consiste en obtener información de los objetos en tiempo de
ejecución. Los dos operadores que permiten obtener
información sobre los objetos son is y as.

El operador is

Algunas veces pasaremos objetos como parámetros
especificando una clase superior de forma que todos los objetos
inferiores sean compatibles. Esto, pero tiene un inconveniente,
no podemos llamar los métodos específicos de la
clase inferior ya que sólo podemos llamar los de la clase
superior. Para corregir este problema podemos preguntarle a la
clase si es de un tipo inferior.

El operador is permite resolver este problema. Este
operador devuelve una expresión boolean que es cierta si
el objeto es del tipo que hemos preguntado.

En el ejemplo del capítulo 14 sobre TVehiculo,
TCoche y TMoto tenemos que :

TVehiculo is Tobject

es cierto porque todos los objetos son
TObject

TCoche is Tvehiculo

es cierto porque TCoche desciende de
TVehiculo

TMoto is Tcoche

es falso porque TMoto no desciende de TCoche.

TObject is Tvehiculo

es falso porque TObject no será nunca un
TVehiculo.

TVehiculo is Tvehiculo

es cierto, un objeto siempre es igual a si
mismo.

El operador as

El operador as hace un amoldado del objeto devolviendo
un tipo de objeto al cual hemos amoldado. Es parecido a un
typecasting pero resuelve algunos problemas
sintácticos. Por ejemplo :

procedure Limpiar(V : TVehiculo);

begin

if V is TCoche then

TCoche(V).LavaParabrisas;

end.

Esto es sintácticamente incorrecto ya que TCoche
no es compatible con TVehiculo (aunque TVehiculo lo sea con
TCoche). Para resolver este problema tendremos que emplear el
operador as.

if V is TCoche then

(V as TCoche).LavaParabrisas;

Lo que hemos hecho es amoldar V de tipo TVehiculo a
TCoche. El resultado de la expresión V as TCoche es un
objeto del tipo TCoche que llamará a un hipotético
método LavaParabrisas.

El operador as a diferencia de is puede fallar si
hacemos una conversión incorrecta y lanzará una
excepción. El ejemplo siguiente muestra una
conversión incorrecta :

if V is TCoche then (V as TMoto)…

Como sabemos de la jerarquía de TVehiculo, TCoche
no podrá ser nunca un tipo TMoto y viceversa.

Debido a que el resultado de as es un objeto del tipo
del segundo operando, siempre y cuando la operación sea
correcta, podremos asignar este resultado a un objeto y trabajar
con este.

var

C : TCoche;

begin

if V is TCoche then

begin

C := V as TCoche;

end;

end;

Gestión
de excepciones

Errores en tiempo de
ejecución

A diferencia de los errores de compilación, que
suelen ser provocados por errores en la sintaxis del código
fuente, hay otros errores a tener en cuenta en nuestros programas. Aparte
de los errores que hacen que nuestro programa no lleve a cabo su
tarea correctamente, causado en general por una
implementación errónea, hay otros errores los
cuales suelen causar la finalización de un programa. Estos
errores reciben el nombre de errores en tiempo de
ejecución (errores runtime) y como ya se ha dicho provocan
la finalización anormal del programa. Por este motivo es
importante que no ocurran.

Una forma eficiente de proteger nuestras aplicaciones
pasa por lo que se llaman excepciones. Las excepciones son como
alertas que se activan cuando alguna parte del programa falla.
Entonces el código del programa, a diferencia de los
errores runtime que son difícilmente recuperables, permite
gestionar estos errores y dar una salida airosa a este tipo de
fallos.

El soporte de excepciones lo aporta la unit SysUtils que
transforma los errores runtime estándar en excepciones. No
se olvide tampoco de incluir la directiva {$MODE OBJFPC} ya que
las excepciones son clases.

Protegerse no duele

Aunque no todas las instrucciones lanzan excepciones, es
importante proteger el código de nuestras aplicaciones.
Una forma muy segura de anticiparse a los fallos es lanzar la
excepción cuando se detecta que algo va mal, antes de que
sea demasiado tarde. Como veremos, lanzar excepciones pone en
funcionamiento el sistema de
gestión
de errores que FreePascal incorpora en el
código.

Lanzar excepciones

Obsérvese el código siguiente. Es un
algoritmo de
búsqueda de datos en arrays
de enteros. El algoritmo supone que el array está ordenado
y entonces la búsqueda es más rápida puesto
que si el elemento comprobado es mayor que el que buscamos
entonces no es necesario que busquemos en posiciones posteriores,
sino en las posiciones anteriores. La función devuelve
cierto si se ha encontrado el elemento d en el array t, p
contendrá la posición dentro del array. En caso de
que no se encuentre la función devuelve falso y p no
contiene información relevante.

// Devuelve cierto si d está en el array t, p
indica la posición de la ocurrencia

// Devuelve falso si d no está en el array
t

// El array t TIENE QUE ESTAR ORDENADA sino el algoritmo
no funciona

function BuscarDato(d : integer; t : array of
integer; var p : integer) : boolean;

var

k, y : integer;

begin

p := Low(t); y := High(t);

while p <> y do

begin

k := (p + y) div 2;

if d <= t[k] then

begin

y := k;

end

else

begin

p := k + 1;

end;

end;

BuscarDato := (t[p] = d);

end;

Esto funciona bien si los elementos del array
están ordenados en orden creciente, en caso que no
esté ordenado no funciona bien. Por este motivo
añadiremos algo de código que comprobará que
la tabla está ordenada crecientemente. En caso contrario
lanzaremos la excepción de array no ordenado
EArrayNoOrdenado. La declaración, que irá antes de
la función, de la excepción es la
siguiente :

type EArrayNoOrdenado = *class* (Exception);

No es necesario nada más. Para lanzar una
excepción emplearemos la sintaxis
siguiente :

raise NombreExcepcion.Create('Mensaje');

El código que añadiremos al principio de
la función es el siguiente :

function CercarDada(d : integer; t : array of
integer; var p : integer) : boolean;

const

EArrNoOrdMsg = 'El array tiene que estar bien
ordenado';

var

k, y : integer;

begin

// Comprobamos si está bien ordenada

for k := Low(t) to High(t)-1 do

begin

if t[k] > t[k+1] then // Si se cumple quiere decir
que no está ordenado

raise EArrayNoOrdenado.Create(EArrNoOrdMsg); // Lanzamos
la excepción !

end;

… // El resto de la función

end;

Si el array no está bien ordenado se
lanzará una excepción. Si nadie la gestiona, la
excepción provoca el fin del programa. Por lo que vamos a
ver como gestionar las excepciones.

Trabajar con excepciones

Código de gestión de
excepciones

Si dentro de un código se lanza una
excepción entonces el hilo de ejecución salta hasta
el primer gestor de excepciones que se encuentra. Si no hay
ninguno, el gestor por defecto es el que tiene la unit SysUtils.
Lo único que hace es escribir el mensaje de la
excepción y terminar el programa. Como se ve, esto no es
práctico, sobretodo si la excepción no es grave
(como el caso que hemos visto) y esta puede continuar
ejecutándose.

Es posible añadir código de
protección que permita gestionar y resolver las
excepciones tan bien como se pueda. Para hacerlo emplearemos la
estructura try
.. except .. end.

Entre try y except pondremos el código a
proteger. Si dentro de esta estructura se produce una
excepción entonces el hilo de ejecución irá
a la estructura except .. end. Ahora podemos realizar acciones
especiales en función de diferentes excepciones mediante
la palabra reservada on.

En el caso de que no haya habido ninguna
excepción en el bloque try entonces el bloque except se
ignora.

Veamos un ejemplo que casi siempre fallará puesto
que inicializaremos la tabla de enteros con valores
aleatorios.

var

tabla : array [1..50] of Integer;

i : integer;

begin

try

Randomize;

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

begin

tabla[i] := Random(400);

end;

BuscarDato(2, tabla, i);

except

on EArrayNoOrdenat do

begin

Writeln('La tabla no está ordenada!');

end;

end;

end.

Como se puede apreciar la estructura try y except no
precisa de begin y end para incluir más de una
instrucción. Cuando se produce una excepción de
tipo EArrayNoOrdenado entonces se escribe en pantalla que la
tabla no está ordenada. Para gestionar otras excepciones
podemos añadir nuevas estructuras
del tipo on .. do. También es posible añadir una
estructura else que se activará en el caso de que la
excepción no coincida con ninguna de las
anteriores.

try

// Instrucciones protegidas

except

on EExcepcion1 do begin … end; // Varias
sentencias

on EExcepcion2 do …; // Una sola sentencia

else // En caso de que no haya sido ninguna de las
anteriores

begin// Sentencias

end;

end;

También es posible especificar un código
único de gestión para todas las excepciones que se
produzcan. Simplemente no especificamos ninguna excepción
y escribimos directamente las sentencias.

try

// Sentencias protegidas

except

// Sentencias de gestión de las
excepciones

end;

Dentro de una estructura on EExcepcion do podemos
especificar un identificador de trabajo de la
excepción. Por ejemplo podemos escribir el mensaje propio
de la excepción, el que hemos establecido en la orden
raise, de la forma siguiente :

try

// Sentencias protegidas

except

on E : Exception do Writeln(E.Message);

end;

El identificador E sólo existe dentro del
código de gestión de excepciones. Este
código se ejecutará siempre puesto que todas las
excepciones derivan de Exception.

El tipo Exception tiene la definición
siguiente :

Exception = class(TObject)

private

fmessage : string;

public

constructor Create(const msg : string);

property Message : string read fmessage write
fmessage;

end;

De hecho la declaración es algo más
extensa para mantener la compatibilidad con Delphi, pero
estos son los elementos básico de las excepciones en
FreePascal. Como se ve, la propiedad
Message es libremente modificable sin ningún problema.
Esta propiedad se asigna en la llamada al constructor
Create.

Código de
finalización

Muchas veces nos interesará que un código
de finalización se ejecute siempre, incluso si ha habido
una excepción. Esto es posible gracias a la estructura try
.. finally. El código siguiente es bastante
habitual :

try

MiObjeto := TMiObjeto.Create(…);

MiObjeto.Metodo1;

MiObjeto.Metodo2;

MiObjeto.MetodoN;

finally

MiObjeto.Free;

end;

La sentencia MiObjeto.Free se ejecutará siempre,
haya habido o no alguna excepción en las sentencias
anteriores a finally. En el caso de que haya una
excepción, el hilo de ejecución pasará
directamente al bloque finally sin completar las otras
sentencias. Este código es sumamente útil para
asegurarse de que una excepción no impedirá que se
llamen las sentencias de finalización.

Sobrecarga de operadores

Ampliar las capacidades del lenguaje

Uno de los problemas más graves del lenguaje de
programación Pascal es su rigidez sintáctica.
FreePascal añade una característica interesante, a
la vez que potente, llamada sobrecarga de operadores. La
sobrecarga de operadores se basa en añadir nuevas
funcionalidades a algunos operadores. Como sabemos, un mismo
operador no siempre hace la misma función. Sin ir
más lejos, el operador + concatena cadenas y hace sumas
enteras y reales.

Dónde más utilidad puede tener la
sobrecarga de operadores está en añadir nuevas
posibilidades al lenguaje de forma que es posible emplear los
operadores de una forma más intuitiva. Este hecho abre un
abanico enorme de posibilidades desde el punto de vista
programático. Ya no será necesario emplear
funciones especiales para poder sumar
unos hipotéticos tipos TMatriz o TComplejo sino que
podremos emplear los operadores que hemos
sobrecargado.

Operadores sobrecargables

No es posible sobrecargar todos los operadores,
sólo los más típicos dentro de expresiones.
Estos operadores son :

Aritméticos : + – * / **

Relacionales : = < <= > >=

Asignación : :=

Como se puede ver, no es posible sobrecargar el operador
de desigualdad (<>) ya que no es nada más que el
operador de igualdad pero
negado.

El resto de operadores de Pascal (^, @) o las palabras
reservadas que hacen las veces de operadoras tampoco se pueden
sobrecargar (div, mod, is, …).

Un ejemplo completo : operaciones
dentro del cuerpo de matrices

Una matriz es una
tabla formada por elementos. Generalmente estos elementos suelen
ser reales, pero también podría ser una tabla de
enteros. Esto es un ejemplo de matriz :

No se pudo entender (Falta el
ejecutalbe de texvc; por favor, lee math/README para
configurarlo.):
A_{2×3}=begin{bmatrix}-2&1&-5\10&-7&3end{bmatrix}

Esta matriz es de 2 filas por 3 columnas. Vamos a
definir una clase que represente una matriz. Dado que FreePascal
no admite los arrays dinámicos, tendremos que hacer alguna
"trampa" para poder disponer de un comportamiento similar a un
array dinámico, o sea, de tamaño no prefijado antes
de compilar. Nos aprovecharemos de una propiedad de los punteros
que permite acceder a los datos con sintaxis de array. La clase,
de nombre TMatriz, es la siguiente :

{$MODE OBJFPC}

uses SysUtils; // Para las excepciones

// La matriz estará indexada en cero —>
[0..CountI-1, 0..CountJ-1]

type

TMatriz = class(TObject)

private

PTabla : Pointer;

FTabla : ^Extended;

FCountI, FCountJ : Integer;

procedure SetIndice(i, j : integer; v :
Extended);

function GetIndice(i, j : integer) :
Extended;

public

constructor Create(i, j : integer);

destructor Destroy; override;

procedure EscribirMatriz;

procedure LlenarAleatorio(n : integer);

property Indice[i,j:integer]:Extended read GetIndice
write

SetIndice; default;

property CountI : Integer read FCountI;

property CountJ : Integer read FCountJ;

end;

constructor TMatriz.Create(i, j :
integer);

begin

inherited Create;

FCountI := i;

FCountJ := j;

GetMem(PTabla, SizeOf(Extended)*FCountI*FCountJ); //
Reservamos memoria
suficiente

FTabla := PTabla;

end;

destructor TMatriu.Destroy;

begin

FreeMem(PTabla, SizeOf(Extended)*FCountI*FCountJ); //
Liberamos la tabla

inherited Destroy;

end;

procedure TMatriu.SetIndice(i, j : integer;
v : Extended);

begin

if ((0 <= i) and (i < CountI)) and ((0 <= j)
and (j < CountJ)) then

begin

FTabla[CountJ*i + j] := v;

end

else raise ERangeError.Create('Rango no válido
para la matriz');

end;

function TMatriu.GetIndice(i, j : integer) :
Extended;

begin

GetIndice := 0;

if ((0 <= i) and (i < CountI)) and ((0 <= j)
and (j < CountJ)) then

begin

GetIndice := FTaula[CountJ*i + j];

end

else raise ERangeError.Create('Rango no válido
para la matriz');

end;

procedure TMatriu.EscribirMatriz;

var

i, j : integer;

begin

for i := 0 to CountI-1 do

begin

for j := 0 to CountJ-1 do

begin

Write(Self[i, j]:3:1, ' ');

end;

Writeln;

end;

end;

procedure TMatriu.LlenarAleatorio(n :
integer);

var

i, j : integer;

begin

for i := 0 to CountI-1 do

begin

for j := 0 to CountJ-1 do

begin

Self[i, j] := Random(n);

end;

end;

end;

Trabajar con la matriz es sencillo. Simplemente la
creamos, indicando el tamaño que queramos que tenga.
Después la llenamos con valores aleatorios y siempre que
queramos podemos escribir la matriz. Esto lo podemos hacer
mediante los procedimientos EscribirMatriz y LlenarAleatorio. Por
ejemplo :

var

Matriz : TMatriz;

begin

try

Matriz := TMatriz.Create(2, 3);

Matriz.LlenarAleatorio(20);

Matriz.EscriureMatriu;

finally

Matriz.Free;

end;

end.

Un posible resultado sería el
siguiente :

8.0 16.0 18.0

13.0 19.0 3.0

La operación suma

Dadas dos matrices A_nxm y B_nxm podemos hacer la suma y
obtener una matriz C_nxm donde todos los elementos son c_i,j =
a_i,j + b_i,j Por ejemplo :

No se pudo entender (Falta el
ejecutalbe de texvc; por favor, lee math/README para
configurarlo.):
A_{2×3}=begin{bmatrix}-2&1&-5\10&-7&3end{bmatrix}

Como es evidente sólo podremos sumar matrices del
mismo tamaño y el resultado también será una
matriz del mismo tamaño. La operación suma,
además, es conmutativa por lo que A+B = B+A. Con todos
estos datos ya podemos redefinir el operador +.

Para redefinir operadores emplearemos la palabra
reservada operator que indica al compilador que vamos a
sobrecargar un operador. Justo después hay que indicar el
operador en cuestión, los dos operados, el identificador
de resultado y su tipo. En nuestro caso, el operador +
devolverá una instancia a un objeto de tipo matriz con el
resultado. Si queremos asignar el resultado a una matriz
habrá que tener en cuenta de que no tiene que estar
instanciada. Si las matrices no se pueden sumar porque tienen
dimensiones diferentes, entonces emitiremos una excepción
que será necesario que sea gestionada y el resultado de la
operación será un puntero nil.

operator + (A, B : TMatriz) C :
TMatriz;

var

i, j : integer;

begin

// C no tiene que estar instanciad porque lo haremos
ahora

C := nil;

if (A.CountI = B.CountI) and (A.CountJ = B.CountJ)
then

begin

// Creamos la matriz de tamaño
adecuado

C := TMatriz.Create(A.CountI, A.CountJ);

for i := 0 to A.CountI-1 do

begin

for j := 0 to A.CountJ-1 do

begin

// Sumamos reales

C[i, j] := A[i, j] + B[i, j];

end;

end;

end

else

begin

// Lanzamos una excepción

raise EDimError.Create('Tamaño de matrices
distinto');

end;

end;

Hemos sobreescrito el operador para que permita realizar
operaciones de objetos de tipo TMatriz. Como hemos comentado, la
suma de matrices es conmutativa. Ya que los dos operadores son
iguales, no es necesario sobrecargar el operador para permitir la
operación conmutada. La excepción EDimError la
hemos definida para indicar excepciones de dimensiones de
matrices.

Este es un ejemplo del uso de la suma
sobrecargada :

var

MatrizA, MatrizB, MatrizC : TMatriz;

begin

Randomize; { Para que salgan números aleatorios
}

try

MatrizA := TMatriz.Create(2, 3);

MatrizA.LlenarAleatorio(50);

Writeln('A = ');

MatrizA.EscribirMatriu;

Writeln;

MatrizB := TMatriz.Create(2, 3);

MatrizB.LlenarAleatorio(50);

Writeln('B = ');

MatrizB.EscribirMatriz;

Writeln;

// MatrizC se inicializa con la suma !!!

try

Writeln('C = ');

MatrizC := MatrizA + MatrizB;

MatrizC.EscribirMatriz;

Writeln;

except

on E : EDimError do

begin

Writeln(E.message);

end;

end;

finally

MatrizA.Free;

MatrizB.Free;

MatrizC.Free;

end;

end.

En este caso el código no falla pero si
hubiéramos creado matrices de tamaños distintos
hubiéramos obtenido un mensaje de error. La salida del
programa es parecida a la siguiente :

A =

14.0 10.0 33.0

10.0 23.0 46.0

B =

1.0 48.0 32.0

47.0 44.0 5.0

C =

15.0 58.0 65.0

57.0 67.0 51.0

Producto de una matriz por un
escalar

Es posible multiplicar una matriz por un numero escalar
de forma que todos los elementos de la matriz queden
multiplicados por este número. Téngase en cuenta de
que esta operación también es conmutativa k A = A k
donde k es un real. En este caso, los dos operandos son
diferentes y habrá que redefinir dos veces el producto
puesto que sino no podríamos hacer productos a la
inversa. En estos casos no lanzaremos ninguna excepción
pues el producto siempre se puede llevar a cabo. Ahora tenemos
que redefinir el operador *.

operator * (A : TMatriz; k : Extended)
C : TMatriz;

var

i, j : integer;

begin

C := TMatriz.Create(A.CountI, A.CountJ);

for i := 0 to A.CountI-1 do

begin

for j := 0 to A.CountJ-1 do

begin

C[i, j] := A[i, j]*k;

end;

end;

end;

operator * (k : Extended; A : TMatriz)
C : TMatriz;

begin

C := A*k; // Esta operación ya ha sido
sobrecargada

end;

Un ejemplo del funcionamiento de una matriz por un
escalar :

var

MatrizA, MatrizC : TMatriu;

begin

Randomize;

try

MatrizA := TMatriz.Create(2, 3);

MatrizA.LlenarAleatorio(50);

Writeln('A = ');

MatrizA.EscribirMatriz;

Writeln;

Writeln('A*12 = ');

MatrizC := MatrizA*12;

MatrizC.EscribirMatriz;

MatrizC.Free;

Writeln;

Writeln('12*A = ');

MatrizC := 12*MatrizA;

MatrizC.EscribirMatriz;

MatrizC.Free;

finally

MatrizA.Free;

end;

end.

La salida del programa es la siguiente :

A =

22.0 47.0 37.0

11.0 15.0 28.0

A*12 =

264.0 564.0 444.0

132.0 180.0 336.0

12*A =

264.0 564.0 444.0

132.0 180.0 336.0

Como se ve, la sobrecarga de * que hemos implementado es
forzosamente conmutativa.

Producto de matrices

Las matrices se pueden multiplicar siempre que
multipliquemos una matriz del tipo A_nxm con una matriz del tipo
B_mxt . El resultado es una matriz de tipo C_nxt . El cálculo de
cada elemento es algo más complejo que en las dos
operaciones anteriores :

No se pudo entender (Falta el
ejecutalbe de texvc; por favor, lee math/README para
configurarlo.):
A_{2×3}=begin{bmatrix}-2&1&-5\10&-7&3end{bmatrix}

El algoritmo de multiplicación es más
complicado. El operador sobre cargado es el
siguiente :

operator * (A, B : TMatriz) C :
TMatriz;

var

i, j, t : integer;

sum : extended;

begin

C := nil;

// Es necesario ver si las matrices son
multiplicables

if A.CountJ <> B.CountI then raise
EDimError.Create('Las matrices no son
multiplicables');

C := TMatriz.Create(A.CountI, B.CountJ);

for i := 0 to C.CountI-1 do

begin

for j := 0 to C.CountJ-1 do

begin

sum := 0;

for t := 0 to A.CountJ-1 do

begin

sum := sum + A[i, t]*B[t, j];

end;

C[i, j] := sum;

end;

end;

end;

Veamos un código de ejemplo de esta
operación :

var

MatrizA, MatrizB, MatrizC : TMatriz;

begin

Randomize;

try

Writeln('A = ');

MatrizA := TMatriz.Create(3, 4);

MatrizA.LlenarAleatorio(10);

MatrizA.EscribirMatriz;

Writeln;

Writeln('B = ');

MatrizB := TMatriz.Create(4, 2);

MatrizB.LlenarAleatorio(10);

MatrizB.EscribirMatriz;

Writeln;

try

MatrizC := (MatrizA*MatrizB);

Writeln('A x B = ');

MatrizC.EscribirMatriz;

except

on E : EDimError do

begin

Writeln(E.message);

end;

end;

finally

MatrizA.Free;

MatrizB.Free;

MatrizC.Free;

end;

end.

Este programa genera una salida parecida a la
siguiente :

A =

2.0 -8.0 4.0 -2.0

7.0 2.0 0.0 -3.0

0.0 -3.0 -4.0 -4.0

B =

-4.0 0.0

-5.0 5.0

-1.0 -2.0

4.0 3.0

A x B =

20.0 -54.0

-50.0 1.0

3.0 -19.0

Programación de bajo
nivel

Los operadores bit a bit

Llamamos operadores bit a bit a los operadores que
permiten manipular secuencias de bits. Todos los datos en un
ordenador se codifican en base de bits que pueden tomar el valor
0 o 1. Los enteros, por ejemplo, son conjuntos de
bits de diferentes tamaños (byte, word, double
word, quadruple word…).

Algunas veces, en programación de bajo nivel y ensamblador,
nos interesará modificar los valores
bit a bit. FreePascal incorpora un conjunto de operadores que
hacen este trabajo por nosotros.

Los operadores bit a bit son : not, or, and, xor,
shl y shr. Los operadores bit a bit sólo trabajan sobre
enteros. En los ejemplos emplearemos los números 71 y 53
que en representación binaria de 8 bits
son :

71_10 = 01000111_2 53_10 = 00110101_2

El operador or

El operador or hace un or binario que responde a la
tabla siguiente :

or

0

0

0

0

1

1

1

0

1

1

1

1

En nuestro ejemplo :

71 or 53 = 01000111 or 00110101 = 01110111

El operador and

El operador and hace un and bit a bit que responde a la
tabla siguiente :

and

0

0

0

0

1

0

1

0

0

1

1

1

Ejemplo :

71 and 53 = 01000111 and 00110101 = 00000101

El operador xor

El operador xor hace una or exclusiva que responde a la
tabla siguiente :

xor

0

0

0

0

1

1

1

0

1

1

1

0

71 xor 53 = 01000111 xor 00110101 = 01110010

El operador not

El operador not hace una negación binaria que
responde a la tabla siguiente. Es un operador unario.

not

0

1

1

0

not 71 = not 01000111 = 10111000

Los operadores shl y shr

Los operadores shl y shr hacen un desplazamiento de bits
a la izquierda o derecha respectivamente de todos los bits (SHift
Left, SHift Right). Los bits que hay que añadir de
más son siempre cero. El número de desplazamientos
viene indicado por el segundo operando.

71 shl 5 = 01000111 shl 00000101 = 11100000

71 shr 5 = 01000111 shr 00000101 = 00000010

Las operaciones shl y shr representan productos i
divisiones enteras por potencias de dos.

71 shl 5 = 71*(2**5) (8 bits)

71 shr 5 = 71 div (2**5)

Empleo de código
ensamblador

Es posible incluir sentencias de código
ensamblador en medio del código. Para indicar al
compilador que lo que sigue es ensamblador emplearemos la palabra
reservada *asm*.

Sentencias Pascal

asm

Sentencias de ensamblador

end;

Sentencias Pascal

El código que se encuentra en medio de asm y end
se incluye directamente al archivo de
ensamblaje que se le pasa al enlazador (linker).

Es posible especificar una función exclusivamente
escrita en ensamblador mediante la directiva assembler. Esto
produce funciones en ensamblador ligeramente más
optimizadas.

procedure NombreProcedimiento(parametros);
assembler;

asm

end;

Es posible hacer lo mismo con funciones.

Tipo de sintaxis del
ensamblador

FreePascal soporta dos tipos de sintaxis de ensamblador.
La sintaxis Intel, la más habitual y la más
conocida cuando se trabaja en esta plataforma, y la sintaxis
AT&T que es la sintaxis que se hace servir en el
GNU Assembler, el
ensamblador que emplea FreePascal. Por defecto la sintaxis es
AT&T pero es posible emplear el tipo de sintaxis con la
directiva de compilador $ASMMODE.

{$ASMMODE INTEL} // Sintaxis INTEL

{$ASMMODE ATT} // Sintaxis AT&T

{$ASMMODE DIRECT} { Los bloques de ensamblador se copian
directamente al archivo de ensamblaje }

Consulte en la documentación de FreePascal sobre las
peculiaridades y diferencias entre la sintaxis Intel y la
sintaxis AT&T. Consulte también como puede trabajar
con datos declarados en sintaxis Pascal dentro del código
ensamblador.

Compilación condicional, mensajes y
macros

Compilación
condicional

La compilación condicional permite modificar el
comportamiento del compilador en tiempo de compilación a
partir de símbolos condicionales. De esta forma
definiendo o suprimiendo un símbolo podemos modificar el
comportamiento del programa una vez compilado.

Definición de un
símbolo

Vamos a ver un ejemplo de compilación
condicional. Haremos un un programa que pida un año al
usuario. Este año puede ser de dos cifras o bien puede ser
de cuatro cifras. En ambos casos necesitaremos un entero :
un byte para las dos cifras y un word para las cuatro cifras.
Declararemos el símbolo DIGITOS_ANYOS2 cuando queramos que
los años tengan dos cifras, en otro caso tendrán
cuatro cifras.

Para declarar un símbolo emplearemos la directiva
de compilador {$DEFINE nombresimbolo} en nuestro caso cuando
queramos dos cifras añadiremos al principio del
código :

{$DEFINE DIGITOS_ANYOS2}

Como en la mayoría de identificadores Pascal, el
nombre del símbolo no es sensible a mayúsculas. Por
lo que concierne a la validez del nombre del símbolo se
sigue el mismo criterio que los nombres de variables y
constantes.

Esta directiva es local, de forma que, justo
después (pero no antes) de incluirla el símbolo
DIGITOS_ANYOS2 queda definido. Pero también podemos
desactivar o anular el símbolo más
adelante.

Anulación de símbolos
condicionales

Si llegados a un punto del código nos interesa
anular un símbolo entonces emplearemos la directiva de
compilador {$UNDEF nombresimbolo}. Por ejemplo, en nuestro
caso :

{$UNDEF DIGITOS_ANYOS2}

anularía el símbolo, en caso de que
estuviera definido. Si el símbolo no está definido
entonces simplemente no pasa nada. Esta directiva también
es local, a partir del punto donde la hemos añadido el
símbolo queda anulado.

Empleo de los símbolos
condicionales

Las directivas condicionales más usuales son
{$IFDEF simbolo}, {$else} y {$ENDIF}.

{$IFDEF nombresimbolo} compila todo lo que le sigue
sólo si está definido el símbolo
nombresimbolo. Además, inicia una estructura de
compilación condicional que tenemos que terminar siempre
con un {$ENDIF}. Si nombresimbolo no estuviera definido
podríamos emplear la directiva {$else} para indicar que
sólo se compilará si nombresimbolo no está
definido. {$else} tiene que estar entre $IFDEF y
$ENDIF.

Obsérvese el código
siguiente :

program CompilacionCondicional;

{$IFDEF DIGITOS_ANYOS2}

procedure PedirAnyo(var Dato :
Byte);

begin

repeat

Write('Introduzca el valor del año (aa) :
');

Readln(Dato);

until (0 <= Dato) and (Dato <= 99);

end;

{$else}

procedure PedirAnyo(var Dato : Word);

begin

repeat

Write('Introduzca el valor del año (aaaa) :
');

Readln(Dato);

until (1980 <= Dato) and (Dato <=
2099);

end;

{$ENDIF}

var

{$IFDEF DIGITOS_ANYOS2}

anyo : byte;

{$else}

anyo : word;

{$ENDIF}

begin

PedirAnyo(anyo);

end.

Si compilamos ahora este código, el programa nos
pedirá cuatro cifras para el año. En cambio si
después de la cláusula program añadimos
{$DEFINE DIGITOS_ANYOS2}

program CompilacionCondicional;

{$DEFINE DIGITOS_ANYOS2} // Definimos el
símbolo

{$IFDEF DIGITOS_ANYOS2}

procedure SolicitarAny(var Dato :
Byte);

entonces el programa una vez compilado sólo
pedirá dos cifras. Como se puede deducir, por defecto los
símbolos no están activados.

La directiva {$IFNDEF nombresimbolo} acta al
revés que $IFDEF ya que compilará el bloque
siguiente si no está definido nombresimbolo. En cuanto al
uso, es idéntico a $IFDEF.

Empleo de símbolos comunes a todo
un proyecto

El compilador no permite definir símbolos comunes
a todo un conjunto de archivos sino que
tienen que ser definidos para todos y cada uno de los archivos
que necesitemos. Para no tener que añadirlos cada vez en
cada archivo, sobretodo en proyectos
grandes, podemos emplear la directiva de incluir archivo {$I
nombrearchivo}.

La directiva {$I nombrearchivo} hace que el compilador
sustituya esta directiva por el contenido del archivo que indica.
Por ejemplo, podíamos haber creado un archivo llamado
CONDICIONALES.INC (suelen llevar esta extensión) que
tuviera la línea siguiente :

{$DEFINE DIGITOS_ANYOS2}

y después haberlo incluido en el
programa :

program CompilacionCondicional;

{$I CONDICIONALES.INC}

{$IFDEF DIGIT_ANYS2}

procedure PedirAnyo(var Dato :
Byte);

De esta forma no es necesario cambiar varios $DEFINE en
cada archivo. Simplemente cambiando la directiva contenida en
CONDICIONALES.INC tendremos suficiente.

Es importante que el archivo incluido pueda ser
encontrado por el compilador. Lo más fácil es que
se encuentre en el mismo directorio que el archivo que lo
incluye. También podemos indicarle al compilador que lo
busque al directorio que nosotros queramos mediante el
parámetros -Fidirectorio donde directorio es el directorio
donde puede encontrar el archivo. También podemos emplear
la directiva {$INCLUDEPATH directorio1;..;directorion}

Como se ve, los archivos incluidos permiten resolver
algunos problemas que no podían solventarse sólo
con la sintaxis.

Trabajar con directivas de tipo switch

Hasta el momento, hemos visto algunas directivas de tipo
switch {$I+}/{$I-}, {$H+}/{$H-} que permiten desactivar algunas
opciones del compilador. Para compilar un cierto código en
función del estado de una
directiva de este tipo podemos emplear {$IFOPT directiva+/-}. Por
ejemplo :

{$IFOPT H+}

Writeln('String = AnsiString');

{$else}

Writeln('String = String[255]');

{$ENDIF}

Símbolos predefinidos del
compilador FreePascal.

Los símbolos siguientes están predefinidos
a la hora de compilar programas con FreePascal.

Símbolo

Explicación

FPC

Identifica al compilador FreePascal. Es
útil para distinguirlo de otros compiladores.

VERv

Indica la versión, donde v es la
versión mayor. Actualmente es VER1

VERv_r

Indica la versión, donde v es la
versión mayor y r la versión menor.
Actualmente es VER1_0

VERv_r_p

Indica la versión, donde v es la
versión mayor, r la versión menor y p la
distribución. La versión 1.0.4
tiene activado el símbolo VER1_0_4

OS

Donde OS puede valer DOS, GO32V2, LINUX,
OS2, WIN32, MACOS, AMIGA o ATARI e indica para qué
entorno se está compilando el programa. En nuestro
caso será WIN32.

Mensajes, advertencias y
errores

Durante la compilación, el compilador puede
emitir diversos mensajes. Los más usuales son los de error
(precedidos por Error:) que se dan cuando el compilador se
encuentra con un error que no permite la compilación.
Normalmente suelen ser errores sintácticos. Aún
así se continúa leyendo el código para ver
si se encuentra algún otro error. Los errores fatales
(precedidos por Fatal:) impiden que el compilador continúe
compilando. Suele ser habitual el error fatal de no poder
compilar un archivo porque se ha encontrado un error
sintáctico.

El compilador también emite mensajes de
advertencia (precedidos de Warning:) cuando encuentra que
algún punto es susceptible de error (por ejemplo, una
variable no inicializada). También emite mensajes de
observación (Note:) y consejos (Hint:)
sobre aspectos del programa, como son variables declaradas pero
no empleadas, inicializaciones inútiles, etc.

Es posible hacer que el compilador emita mensajes de
este tipo y que el compilador se comporte de forma
idéntica a que si los hubiera generado él. Para
esto emplearemos las directivas $INFO, $MESSAGE (que emiten un
mensaje), $HINT (un consejo), $NOTE (una indicación),
$WARNING (una advertencia), $ERROR (un error), $FATAL, $STOP (un
error fatal). Todas las directivas toman como parámetro
único una cadena de caracteres que no tiene que ir entre
comas. Por ejemplo

{$IFDEF DIGITOS_ANYOS2}

{NOTE El soporte de cuatro dígitos para
años está desactivado}

{$ENDIF}

Hay que tener en cuenta que dentro de esta cadena de
caracteres no podemos incluir el carácter puesto que el compilador lo
entenderá como que la directiva ya se ha
terminado.

Macros

Las macros son una característica bastante
habitual en C y C++ puesto que este lenguaje posee un
preprocesador. En Pascal no suele haber un preprocesador pero
FreePascal permite activar el soporte de macros, que por defecto
está desactivado, con la directiva {$MACRO on} y para
desactivarlo {$MACRO OFF}.

Una macro hace que se sustituyan las expresiones del
código por otras. Esta sustitución se hace antes de
compilar nada, o sea, desde un punto de vista totalmente no
sintáctico. Las macros se definen de la forma
siguiente:

{$DEFINE macro := expresión}

Expresión puede ser cualquier tipo de
instrucción, sentencia o elemento del lenguaje. En este
ejemplo hemos sustituido algunos elementos sintácticos del
Pascal por otros para que parezca

pseudocódigo :

{$MACRO on} // Hay que activar las macros

{$DEFINE programa := program}

{$DEFINE fprograma := end.}

{$DEFINE si := if}

{$DEFINE entonces := then begin }

{$DEFINE sino := end else begin}

{$DEFINE fsi := end;}

{$DEFINE fvar := begin}

{$DEFINE entero := integer}

{$DEFINE escribir := Writeln}

{$DEFINE pedir := readln}

programa Macros;

var

a : entero;

fvar

escribir('Introduzca un entero cualquiera : ');
pedir(a);

si a mod 2 = 0 *entonces*

escribir('El número es par')

sino

escribir('El número es impar');

fsi

fprograma

1 <#sdfootnote1anc>A consecuencia de esto los
caracteres no ingleses como la ÿ o la ñ se consideran
siempre los últimos. Por ejemplo 'caña' < 'cata'
es falso. En esta cuestión también están
incluidas las vocales acentuadas y los caracteres
tipográficos.

2 <#sdfootnote2anc>La función AssignFile
hace lo mismo que Assign.

3 <#sdfootnote3anc>En vez de Close se puede
emplear CloseFile.

4 <#sdfootnote4anc>Esta convención
está incorporada en el compilador FreePascal pero se
ignora y es sustituida por la convención stdcall. Es la
convención por defecto que emplea el compilador Borland
Delphi de 32-bits.

5 <#sdfootnote5anc>Esto provoca algunos cambios en
el comportamiento del compilador. El más importante
está en la declaración del tipo Integer. En los
modos OBJFPC y DELPHI el tipo Integer es lo mismo que Longint, o
sea un entero de 32-bits con signo. En los modos TP y FPC es un
entero sin signo de 16-bits.

Obtenido de ""

 

 

 

Autor:

Rafael Freites

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