You are on page 1of 97

Revista PROGRAMAR

Compilao de artigos
relacionados com a
linguagem de programao

Object Pascal

Verso 1.1.0 (27 de Outubro de 2014)

Compilado por thoga31


Wiki Team | Moderador
Membro desde 18 de Abril de 2010

Revista PROGRAMAR

Compilado por thoga31

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

ndice
33 Edio
Programao Orientada aos Objectos em Pascal

34 Edio
Mdulos em Pascal

19

35 Edio
Pascal Registos variantes

29

36 Edio
Pascal Construo de uma calculadora grfica

38

42 Edio
Pascal Tipos de dados Variant e tipos genricos

70

44 Edio
Pascal operator overloading

78

45 Edio
Pascal array de argumentos

88

Compilado por thoga31

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

33 Edio

Programao Orientada aos Objectos


em Pascal
Por: Igor Nunes

Compilado por thoga31

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Introduo
H quem pense no Pascal como uma linguagem fraca e antiquada que no
acompanhou os novos tempos. Mas, na realidade, o Pascal evoluiu em vrios dialectos,
sendo o mais proeminente o Delphi.
Hoje em dia possvel programar
nesta
linguagem
segundo
o
paradigma
POO
(Programao
Orientada aos Objectos), programar
DLLs (Dynamic Link Libraries), fazer
programas no s em consola mas
tambm com recurso a GUI. Permite
igualmente a ligao a bases de
dados, como o MySQL, bem como a
criao de IDEs. O grande exemplo
o, infelizmente descontinuado, IDE
Dev-Pascal, da Bloodshed, um IDE
open-source, escrito totalmente em
Delphi e recorrendo a uma velha
verso do FPC (Free Pascal Compiler) e do GPC (GNU Pascal Compiler) como
compiladores opcionais integrados.

Posta esta breve introduo, os objectivos para o presente artigo so:


Dar a conhecer noes tericas bsicas sobre o
paradigma POO;
Apresentar o dialecto Object Pascal;
Utilizar ferramentas do Free Pascal e do Delphi para
criar programas segundo o paradigma POO;
Criar uma classe til para os leitores.

Doravante, a linguagem Object Pascal ser


designada to-somente por Pascal por duas razes:
1. Genericamente, qualquer dialecto derivado do
Pascal (leia-se Standard Pascal) ser designado
exacta e somente por Pascal;
2. Para simplificar a leitura do presente artigo.

o Pascal
evoluiu em
vrios dialectos,
sendo o mais
proeminente o
Delphi.

Os trechos de cdigo aqui presentes foram todos


testados recorrendo ao Free Pascal 2.4.4.
Em todos os cdigos aqui presentes foi utilizado o modo de compatibilidade com
Delphi do Free Pascal, pelo que estar presente nestes a directiva de compilao
{$mode delphi}.

Compilado por thoga31

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Preliminares em POO
O mbito do presente no artigo no uma explicao
alongada deste paradigma. Contudo, vale uma explicao
no mbito do Pascal e daquilo que ser utilizado adiante.
Sero ento abordados os conceitos principais deste
paradigma, sendo estes: classes, mtodos, propriedades,
instncias, herana e visibilidade (ou proteco).

Classes, mtodos, propriedades e instncias


Uma classe um conjunto de objectos com caractersticas iguais. Um mtodo
da classe no mais do que uma capacidade da classe, ou seja, um procedimento ou
funo. Uma propriedade um atributo, isto , uma caracterstica do objecto. Uma
instncia da classe um objecto com todas as caractersticas dessa mesma classe.
De forma prtica, vamos considerar este paradigma num caso da vida real.

Considere-se a classe Ser Humano:


Propriedades altura, peso, idade, sexo, cor do cabelo.
Mtodos andar, falar, sentar, dormir, cozinhar, trabalhar.

Igor Nunes uma instncia desta classe, por exemplo. Sendo uma instncia,
Igor Nunes ter exactamente as propriedades altura, peso, idade, etc., bem como os
mtodos andar, falar, dormir, etc. J a instncia Joo ter exactamente as mesmas
propriedades e mtodos. O prprio leitor uma instncia desta classe! Contudo, de
ter em ateno que cada instncia tem valores distintos para cada propriedade altura,
peso, idade

Compilado por thoga31

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Herana
Uma das capacidades mais impressionantes e teis do paradigma POO a
possibilidade de uma classe herdar mtodos e propriedades de outra. Neste mbito,
temos ento duas classes, uma que deriva de outra:
Classe principal (ou Main Class) classe que tem os mtodos e
propriedades gerais;
Classe derivada (ou Inherited Class) classe que herda os
mtodos e propriedades da anterior deriva da classe principal.
Voltando classe Ser Humano:
1. Mamfero uma classe principal de Ser Humano;
2. Caucasiano uma classe derivada de Ser Humano;
3. Reino Animal uma classe principal de Mamfero;

Representao de herana
entre classes em UML.

Isto significa ento que, respectivamente:


1. Ser Humano um Mamfero;
2. Caucasiano um Ser Humano;
3. Mamfero do Reino Animal.

Visibilidade de mtodos e propriedades

A classe pode ter mtodos e propriedades com visibilidades diferentes:


Pblicas podem ser vistos por qualquer outra classe, programa ou unidade;
Privadas s podem ser vistos pela prpria classe, isto , pelos restantes
mtodos e propriedades, independentemente da sua visibilidade. S esto
ocultos para fora da classe, e no para dentro da classe. So igualmente ocultos
para classes derivadas.
Protegidas so visveis to-somente pela classe e por qualquer outra que
derive desta.

Mais visibilidades existem, e algumas variam de linguagem para linguagem.


Contudo, ficam estas trs, as mais importantes.
Considerando a classe Ser Humano, temos que,
por exemplo:
O mtodo falar protegido, j que no visvel
a outras classes como Leo, Pinguim ou Urso,
mas visvel s classes que lhe tm herana,
como por exemplo Etnias e Raas;
A propriedade altura pblica, j que qualquer
um pode ver e saber quanto mede um ser
humano em altura;
De referir que, se nenhum bloco de visibilidade for criado, ento tudo o que for
declarado na classe considerado pblico por defeito.

Compilado por thoga31

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Colocada a teoria essencial do paradigma POO, segue-se a exemplificao da


sua implementao em Pascal, no antes de dar a conhecer o dialecto do Pascal que
suporta este mesmo paradigma.

O Object Pascal

esta
linguagem
tornou-se muito
mais poderosa e
moderna

Apresentado em 1986, o Object Pascal foi


uma linguagem de programao com a mesma
sintaxe do Standard Pascal, de 1971, e j com
as modificaes introduzidas pelo Extended
Pascal, e foi concebido por uma equipa da Apple
Computer (hoje Apple Inc.), liderada por Larry
Tesler, em conjunto com o criador do Pascal,
Niklaus Wirth.
O seu nome original teria sido Clascal, e foi
concebido devido ao facto de este ser
necessrio para uma Framework da poca, a
MacApp.

Deu-se assim o suporte do Pascal ao paradigma POO.


Com a introduo do POO no Pascal, esta linguagem tornou-se muito mais
poderosa e moderna, sendo cada vez mais um exemplo da estruturao de um
programa, mdulo ou biblioteca.
A sintaxe do Pascal variou consoante os dialectos que foram surgindo. Contudo,
podemos ter dois padres de referncia, sendo o utilizado neste artigo o segundo:
Free Pascal;
Delphi.
Passemos ento implementao em cdigo.

Classes
Um conjunto novo de palavras reservadas surgiu, e entre elas esto as palavras
reservadas Object e Class. de referir que um Object pode ser qualquer coisa, sendo,
portanto, a me de todos os tipos de dados, incluindo procedimentos e funes.
Para agora, a palavra que nos interessa a Class. Com esta palavra reservada
criamos uma classe, tal como o seu nome sugere.
Em Pascal, uma classe dever ser sempre declarada num tipo (type) j que as
instncias da classe sero criadas como variveis (var).

Compilado por thoga31

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Este tipo no comea com a palavra reservada begin mas sim com a prpria
palavra class, mas termina obrigatoriamente com end. Dentro deste bloco que se cria
teremos a declarao, mas no programao, dos mtodos da classe. Igualmente, aqui
estaro as propriedades.
Para definir as visibilidades, podemos criar trs blocos. Nenhum deles necessita
de estar num bloco begin-end j que se considera que o bloco termina quando
encontrada a declarao de um outro bloco, ou o fim da classe.
Na prtica, a estrutura de uma classe ser a seguinte:
type Nome_Classe = class
private
// parte privada
protected
// parte protegida
public
// parte pblica
end;

Mtodos
A parte principal de uma classe o conjunto dos seus mtodos as
habilidades da classe, os processos que esta capaz de realizar1. E, em Pascal, os
mtodos no so mais do que procedimentos e funes.
Na classe, estes apenas so declarados. Nunca so implementados nesta parte:
type Nome_Classe = class
private
function funcao(const texto : tipo_dado) : tipo_output;
public
procedure procedimento(const argumentos : tipo_dado);
end;
Neste caso no temos mtodos protegidos. Como podemos verificar, os mtodos
da classe so declarados dentro dos seus blocos.
Ento, onde so implementados? A resposta : tal como outro procedimento e
funo quaisquer: fora do bloco principal do programa.

Pode-se dizer que os mtodos so a parte principal j que, regra geral, uma classe no tem sentido em
existir s para ter atributos/propriedades. Excepo feita, por exemplo, a classes criadas para o efeito em
que se tornam extremamente teis para organizar informao com uma estrutura semelhante ou igual.

Compilado por thoga31

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Neste caso, queremos que a funo privada conversor converta um carcter


ASCII para o seu sucessor (A passar a ser B, por exemplo), e escrever ser o
mtodo pblico que ir escrever o resultado da funo privada conversor.
PROGRAM exemplo;
type CEx = class
private
function conversor(const texto : string) : string;
public
procedure escrever(const texto : string);
end;
procedure CEx.escrever(const texto : string);
begin
writeln(conversor(texto));
end;
function CEx.conversor(const texto : string) : string;
var i : integer;
t : string;
begin
t := '';
for i:=1 to length(texto) do
t := t + succ(texto[i]);
result := t;
end;
BEGIN
(* Bloco principal de execuo *)
END.
Como podemos reparar, apesar de conversor ser uma funo privada, escrever
pode-lhe aceder j que ambos estes mtodos pertencem mesma classe. Em suma, o
programa principal poder aceder ao procedimento escrever mas nunca funo
conversor.
Alm disso, e muito importante de reter, o facto de o mtodo de cada classe
ser programado identificando em primeiro lugar qual a classe a que pertence o mtodo,
e s depois dizendo o nome do mtodo.
procedure/function classe.mtodo({argumentos}) {: tipo_output};

Compilado por thoga31

10

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Propriedades
Uma propriedade, por si s, no possui nenhum valor. Uma propriedade um
atalho para uma varivel que, e essa sim, possui um valor especfico. Por exemplo, o
nosso peso uma medida de massa que aparece na balana, mas, por detrs desse
valor, est um mecanismo que mediu, registou e fez output desse mesmo valor. Neste
exemplo, abordmos tudo o que h numa propriedade:
Input de um dado (com o seu devido registo);
Output desse mesmo dado (com a sua devida leitura).
Uma propriedade , ento, um atributo da classe que inclui, opcionalmente, um
conjunto de dois mtodos (um input e outro output) que atribuem e lem o valor de uma
varivel da classe. Opcionalmente por uma razo: uma propriedade ser ReadOnly (s
de leitura) se s fornecer output e WriteOnly (s de escrita) se s poder ser atribuda
mas no acedida para leitura.
Considerando o caso geral de uma propriedade que permite leitura e escrita:
property propriedade : tipo_dado read metodo_leitura write metodo_escr
ita;
Para melhor entender este conceito, vamos partir de um exemplo prtico e que
ser j parte da nossa implementao prtica:
type TAngle = class
private
VDEG : real; // Varivel da qual depende a propriedade
procedure DefinirValorDEG(const valor : real);
public
property ValorDEG : real read VDEG write DefinirValorDEG;
end;
procedure TAngle.DefinirValorDEG(const valor : real);
(* Atribui valor propriedade em graus *)
begin
self.VDEG := valor;
end;
Isto o que acontece na prtica e na grande maioria das propriedades. Como
podemos ver, a propriedade, de seu nome ValorDEG, tem uma varivel por detrs
que possui o seu valor: a varivel privada VDEG.
Aqui comeamos j a ganhar noo da vantagem das visibilidades das
propriedades: o que nos interessa que se veja a propriedade da classe, e no a
varivel na qual est atribuda o valor dessa propriedade. Assim, mantemos essa
varivel privada.

Compilado por thoga31

11

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Para fazer o output da propriedade, bastar ler o valor da sua varivel VDEG,
pelo que o nome do mtodo de escrita o prprio nome da varivel. O mtodo de
escrita dever ser sempre uma funo, mas como a varivel em si uma funo, este
processo torna-se vlido.
J a escrita necessita de um procedimento. Neste caso, crimos o
procedimento DefinirValorDEG que tem nica e exclusivamente um argumento. Todo
e qualquer mtodo de escrita dever ter um e um s argumento, sendo este do mesmo
tipo que a varivel da qual depende a propriedade. este argumento que recebe o valor
a ser atribudo propriedade, leia-se varivel.
Neste caso, DefinirValorDEG recebe valor e atribu-o varivel VDEG. Para
no confundir com nenhuma varivel global com o mesmo nome que pode ser criado
para o programa, utilizamos a palavra reservada self, que indica ao compilador que a
varivel VDEG a referente classe cujo mtodo est ali a ser programado: neste caso,
a classe TAngle.

Construtores
Uma instncia de uma classe, que ser vista a seguir, no pode surgir do nada.
A instncia tem de ter um construtor de modo a que a varivel que criarmos seja, de
facto, uma instncia. Caso no faamos a construo, a varivel do tipo da classe ser
uma referncia nula e, logo, surgir uma excepo na execuo do programa.
O tipo de dados geral TObject tem o seu
construtor: o Create. Vamos invocar este
construtor nas nossas classes para podermos
construir o nosso. O processo de herana ser
feito pela palavra reservada inherited.
O construtor dever ser pblico, e
declarado pela palavra reservada constructor.
Poder receber argumentos ou no, depende do
processo de construo da classe.
Regra geral, com o construtor damos os
valores por default s propriedades da classe. Estes valores podero vir dos
argumentos.
type Nome_Classe = class
private
// parte privada
protected
// parte protegida
public
constructor Create({argumentos do construtor});
end;

Compilado por thoga31

12

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

constructor Classe.Create({argumentos do construtor});


begin
inherited Create; // Invoca construtor da classe ascendente
TObject.
(* Cdigo do construtor *)
end;

Instncia
Por fim, agora que a classe est criada, vamos criar instncias desta. Uma
instncia criada por uma varivel do tipo da classe. Em Pascal:
var Instncia : Nome_Classe;
No bloco de execuo, antes de utilizar qualquer mtodo ou atribuir ou ler
qualquer propriedade, necessrio criar a instncia atravs do construtor:
Instncia := Nome_Classe.Create({argumentos do construtor});
Note-se bem que, no construtor, o nome da classe que se coloca e no o nome
da instncia!
E, daqui em diante, pode-se utilizar qualquer mtodo da classe, atribuir e ler
propriedades desta, mas, para cada instncia, cada valor. Se tivermos dez instncias,
cada uma pode ter um valor diferente para uma mesma propriedade. Isto perceptvel
no caso da classe Ser Humano:
O Joo pesa 75Kg;
O Andr pesa 64Kg;
A Joana pesa os seus leves 52Kg;
J a Mariana chega aos 86Kg;

Destrutor
J que neste artigo no vamos criar destrutores, fica a referncia que, para
qualquer classe, podemos utilizar o destrutor geral: o Free.
Antes de terminar um programa, as instncias devem ser, ento, destrudas:
Instncia.Free;

Compilado por thoga31

13

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Implementao prtica
Terminada que est a introduo terica utilizao de Programao Orientada
aos Objectos em Pascal, a melhor forma de consolidar estes conhecimentos com um
exerccio prtico.
Criemos, ento, uma classe, de seu nome TAngle, e que permita guardar o valor
de um ngulo em graus e radianos e tenha as devidas funes de converso. Para tal,
teremos:

Mtodos:
o Conversor radianos graus;
o Conversor graus radianos.
Propriedades:
o Valor do ngulo em graus;
o Valor do ngulo em radianos.

Para as propriedades, necessitaremos dos devidos e


respectivos mtodos de escrita:
Definir valor em graus;
Definir valor em radianos.
E, para a criao de uma instncia da classe, necessitaremos de um construtor,
construtor esse que ter dois argumentos:
Valor do ngulo;
Tipo de ngulo (graus ou radianos).
Para o tipo de ngulo, vamos criar especialmente o tipo de dados
TMedidasAngulo, e que no deve ser confundido com a classe TAngle, e que assumir
os valores deg ou rad, respectivamente para graus e para radianos.
Alm disso, o objectivo que, quando se cria ou se modifica o ngulo, o valor
mude automaticamente para os respectivos valores no outro tipo de ngulo. Ou seja, se
atribuirmos em graus, o valor em radianos modificado automaticamente para o valor
correspondente.
Por fim, no programa, vamos testar rapidamente o construtor e os conversores.
Passando tudo isto para Pascal:
{$mode delphi} // Utiliza a nomenclatura do Delphi.
PROGRAM Teste_Classes;
uses crt, math; // Unidades necessrias
type TMedidasAngulo = (Deg, Rad); // Tipo de ngulo: Graus ou Radianos.
TAngle = class
private (* S visvel para a classe *)

Compilado por thoga31

14

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

// Variveis onde so guardados os valores das propriedades.


VRAD : real; // Radianos
VDEG : real; // Graus
// Mtodos que permitem atribuir valores s propriedades.
procedure DefinirValorRAD(const valor : real);
procedure DefinirValorDEG(const valor : real);
public (* Visvel para todo o programa *)
// Propriedades
property ValorRAD : real read VRAD write DefinirValorRAD;
property ValorDEG : real read VDEG write DefinirValorDEG;
// Conversores
function Deg2Rad(const deg : real) : real;
function Rad2Deg(const rad : real) : real;
// Construtor da classe
constructor Create(const valor : real; TipoAng : TMedidasAngulo);
end;

constructor TAngle.Create(const valor : real; TipoAng : TMedidasAngulo);


(* Construtor *)
begin
inherited Create; // Invoca construtor da classe ascendente TObject.
case TipoAng of
// Conversores, consoante o tipo de ngulo introduzido.
Rad : begin
self.ValorRAD := valor;
self.ValorDEG := Rad2Deg(valor);
end;
Deg : begin
self.ValorDEG := valor;
self.ValorRAD := Deg2Rad(valor);
end;
end;
end;
procedure TAngle.DefinirValorRAD(const valor : real);
(* Atribui valor propriedade em radianos *)
begin
self.VRAD := valor;
self.VDEG := self.Rad2Deg(valor);
end;
procedure TAngle.DefinirValorDEG(const valor : real);
(* Atribui valor propriedade em graus *)
begin
self.VDEG := valor;
self.VRAD := self.Deg2Rad(valor);

Compilado por thoga31

15

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

end;
function TAngle.Deg2Rad(const deg : real) : real;
begin
result := (pi * deg) / 180;
end;
function TAngle.Rad2Deg(const rad : real) : real;
begin
result := (rad * 180) / pi;
end;

var Angulo : TAngle;


BEGIN
Angulo := TAngle.Create(90, deg); // cria ngulo de 90
writeln('Em radianos: ', Angulo.Deg2Rad(Angulo.ValorDEG):0:3);
// Converte em radianos
Angulo.ValorRAD := pi; // atribui 180 em radianos
writeln('pi(rad) em graus: ', Angulo.Rad2Deg(Angulo.ValorRAD):0:3);
readln; // pausa
Angulo.Free();
END.

Apesar de os valores convertidos serem facilmente obtidos pela propriedade


correspondente ao tipo de ngulo pretendido, a forma como realizmos a converso nos
procedimentos writeln demonstram como se podem utilizar vrios mtodos de uma vez
s.
Note-se que se pode utilizar o bloco with para se evitar escrever continuamente
o nome da instncia seguido do mtodo ou propriedade que se pretende. Contudo
necessria precauo no caso de haver vrias instncias e classes cujos mtodos e
propriedades partilham os mesmos nomes. Em caso de dvida sobre o que vai o
compilador
assumir, considere sempre escrever
na forma completa:
nome_classe.mtodo().
Fica ento assim uma classe til que o leitor poder utilizar livremente nas suas
programaes em Pascal. Sinta-se vontade para a expandir com novos mtodos e
propriedades, j que a melhor forma de aprender a tentar.

Compilado por thoga31

16

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Concluso
Aps este artigo, percebemos que o Pascal, j h
quase 30 anos atrs, suporta o paradigma da Programao
Orientada aos Objectos sem que a sua sintaxe original
tivesse de ser alterada: apenas houve ligeiras adaptaes e
a entrada de novas palavras reservadas.
A entrada deste paradigma reforou ainda mais o
objectivo desta linguagem de programao: a programao
estruturada, com cabea, tronco e membros como se diz na
gria popular.
Conclui-se, sendo assim, que, apesar de muitos lhe
chamarem uma linguagem ultrapassada, o Pascal est longe
de ser uma linguagem do passado. Pascal uma linguagem
do sculo XXI.

o Pascal
est longe de ser
uma linguagem
do passado.
Pascal uma
linguagem do
sculo XXI.

Links teis
Uma lista de documentos teis da Wiki P@P, relacionados com o presente
artigo.

Tutorial de Pascal (2011)


Parte I Procedimentos e funes
Parte VI Sucessor e predecessor
Parte VI Lista padro do Pascal
Tutorial de Delphi noes bsicas
Procedimentos e Funes Passagem por Parmetro / Passagem por
Referncia
Indentao

Compilado por thoga31

17

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Sobre o autor
Igor Nunes
Entrou no mundo da programao aos 14 anos com TI-Basic. Dois anos depois
descobriu o Pascal, que ainda hoje a sua Linguagem de Programao de eleio.
Mais recentemente introduziu-se ao VB.NET e ao Delphi.
Membro do P@P desde Abril de 2010 (@thoga31), actualmente membro da
Wiki Team e Moderador Local dos quadros de Pascal e Delphi/Lazarus. Escreveu o
novo Tutorial de Pascal da Wiki P@P, bem como os Tutoriais de TI-Basiz Z80 e de
Introduo Lgica e Algoritmia.

Compilado por thoga31

18

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

34 Edio

Pascal Mdulos
Por: Igor Nunes

Compilado por thoga31

19

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Cdigo reutilizvel. Reciclagem de cdigo. Duas expresses que significam e


reflectem basicamente o mesmo: a necessidade de se estruturar o cdigo e que este
possa ser utilizado por mais que uma aplicao sem ter de ser programado todo de
novo.
Em especial, com as DLLs (Dynamic Link Libraries) do Windows, vrias
aplicaes servem-se de uma biblioteca externa que contm cdigo comum a todas.
Assim, em vez de se ter, por exemplo, dez vezes esse cdigo em cada programa, est
s implementado uma vez numa biblioteca externa.
Mas antes das DLLs, h que ver uma particularidade comum grande maioria
das linguagens de programao mais conhecidas. No C temos o #include, em Python
ou Visual Basic .NET temos o Imports, e j no que toca a Pascal e suas variantes temos
o uses.
Estas trs palavras reservadas tm o mesmo em comum: importam bibliotecas
de cdigo, feitas na mesma linguagem, e implementam-nos no nosso programa durante
o processo de compilao sem uma nica vez termos de ver esse cdigo.

Assim, os objectivos para o presente artigo so:


Dar a conhecer os mdulos em Pascal;
Empregar princpios de estruturao de cdigo;
Perceber as diferenas entre interface, implementao, inicializao e
finalizao.

Biblioteca, mdulo ou unidade?


Antes de entrar no assunto em si, h que discutir brevemente a designao
destes conjuntos de cdigo. Nos primeiros tempos, a designao era Mdulo, tanto
que a palavra reservada que o iniciava era module. Contudo, com a evoluo do Pascal
e em particular do Delphi, esta palavra reservada foi substituda por unit, e a designao
de Unidade comeou a surgir, e Mdulo passou a ser algo, e erroneamente, arcaico.
A designao de Biblioteca fica reservada para as Dynamic Link Libraries cuja
palavra reservada que as inicia library.
Doravante, neste artigo, a designao utilizada ser a primeira e a mais correcta:
Mdulo.

Compilado por thoga31

20

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Estrutura de um Mdulo
Um Mdulo um conjunto de cdigo extremamente bem estruturado, iniciado
com a palavra reservada unit, estando dividido em blocos variados, desde blocos de
declarao das funes e procedimentos at de finalizao. Estes blocos, com
respectivas palavras reservadas que as identificam no Mdulo, so os seguintes:

Interface ou Declarao interface bloco onde os procedimentos e funes


so declarados, bem como tipos novos de dados, constantes e variveis globais.
Implementao implementation bloco onde as funes e procedimentos
so implementados.
Inicializao initialization bloco opcional que realiza operaes
especficas aquando o incio do programa que implemente o Mdulo.
Finalizao finalization bloco opcional que finaliza o que foi feito na
inicializao ou faz certas operaes no fecho do programa que implemente o
Mdulo.

Em Pascal, a estrutura de um mdulo, incluindo os blocos opcionais, a


seguinte:
UNIT Modulo;
INTERFACE
IMPLEMENTATION
INITIALIZATION
FINALIZATION
END.

Interface e Implementao
Um mdulo, sendo um conjunto de cdigo a ser implementado num qualquer
programa que o importe, dever ter uma zona que identifique que funes e
procedimentos tem, e s depois haver uma zona de implementao.
A forma como as funes so declaradas na Interface dever coincidir
perfeitamente com a implementao feita. Isto torna-se claro com um exemplo.
{$MODE Delphi}
UNIT modulo_exemplo;
//declarao do mdulo
INTERFACE (*Interface - declarao*)
function Positivo
(const numero:integer):boolean;

Compilado por thoga31

21

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

IMPLEMENTATION (*Implementao*)
(* 1
Errado *)
function Positivo;
(* 2
Errado *)
function Positivo
(numero:integer):boolean;
(* 3
Errado *)
function Positivo
(const num:integer ):boolean;
(* 4 Correcto *)
function Positivo
(const numero:integer):boolean;
begin
If (numero > 0) then
result:=true
else
result:=false;
end;
END.

Na interface a funo Positivo recebe o argumento numero, constante, e


retorna um valor booleano. Ora, na implementao a funo dever ter exactamente o
mesmo cabealho. Analisemos os erros nos trs primeiros exemplos de implementao:
1. A funo s tem o seu nome faltam os argumentos e o tipo de dado de output;
2. O argumento no passado por constante como na interface;
3. O nome do argumento no igual ao da interface.
S no ponto n 4 o cabealho coincide na perfeio com o declarado na interface.
Um ponto importante a reter que, a partir do momento em que os
procedimentos e as funes esto declarados na interface, a ordem pelo qual so
programados na implementao no interessa. Por consequncia, a palavra reservada
forward nunca ir aparecer num mdulo.
Um outro ponto muito importante a reter o facto de podermos brincar com as
visibilidades das nossas funes e dos nossos procedimentos atravs da Interface e da
Implementation. que nem todas as funes e procedimentos que esto
implementados na Implementation tm de estar declarados na Interface ou seja, s
colocamos na Interface os mtodos que pretendemos que sejam visveis ao
programador que utilizar o nosso mdulo. Os restantes mtodos implementados mas
no declarados na interface so, por fim, privados.
Isto torna-se extremamente til quando criamos mtodos auxiliares e no
queremos que sejam visveis e utilizveis pelo programador que inclua o nosso mdulo.
Assim implementamos estes mtodos no bloco Implementation e no os declaramos
na Interface.
Em suma, s os mtodos declarados na Interface so visveis e utilizveis para
os programas que incluam o mdulo.

Compilado por thoga31

22

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Inicializao e Finalizao
Sendo os blocos de interface e de implementao obrigatrios, os blocos agora
abordados so totalmente opcionais os de inicializao e os de finalizao.
Como referido anteriormente, o bloco de inicializao permite-nos realizar certas
operaes no incio do programa. Estas operaes so realizadas antes de qualquer
processo do programa onde o mdulo est includo. J as operaes localizadas no
bloco de finalizao so executadas imediatamente antes do programa fechar.
Para quem conhece bem Visual Basic .NET, fica uma ptima comparao:
quando a Form abre, todos os processos do evento Load so executados equivale ao
bloco de inicializao do nosso mdulo em Pascal. Quando pretendemos que outros
processos sejam feitos mesmo antes da Form fechar, colocamo-los no evento
FormClosing (e no no FormClosed) equivale ao nosso bloco de finalizao.
Vamos recorrer a duas Message Boxes para mostrar a aco destes blocos num
programa que inclua o mdulo que se segue, e cuja explicao se encontra de seguida.
UNIT Modulos_Pascal;
INTERFACE
uses windows;
procedure Escrever(texto:string);
IMPLEMENTATION
procedure Escrever(texto:string);
var conv:string;
i:word;
begin
conv:='';
texto:=UpCase(texto);
for i:=1 to length(texto) do
begin
if not(texto[i]='Z')
then conv:=conv+succ(texto[i])
else conv:='A';
end;
WriteLn(conv);
end;
INITIALIZATION
MessageBox(0,
'A unit "Modulos_Pascal" est neste programa!',
'Mdulos Pascal', MB_ICONASTERISK);
FINALIZATION
MessageBox(0, 'At prxima!' ,
'Mdulos Pascal', MB_ICONASTERISK);
END.

Compilado por thoga31

23

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Neste exemplo, o nosso mdulo, de nome Modulos_Pascal, tem o


procedimento Escrever que recebe por argumento uma String, texto, e a converte em
duas fases: numa primeira fase toda a String colocada em maisculas e, de seguida,
cada caracter convertido no seu sucessor, e o resultado fica na varivel local conv.
Finalmente, o procedimento faz o output deste resultado pelo tradicional mtodo
WriteLn.
O mais importante a focar neste mdulo, neste momento, so os dois novos
blocos: initialization e finalization. O que ir acontecer ao programa que o
implemente?
Quando iniciar, ir aparecer a Message Box de ttulo Mdulos Pascal e com a
mensagem A unit Modulos_Pascal est neste programa!, onde o nico boto ser o
OK e o cone ser o de informao (dado pela constante MB_ICONASTERISK).
No momento de encerrar, ir aparecer outra Message Box, em tudo igual
anterior, com excepo mensagem que, desta vez, ser At prxima!.
Vamos, ento, testar este mdulo num programa que o implemente.
PROGRAM Teste_Modulo;
uses crt, Modulos_Pascal;
BEGIN
writeln('So agora comecei!');
Escrever('Olha um codigo');
write('ENTER...');
readln;
END.

Ao compilar e executar este programa, vamos obter a seguinte sequncia de


output:

Compilado por thoga31

24

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Como se pode verificar, o programa no tem nenhuma linha de cdigo que


ordene a mostragem de uma Message Box e, no entanto, elas aparecem. So os blocos
initialization e finalization do mdulo a funcionar. Como o bloco de inicializao o
primeiro a ser executado antes de qualquer cdigo do programa, a Message Box
aparece antes sequer do programa fazer o output das suas trs linhas de texto. E como
o bloco de finalizao o ltimo a ser executado antes do programa encerrar (leia-se
aquando o processo de encerramento), ento a Message Box correspondente apareceu
e o programa s terminou de facto quando carregamos no OK.

Novos tipos de dados


Juntamente com procedimentos e funes, podemos criar novos tipos de dados
numa unidade. Por exemplo, o tipo de dados DateTime s reconhecido pelo
compilador na presena da unidade DateUtils do Free Pascal. Sem este mdulo
declarado no uses, haver um erro de compilao.
Imagine-se que temos um mdulo com procedimentos e funes relacionados
com registos de dados pessoais de pessoas. Um tipo de dados que pode ser criado o
seguinte:

Compilado por thoga31

25

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

UNIT Registos;
INTERFACE
TYPE TRegPessoa = RECORD
Nome:string[80];
Idade:byte;
BI:string[9];
Profissao:string[40];
END;
//resto do mdulo

A partir deste momento, este tipo de dado pode ser utilizado no s no mdulo
como tambm no programa que o incluir:
PROGRAM Teste;
USES Registos;
VAR Pessoas :
array[1..100] of TRegPessoa
//resto do programa

Doravante, temos 100 registos de dados pessoais de pessoas do tipo


TRegPessoas, tipo de dado criado no mdulo Registos.

Ficheiros gerados pela compilao


Para que um programa possa utilizar um mdulo criado pelo prprio
programador, o programa dever ter acesso a esse mdulo. O mais fcil ser ter os
ficheiros necessrios do mdulo junto ao cdigo-fonte do programa. Mas quais so
esses ficheiros?
Quando compilamos um mdulo alguns ficheiros so criados. Por exemplo,
utilizando o Free Pascal, obtemos os seguintes ficheiros com a compilao do mdulo
Modulos_Pascal, criado anteriormente:

modulo~1.ppw
modulo~1.ow
modulos_pascal.ow
modulos_pascal.ppw

O mdulo pronto a ser implementado est no ficheiro modulos_pascal.ppw


bastar ter este ficheiro junto do cdigo-fonte que utilizar este mdulo. Contudo,
verificamos que o ficheiro com o mesmo nome mas com extenso *.ow gerado quando
o programa compilado. Por isso, este tambm pode estar junto do cdigo-fonte, apesar
de vir a ser criado aquando a primeira compilao do programa.

Compilado por thoga31

26

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Ateno! necessrio ter em conta que isto pode variar conforme o compilador
utilizado. Se recorrer a um compilador que no o Free Pascal, verifique os ficheiros
gerados pela compilao do mdulo. Se forem diferentes, faa experincias ou ento
copie todos os ficheiros gerados. No far muita diferena no que toca a espao em
disco j que estes ocupam uma mdia de 2 a 10 KB cada um, conforme a quantidade
de cdigo do mdulo.

Concluso
Com este artigo ficmos a conhecer uma das maiores potencialidades do Pascal:
a hiptese de se criar mdulos prprios e que podem ser utilizados por qualquer
programa que necessite dos processos neles implementados.
Descobrimos assim o que est por detrs da palavra reservada uses. Um
mdulo recheado de procedimentos e funes teis, bem como novos tipos de dados.
Os mdulos so um dos exemplos mximos de boas prticas de programao
em Pascal. Para um programa complexo torna-se muito til organizar os vrios
processos em mdulos diferenciados, sendo que cada um ter processos relacionados.
Por exemplo, processos matemticos ficam num mdulo e processos de manipulao
de Strings noutra. No fim inclui-se estes mdulos no programa final na palavra reservada
uses e o programa ter um cdigo muito mais limpo j que o cerne est nos mdulos.

E assim criamos, igualmente, cdigo reutilizvel. Deixa de ser necessrio


implementar de novo os procedimentos e funes a cada novo programa, mas basta
sim colocar o ficheiro compilado do mdulo junto do cdigo-fonte do programa e declarlo. Os processos so includos automaticamente na hora da compilao do programa.
Mais uma vez, o Pascal mostra-nos as suas infindveis formas de estruturar e
optimizar os nossos programas de uma forma sem igual.

Links teis
Uma lista de documentos teis da Wiki P@P, relacionados com o presente
artigo.

Ns primos Mdulo Primes http://tinyurl.com/849juu8


Tutorial de Pascal (2011) http://tinyurl.com/6wjejqo
Parte I Procedimentos e funes http://tinyurl.com/845gwjo
Parte V Estruturao de um programa em Pascal
http://tinyurl.com/79lfx4k
Parte VII Message Box http://tinyurl.com/6lpeu48
Indentao http://tinyurl.com/6p3w63y

Compilado por thoga31

27

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Sobre o autor
Igor Nunes
Estudante universitrio, entrou no mundo da programao aos 14 anos com TIBasic. Dois anos depois descobriu o Pascal, que ainda hoje a sua Linguagem de
Programao de eleio. Mais recentemente introduziu-se ao VB.NET e ao Delphi.
Membro do P@P desde Abril de 2010 (@thoga31), actualmente membro da
Wiki Team e Moderador Local dos quadros de Pascal e Delphi/Lazarus. Escreveu o
novo Tutorial de Pascal da Wiki P@P, bem como os Tutoriais de TI-Basiz Z80 e de
Introduo Lgica e Algoritmia.

Compilado por thoga31

28

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

35 Edio

Pascal Registos variantes


Por: Igor Nunes

Compilado por thoga31

29

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Os registos so um dos tipos de dados compostos mais teis em qualquer


linguagem de programao que os suporte, como o C ou o Pascal. Permitem-nos criar
formulrios com vrios campos em que cada campo de um tipo de dado distinto.
E, em conjunto com matrizes (regra geral, as unidimensionais), ficamos com uma
autntica lista de registos, o que nos d um poder tremendo de manipular informao
em poucas linhas de cdigo.
Contudo, h certos registos cujos campos que mudam so poucos, e torna-se
algo frustrante criar mais um registo s para ter um campo ou dois diferentes.
Ento, o Pascal oferece-nos mais uma poderosa ferramenta para simplificar. Se
s alguns campos mudam, para qu criar um registo totalmente novo? No mesmo
registo, fazemos campos que alteram consoante determinadas condies. E temos,
ento, os chamados registos variantes.

Assim, os objectivos para o presente artigo so:


Rever os registos (tipo de dado composto Record);
Dar a conhecer os registos variantes;
Comparar os registos estticos com os variantes e conhecer as suas vantagens;

Breve reviso dos registos


Um registo um tipo de dado composto constitudo
por vrios campos, os quais podem ser, igualmente,
outros registos. Permitem juntar uma srie de dados
relacionados e permitem manipul-los de uma forma
to simples quanto a manipulao de dados simples.
Um exemplo de um registo de um pequeno
formulrio para registar os dados de uma pessoa:
Formulario:REGISTO
Nome :String;
Idade:Byte;
Sexo :Char;
BI
:String[8];
EstadoCivil:
(Solteiro,Casado,Outro);
FIM REGISTO

O campo Nome pode ser acedido fazendo Formulario.Nome, ou seja, referese qual o registo a que queremos aceder e com um ponto separamos o nome do registo
do seu respectivo campo que pretendemos manipular. Isto em Pascal ser o seguinte:

Compilado por thoga31

30

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

TYPE
(* Estado Civil *)
TEstCiv =
(Solteiro,Casado,Outro);
(* Registo *)
TFormulario=RECORD
Nome:String;
Idade:Byte;
Sexo:Char;
BI:String[8];
EstadoCivil:TEstCiv;
END;
VAR
Pessoa:TFormulario;
(* Manipular "Nome" *)
Pessoa.Nome:='Jose';

Para facilitar a manipulao dos campos do registo Pessoa podemos recorrer


ao bloco with:
with Pessoa do begin
Nome:='Jose';
Idade:=63;
Sexo:='M';
BI:='12345678';
EstadoCivil:=Casado;
end;

Isto simplifica muito, pois evita escrevermos sempre o nome do registo. Ou seja,
este bloco infra-apresentado substitui o seguinte:
Pessoa.Nome:='Jose';
Pessoa.Idade:=63;
Pessoa.Sexo:='M';
Pessoa.BI:='12345678';
Pessoa.EstadoCivil:=Casado;

Os registos variantes

Compilado por thoga31

31

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Os registos so, regra geral, de campos fixos. Podemos-lhes chamar de


registos estticos. Ou seja, os seus campos so bem definidos e no se alteram em
qualquer circunstncia. Contudo, torna-se til variar certos campos. Vejamos um
exemplo prtico para entender porqu e como.
Necessitamos de registar, ou catalogar, determinados produtos electrnicos,
mais especificamente computadores (PC), impressoras e ratos. Cada um destes
produtos tem descries prprias:
PC CPU (processador) e HDD (disco rgido);
Rato de bola ou ptico (laser);
Impressora impresso a laser ou de jacto de tinta.

Contudo, h dados que lhes so comuns:


Nome (ou descrio, conforme);
Marca;
Modelo;
Preo.

A questo que se coloca : vale a pena trs registos distintos para cada um dos
produtos? A resposta : no. Num s registo vamos variar os campos conforme o tipo
de produto. Como?
Um determinado campo esttico determinar quais os campos variantes que iro
surgir. Por exemplo, ao indicarmos a um campo esttico, destinado para o efeito, que
estamos a registar uam impressora, surgir um campo variante que indicar se a
impressora a laser ou a jacto de tinta.
A estrutura que nos vai criar campos variveis a partir de campos estticos ser
a Case Of.
TYPE
TConjuntoProdutos =
(PC, Impressora, Rato);
// ...

case TipoProduto :
TConjuntoProdutos of
PC : ;
Impressora : ;
Rato : ;
end;

Todavia, ao invs de se implementar qualquer cdigo (aco) como habitual


nesta estrutura, vamos declarar os campos que correspondem a cada tipo de produto,
tal como listado anteriormente:

Compilado por thoga31

32

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

case TipoProduto :
TConjuntoProdutos of
PC : (CPU:string[30];
HDD:string[50]);
Impressora :
(Tinta:TTipoImpressao);
Rato : (Optico:Boolean);
end;

Note-se ento que TipoProduto ser um campo esttico do registo, campo


este que vai indicar quais so os campos variantes ou seja, consoante o valor do
campo esttico TipoProduto, os campos variantes mudam.
Neste caso, se TipoProduto for PC, ou seja, estamos a registar um computador,
ento so os campos CPU e HDD que podem ser atribudos, sendo que os outros ficam
invisveis. Quando pretendemos registar uma impressora, o campo que ficar
disponvel ser Tinta que nos indica se a impressora a laser ou a jacto de tinta. O
mesmo se passa para quando queremos registar um rato, onde o campo que criado
indicar-nos- se ptico ou dos antigos, de bola.
Consolidando isto, o nosso registo, incluindo os tipos de dados novos
necessrios, ser o seguinte:
TYPE
(*TIPOS necessrios*)
TConjuntoProdutos =
(PC, Impressora, Rato);
TTipoImpressao =
(JactoTinta, Laser);
(*REGISTO de produto*)
TRegProduto=RECORD
Nome:string[50];
Preco:Real;
Marca:string[20];
Modelo:string[30];
//Campos variantes:
case TipoProduto :
TConjuntoProdutos of
PC : (CPU:string[30];
HDD:string[50]);
Impressora :
(Tinta:TTipoImpressao);
Rato : (Optico:Boolean);
END;

Vamos, ento, esquematizar os campos estticos e os variantes deste registo:

Compilado por thoga31

33

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Figura 1 Esquematizao dos campos do registo TRegProduto.

Assim compreendemos melhor que TipoProduto um campo esttico que vai


influenciar os campos variantes.
de referir que um registo pode ter mais do que um grupo de campos variantes,
e que dentro de cada campo variante pode ser criado um outro registo que tambm
contenha campos variantes. As possibilidades so, ento, imensas.

Input e output de registos variantes


Regra geral, os compiladores no detectam se esto a ser atribudos valores a
um campo que, nesse ponto do programa, no esto visveis. O debug ser a nica
forma de confirmar se a operao realizada ilegal naquele ponto ou no. Tenha em
ateno quais os campos variantes que esto em voga aquando operaes de
atribuio nestes.
Portanto, como se deve prevenir e no remediar, convm controlar em cdigo
que input ou output para ser feito em cada ponto do programa. Mais uma vez, a
estrutura de deciso Case Of ir-nos- resolver esta questo.

Compilado por thoga31

34

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Seguindo os exemplos infra-apresentados, fazemos o Case Of ao campo


esttico TipoProduto e, consoante o valor deste, variamos o input/output pretendido.
Por exemplo:
case Reg.TipoProduto of
Impressora:begin
repeat
write('Impressao? ');
write('T=Tinta; L=Laser ');
c:=ReadKey;
c:=UpCase(c);
until (c in ['T','L']);
case c of
'L':Tinta:=Laser;
'T':Tinta:=JactoTinta;
end;
writeln;
end;
// outros casos...
end;

Para este trecho de cdigo, no caso do produto (TipoProduto) ser uma


impressora vamos pedir ao utilizador que nos informe qual o tipo de impresso: se a
laser ou a jacto de tinta. Ou seja, o input de dados relativos impressora s ocorre
caso tenhamos definido que o tipo de produto de Reg (varivel hipottica do tipo
TRegProduto) , especificamente, uma impressora. Se for um rato ou um PC outros
comandos sero executados.

Matrizes de registos variantes


Aps toda a teoria, segue o caso da vida real. O que nos d mais jeito , claro
est, criar uma matriz unidimensional de registos. Por exemplo:
RegProdutos:array[1..10]
of TRegProduto;

Temos aqui dez registos de produtos, registos estes que tm campos variantes.
O que vai acontecer?
Comecemos no ndice 1 da matriz. Estamos a registar um PC, pelo que
TipoProduto ser PC e os campos variantes sero HDD e CPU. Seguindo para o ndice
2 definimos uma impressora, e o campo variante ser Tinta. J no ndice 3 definimos
um rato e o campo variante ser Optico.

Compilado por thoga31

35

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Constatamos que, para uma mesma matriz, cada ndice tem um conjunto de
campos diferente os campos em comum so os estticos e aqueles que diferem so,
obviamente, os variantes.
Verificamos, portanto, mais um poder dos registos variantes. Uma matriz destes
registos vai diferir nos campos em cada ndice. Estamos habituados a ver uma matriz
de um tipo de dado certo e que, para cada ndice, nada muda (a no ser os valores
atribudos, claro). Com os registos variantes isto no acontece.
A seguinte tabela mostra o exemplo de um excerto dos primeiros cinco ndices
do Array unidimensional RegProduto, declarado anteriormente. A tabela s faz
referncia aos campos variantes e ao campo esttico que os varia.
ndice
do
Array
1
2
3
4
5

Campos variantes
TipoProduto
PC
Impressora
Rato
Rato
Impressora

HDD

CPU

SATA 1TB

i5 2.80GHz

Tinta

Optico

JactoTinta
True
False
JactoTinta

Concluso
Ficmos a conhecer, por fim, mais uma das capacidades poderosas do Pascal.
Para qu criar uma srie de registos semelhantes se lhes podemos variar certos campos
consoante parmetros muito especficos?
Os registos ganham, assim, uma nova dimenso aos nossos olhos. Simplificam
o nosso trabalho em larga escala e demonstram capacidades que no muitas
linguagens tm neste aspecto. E esta uma capacidade que j vem desde os primrdios
da existncia do Pascal, ou seja, desde os anos 70.

Links teis
Uma lista de documentos teis da Wiki P@P, relacionados com o presente
artigo.

Tutorial de Pascal (2011) http://tinyurl.com/6wjejqo


Parte I Procedimentos e funes http://tinyurl.com/845gwjo
Parte IV A varivel Record http://tinyurl.com/7q4efd3
Parte V Estruturao de um programa em Pascal
http://tinyurl.com/79lfx4k
Indentao http://tinyurl.com/6p3w63y

Compilado por thoga31

36

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Sobre o autor
Igor Nunes
Estudante universitrio, entrou no mundo da programao aos 14 anos com TIBasic. Dois anos depois descobriu o Pascal, que ainda hoje a sua Linguagem de
Programao de eleio. Mais recentemente introduziu-se ao VB.NET e ao Delphi.
Membro do P@P desde Abril de 2010 (@thoga31), actualmente membro da
Wiki Team e Moderador Local dos quadros de Pascal e Delphi/Lazarus. Escreveu o
novo Tutorial de Pascal da Wiki P@P, bem como os Tutoriais de TI-Basiz Z80 e de
Introduo Lgica e Algoritmia.

Compilado por thoga31

37

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

36 Edio

Pascal Construo de uma


calculadora grfica
Por: Igor Nunes

Contedo disponvel no Portal de Downloads


Todos os cdigos-fonte implementados no presente artigo podem ser encontrados
seguindo este hyperlink.

Compilado por thoga31

38

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Muitos acharo que Pascal e as suas variantes, com a excepo para o Delphi,
mais no fazem se no programas para consola, ou seja, Console Applications. Isso
seria verdade no fosse o facto do Turbo Pascal ter dado h vrios anos, ainda na
dcada de 80, um grande passo para a criao de ambientes grficos mais apelativos.
Tinha surgido, ento, a unidade Graph. Esta nova unidade traz consigo uma
panplia de mtodos que nos permitem criar desenhos pixel a pixel, ou seja, criam
ambientes para alm da tradicional e velhinha consola.
Na sua essncia mais simples, esta unidade detecta as caractersticas grficas
do computador onde corre o programa e cria uma rea grfica onde se podem criar os
mais variados desenhos e textos.
Para quem est familiarizado com Visual Basic .NET, a classe Graphics bem
como as namespaces Drawing e Drawing2D so os exemplos com termo de
comparao mais prximo.
Para auxiliar o programador, alguns novos tipos de dados foram includos na
unidade types, sendo de enfatizar as duas mais simples e usuais, TPoint e TSize.
O objectivo deste artigo no ser, contudo, dar noes introdutrias desta
unidade de um ponto de vista puramente terico. Esta unidade to nica e to diferente
de qualquer outra que o Free Pascal oferece que merece uma abordagem totalmente
diferente.
Para dar a conhecer devidamente o poder desta unidade, o objectivo do artigo
ser a construo daquilo que a base de qualquer calculadora grfica: uma Plotter.
Para quem no est familiarizado com o termo, uma Plotter no mais do que
uma calculadora grfica. As suas capacidades so diversas, consoante as
funcionalidades que o seu programador lhe coloque. Contudo, qualquer Plotter ter de
receber uma funo, calcular os seus valores, que sero pontos no grfico, e unir esses
mesmos pontos com linhas de forma a dar uma visualizao contnua e no picotada.
Assim sendo, neste artigo ser criada uma pequena unidade que ter um novo
tipo de dado que conter as propriedades e os mtodos bsicos de uma Plotter uma
classe, portanto , bem como um pequeno programa de teste que ir usar essa unidade
para representar graficamente a seguinte funo:
() = 2 8
Ou seja, esperamos que o resultado final da utilizao da unidade por ns criada
para este feito seja semelhante ao da Figura 1.

Compilado por thoga31

39

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Figura 1 Exemplo de output esperado com a construo da Plotter.

Logo com esta imagem denotamos as diferenas entre a rea grfica criada pela
unidade Graph e a consola a que estamos to acostumados: a personalizao colorida
pixel a pixel.

Portanto, em suma, os objectivos para este artigo so os seguintes:


Dar a conhecer a unit Graph no seu conceito geral;
Criar uma classe que permita gerar e personalizar uma Plotter bsica;
Conceber uma unit que tenha esta classe como um novo tipo de dados, bem
como inclua os tipos de dados auxiliares que sejam necessrios formar;
Criar um pequeno programa de teste desta nova unit que permita experimentar
a Plotter;

Mais uma vez, a par dos meus artigos anteriores, identificarei sempre a
linguagem de programao por Pascal e no por Object Pascal por uma questo de
simplificao. Ser usada, doravante, as notaes unit ou unidade, e no mdulo.
Todos os mtodos e propriedades, tipos de dados e constantes sero nomeados
em ingls.
Por fim, o cdigo aqui presente foi todo feito para ser compilado no Free Pascal
2.6.0 em Windows. Algumas adaptaes podero ter de ser feitas para Mac OS e para
GNU/Linux.

A unit Graph noes e conceitos


bvio que no faria muito sentido comear a construir a Plotter sem antes ter
algumas noes introdutrias sobre a unit na qual nos vamos basear.
A unit Graph uma unidade que contm uma srie de mtodos e constantes
que permitem criar um ambiente grfico numa janela parte da consola e que permite
o desenho de formas avanadas.

Compilado por thoga31

40

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Para se ter uma noo geral da imensido desta unit, esta contm:
Mais de 70 mtodos de desenho e personalizao;
Mais de 200 constantes;
Mais de 20 tipos de dados.

A criao do ambiente grfico (Figura 2) feita custa de um procedimento,


InitGraph, que ir receber por parmetro a resoluo e a driver. Contudo, como estes
valores fazem parte das mais de 200 constantes da unidade, um outro procedimento,
DetectGraph, oferece a possibilidade de detectar automaticamente estes parmetros.

Figura 2 A rea grfica criada pelo InitGraph com a tradicional consola sobre ela.

Assim sendo, e sabendo que os valores obtidos so do tipo SmallInt,


necessria a declarao de duas variveis que iro receber atravs do DetectGraph os
valores a utilizar no procedimento InitGraph:
var Driver, Modus : SmallInt;
// ...
DetectGraph(Driver, Modus);
InitGraph(Driver, Modus, '');

Compilado por thoga31

41

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Como podero ter reparado, o procedimento InitGraph recebe um terceiro


parmetro, do tipo String, e que, neste caso, recebeu uma String vazia. Este
procedimento para ser, regra geral, ignorado, a no ser que utilize o Borland Pascal
nesse caso, ter de passar a String C:\PP\BGI, isto supondo que tem o Borland Pascal
instalado na raz da unidade C.
Por fim, antes do programa ser terminado, ou quando a rea grfica j no for
necessria, esta deve ser encerrada atravs do seguinte procedimento:
CloseGraph;
Em princpio no devero ocorrer erros no decorrer destes processos j que,
segundo a documentao do Free Pascal, estes no devolvem quaisquer erros na sua
execuo. Contudo, outros mtodos da unit Graph podero gerar erros, e, por este
motivo, a parte do programa que estiver a trabalhar com esta unidade dever estar
enquadrada numa estrutura de tentativa. Como em Pascal esta no inclui os blocos
Except e Finally numa s estrutura e devem estar sim encadeadas, recomendo a
seguinte:
try
try
DetectGraph(Driver, Modus);
InitGraph(Driver, Modus, '');
// cdigo...
except
ON ex:exception do begin
// em caso de erro
end
end
finally
CloseGraph;
end;
Ou seja, qualquer excepo que ocorra dever ser detectada e, caso o
programador assim o deseje, informada. Por fim, aquando o final de todas as aces, a
rea grfica encerrada (CloseGraph), e como este processo no sofre erros, ficar
no bloco Finally.
Para a possibilidade de utilizar esta estrutura, dever-se- incluir, no Free Pascal,
a directiva de compilao {$mode objfpc} na primeira linha do cdigo-fonte. A unit
sysutils dever ser igualmente includa caso se queira apanhar e tratar as excepes
ocorridas no decorrer do programa.
A unit Graph permite, ento, desenhar e preencher objectos variados (Figura
3), tais como:
Pontos;
Linhas;

Compilado por thoga31

42

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Rectngulos;
Polgonos;
Elipses;
Crculos;
Queijos (pies);

Figura 3 Exemplo muito simples da utilizao das capacidades da unidade Graph.

A unit Graph permite tambm usar preenchimentos que no sejam slidos mas
que tenham no seu lugar um padro (pattern), sendo mesmo possvel criar padres
personalizados, entre muitas outras capacidades.
Dada esta muito breve introduo genrica unidade sobre a qual vamos
trabalhar, chegado o momento de iniciar a construo da Plotter, mas sem antes
definir o que esta far.

A Plotter funcionalidades e personalizaes a incluir

A nossa Plotter, como calculadora grfica que , dever incluir:


Uma rea de trabalho, rea esta que tem um tamanho (Size);
Eixos das abcissas (XX) e das ordenadas (YY) (Axes);
Uma grelha que marca os pontos relevantes do zoom actual (Grid);
Um Zoom;

Compilado por thoga31

43

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Para a personalizar, poderemos incluir as seguintes opes:


Cor de fundo;
Estilo de linha dos eixos;
Estilo de linha da grelha;
Estilo de linha da funo.

Para esclarecer bem o que so a grelha, os eixos, a funo e a rea de trabalho


segue-se uma imagem elucidativa (Figura 4).

Figura 4 Estrutura bsica da Plotter a ser criada.

Para facilitar o programador que utilizar a classe, podemos incluir valores


padro, isto , constantes, na unidade que ir conter a nova classe:
Zoom padro;
Estilos de linha padro para os eixos, a grelha e a funo;
Cor de fundo padro.
Deveremos, ento, comear a definir os mtodos e propriedades da classe.
Por conveno, novos tipos de dados iniciam-se com a letra T. Para a nossa
classe, os valores privados tero a letra inicial v, de value (valor), e os parmetros das
funes e procedimentos tero a letra inicial c, de constant (constante), de forma a
poder distinguir devidamente os valores dos parmetros e das propriedades sem ter de
diferenciar em demasia os nomes.

Compilado por thoga31

44

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

A nossa classe ser, ento, a TPlotter e esta ir herdar daquela que classe
principal do Object Pascal: a TObject.
TPlotter = class(TObject)

Como propriedades e respectivos tipos de dados, a classe TPlotter incluir:


Grelha, do tipo Estilo de Linha, a definir mais frente;
Eixos e Estilo da Funo, do mesmo tipo de dado;
Posio, do tipo TPoint;
Dimenso, do tipo TSize;
Zoom, do tipo Zoom, igualmente a definir mais frente;
Cor de fundo, do tipo Word;

Outras propriedades podero ser necessrias e definidas ao longo da


construo da Plotter, mas estas so as essenciais.

Por fim, os mtodos da classe sero, essencialmente:


Criar (construtor);
Libertar (destrutor);
Mostrar;
Apagar;
Relocalizar;
Redimensionar;
Redefinir Zoom;
Carregar funo;
Desenhar funo;
Apagar funo;
Libertar funo;

Iremos definir os novos tipos de dados quanto a classe for construda, j que,
desta forma, se tornar mais bvia a necessidade no s da sua existncia mas tambm
da sua estrutura final.
Devido s dificuldades que se colocam em explicar a construo de uma classe
destas, iremos construi-la por passos segundo a ordem lgica de construir primeiro os
alicerces e s depois o telhado todos estes passos sero, doravante, numerados para
uma melhor interligao entre estes.

Compilado por thoga31

45

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

1. Declarao da nova unidade e definio da classe


Como estamos a criar uma pequena calculadora grfica, vamos denominar a
nossa unidade de GraphCalc, que ao mesmo tempo aluz ao facto de se utilizar a unit
Graph.
Como vamos necessitar dos tipos de dados TPoint e TSize, necessitaremos
igualmente da unit types.
UNIT GraphCalc;
INTERFACE
uses graph, types;
// declarao
IMPLEMENTATION
// implementao
END.
A nossa classe TPlotter herda da classe genrica TObject, e ter valores
privados e mtodos e propriedades pblicas:
TYPE
TPlotter = CLASS(TObject)
PRIVATE
// privado
PUBLIC
// pblico
END;

2. Propriedades de localizao, tamanho e cor de fundo


A cor de fundo no necessita de nenhum mtodo especial de escrita nem de
leitura basta-lhe atribuio directa , nem necessita de ser s de leitura ou s de
escrita (ou seja, ReadOnly ou WriteOnly). Dado isto, podemos declarar a cor de fundo
directamente como uma varivel na seco pblica da classe:
PUBLIC
BackColor : Word;
Por sua vez, a posio e a dimenso podem ser lidos, mas a sua alterao
implica que o desenho da Plotter mude a escrita destas duas propriedades dever ser
controlada. Para relocalizar e redimensionar a Plotter iremos usar dois procedimentos
separados. Ou seja, teremos estas propriedades como ReadOnly e a sua alterao darse- com mtodos independentes destas propriedades e que sero implementadas
mais frente.

Compilado por thoga31

46

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Em cdigo, teremos o seguinte:


PRIVATE
vPosition : TPoint;
vSize : TSize;
PUBLIC
property Position : TPoint read vPosition;
property Size : TSize read vSize;
Caso utilize outro compilador e necessite de declarar estes dois tipos de dados
devido inexistncia da unit types, segue-se a sua definio segundo a documentao
do Free Pascal, pronto a implementar:
TYPE
TSize = packed record
cx: LongInt; // largura em pixis
cy: LongInt; // altura em pixis
end;
TPoint = packed record
X: LongInt; // posio horizontal
Y: LongInt; // posio vertical
end;

3. Estilos das linhas da grelha, dos eixos e da funo


definio do tipo de dados TLineStyle
Os estilos de linha da grelha, dos eixos e da funo podem ser alterados e lidos
a qualquer momento. Para que as alteraes sejam aplicadas, o mais fcil ser criar um
procedimento genrico de actualizao (Refresh) que ir limpar a rea da Plotter e
redesenh-la. Por este motivo, os estilos de linhas sero de acesso geral, sem qualquer
restrio na leitura ou na escrita:
PUBLIC
Grid : TLineStyle;
Axes : TLineStyle;
FunctionLine : TLineStyle;
Como constatvel, este tipo de dados no existe, pelo que ser criado por ns.
Seguindo a mesma estrutura dos tipos TPoint e TSize, iremos criar o TLineStyle como
um Packed Record, ou seja, este ocupar em memria nica e exclusivamente o
espao necessrio.

Compilado por thoga31

47

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Este tipo de dados incluir as seguintes definies de linha:


Cor;
Espessura;
Estilo (contnua, tracejada);
Visibilidade.

TYPE
TLineStyle = PACKED RECORD
Color : word;
Thickness : word;
Style : word;
Visible : boolean;
END;
A visibilidade fica definida pois tornar-se- fcil controlar quais as linhas que
aparecem fazendo um controlo com uma estrutura de controlo if then.
Desta forma, se definirmos Grid.Visible a False, a grelha no desenhada e o
output j ser do gnero da Figura 5.

Figura 5 Output da Plotter com a visibilidade da grelha definida a False.

4. Zoom do grfico definio do tipo de dados TZoom


Algo muito importante numa calculadora grfica, e possivelmente aquilo que
mais complicado de aplicar, o Zoom. O Zoom vai depender no s das definies dos
eixos mas tambm das dimenses da rea de trabalho. Caso a rea de trabalho seja
fixa, esta questo ganha contornos mais simples.
Contudo, como j foi feito no Passo 2, a nossa Plotter tem dimenso varivel,
definida pelo programador que utilizar a classe, pelo que o nosso trabalho no
simplificado.

Compilado por thoga31

48

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Contudo, para j, vamos apenas definir a propriedade Zoom da classe e, para


tal, criar um novo tipo de dados que contenha os dados relativos a esta caracterstica
da calculadora grfica.
Visto que a redefinio do Zoom implica uma alterao do grfico, um
procedimento parte ser criado para esta redefinio mais adiante.

PRIVATE
vZoom : TZoom;
PUBLIC
property Zoom : TZoom read vZoom;
Ora, temos de definir o tipo de dados TZoom. A Figura 6 representa os dados
que devem estar presentes neste.

Figura 6 Definies do Zoom.

Com base nesta definio de Zoom, podemos definir o tipo TZoom do seguinte
modo:
TYPE
TZoom = PACKED RECORD
XMin, XMax, XScl : real;
YMin, YMax, YScl : real;
END;

Compilado por thoga31

49

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

5. Clculo do centro do grfico e da distncia em pixis


entre cada linha da grelha
Eis algo que o leitor possivelmente no estava espera. Contudo, antes de
podermos avanar mais, de extrema importncia definir e calcular qual o centro do
grfico e qual a distncia, em pixis, entre cada linha da grelha em ambos os eixos.
Como se ver mais adiante, isto ser importante para o desenhar da Plotter e
para o clculo dos pontos da funo nesta.
Para tornar o procedimento de desenho da Plotter e do grfico em si mais fcil,
vamos calcular tudo em funo da posio e do tamanho da rea de trabalho ou seja,
determinamos em que pixis da rea grfica produzida pelo InitGraph dever aparecer
cada ponto e cada linha.
Comecemos pelo clculo da distncia entre as linhas da grelha. Este clculo
independente da posio da Plotter, mas depende directamente da dimenso e do
zoom:
Mantendo o zoom constante, medida que a dimenso aumenta, a distncia
entre as linhas da grelha tambm aumenta.
Para cada um dos eixos, a distncia entre linhas da grelha dada por:
Distncia = Zoom. Escala

Dimenso
Zoom. Mximo Zoom. Mnimo

Por exemplo, para o eixo XX a frmula ser:


Como este clculo interno e ser necessrio para alguns mtodos (bem como
o clculo do centro), vamos declar-lo como um procedimento privado. E como h uma
distncia para cada um dos eixos, vamos aproveitar a estrutura do TPoint para o gravar:
PRIVATE
DistBetweenGrid : TPoint;
procedure CalcDistGridAndCenter;
Implementando este procedimento segundo a frmula infra-apresentada:
PROCEDURE TPlotter.CalcDistGridAndCenter;
begin
// eixo XX:
self.DistBetweenGrid.X :=
Round(self.Zoom.XScl *
(self.Size.cx / (self.Zoom.XMax -

Compilado por thoga31

50

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

self.Zoom.XMin)));
// eixo YY:
self.DistBetweenGrid.Y :=
Round(self.Zoom.YScl *
(self.Size.cy / (self.Zoom.YMax self.Zoom.YMin)));
end;
Falta acrescentar a este procedimento o clculo do centro. O centro depende do
Zoom, e poder no ser necessrio calcul-lo:
Se o Zoom estiver definido tal que a janela esteja toda na rea positiva do eixo
XX (por exemplo, XMin vale 2 e XMax vale 8), ento o centro estar fora da rea
e pode-se definir a coordenada X do centro como sendo a posio X da Plotter.
Tendo em conta esta condio, e sabendo que o centro pode ser calculado
segundo a frmula que se segue, resta-nos implement-lo em Pascal.
Centro = Posio + |Zoom. Mnimo|

Distncia_entre_linha_grelha
Zoom. Escala

// centro em X:
if (self.Zoom.XMin < 0) and
(self.Zoom.XMax > 0) then begin
self.vCenter.X :=
round(self.Position.X +
abs(self.Zoom.XMin) *
(self.DistBetweenGrid.X /
self.Zoom.XScl));
end else begin
if (self.Zoom.XMin < 0) and
(self.Zoom.XMax < 0) then
self.vCenter.X :=
self.Position.X +
self.Size.cx
else self.vCenter.X :=
self.Position.X;
end;
// centro em Y:
if (self.Zoom.YMin < 0) and
(self.Zoom.YMax > 0) then begin
self.vCenter.Y :=
round(self.Position.Y +
abs(self.Zoom.YMax) *
(self.DistBetweenGrid.Y /
self.Zoom.YScl));

Compilado por thoga31

51

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

end else begin


if (self.Zoom.YMin < 0) and
(self.Zoom.YMax < 0) then
self.vCenter.Y :=
self.Position.Y +
self.Size.cy
else self.vCenter.Y :=
self.Position.Y;
end;
Como se pode verificar, o clculo do centro deve ser feito a posteriori j que este
depende da distncia em pixis entre as linhas da grelha previamente calculada.

6. Constantes necessrias
Agora que a base em termos de propriedades e valores necessrios est feita,
e antes de se avanar para a criao dos construtores, podemos definir as constantes
discutidas aquando a definio terica da classe.
Recordando, as constantes sero as seguintes:
CONST
// Zoom:
ZoomStandard : TZoom =
(XMin:-10; XMax:10; XScl:1;
YMin:-10; YMax:10; YScl:1);
// Grelha:
DefaultGrid : TLineStyle =
(Color:DarkGray; Thickness:NormWidth;
Style:DashedLn; Visible:True);
// Eixos:
DefaultAxes : TLineStyle =
(Color:White; Thickness:ThickWidth;
Style:SolidLn; Visible:True);
// Linha de funo:
DefaultFunctionLine : TLineStyle =
(Color:Red; Thickness:ThickWidth;
Style:SolidLn; Visible:True);
// Cor de fundo:
DefaultPlotterBackColor : word = Green;

Estes valores podem ser alterados ao gosto do leitor:


Zoom padro:
o XMin = -10
o XMax = 10

Compilado por thoga31

52

Revista PROGRAMAR

o
o
o
o

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

XScl = 1
YMin = -10
YMax = 10
YScl = -1

Estilo padro da grelha:


o Tipo de linha tracejada (dashed)
o Espessura normal (1 pixel)
o Cor cinzento escuro
o Visvel sim

Estilo padro dos eixos:


o Tipo de linha contnua (solid)
o Espessura espessa (2 pixis)
o Cor branco
o Visvel sim

Estilo padro da linha da funo:


o Tipo de linha contnua (solid)
o Espessura espessa (1 pixis)
o Cor vermelho
o Visvel sim

Cor de fundo padro verde.

7. Construtores
O meu objectivo no vai ser criar uma panplia de construtores com todos os
overloads possveis. Criarei o construtor completo e, de seguida, criarei uma verso
muito mais simples. A partir destes ser fcil criar mais construtores basta recorrer
directamente ao construtor completo.
O meu construtor completo ir receber 7 parmetros, permitindo uma total
personalizao da instncia da Plotter a priori. O construtor mais simples ir receber
apenas os primeiros 2 parmetros, que sero a posio e a dimenso.
PUBLIC
constructor Create
(cPosition : TPoint; cSize : TSize);
constructor Create
(cPosition : TPoint; cSize : TSize;
cZoom : TZoom;
cGrid, cAxes, cFnLine : TLineStyle;
cBkColor : word); overload;
A implementao do construtor em si simples: simplesmente atribuem-se os
parmetros s respectivas propriedades, e fazem-se os clculos do Passo 5:

Compilado por thoga31

53

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

CONSTRUCTOR TPlotter.Create
(cPosition : TPoint; cSize : TSize;
cZoom : TZoom;
cGrid, cAxes, cFnLine : TLineStyle;
cBkColor : word); OVERLOAD;
begin
self.vPosition := cPosition;
self.vSize := cSize;
self.vZoom := cZoom;
self.Grid := cGrid;
self.Axes := cAxes;
self.FunctionLine := cFnLine;
self.vFunction := nil;
self.BackColor := cBkColor;
self.CalcDistGridAndCenter;
end;
O construtor mais simples ir-se- servir do construtor completo, e ir atribuir aos
restantes 5 parmetros os valores padro definidos nas constantes:
CONSTRUCTOR TPlotter.Create
(cPosition : TPoint; cSize : TSize);
begin
self.Create(cPosition, cSize,
ZoomStandard, DefaultGrid,
DefaultAxes, DefaultFunctionLine,
DefaultPlotterBackColor);
end;
No sero criados destrutores j que, para esta classe, bastar utilizar o Free,
visto que esta herda da classe genrica TObject.

8. Mtodo Show desenho da Plotter


Aps a criao dos construtores comea a fase de desenho da Plotter na rea
grfica e a criao dos mtodos que redefinem as propriedades ReadOnly, j
previamente criadas. E a partir desta fase que comea realmente o uso da unit Graph.
Para mostrar a Plotter vamos utilizar um procedimento denominado Show:
PUBLIC
procedure Show;

Compilado por thoga31

54

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Primeiramente temos de definir o modo de preenchimento e, de seguida,


devemos pintar a rea de trabalho da Plotter com a cor de fundo definida aquando a
criao da instncia da classe:
PROCEDURE TPlotter.Show;
begin
SetFillStyle(SolidFill, self.BackColor);
Bar(self.Position.X, self.Position.Y,
self.Position.X + self.Size.cx,
self.Position.Y + self.Size.cy);
end;
Constatamos dois novos mtodos que pertencem unit Graph:
Mtodo & sintaxe

SetFillStyle(estilo,
cor);

Bar(X1, Y1, X2, Y2);

Descrio
Define o estilo de preenchimento de todos os mtodos
da famlia.

Estilo do tipo Word, define o tipo de


preenchimento, ou seja, qual o padro de
preenchimento;
Cor do tipo Word, define qual a cor de fundo.
Preenche um rectngulo com o estilo definido pelo
SetFillStyle.

(X1,Y1) canto superior esquerdo do rectngulo;


(X2,Y2) canto inferior direito do rectngulo.

No mtodo SetFillStyle recorreu-se a uma das 200 constantes da unit Graph


SolidFill que define um preenchimento slido e simples.
De seguida deve-se desenhar a grelha e s depois os eixos, isto porque, se a
grelha for desenhada aps os eixos, esta ir-se- sobrepor aos eixos, e a inteno dos
eixos estarem sobrepostos grelha.
Para desenhar a grelha necessitamos de uma estrutura de repetio, ou seja,
um ciclo. Para tal, iremos utilizar um ciclo Repeat Until.
if self.Grid.Visible then begin
// linhas verticais:
SetColor(self.Grid.Color);
SetLineStyle(self.Grid.Style, 0,
self.Grid.Thickness);
counter := round(self.Zoom.XMin);
repeat
Line(self.Center.X + counter *
self.DistBetweenGrid.X,
self.Position.Y, self.Center.X +

Compilado por thoga31

55

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

counter * self.DistBetweenGrid.X,
self.Position.Y + Self.Size.cy);
Inc(counter, 1);
until counter >= self.Zoom.XMax;
// linhas horizontais:
counter := round(self.Zoom.YMin);
repeat
Line(self.Position.X,
self.Center.Y - counter *
self.DistBetweenGrid.Y,
self.Position.X + self.Size.cx,
self.Center.Y - counter *
self.DistBetweenGrid.Y);
Inc(counter, 1);
until counter >= self.Zoom.YMax;
end;
Mais uma srie de mtodos da unit Graph foram utilizados:
Mtodo & sintaxe
SetColor(cor);

SetLineStyle(estilo,
padro, espessura);

Line(X1, Y1, X2, Y2);

Descrio
Define a cor das linhas de todos os mtodos da famlia.
Cor do tipo Word, define qual a cor.
Define o estilo das linhas de todos os mtodos da
famlia.

Estilo do tipo Word, define o estilo da linha;


Padro regra geral ignorado;
Espessura do tipo Word, define a espessura da
linha.
Desenha uma linha entre dois pontos com o estilo
definido pelo SetLineStyle.

(X1,Y1) primeiro ponto;


(X2,Y2) segundo ponto.

De referir que o contador utilizado, counter, do tipo Integer.


Como se pode verificar, a distncia entre as linhas da grelha fundamental para
se saber onde a linha da grelha a cada passo deve ser colocada como a distncia em
pixis entre cada linha depende no s do Zoom mas tambm da dimenso da Plotter,
coloca-se na prtica a importncia desta distncia.
Por fim, deve-se desenhar os eixos que so colocados em funo do centro do
grfico, tambm previamente determinado:

Compilado por thoga31

56

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

if self.Axes.Visible then begin


SetColor(self.Axes.Color);
SetLineStyle(self.Axes.Style, 0,
self.Axes.Thickness);
Line(self.Center.X,
self.Position.Y, self.Center.X,
self.Position.Y + self.Size.cy);
Line(self.Position.X,
self.Center.Y, self.Position.X +
self.Size.cx, self.Center.Y);
end;
De notar o facto da grelha e dos eixos s serem desenhados caso tenham a
propriedade Visible definida a True caso contrrio, o seu desenho ignorado.

9. Mtodo Clear apagar a Plotter


Agora que se sabe preencher um rectngulo com o mtodo Bar, o princpio do
mtodo Clear simples: apagar a rea de trabalho com a cor de fundo da rea grfica.
PUBLIC
procedure Clear;
PROCEDURE TPlotter.Clear;
begin
SetFillStyle(EmptyFill, Black);
Bar(self.Position.X,
self.Position.Y,
self.Position.X + self.Size.cx,
self.Position.Y + self.Size.cy);
end;
De referir que a constante EmptyFill utiliza a cor de fundo da rea grfica, pelo
que, qualquer que esta seja, o resultado final ser sempre uma rea grfica limpa. Por
defeito, o segundo parmetro deve ser recebido, e por conveno utiliza-se a constante
Black para este caso.

10. Mtodo Refresh actualizao


Este mtodo, pblico, faz uma actualizao da Plotter. Este mtodo til na
medida em que h propriedades que podem ser alteradas sem passar por mtodos
controlados, pelo que, para estas fazerem efeito, necessrio actualizar a Plotter.

Compilado por thoga31

57

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

PUBLIC
procedure Refresh;
Este mtodo simplesmente limpa a actual Plotter e desenha-a toda do zero:
PROCEDURE TPlotter.Refresh;
begin
self.Clear;
self.Show;
end;

11. Mtodo Relocate relocalizao


A partir deste momento, com os mtodos Show, Clear e Refresh disposio e
prontos a usar, torna-se deveras fcil modificar as propriedades da Plotter e, por
conseguinte, modificar o desenho desta que est na rea grfica:
1) Limpa-se o desenho da Plotter actual;
2) Muda-se a propriedade desejada;
3) Volta-se a desenhar a Plotter, j com as propriedades desejadas.
Para algumas propriedades a limpeza pode ser efectuada mesmo aps a
mudana da propriedade, pelo que o mtodo Refresh pode ser utilizado para optimizar
o cdigo. Contudo, outras propriedades, como a posio, no podem ter este
tratamento ptimo: se mudarmos a posio antes de limpar a rea grfica, o mtodo
Clear vai efectuar a sua aco j com a nova propriedade de posio, pelo que parte ou
a totalidade do desenho da Plotter na antiga posio no limpa. Desta forma, primeiro
deve ser executado o Clear e s alterada a propriedade para que aquele realize a sua
aco ainda na posio inicial.
Para comear vamos criar o mtodo Relocate, pblico, que ir relocalizar a
Plotter numa nova posio. Como foi agora mesmo discutido, o Refresh no pode ser
utilizado:
PUBLIC
procedure Relocate(cNewPosition:TPoint);
procedure Relocate(cX,cY:LongInt);
overload;
Para facilitar o trabalho do programador, criamos um overload do mtodo
principal em que se passam por parmetro as coordenadas da nova posio ao invs
de se passar as coordenadas com o tipo TPoint.
Implementando um destes mtodos, o outro pode-se basear nesse:

Compilado por thoga31

58

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

PROCEDURE TPlotter.Relocate
(cNewPosition : TPoint);
begin
self.Clear;
self.vPosition := cNewPosition;
self.Show;
end;
PROCEDURE TPlotter.Relocate
(cX, cY : LongInt); OVERLOAD;
var NewPos : TPoint;
begin
NewPos.X := cX;
NewPos.Y := cY;
self.Relocate(NewPos);
end;

12. Mtodos para a funo


Se modificarmos o Zoom ou a dimenso da Plotter, as coordenadas dos pontos
da funo vo mudar, pelo que, ao alterar estas propriedades, temos de recalcular estes
pontos. Por isso, antes de implementar os mtodos Resize e ReZoom, devemos
implementar os mtodos que iro manipular a funo:
Receber (carregar) a funo: LoadFunction;
Desenhar a funo: DrawFunction;
Limpar a funo da Plotter: ClearFunction;
Libertar os recursos da funo: DisposeFunction.
A nossa inteno que a Plotter receba uma funo real e de varivel real por
referncia (ou seja, por apontador), pelo que a nossa classe dever ter uma varivel,
de carcter privado, que guarde qual a funo com que vai trabalhar:
PRIVATE
vFunction : TRealFunction;
vValuesOfFunction :
array [0..2000] of real;
Denota-se de imediato que os valores da funo vo ser guardados num Array.
Mais frente veremos como este ser utilizado.
Um novo tipo de dados foi aqui introduzido, pelo que o vamos definir. Tratandose esta uma Plotter que trabalha com funes reais de varivel real, ento estas devem
ter um parmetro real e devolver um valor real:
TYPE
TRealFunction=FUNCTION(x:real):real;

Compilado por thoga31

59

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Primeiramente necessitamos de carregar a funo (LoadFunction) e, neste


processo, calcular os pontos na Plotter. Por uma questo de optimizao, s vamos
calcular os pontos estritamente necessrios e no todos os 2001 pontos que o Array
coloca ao nossos dispor.
PUBLIC
procedure LoadFunction
(fn:TRealFunction);

Este mtodo deve:


Gravar a funo;
Calcular os pontos da funo. Para tal:
o Calcular o valor real da funo atravs da abcissa;
o Transformar este valor numa coordenada Y a ser gravada em
vValuesOfFunction;

Para este processo, o clculo deve entrar com o Zoom, a dimenso e o centro
do grfico, para alm do valor real da funo a cada X. Implementando estes princpios,
deveremos chegar ao seguinte cdigo:
PROCEDURE TPlotter.LoadFunction
(fn : TRealFunction);
var abscissa : real;
counter : integer;
step : real;
begin
// grava funo:
self.vFunction := fn;
// inicia contadores:
counter := self.Position.X;
abscissa := self.Zoom.XMin;
// define passo de incrementao:
step := self.Zoom.XScl /
self.DistBetweenGrid.X;
repeat
// clculo do ponto:
self.vValuesOfFunction[counter]:=
self.vCenter.Y self.vFunction(abscissa) *
(self.DistBetweenGrid.Y /
self.Zoom.YScl);
// incrementa contadores:
abscissa := abscissa + step;
Inc(counter, 1);
until counter >= self.Position.X +

Compilado por thoga31

60

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

self.Size.cx;
end;

Os dois contadores so os seguintes:


counter define em que ndice do Array vValuesOfFunction deve ser gravado
o valor calculado;
abscissa define o valor da abcissa a cada pixel da rea de trabalho da Plotter.
Para determinar o ponto da funo na Plotter, sigamos o seguinte raciocnio:

Tendo em conta o centro do grfico, vamos andar para trs ou para a frente
deste consoante o valor real da funo: isto definido atravs de uma
subtraco;

self.vCenter.Y - self.vFunction(abscissa)

Contudo, o valor real da funo deve ser multiplicado por um factor que nos
indique a verdadeira posio, em pixis, do ponto. Assim sendo, se a distncia
entre linhas da grelha est para a escala, ento 1 pixel est para o factor que
procuramos:
Distncia Escala
1
Factor
Este factor , ento:

self.DistBetweenGrid.Y / self.Zoom.YScl
E, assim, temos o clculo do ponto da funo na Plotter.
Depois de carregar a funo, obviamente queremos que esta seja desenhada,
ou representada (DrawFunction).
PUBLIC
procedure DrawFunction;
A funo s ser desenhada se a propriedade FunctionLine.Visible estiver
definida a True, e s ser desenhada a parte do grfico que estiver dentro da rea da
Plotter, pelo que este controlo ser feito dentro do prprio ciclo que ir desenhar a
funo, e esta anlise ter de ser, obviamente, ponto a ponto. A vantagem de ter
calculado os pontos aquando o LoadFunction reside no facto de se evitarem mais
clculos no desenho da funo, bastando fazer o ouput das linhas que unem os pontos.

Compilado por thoga31

61

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

PROCEDURE TPlotter.DrawFunction;
var counter : integer;
begin
if self.FunctionLine.Visible
and not(self.vFunction=nil)
then begin
// define estilo da linha
SetLineStyle(self.FunctionLine.Style,
0, self.FunctionLine.Thickness);
// define cor
SetColor(self.FunctionLine.Color);
// desenha a funo:
for counter := self.Position.X to
(self.Position.X + self.Size.cx - 2)
do begin
{evita que o grfico seja desenhado
fora da rea da Plotter}
if not ((round(
self.vValuesOfFunction[counter+1]) <
self.Position.Y) or (round(
self.vValuesOfFunction[counter+1]) >
self.Position.Y + self.Size.cy))
then begin
//usa directamente vValuesOfFunction
Line(counter, round(
self.vValuesOfFunction[counter]),
counter+1, round(
self.vValuesOfFunction[counter+1]));
end;
end;
end;
end;
Como ser bvio, grande parte deste cdigo o conjunto de todas as condies
de controlo para o desenho da funo dentro da rea. O verdadeiro acto de desenhar a
funo est quase no fim, aquando a utilizao do mtodo Line.
De seguida, poderemos querer limpar a funo (ClearFunction), ou seja, voltar
a ter a Plotter sem qualquer funo desenhada.
PUBLIC
procedure ClearFunction;

Compilado por thoga31

62

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Para isto, definimos a visibilidade a False, fazemos o Refresh, e voltamos a


definir a visibilidade a True. Voltamos a definir a True porque, caso o programador queira
fazer DrawFunction mais tarde, teria de redefinir a visibilidade a True a ordem para
limpar a funo, e no mudar a sua visibilidade. Apenas a mudamos para tirar proveito
dos mtodos que j crimos at ao momento. Desta forma:
PROCEDURE TPlotter.ClearFunction;
begin
self.FunctionLine.Visible:=False;
self.Refresh;
self.FunctionLine.Visible:=True;
end;
Por fim, vamos libertar os recursos ocupados pela funo (DisposeFunction),
deixando a Plotter sem qualquer funo com que trabalhar.
PUBLIC
procedure DisposeFunction
(cClearPlotter : boolean);
Visto que o tipo de dados de vFunction no mais do que um apontador,
definimos que esta vale NIL. Para libertar os recursos podemos dar a opo de o grfico
ser tambm apagado da rea de trabalho ou no:
PROCEDURE TPlotter.DisposeFunction
(cClearPlotter : boolean);
var counter : integer;
begin
self.vFunction := nil;
for counter:=0 to 2000 do
self.vValuesOfFunction[counter]:=0;
if cClearPlotter then
self.ClearFunction;
end;
Desta forma, a Plotter fica pronta a receber uma nova funo, tendo passado
previamente por uma limpeza devida de todos os recursos que, de outra forma, o
mtodo LoadFunction no faria.
Temos ento os principais mtodos de manipulao da funo criados, pelo que
podemos continuar a construir os mtodos que redefinem as propriedades grficas da
Plotter.

Compilado por thoga31

63

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

13. Mtodo Resize redimensionar a Plotter


Redimensionar a Plotter implica que a funo seja redesenhada, da j termos
tratado de todos os mtodos relativos manipulao da funo.

O mtodo Resize ter dois cabealhos:


Recebe um parmetro do tipo TSize;
Recebe dois parmetros largura e altura do tipo LongInt.

PUBLIC
procedure Resize(cNewSize:TSize);
procedure Resize(ccx, ccy:LongInt);
overload;
Como a alterao da dimenso implica uma nova distncia entre as linhas das
grelhas, bem como a mudana do centro do grfico, estes devem ser recalculados. Do
mesmo modo, a funo ter de se adaptar, pelo que a soluo recarregar a funo
fazendo um LoadFunction da funo que a Plotter j tiver.
PROCEDURE TPlotter.Resize
(cNewSize : TSize);
begin
self.vSize := cNewSize;
self.CalcDistGridAndCenter;
if not(self.vFunction = nil) then
self.LoadFunction(self.vFunction);
self.Refresh;
end;
PROCEDURE TPlotter.Resize
(ccx, ccy : LongInt); OVERLOAD;
var NewSize : TSize;
begin
NewSize.cx := ccx;
NewSize.cy := ccy;
self.Resize(NewSize);
end;

14. Mtodo ReZoom redefinir o zoom


Como o zoom necessita de 6 parmetros, vamos criar apenas um mtodo geral
que recebe um parmetro do tipo TZoom. Se se pretender criar um mtodo que receba
os 6 argumentos totais relativos aos 6 dados do zoom, bastar fazer o mesmo que os
mtodos Resize e Relocate.

Compilado por thoga31

64

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

PUBLIC
procedure Rezoom(cNewZoom : TZoom);
O zoom, tal como a dimenso, obriga a um reclculo da distncia entre as linhas
da grelha e do centro do grfico, bem como ao reclculo dos pontos da funo.
PROCEDURE TPlotter.ReZoom
(cNewZoom : TZoom);
begin
self.vZoom := cNewZoom;
self.CalcDistGridAndCenter;
if not(self.vFunction = nil) then
self.LoadFunction(self.vFunction);
self.Refresh;
end;

Criao de um pequeno programa de teste


De facto j terminmos a construo da nossa classe! Reunindo toda a
informao e todo o cdigo na unidade CalcGraph, ficamos com uma unit pronta-ausar.
A classe criada pode ser melhorada, bem como a unidade. O intuito deste artigo
no era criar uma classe altamente profissional, mas sim criar uma Plotter bsica e
com os mtodos mais bsicos. Esta classe est passvel a sofrer bugs no seu uso pois
no fizemos, por exemplo, o controlo dos valores do zoom (XMax deve ser maior do que
XMin, por exemplo) nem de outros parmetros. De modo a tornar a classe mais segura,
o leitor poder, e dever, acrescentar-lhe condies e mtodos de segurana e de
controlo. Contudo, no se esquea de deixar um certo nvel de liberdade para o
programador final.
Agora vamos testar a unidade criada. No vou criar um programa que teste a
fundo a unidade. Farei apenas um pequeno programa que cria e personaliza uma
Plotter, carrega uma funo, desenha-a e, depois, encerra o programa.
A funo ser, relembrando o incio do artigo, () = 2 8. Vamos alterar
algumas propriedades da Plotter para termos um resultado diferente do das imagens
iniciais.
Segue, portanto, o cdigo do programa seguido de uma breve explicao.

Compilado por thoga31

65

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

PROGRAM Teste;
uses Graph,
CalcGraph,
SysUtils,
Windows;
FUNCTION f(x:real):real;
begin
f := sqr(x)-8;
end;
CONST
InitPos:TPoint=(X:10; Y:10);
InitSize:TSize=(cx:400; cy:200);
PosZoom:TZoom =
(XMin:-4; XMax:8; XScl:2;
YMin:-9; YMax:2; YScl:1);
VAR Driver, Modus : SmallInt;
Plot : TPlotter;
BEGIN
TRY
TRY
// inicia rea grfica
DetectGraph(Driver, Modus);
InitGraph(Driver, Modus, '');
// cria Plotter
Plot:=
TPlotter.Create(InitPos,InitSize);
// altera propriedades
Plot.BackColor:=Blue;
Plot.Grid.Color:=Black;
Plot.Axes.Color:=Yellow;
Plot.Axes.Thickness:=NormWidth;
// desenha a Plotter
Plot.Show;
// carrega a funo
Plot.LoadFunction(@f);
// altera cor da funo
Plot.FunctionLine.Color:=LightRed;
// desenha funo
Plot.DrawFunction;
readln; // pausa
Plot.Resize(700,400);
Plot.ReZoom(PosZoom);
Plot.DrawFunction;

Compilado por thoga31

66

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

EXCEPT
ON ex:exception do begin
MessageBox(0,@ex.message[1],'Erro!',
MB_ICONASTERISK);
end;
END
FINALLY
write('FIM.'); readln;
// liberta recursos da Plotter
Plot.Free;
CloseGraph;
END
END.
Neste programa crimos uma Plotter atravs da varivel Plot, ou seja, uma
instncia da classe TPlotter. Os seus valores padro para as vrias propriedades foram
alterados:
A cor de fundo foi definida a azul, em vez da predefinio (verde);
A cor da grelha foi definida a preto e no a cinzento-escuro;
A cor dos eixos passou a ser amarela, e a sua espessura passou a ser de 1
pixel;
A cor da funo passou para vermelho-vivo (LightRed) em vez de vermelhoescuro (Red).
Aps a alterao destas propriedades, o resultado final (Figuras 7 e 8)
diferente do inicialmente representado na Figura 1, que seria o output gerado caso se
mantivesse a predefinio.

Figura 7 Output gerado pelo programa de testes antes da pausa.

Compilado por thoga31

67

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Figura 8 Pormenor da Plotter criada no programa antes da pausa de realar as


propriedades alteradas.

A Figura 7 mostra a importncia da posio e do tamanho da Plotter: todo o


fundo negro a rea grfica gerada pelo InitGraph que, no caso desta figura, foi de
1280x800.
Contudo foi feita uma pausa no programa e depois foram aplicados os mtodos
Resize e ReZoom. Como estes mtodos no redesenham a funo, fazemos de novo o
output desta, e obtemos o resultado representado pela Figura 9.

Figura 9 Output gerado pelo programa de testes aps a pausa.

Notamos a adaptao realizada por todos os clculos auxiliares, privados, na


alterao das propriedades de zoom e dimenso.

Compilado por thoga31

68

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Resumo
Com este artigo recorremos unit Graph para conceber uma Plotter, tendo a
sua implementao sido feita com o uso do OOP, ou seja, o paradigma da Programao
Orientada aos Objectos.
Desta forma ser possvel ter, numa s rea grfica, vrias Plotters com
caractersticas diferentes e a trabalhar com funes diferentes, mas totalmente
independentes umas das outras para tal, bastar criar diferentes instncias da classe
TPlotter.
Apesar de no se ter aprofundado a unidade na qual nos baseamos, ficou claro
que com pouco se pode fazer muito, e com meia dzia de mtodos da Graph uma
Plotter totalmente funcional e personalizvel foi construda.
Destacam-se, ento, os mtodos Bar, Line, SetFillStyle e SetLineStyle, assim
como as constantes NormWidth, ThickWidth, EmptyFill, SolidFill, SolidLn e
DashedLn.

A unidade e o programa de testes esto disponveis para download no Portal de


Downloads do P@P seguindo o seguinte link: <LINK>

Sobre o autor
Igor Nunes
Curioso na rea das tecnologias e em especial da programao, tem um grande
interesse pelas tecnologias de ensino da Texas Instruments e pelo TI-Basic, alm de
ter uma saudvel relao com o Pascal. conhecedor de outras linguagens de
programao, como VB.NET.
No P@P, membro da Wiki Team e Moderador Local dos quadros de Pascal e
Delphi/Lazarus. Escreveu o novo Tutorial de Pascal, recentemente disponibilizado em
PDF.

Compilado por thoga31

69

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

42 Edio

Pascal Tipos de dados Variant e tipos


genricos
Por: Igor Nunes

Compilado por thoga31

70

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

So vrias as linguagens cujas variveis so dinmicas pelo facto de no terem


um tipo de dados bem definido na hora da compilao, sendo antes verificados em
runtime. Em vrias ocasies, til ter uma varivel cujo tipo de dados varie, e nas
linguagens estaticamente tipadas isso pode tornar-se um pouco trabalhoso ou mesmo
impossvel.
Neste campo, Pascal tem vindo a evoluir, chegando a um pequeno conjunto de
solues relativamente simples e muito versteis: o tipo de dados Variant, que uma
forma de tipagem dinmica, e os tipos de dados genricos, que permitem abstrair
certos conceitos que podero mais tarde ser especializados falaremos deste aspecto
mais a fundo na continuao do artigo. Note-se que os tipos genricos so, mais
especificamente, classes genricas.
Todo o artigo centra-se no Free Pascal (verso 2.6.0) (FPC), podendo haver
diferenas para o Delphi e/ou outros compiladores.

Tipo de dados Variant


O tipo de dados Variant isso mesmo: um tipo de dados que varia. suportado
nativamente pela unit system desde a verso 1.1 do FPC, mas para tirar o mximo
proveito deste tipo de dados recomenda-se o uso da unit variants. O verdadeiro tipo de
dados que assume s definido em runtime e pode ir mudando ao longo do programa.
uses variants;
var V : variant;
Nem todos os tipos de dados podem ser atribudos a um variant. Por exemplo,
um record no poder ser atribudo a uma varivel do tipo variant. O mesmo se aplica
a sets, arrays, files, objects e classes. De resto, qualquer tipo de dados simples poder
ser directamente atribudo. Portanto, o seguinte programa totalmente vlido pois todas
as atribuies feitas tambm o so:
program exemplo;
uses variants;
var V : variant;
R : real;
I : integer;
W : word;
I64 : Int64;
S : string;
begin
V := R;
V := I;
V := W;

Compilado por thoga31

71

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

V := I64;
V := S;
end.
Contudo, h uma excepo para o caso das arrays. Apesar de no ser possvel
atribuir directamente uma array a uma varivel do tipo variant, esta poder conter uma
array desde que seja criada pela funo VarArrayCreate.
A unit system contm um pequeno conjunto de procedimentos e funes que
permitem que uma varivel variant se torne numa array e que esta seja manipulada.
Analisemos a situao atravs do seguinte cdigo:
var i : integer;
v : variant;
begin
v:=VarArrayCreate([1,10], varInteger);
for i:=1 to 10 do
v[i]:=i;
VarArrayRedim(v,20);
for i:=11 to 20 do
v[i]:=i;
for i:=1 to 20 do
write(v[i], ',');
end.
VarArrayCreate uma funo que permite a criao de uma array numa varivel
variant, onde lhe fornecida por argumento a dimenso (neste caso seria uma
array[1..10]) e qual o tipo de dados armazenado na array (varInteger uma constante
da System que indica que a array ir armazenar integers).
De seguida, podemos redimensionar a array com o procedimento
VarArrayRedim, onde passamos a varivel variant que representa, neste momento,
uma array, seguida da nova dimenso: neste caso ficaremos com uma array[1..20].
Como possvel observar, a partir do momento em que se cria a array na varivel
variant com este mtodo, podemos aceder aos seus elementos atravs dos seus ndices
como se de uma array tradicional se tratasse.
Uma nota importante acerca das variveis variant que assumem strings: ao
contrrio de uma varivel string, as variants no permitem o acesso aos caracteres da
string atravs dos seus ndices. Se o fizermos, ser levantada uma excepo do tipo
EVariantInvalidArgError.
Um pormenor interessante que pode levantar dvidas prende-se com a
atribuio do valor contido numa varivel variant a outra varivel de tipo esttico. Isto
possvel e traz, claro, alguns pormenores relacionados com a compatibilidade dos tipos
de dados e a converso implcita que da poder surgir. Vejamos o seguinte exemplo:

Compilado por thoga31

72

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

var V : variant;
I : integer;
begin
V := 42;
I := V;
end.
Estamos a atribuir a uma varivel do tipo integer o valor de uma varivel variant
que representa, no momento, uma string. Esta atribuio vlida porque a string 42
representa um nmero inteiro vlido, pelo que ocorre a converso implcita para
integer. Contudo, se a string fosse, por exemplo, 42A, a converso seria impossvel,
ocorrendo ento uma excepo do tipo EConvertError.
Este tipo de dados no propriamente eficiente, como lgico. O programa, em
runtime, dever avaliar constantemente os valores que so atribudos e manipulados
nas variveis variant e isto implica processamento extra. Portanto, havendo tipos
estticos que so mais eficientes, o tipo variant dever ser utilizado apenas quando
estritamente necessrio.

Tipos genricos
J do conhecimento geral que a famlia de linguagens Pascal tem pleno
suporte ao paradigma OOP com as suas variantes Object Pascal e Delphi. Contudo, o
que poucos sabero que as classes podem ser genricas.
Regra geral, uma classe ter em si uma varivel, ou conjunto de variveis, onde
os seus dados esto armazenados e os quais so manipulados com os mtodos da
classe. Se pretendemos, por exemplo, guardar informaes acerca de carros teramos
uma lista do tipo TCarro, se pretendermos guardar informaes acerca de pessoas
teremos uma lista do tipo TPessoa. Podemos j constatar que ambos estes exemplos
tm algo em comum: so listas com o objectivo de registar e manipular dados relativos
a objectos. Ora, ser natural pensar que os mtodos que realizam essas operaes so
no s equivalentes como muito provavelmente iguais a nica diferena est no tipo
dos objectos.
Deparamo-nos, portanto, com o seguinte dilema: pretende-se criar uma classe
que possa lidar com um conjunto de objectos, mas estes objectos podem ser de
qualquer tipo. O primeiro pensamento do programador ser preciso de implementar
uma classe para cada tipo de dados.
E aqui onde o programador estar equivocado.
Uma classe genrica uma classe que est preparada para trabalhar com
dados de qualquer tipo, tipo este que informado classe por um processo designado
especializao. Portanto, h duas fases:
1. Implementao da classe genrica;
2. Especializao da classe genrica com a criao de um novo tipo de dados.

Compilado por thoga31

73

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Ou seja, criamos a classe para que ela trabalhe com um tipo de dados genrico
T, e mais tarde o programador que precisar da classe especializa-a, indicando o que
que este tipo T dever ser: poder ser um Integer, um Real, uma String, uma Array
ou at mesmo uma classe!
A sintaxe da declarao de uma classe genrica em Free Pascal a seguinte:
type generic TClasse<T> = class
A keyword generic indica que a classe genrica, e frente do nome da
classe, entre os sinais de maior e menor, damos o nome ao tipo genrico, neste caso
T (que o nome dado mais comum).
Quando o programador necessitar da classe, dever indicar um novo tipo de
dados onde a classe genrica especializada, sendo a sintaxe a seguinte:
type TClasseEspecializada = specialize TClasse<Tipo>;
Tipo no poder ser uma classe genrica, mas poder ser uma classe

especializada. Ou seja, se especializarmos uma classe genrica num novo tipo de


dados, este tipo poder ser utilizado para especializar outra classe genrica. Contudo,
uma classe genrica no poder ser utilizada directamente para a especializao de
outra classe genrica.
Colocada a teoria acerca de tipos genricos e variantes, vamos aplicar estes
conceitos na prtica.

Implementao de uma stack com recurso a tipos genricos


Para quem programa noutras linguagens h uma pergunta que poder surgir:
faz sentido implementar uma stack recorrendo a uma classe genrica? No h forma de
dizer que no. Alis, mesmo uma estratgia com bastantes vantagens. A utilizao de
mtodos mais tradicionais implicaria, de uma forma geral, a implementao de uma
stack para cada tipo de dados necessrio. A utilizao de uma stack com dados variant
no seria eficiente nem desejvel se pretendemos ter uma stack cujos dados sejam
do tipo Integer, esta soluo no seria, de todo, a melhor.
Portanto, as classes genricas oferecem a possibilidade de implementar a stack
independentemente do tipo de dados com que v lidar, havendo posteriormente a sua
especializao. Isto significa que, em vez de haver a implementao repetitiva de stacks
para diferentes tipos, h uma implementao genrica de uma classe que poder ser
especializada nos tipos que forem necessrios.

Compilado por thoga31

74

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Para comear, a classe ter, ento, a seguinte definio:


type generic TStack<T> = class(TObject)
Esta ser uma classe que ir herdar de TObject e ter por base uma array
dinmica para armazenar os valores.
A stack dever aumentar de tamanho medida que lhe so adicionados
elementos. Para tornar esta expanso eficiente, sempre que se tentar adicionar um
elemento e a array j no tiver capacidade, duplicamos-lhe o tamanho. Isto eficiente
no s por causa do funcionamento em memria do procedimento SetLength que
iremos utilizar para redimensionar a array como tambm leva a um muito menor nmero
de redimensionamentos (no h nova alocao de memria sempre que se acrescenta
um elemento stack).
Necessitaremos tambm de uma varivel privada que nos indique quantos
elementos tem a stack no momento (ou seja, quantos elementos tem a array).
O construtor da classe oferecer duas opes ao programador: ele poder
indicar ou no quantos elementos dever ter a stack inicialmente. Caso ele no o defina,
um tamanho ser atribudo por defeito, tamanho este representado pela constante
INITSIZE.
Por fim, necessitamos dos mtodos tpicos de uma stack: pop e push.
Adicionalmente criaremos uma funo que informa se a stack est vazia: IsEmpty.
type generic TStack<T> = class(TObject)
private
const INITSIZE = 32;
var data : array of T;
top : word;
public
function Pop : T;
procedure Push(value : T);
function IsEmpty : boolean;
constructor Create;
constructor Create(limit:word); overload;
end;
Resta-nos implementar estes mtodos. Comecemos pelos construtores:
constructor TStack.Create();
begin
self.Create(self.INITSIZE);
end;
constructor TStack.Create(limit:word);
overload;
begin
if limit < 1 then
limit := 1;

Compilado por thoga31

75

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

SetLength(self.data, limit);
self.top := 0;
end;
Como vimos antes, o programador pode definir um tamanho inicial para a stack
ou no, e no caso de no o definir, esta ter um tamanho por defeito, indicado com a
constante privada INITSIZE.
Podemos tambm j definir a funo IsEmpty pela sua simplicidade:
simplesmente verificamos se top igual a zero, o que indica que no h elementos na
stack.
function TStack.IsEmpty:boolean;
begin
IsEmpty := (self.top = 0);
end;
De seguida vamos implementar o mtodo push que vai permitir adicionar um
elemento stack. Como foi referido, se a array no tiver tamanho suficiente, a sua
dimenso ser duplicada:
procedure TStack.Push(value : T);
begin
inc(self.top);
if self.top > length(self.data) then
SetLength(self.data, length(self.data)*2);
self.data[self.top-1] := value;
end;
Ou seja, aumentamos top em uma unidade, verificamos o tamanho da array e
atribumos o valor, fornecido no argumento value.
Por fim, s nos resta implementar a funo pop. Esta funo vai retornar o ltimo
valor da stack, decrementando o indicador top. No caso de o programador realizar um
pop indevido (quando a stack est vazia), ele dever lidar com o erro que da surgir
h um mtodo IsEmpty que o auxilia, pelo que no est no mbito do artigo a gesto
deste erro. Traduzindo em cdigo:
function TStack.Pop : T;
begin
Pop := self.data[self.top-1];
dec(self.top);
end;
Temos, portanto, a nossa stack implementada. S nos resta test-la.
{$mode objfpc}
program Teste_Stack;
// Cdigo da classe aqui
type TIntegerStack =

Compilado por thoga31

76

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

specialize TStack<Integer>;
var s : TIntegerStack;
i : integer;
begin
s := TIntegerStack.Create(5);
for i in [1..10] do
s.Push(i);
while not s.IsEmpty do
write(s.Pop, ', ');
writeln('FIM');
s.Free;
readln;
end.
Especializmos a stack para lidar com dados do tipo Integer, e indicmos que
estamos a prever que esta tenha 5 elementos, o que, neste caso, no se concretiza
vamos testar a capacidade de autoexpanso da stack. Fizemos, portanto, push de
nmeros de 1 a 10 por ordem, e de seguida ordenmos o esvaziamento completo por
pop encaixado numa estrutura while que termina quando a stack estiver vazia (verificado
pela funo IsEmpty), escrevendo os valores no output. Eis o resultado deste programa:
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, FIM
Como podemos constatar, a stack est a cumprir o seu propsito LIFO (Last In
First Out) o ltimo elemento a entrar o primeiro a sair , bem como foi capaz de se
redimensionar de forma a acomodar todos os valores.
Por curiosidade, podamos ter especializado a classe com o tipo variant ou
seja, a stack podia literalmente conter elementos de qualquer tipo de dados simples.
Isto revela, em ltima instncia, a versatilidade dos tipos variantes e genricos no s
em separado como em conjunto.

Sobre o autor
Igor Nunes
Curioso na rea das tecnologias e em especial da programao, tem um grande
interesse pelas tecnologias de ensino da Texas Instruments e pelo TI-Basic, alm de
ter uma saudvel relao com o Pascal. conhecedor das bases de outras linguagens
de programao, como VB.NET, Python e Haskell.
No P@P, membro da Wiki Team e Moderador Global. Escreveu o novo Tutorial
de Pascal, disponibilizado em PDF.

Compilado por thoga31

77

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

44 Edio

Pascal operator overloading


Por: Igor Nunes

Compilado por thoga31

78

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Vrias so as linguagens nas quais podemos fazer overload de funes; esta


funcionalidade permite que uma funo possua vrias verses que admitam diferentes
conjuntos de argumentos, ficando o compilador encarregue de seleccionar qual dos
overloads o correcto aquando da invocao dessa funo. Uma das linguagens com
essa capacidade o Object Pascal moderno.
Em Pascal, este tipo de polimorfismo tambm se aplica aos operadores, os quais
podem de igual forma ser overloaded.
Na definio da linguagem Pascal segundo a documentao do Free Pascal, os
tokens (palavras que constituem o cdigo do nosso programa) podem pertencer a vrias
categorias, cada uma delas com funes ou caractersticas particulares. Uma dessas
categorias a dos operadores, os quais so um smbolo ou conjunto de dois smbolos,
com uma funo especfica, admitindo um ou dois operandos e devolvendo um
resultado. Os operadores so habitualmente funes com nomes e sintaxe especiais
(nomes compostos por smbolos e invocao infixa, por oposio tradicional invocao
prefixa).
Dentro destes operadores, muitos deles podem ser overloaded, permitindo ao
programador defini-los para novos tipos de operandos, e com comportamentos
diferentes do habitual, aumentando assim a expressividade do cdigo. A isto chamamos
operator overloading, objecto de estudo do presente artigo.
Todo o cdigo do artigo foi compilado com Free Pascal Compiler, verso 2.6.2,
em ambiente GNU/Linux (Ubuntu 12.04 LTS).

Overload de operadores aritmticos


Nem todos os operadores podem ser overloaded, mas os mais comuns podem:
os operadores de atribuio, aritmticos e de comparao.
Vamos partir de casos prticos para entender como se faz overload de
operadores em Free Pascal. Imagine-se, por exemplo, que pretendemos multiplicar os
valores de um array de nmeros inteiros, do tipo Integer, por um nmero tambm ele
Integer, retornando um novo array.
Regra geral, o que ns fazemos iterar pelos elementos do array, aplicando o
dito clculo. O resultado pode ficar no mesmo array ou ser atribudo a um novo. Decerto
o leitor j teve situaes nas quais necessitou de realizar aplicao de clculos simples
a um array e tornou-se possivelmente cansativo implementar estes ciclos, mesmo com
recurso a funes.

Compilado por thoga31

79

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Com a capacidade de overloading dos operadores torna-se possvel reduzir o


rudo do cdigo. Para tal, implementamos numa unit os overloads que nos forem teis.
Podemos, ento, comear a tirar apontamentos acerca da sintaxe da
implementao de operating overloading:
operator simbolo (operando1 : tipo1;
operando2 : tipo2) resultado : tipo3;
Operator uma palavra reservada comummente no reconhecida pela
esmagadora maioria dos editores de texto e IDE's (embora muitos nos permitam definir
palavras reservadas adicionais). No entanto, uma palavra reservada que equivale
essencialmente function ou procedure. Neste caso, indica ao programa que vai
haver um overload de um operador pr-existente, representado por simbolo.
Entre parntesis indicamos os operandos, tal como o fazemos com os
argumentos de uma funo (afinal de contas, um operador uma funo com dois
argumentos). De seguida, vem algo que difere da sintaxe comum: aparece um novo
identificador. Em Pascal (Standard e Free Pascal), o resultado de uma funo
atribudo a uma varivel local com nome igual ao da prpria funo; no caso de um
operador no existe um identificador alfanumrico mas sim um smbolo (o operador), o
qual um nome vlido para uma varivel, e ao qual no pode ser atribudo um valor.
Portanto, temos de criar o identificador que represente o resultado. Especificamos o seu
nome, por conseguinte, aps a declarao dos operandos, seguido do tipo de dados de
output.
Vejamos com mais pormenor a seguinte implementao:
operator * (n : integer; list : TIntegerArray)
res : TIntegerArray;
var i : word;
begin
SetLength(res, Length(list));
for i := Low(list) to High(list) do
res[i] := n * list[i];
end;
Estamos a fazer overload ao operador de multiplicao *, o qual tem dois
operandos: n, do tipo integer, que vai ficar esquerda do operador, e list, do tipo
TIntegerArray, que vai ficar direita. O resultado da operao ser atribudo ao
identificador res, e -nos indicado que o output do tipo TIntegerArray.
No podemos indicar de forma explcita que o tipo de dados array of integer,
uma vez que o compilador no o aceita. Deste modo, temos de criar um novo tipo de
dados de modo a que possamos fornecer este tipo na forma de um identificador.

Compilado por thoga31

80

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

type TIntegerArray = array of integer;


A partir do momento em que temos este overload definido, podemos utilizar o
operador * para multiplicar uma varivel do tipo integer por outra do tipo TIntegerArray.
No entanto, foi referido que um ficava direita e outro esquerda. Vejamos o que
acontece se tentarmos compilar o seguinte cdigo (o overload do operador est na unit
operover (de Operator Overloading) uma unit prpria onde iremos colocar o cdigo).
{$mode objfpc}
program artigo44;
uses operover;
var list1 : TIntegerArray = nil;
list2 : TIntegerArray = nil;
i : integer;
begin
(* Cdigo que adiciona valores a list1 *)
list2 := list1 * 2;
for i := Low(list1) to High(list1) do
writeln('2 * ',list1[i]:2,' = ',list2[i]:2);
end.
O Free Pascal vai indicar o seguinte:
artigo44.pas(16,20) Error: Operator is not overloaded: "TIntegerArray" * "ShortInt"
Ns definimos que o primeiro operando integer, e ns fornecemos-lhe um
TIntegerArray. Isto acontece porque o overloading de operadores sensvel ordem
pela qual recebe os operandos. Vamos, portanto, criar um segundo overload que define
a ordem inversa dos operandos:
operator * (list : TIntegerArray; n : integer)
res : TIntegerArray;
begin
res := n * list;
end;
Desta vez, a varivel do tipo TIntegerArray aparece esquerda do operador, e
a integer direita. Para implementar este overload recorremos ao overload definido
anteriormente. A partir deste momento, podemos multiplicar um integer por um
TIntegerArray em qualquer ordem. Vamos testar o cdigo anterior, e consideremos que
list1 tem os valores {2, 7, 5, 9, 4}; este o output do programa:

Compilado por thoga31

81

Revista PROGRAMAR

2
2
2
2
2

*
*
*
*
*

2
7
5
9
4

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

= 4
= 14
= 10
= 18
= 8

Como podemos verificar, list2 possui os valores de list1 multiplicados por 2 e


pela ordem original. O nosso operador overloaded funcionou. Podemos fazer o mesmo
para a soma. No entanto, a subtraco e a diviso no tm a propriedade comutativa,
ou seja, a-b no o mesmo que b-a, e a/b no o mesmo que b/a. Nestes casos, talvez
seja de vital importncia definir bem o overloading para cada ordem de operandos. Isto
, 5-list dever dar um resultado diferente de list-5.
Vamos continuar com o overload do operador de adio +.
operator + (n : integer; list : TIntegerArray)
res : TIntegerArray;
var i : word;
begin
SetLength(res, Length(list));
for i := Low(list) to High(list) do
res[i] := n + list[i];
end;
operator + (list : TIntegerArray; n : integer)
res : TIntegerArray;
begin
res := n + list;
end;
Como se pode verificar, em tudo igual ao overload do operador *. De seguida
iremos implementar o overload do operador de subtraco -. No entanto, para
aumentar a versatilidade dos operadores e tornar, por ltimo, esta implementao mais
compacta, iremos primeiro implementar o menos unrio (o operador - que indica um
valor negativo, como -4):
operator - (list : TIntegerArray)
res : TIntegerArray;
var i : word;
begin
SetLength(res, Length(list));
for i := Low(list) to High(list) do
res[i] := -list[i];
end;

Compilado por thoga31

82

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Este operador s tem um operando, direita. Neste momento podemos definir o


overload do operador -, de subtraco, de forma bastante compacta:
operator - (n : integer; list : TIntegerArray)
res : TIntegerArray;
begin
res := n + (-list);
end;
operator - (list : TIntegerArray; n : integer)
res : TIntegerArray;
begin
res := (-n) + list;
end;
Repare-se que recorremos ao operador + e, no primeiro caso, ao menos unrio
para definir estes overloads. Podamos ter feito com ciclos, mas esta uma tcnica que
permite poupar algumas linhas de cdigo, tornando o cdigo mais legvel.
Resta-nos o operador de diviso /, que retorna um nmero real. Todavia, o
nosso output tem sido um array of integer. Para este caso, necessitaremos de um novo
tipo de dados para o output:
type TRealArray = array of real;
operator / (n : integer; list : TIntegerArray)
res : TRealArray;
var i : word;
begin
SetLength(res, Length(list));
for i := Low(list) to High(list) do
res[i] := n / list[i];
end;
operator / (list : TIntegerArray; n : integer)
res : TRealArray;
var i : word;
begin
SetLength(res, Length(list));
for i := Low(list) to High(list) do
res[i] := list[i] / n;
end;
Se pretendermos resultados inteiros, tambm podemos fazer o overload dos
operadores mod e div, ou seja, o resto da diviso e a diviso inteira, respectivamente.
Outros operadores aritmticos que podem ser overloaded so a exponenciao
(**) e a diferena simtrica (><).

Compilado por thoga31

83

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Overload de operadores de comparao


Na mesma medida em que podemos fazer o overload de operadores aritmticos,
os operadores de comparao tambm tm essa capacidade.
A primeira coisa a saber que estes operadores s podem retornar um boolean.
Caso tentemos que o output seja outro tipo de dados, ser emitido um erro de
compilao:
artigo44.pas(37,67) Error: Comparative operator must return a boolean value
Para os analisar, vamos considerar o seguinte exerccio:
Implemente o overload de todos os operadores de comparao (excepto o de igualdade
e o de diferena) de forma a comparar dois arrays. Caso os arrays tenham tamanhos
diferentes, dever retornar False. Caso contrrio, se todo o par de elementos cumprir a
comparao, devolve True.
Vamos trocar por midos: se fizermos {1,2,3}>={1,4,2} iremos obter False. Os
arrays tm o mesmo tamanho, mas o segundo par de elementos (o 2 do primeiro array,
e o 4 do segundo) no cumprem a comparao: 2>=4 falso, pelo que a comparao
dos arrays vai retornar False.
No implementamos os operadores de igualdade e de diferena pelo simples
motivo de estes, por defeito, j terem a capacidade de comparar arrays.
Para implementar estes overloads de forma compacta, vamos analisar a
seguinte propriedade matemtica:
a>b o mesmo que b<a;
Portanto, basta implementar um dos operadores, e o outro ser a aplicao do
anterior, mas invertido. Em cdigo:
operator > (list1, list2 : TIntegerArray)
res : boolean;
var i : word;
begin
res := Length(list1) = Length(list2);
if res then
for i := Low(list1) to High(list1) do
if not(list1[i] > list2[i]) then begin
res := false;
break;
end;
end;

Compilado por thoga31

84

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

operator < (list1, list2 : TIntegerArray)


res : boolean;
begin
res := list2 > list1;
end;
Portanto, para implementar o overload de um operador de comparao com a
ordem dos operandos invertidos, recorremos ao overload do operador inverso.
O mesmo se aplica aos operadores <= e >=.
Por fim, os operadores de igualdade (=) e diferena (<>) tambm podem ser
overloaded.

Overload do operador IN
Desde a verso 2.6.0 do Free Pascal Compiler que o operador IN tambm pode
ser overloaded. Este operador verifica, por defeito, se um determinado valor pertence a
um set. No entanto, ele no tem a capacidade de fazer esta verificao com um array.
Desta forma, vamos implementar um overload que o permita:
operator in (n : integer; list : TIntegerArray)
res : boolean;
var elem : integer;
begin
res := false;
for elem in list do
if n = elem then begin
res := true;
break;
end;
end;

Overload do operador de atribuio


Para terminar, s falta abordar um operador que tambm pode ser overloaded:
o operador de atribuio, :=.
A par do menos unrio, este operador s tem um operando, o qual se encontra
igualmente do seu lado direito. Para demonstrar como se faz overloading deste
operador, vamos receber um set of byte e transform-lo num array of integer.

Compilado por thoga31

85

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

type TIntegerArray = array of integer;


TByteSet = set of byte;
operator := (list : TByteSet) res : TIntegerArray;
var elem : byte;
begin
res := nil;
for elem in list do begin
SetLength(res, Length(res)+1);
res[High(res)] := elem;
end;
end;
O tipo de dados set implica que os dados fiquem automaticamente por ordem,
independentemente da ordem em que os declaramos no cdigo. Isto ser visto de
seguida.

Concluso
Consideremos a implementao de todos estes overloads numa unit
denominada operover, tal como foi referido ao longo do artigo. Vamos test-la com o
seguinte programa:
{$mode objfpc}
program artigo44;
uses operover;
var list1 : TIntegerArray = nil;
list2 : TIntegerArray = nil;
i : integer;
begin
list1 := [2,7,5,9,4];
list2 := -list1 * 2;
for i := Low(list1) to High(list1) do
writeln('2 * ',-list1[i]:2,' = ',list2[i]:3);
writeln('list1 > list2? ':16, list1 > list2);
writeln('list1 <= list2? ':16, list1 <= list2);
writeln('-4 in list2? ':16, -4 in list2);
end.
O output deste programa o seguinte:

Compilado por thoga31

86

Revista PROGRAMAR

2 * -2 =
2 * -4 =
2 * -5 =
2 * -7 =
2 * -9 =
list1 >
list1 <=
-4 in

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

-4
-8
-10
-14
-18
list2? TRUE
list2? FALSE
list2? TRUE

Como podemos constatar, apesar do set ter sido fornecido numa ordem
aleatria, os dados foram colocados por ordem crescente, isto por causa do modo como
funciona o tipo de dados set. Podemos portanto concluir que os operadores esto a
funcionar conforme era a nossa inteno.
Desta forma terminamos esta incurso pelo mundo do overloading de
operadores em Free Pascal. Basicamente qualquer tipo de dados pode ser fornecido a
um operador, desde que seja feito o seu overload. Esta uma ferramenta que permite
tornar a linguagem muito mais expressiva, compacta e at intuitiva.

Por: Igor Nunes


Curioso na rea da tecnologia e em especial da programao, tem uma saudvel
relao com o Object Pascal e conhecedor das bases de outras linguagens de
programao, como Haskell, C, Python e VB.NET.
No P@P, membro da Wiki Team e Moderador Global.

Compilado por thoga31

87

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

45 Edio

Pascal array de argumentos


Por: Igor Nunes

Compilado por thoga31

88

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

A definio da linguagem Pascal estabelece desde os seus primrdios regras


bastante rgidas acerca da passagem de argumentos a uma funo ou procedimento.
No seu conjunto, uma das consequncias destas regras a impossibilidade de se
implementarem funes varidicas, isto , funes com um nmero indefinido de
argumentos s quais podemos fornecer virtualmente uma infinidade de argumentos, no
havendo a restrio de ser possvel passar apenas N argumentos em determinada
ordem e com determinados tipos.
Vrias linguagens permitem a implementao de funes varidicas, como por
exemplo C e at Haskell (com recurso a alguns truques que envolvem aspectos
avanados dos tipos de dados).
O facto de Pascal no permitir a implementao de funes varidicas pode
suscitar algumas dvidas. Mtodos standard que formam a base do Pascal so
aparentemente varidicos, como o writeln e o readln. No entanto, estes mtodos no
so exactamente funes ou procedimentos verdadeiros. A sua implementao no
definida em termos da linguagem Pascal (que, como foi dito, no permite este tipo de
definies); so apenas instrues que o compilador trata de forma especial para nos
permitir utiliz-las como se fossem varidicas.
Todavia, o rio no encontra a sua foz neste ponto. Apesar de esta funcionalidade
no ser permitida, existe uma forma de a simular. Compiladores e dialectos mais
recentes, como o Delphi e o Free Pascal, permitem a passagem de arrays de
argumentos, a qual tem por princpio a passagem de arrays abertos, conceito que ser
brevemente revisto.
Todo o cdigo presente neste artigo foi compilado recorrendo ao Free Pascal
Compiler, verso 2.6.2, em ambiente Windows, sendo totalmente portvel para outras
plataformas.

Passagem de open arrays


Quando o Pascal foi dado a conhecer ao mundo em 1971, os arrays eram
estticos, com uma dimenso bem definida, uma vez que na altura no fora ainda
introduzido o conceito de array dinmico.
Com o evoluir da linguagem, este conceito foi implementado, e pela primeira vez
os arrays no necessitavam de ter uma dimenso bem definida esta podia ser
controlada em runtime. Dadas as suas caractersticas, tornou-se uma forma de criar o
equivalente a pequenas listas ligadas com muito maior segurana uma vez que a gesto
de recursos no fica a cargo do programador. De referir que os arrays dinmicos tm
os seus dados contguos em memria semelhana de um array esttico e ao contrrio
das listas ligadas.

Compilado por thoga31

89

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Da mesma forma, deixou de ser obrigatria a criao de tipos de dados para a


passagem de arrays em argumentos. Se antes era necessrio um tipo para cada
dimenso, e um procedimento para cada tipo, o trabalho do programador fora
imensamente facilitado.
Por exemplo, pode-se receber um array de nmeros inteiros de pequena
dimenso e sem sinal da seguinte forma:
function Average(list : array of byte) : real;

Recorrendo s funes Low e High, e mais recentemente com a estrutura de


repetio for-in, torna-se simples iterar os elementos do array.
function Average(list : array of byte) : real
var i : byte;
begin
Average := 0.0;
for i:=Low(list) to High(list) do
Average := Average + list[i]);
Average := Average / Length(list);
end;

Outra caracterstica de elevado interesse o facto de se poder passar um


fragmento de um array. Vejamos o seguinte exemplo:
var xs : array[1..20] of byte;
// ...
writeln(Average(xs[10..20]):0:3);

Neste caso, apenas do nosso interesse calcular a mdia da segunda metade


do array xs. Portanto, definimo-lo com a sintaxe xs[10..20]. Desta forma, apenas os
elementos do ndice 10 ao 20 so passados funo Average.
Todas estas funcionalidades compe aquilo a que se denomina de passagem
de arrays abertos (em ingls, open arrays): a possibilidade de receber um array sem
conhecimento prvio da sua dimenso, bem como a possibilidade de realizar a
passagem parcial de um array.

Passagem de um array de argumentos


A evoluo da passagem de arrays abertos levou naturalmente implementao
de uma funcionalidade til: o array of const.
Classicamente, os elementos de um array so de um tipo bem definido. No
entanto, const uma palavra reservada que define constantes. Desta forma, um array
of const define algo diferente: um array cujos elementos so constantes de tipo
indefinido.

Compilado por thoga31

90

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Como primeira nota, h que referir que estruturas no podem ser passadas
nestes arrays. Apenas tipos de dados simples (tipos numricos, alfanumricos e
apontadores), objectos, classes e interfaces podem.
Vamos criar um procedimento que recebe um array of const e nos d
informaes acerca dos argumentos recebidos:
procedure Foo(args : array of const);

Quando o procedimento recebe este array, ocorre uma converso dos seus
elementos para um record variante com caractersticas particulares. Segundo a
documentao do Free Pascal, esta a sua definio:
Type
Ptrint = LongInt;
PVarRec = ^TVarRec;
TVarRec = record
case VType : Ptrint of
vtInteger
:
(VInteger: Longint);
vtBoolean
:
(VBoolean: Boolean);
vtChar
:
(VChar: Char);
vtWideChar
:
(VWideChar: WideChar);
vtExtended
:
(VExtended: PExtended);
vtString
:
(VString: PShortString);
vtPointer
:
(VPointer: Pointer);
vtPChar
:
(VPChar: PChar);
vtObject
:
(VObject: TObject);
vtClass
:
(VClass: TClass);
vtPWideChar :
(VPWideChar: PWideChar);
vtAnsiString :
(VAnsiString: Pointer);
vtCurrency
:
(VCurrency: PCurrency);
vtVariant
:
(VVariant: PVariant);
vtInterface :
(VInterface: Pointer);
vtWideString :
(VWideString: Pointer);
vtInt64
:
(VInt64: PInt64);
vtQWord
:
(VQWord: PQWord);
end;

Compilado por thoga31

91

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Portanto, cada elemento ser um record no qual o campo VType indica qual o
tipo de dados do argumento, e conforme o valor deste campo haver um segundo que
contm o valor do argumento. Por exemplo, se VType for vtString, ento o argumento
do tipo ShortString (comummente designado apenas como string), e o seu valor pode
ser acedido atravs do campo VString.
Uma vez que cada elemento pode ter um tamanho em bytes diferente dos
restantes, o armazenamento dos dados referentes aos argumentos no pode ser feito
da forma tradicional: um array clssico tem os seus elementos armazenados em reas
contguas de memria em que cada elemento ocupa exactamente N bytes. Neste caso,
cada argumento ter a sua localizao num bloco da memria distinto. Isto explica o
facto de haver um apontador (o tipo PVarRec).
Conclui-se que um array of const , em ltima instncia, um array of TVarRec
dentro do procedimento ou funo. Mas ateno, nunca se deve declarar o argumento
desta forma!
procedure Foo(args : array of TVarRec);

Para implementar esta funo, vamos primeiramente definir o seu objectivo:


Receber um array of const e devolver, no monitor, o tipo de dados de cada
argumento, assim como o seu valor ou nome.

Para tal, ser til controlar quantos argumentos foram passados. Esta
informao pode ser obtida com a funo Length. Caso no haja argumentos, iremos
mostrar a mensagem Sem argumentos.
procedure Foo(args : array of const);
var i : smallint;
begin
if Length(args) > 0 then
// anlise dos argumentos
else
writeln('Sem argumentos.');
end;

Para proceder anlise dos argumentos, iremos iterar pelos elementos do array.
Sendo este um open array, cuja dimenso desconhecemos, necessitaremos das
funes Low e High que devolvem, respectivamente, os ndices menor e maior do array
(isto , as posies do primeiro e ltimo elemento).
for i:=Low(args) to High(args) do
// anlise dos argumentos

No sendo objectivo do presente artigo fazer uma explorao exaustiva de todos


os tipos de dados possveis de serem passados no array of const, iremos tratar apenas
os mais comuns e alguns que possuem algumas particularidades.

Compilado por thoga31

92

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Como foi referido, os elementos do array so convertidos ao record infraapresentado, pelo que possuem campos. Naturalmente, uma estrutura de deciso
case-of ir analisar o valor do campo vtype, informando acerca do tipo de dados do
argumento. Caso no seja conhecido, mostrar-se- a mensagem desconhecido.
Comecemos por analisar um caso simples: o argumento um Integer. Desta forma,
vtype ir assumir o valor vtInteger (uma constante do tipo LongInt):
case args[i].vtype of
vtinteger :
writeln('Integer = ',
args[i].vinteger);
else
writeln('desconhecido!');
end

Sendo um Integer, o campo variante vinteger possui o valor deste argumento.


Notificamos acerca do tipo de dados do argumento, seguido do valor. O mesmo se
aplica aos tipos de dados Boolean e Char.
No entanto, alguns tipos de dados necessitam de uma forma diferente de aceder
ao valor. Alguns campos variantes so, na verdade, apontadores (o seu tipo de dados
comea por P, como por exemplo PShortString). Para alguns destes necessitamos de
recorrer ao operador ^, o qual nos indica o valor armazenado no ponteiro que lhe
fornecemos (apontador^). Para outros casos, necessitaremos de fazer type casting
uma vez que os seus tipos de dados permitem a recepo de um apontador, devolvendo
automaticamente o valor l armazenado (por exemplo, o tipo de dados AnsiString).
Comecemos por analisar o tipo de dados Extended:
vtextended :
writeln('Extended = ',
args[i].VExtended^);

Este um caso simples. No entanto, as strings tm algumas diferenas. No


Pascal moderno (leia-se Free Pascal, Object Pascal e Delphi), no existe apenas um
tipo de dados string. O tipo de dados string apareceu aps o aparecimento do Pascal, e
apenas podia armazenar 255 caracteres. Hoje em dia, existem vrios tipos de dados da
famlia da string, sendo os mais proeminentes os seguintes:
ShortString apenas permite 255 caracteres;
AnsiString null-terminated e no tem limite de caracteres;
WideString semelhante ao AnsiString, cada caracter ocupa 2 bytes ao invs
de apenas 1, e permite armazenar caracteres no formato UTF-16.
Desta forma, uma ShortString o equivalente a um array of char com 255
elementos. Naturalmente, o argumento ser um ponteiro para a localizao deste array.
Internamente, o compilador trata as strings de forma automtica, no sendo, portanto,
responsabilidade do programador determinar onde esta comea e termina (isto difere
do C, por exemplo). Bastar, portanto, aceder ao valor armazenado no bloco de
memria indicado pelo apontador, e ser-nos- devolvido o contedo da ShortString:

Compilado por thoga31

93

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

vtString :
writeln('ShortString = ',
args[i].VString^);

O tipo AnsiString necessita de ser tratado com um processo ligeiramente


diferente. Uma vez que o campo VAnsiString do tipo Pointer (ou seja, o tipo de dados
do valor armazenado no endereo desconhecido ou tem uma dimenso indefinida),
no basta recorrer ao operador ^. Como foi dito, o compilador trata deste processo
automaticamente, e sempre que necessrio fornece ao prprio programa ferramentas
para gerir estas strings de forma autnoma. Portanto, bastar neste caso realizar type
casting o programa ir aceder localizao na memria onde a AnsiString est
armazenada, e automaticamente vai process-la:
vtAnsiString :
writeln('AnsiString = ',
AnsiString(Args[i].VAnsiString));

Para apontadores, e tendo em conta a quantidade de endereos que


actualmente uma memria RAM possui, fazemos type casting para o tipo LongInt de
forma a conhecermos o endereo:
vtPointer :
writeln('Pointer = ',
Longint(Args[i].VPointer));

Para finalizar, iremos analisar as classes e os objectos. Estes no possuem um


valor visto no serem tipos de dados simples. Apesar disso, possvel passar um
objecto ou uma classe como argumento num array of const. Ambos os tipos de dados
TObject e TClass possuem uma propriedade denominada ClassName. Desta forma, o
nosso objectivo neste procedimento determinar o nome da classe ou objecto a que
pertence o nosso argumento:
vtObject :
writeln('Object = ',
Args[i].VObject.Classname);
vtClass :
writeln('Class reference = ',
Args[i].VClass.Classname);

O nosso procedimento Foo ter ento este aspecto:


procedure Foo(args : array of const);
var i : smallint;
begin
if Length(args) > 0 then
for i:=Low(args) to High(args) do
case args[i].vtype of
vtinteger :
writeln('Integer = ',
args[i].vinteger);
vtboolean :

Compilado por thoga31

94

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

writeln('Boolean = ',
args[i].vboolean);
vtchar :
writeln('Char = ',
args[i].vchar);
vtextended :
writeln('Extended = ',
args[i].VExtended^:0:10);
vtString :
writeln('ShortString = ',
args[i].VString^);
vtAnsiString :
writeln('AnsiString = ',
AnsiString(Args[i].VAnsiString));
vtPointer :
writeln('Pointer = ',
Longint(Args[i].VPointer));
vtObject :
writeln('Object = ',
Args[i].VObject.Classname);
vtClass :
writeln('Class = ',
Args[i].VClass.Classname);
else
writeln('desconhecido!');
end
else
writeln('Sem argumentos.');
writeln;
end;

Ensaio com os arrays de argumentos


Coloquemos o procedimento Foo numa unit denominada ArgMgr (de Argument
Manager). Para a testar, iremos importar a unit classes para podermos testar a
passagem de classes e objectos. Note-se que necessria a compiler directive {$mode
objfpc} para permitir o seu uso.
{$mode objfpc}
program artigo45;
uses ArgMgr, classes;
var s : string = 'variavel s';
sl : TStringList;
begin
s := TStringList.Create;
Foo([]);
Foo(['Igor Nunes', 31]);
Foo([@Foo, NIL, false, 'K']);
Foo([s, sl, 3.14]);
s.Free;
end.

Analisemos o output do programa:

Compilado por thoga31

95

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Sem argumentos.
AnsiString = Igor Nunes
Integer = 31
Pointer = 471399
Pointer = 0
Boolean = FALSE
Char = K
ShortString = variavel s
Class = TStringList
Extended = 3.1400000000

Repare-se que a varivel s, do tipo string, foi considerada uma ShortString. Isto
acontece uma vez que, por defeito, o Free Pascal considera que o tipo de dados string
se refere ao tipo ShortString.
Por outro lado, uma string escrita directamente no argumento considerada uma
AnsiString. Mais uma vez, o Free Pascal assume que uma string escrita directamente
como argumento deste tipo, uma vez que, segundo as actuais regras, estas strings
no tm um tamanho definido e s podem assumir caracteres ASCII por defeito.
Como ltima referncia, de notar o output para a varivel sl: o procedimento
informou-nos que esta uma classe, mais propriamente a classe TStringList.

printf criao de bindings para a linguagem C


O Free Pascal oferece uma utilidade que outros compiladores, incluindo o prprio
Delphi, no oferecem do mesmo modo. Com os arrays of const, possvel criar
facilmente um binding com a funo printf da linguagem C. O seguinte cdigo indica
como se deve declarar a funo do standard da linguagem C, exemplificando de seguida
o seu uso:
{$mode objfpc}
program artigo45;
// Declarao standard da funo
procedure printf(fmt : pchar;
args : array of const);
cdecl; external 'c';
begin
printf('%s igual a %d.',
['Dobro de 3', 6]);
end.

Compilado por thoga31

96

Revista PROGRAMAR

Compilao de artigos relacionados com


a linguagem de programao Object Pascal

Conclumos assim a nossa viagem pelos arrays de argumentos, comummente


designados apenas por arrays of const, os quais nos permitem simular procedimentos
e funes varidicos. No , decerto, uma das ferramentas mais utilizadas pelos
programadores de Free Pascal, Object Pascal e Delphi. Todavia, um instrumento til
que est disposio, o qual fornece imensa flexibilidade na passagem de argumentos
a procedimentos e funes.

Por: Igor Nunes


Curioso na rea da tecnologia e em especial da programao, tem uma saudvel
relao com o Object Pascal e conhecedor das bases de outras linguagens de
programao, como Haskell, C, Python e VB.NET.
No P@P, membro da Wiki Team e Moderador Global.

Compilado por thoga31

97

You might also like