Descargar

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