08 agosto 2008

Pasando de Delphi 7 a RAD Studio 2007 (5)

Hoy vamos a comenzar a ver las novedades que incorpora RAD Studio 2007 en el lenguaje de programación Object Pascal respecto a Delphi 7.

REGISTROS CON MÉTODOS

Si hasta ahora no se le daba mucha importancia a los registros (record) a favor de las clases para crear y almacenar nuestros propios tipos de datos, ahora podemos aprovecharlos de nuevo con la posibilidad de añadir métodos como si fueran clases.

Y no sólo eso, sino que además se pueden tener su propio constructor e incluso métodos y propiedades. De este modo podemos encapsular tanto los datos así la manipulación de los mismos dentro de un mismo registro y además gozando de la ventaja de no tener que instanciar ni liberar la clase.

Veámoslo con un ejemplo. Supongamos que tenemos este registro para definir un artículo:

type
TArticulo = record
ID: Integer;
Nombre: String;
Existencias, Precio: Real;
end;

Pues ahora podemos hacer algo como esto:

TArticulo = record
var
ID: Integer;
Nombre: String;
Existencias, Precio: Real;

constructor Create( ExistenciasIniciales, Precio: Real );
end;

La palabra reservada var no es obligatoria, aunque deja el código más elegante. Aquí tenemos la implementación de su constructor:

constructor TArticulo.Create(ExistenciasIniciales, Precio: Real);
begin
Existencias := ExistenciasIniciales;
Self.Precio := Precio;
end;

De este modo podemos crear un artículo al igual que una clase sin instanciarlo:

var
Articulo: TArticulo;
begin
Articulo.Create( 10, 132.25 );
ShowMessage( 'Existencias=' + FloatToStr( Articulo.Existencias ) + #13 +
'Precio=' + FloatToStr( Articulo.Precio ) );
end;

Nos mostraría esto en pantalla:


También podemos crear propiedades que accedan a las variables encapsulando de este modo todo el registro:

type
TArticulo = record
var
ID: Integer;
Nombre: String;
Existencias, Precio: Real;
Comprado, Vendido: Real;

constructor Create( ExistenciasIniciales, Precio: Real );
procedure Comprar( Unidades: Real );
procedure Vender(Unidades: Real);
function CalcularExistencias: Real;
property ExistenciasReales: Real Read CalcularExistencias;
end;

Esta sería su implementación:

constructor TArticulo.Create(ExistenciasIniciales, Precio: Real);
begin
Existencias := ExistenciasIniciales;
Self.Precio := Precio;
Comprado := 0;
Vendido := 0;
end;

procedure TArticulo.Comprar(Unidades: Real);
begin
Comprado := Comprado + Unidades;
end;

procedure TArticulo.Vender(Unidades: Real);
begin
Vendido := Vendido + Unidades;
end;

function TArticulo.CalcularExistencias: Real;
begin
Result := Existencias + Comprado - Vendido;
end;

Ahora aprovechamos del registro Articulo como si fuera una clase:

var
Articulo: TArticulo;
begin
Articulo.Create( 10, 132.25 );
Articulo.Comprar( 100 );
Articulo.Vender( 50 );
ShowMessage( 'Existencias Reales=' + FloatToStr( Articulo.ExistenciasReales ) + #13 +
'Precio=' + FloatToStr( Articulo.Precio ) );
end;

Este sería el resultado:


Sin embargo, los registros todavía no gozan de muchas de las características que poseen las clases:

- Un registro no puede tener destructores.

- No se pueden crear métodos virtuales.

- No se puede heredar de otro registro.

LA SOBRECARGA DE OPERADORES EN REGISTROS

La sobrecarga de operadores nos permite operar con los registros como si fueran valores numéricos. Por ejemplo:

TFactura = record
private
Importe: Real;
public
class operator Implicit( Importe: Real ): TFactura;
class operator Add( F1, F2: TFactura ): TFactura;
class operator Subtract( F1, F2: TFactura ): TFactura;
end;

Las palabras reservadas class operator sirven para redefinir los operadores +, -, =, etc. que se utilizan al tratar con los registros. En esta ocasión sólo he redefinido los operadores de suma y resta para aplicarlos a la factura.

Esta sería su implementación:

class operator TFactura.Implicit( Importe: Real ): TFactura;
begin
Result.Importe := Importe;
end;

class operator TFactura.Add( F1, F2: TFactura ): TFactura;
begin
Result.Importe := F1.Importe + F2.Importe;
end;

class operator TFactura.Subtract( F1, F2: TFactura ): TFactura;
begin
Result.Importe := F1.Importe - F2.Importe;
end;

El operador Implicit se utiliza para que podamos asignar valores al registro directamente como si fuera un valor numérico:

var
F1, F2, F3: TFactura;
begin
F1 := 50;
F2 := 100;
F3 := 20;
F1 := F1 + F2 - F3;

ShowMessage( 'Total facturas=' + FloatToStr( F1.Importe ) );
end;

Como puede apreciarse en el código he asignado los valores directamente al objeto en vez de hacer esto:

F1.Importe := 50;
F2.Importe := 100;
F3.Importe := 20;

Esto me lo ha permitido el operador Implicit. Luego he sumado las dos primeras facturas y les he restado la tercera:

F1 := F1 + F2 - F3;

Que equivale a esto:

F1.Importe := F1.Importe + F2.Importe – F3.Importe;

Al ejecutar este código este es el resultado:


Esto nos da mucha potencia a la hora de crear registros para operar con facturas, recibos, calcular saldos, asientos contables, etc.

LOS BUCLES FOR IN

Esto es algo que ya se echaba de menos respecto a otros lenguajes más modernos que Pascal, como pueden ser Java, Python, Ruby, C#, etc. Es la posibilidad de poder crear un bucle sin tener que crear una variable entera para recorrerlo.

Por ejemplo, si tengo que recorrer un array y sumar su resultado esto es lo que tenía que hacer hasta ahora:

var
Facturas: array[1..3] of Real;
Suma: Real;
i: Integer;
begin
Facturas[1] := 10;
Facturas[2] := 20;
Facturas[3] := 30;

for i := 1 to 3 do
Suma := Suma + Facturas[i];

ShowMessage( 'Suma=' + FloatToStr( Suma ) );
end;

Con este método tenemos que saber el número de elementos en el array. Ahora, mediante el bucle for in podemos recorrer un array sin tener que saber cuantos elementos contiene:

var
Facturas: array[1..3] of Real;
Suma, Factura: Real;
begin
Facturas[1] := 10;
Facturas[2] := 20;
Facturas[3] := 30;

for Factura in Facturas do
Suma := Suma + Factura;

ShowMessage( 'Suma=' + FloatToStr( Suma ) );
end;

En este caso ya no es necesaria la variable entera i para recorrer los elementos del bucle. Sólo hace falta una variable que contenga el elemento actual (Factura). Esto es muy útil para recorrer arrays dinámicos sin preocuparse en el número de elementos que tienen.

Aunque esto lo considero algo incompleto, porque no se puede hacer algo como esto:

var
Componente: TComponent;
begin
for Componente in Form1.Components do
ShowMessage( Componente.Name );
end;

Al compilarlo nos da este error:

‘[‘ Expected but ‘DO’found

Es decir, que esperaba los corchetes después de la palabra Components. Si no podemos utilizar un for in para este tipo de elementos la verdad es que nos quedamos casi igual que antes. Si sólo podemos utilizarlo en arrays y en colecciones simples la verdad es que se nos queda algo corto.

LA DIRECTIVA INLINE

Esta directiva puede aplicarse a funciones o procedimientos modificando el modo de compilación de los mismos. Vamos a verlo con un pequeño ejemplo.

Este es el típico bucle para ordenar los elementos de un array por el método de la burbuja:

var
i, j, tmp: Integer;
Elementos: array[1..10000] of Integer;
Tiempo: Dword;
begin
// Rellenamos el array con valores aleatorios

Randomize;
for i := 1 to 100 do
Elementos[i] := Random( 500 ) + 1;

Tiempo := TimeGetTime;

// Ordenamos los elementos del array con el método de la burbuja

for j := 1 to 9999 do
for i := 1 to 9999 do
if Elementos[i] > Elementos[i+1] then
begin
tmp := Elementos[i];
Elementos[i] := Elementos[i+1];
Elementos[i+1] := tmp;
end;

ShowMessage( 'Milisegundos=' + IntToStr( TimeGetTime - Tiempo ) );
end;

Para poder utilizar la función TimeGetTime es necesario añadir en uses la unidad mmsystem. Lo que he hecho es crear un array de 10000 números enteros y les he metido a cada uno un valor aleatorio entre 1 y 500. Después he utilizado el método de la burbuja para ordenarlos. Al ejecutar el programa me tarda 155 milisegundos:


Ahora supongamos que quiero refactorizar el código y quiero sacar a parte el núcleo del bucle en otro procedimiento;

procedure Intercambiar(var a, b: Integer);
var
tmp: Integer;
begin
tmp := a;
a := b;
b := tmp;
end;

De modo que el procedimiento original se nos quedaría así:

var
i, j: Integer;
Elementos: array[1..10000] of Integer;
Tiempo: Dword;
begin
// Rellenamos el array con valores aleatorios

Randomize;
for i := 1 to 100 do
Elementos[i] := Random( 500 );

Tiempo := TimeGetTime;

// Ordenamos los elementos del array con el método de la burbuja
for j := 1 to 9999 do
for i := 1 to 9999 do
if Elementos[i] > Elementos[i+1] then
Intercambiar( Elementos[i], Elementos[i+1] );

ShowMessage( 'Milisegundos=' + IntToStr( TimeGetTime - Tiempo ) );
end;

Al ejecutar el programa vemos que tarda más:


Esto tiene su explicación: cada llamada al procedimiento Intercambiar obliga a realizar un salto a otro procedimiento para luego volver a este. Si el bucle no tiene muchas iteraciones el cambio no suele notarse mucho, pero si se realiza miles de veces entonces la diferencia de tiempo entre uno y otro método suele ser notable.

Para solucionar esto tenemos la directiva inline. Esta directiva aplicada al procedimiento Intercambiar hace que se compile el código de este procedimiento como si realmente nunca lo hubiésemos sacado del bucle. De este modo ganamos claridad en el código fuente sin perder rendimiento en el código compilado.

Sólo hay que añadir la palabra inline al final del procedimiento:

procedure Intercambiar(var a, b: Integer); inline;
var
tmp: Integer;
begin
tmp := a;
a := b;
b := tmp;
end;

Sin ser tan rápido como la manera original si se obtiene un aumento de velocidad:



Quizás en un programa de gestión no es necesario utilizar esta función, pero en la programación de videojuegos algunas funciones se ejecutan tantas veces por segundo que puede marcar la diferencia entre renderizar un mundo 3D con suavidad a 40 o 50 frames por segundo o relentizarse y dar tirores a 20 frames por segundo. Cuando escriba en un futuro articulos de programación de videojuegos 3D con SDL y OpenGL veréis a que me refiero.

En el próximo artículo seguiremos viendo otras novedades en el lenguaje.

Pruebas realizadas en RAD Studio 2007.

Publicidad