int21h

Méně známé konstrukce v Turbo Pascalu - 1. díl

Variant record
Open parameters
Vnořování parametrů
Break a Continue
Procedurální proměnné

V tomto textu se podíváme na některé méně známé konstrukce jazyka pascal. Pro mnohé z vás to jistě budou triviální věci,
ale čtou nás i začínající programátoři, kterým může být tento text užitečný.
V první části se budeme věnovat pouze možnostem Turbo pascalu od firmy Borland. Někdy příště si
povíme o rozšířeních, které se objevily v Delphi a ve Freepascalu.

Variant record

Variant record je užitečné rozšíření typu record.
Představte si, že máte databázi zvířat v ZOO. Máte tam
ryby, ptáky a savce
O každém musíte udržovat relevantní informace. Například je důležité, zda druh ryby potřebuje slanou nebo sladkou vodu.
Pro ptáky je tato informace ovšem nesmyslná. Pro ty je zase naopak podstatná třeba doba hnízdění.
Neznalý programátor v pascalu udělá ohromný typ RECORD, kde podchytí všechny alternativy aniž by ho zajímalo, jaké je to zvíře.
I o rybách bude udržovat informaci o hnízdění - byť ji jistě nepoužije a nejspíš tam dosadí hodnotu nula.
Ale takhle nám zbytečně naroste velikost záznamu (ve smyslu bude mít zbytečně hodně bajtů) a pokud budeme mít zvířat hodně, můžeme se
dostat do problémů.
Znalý programátor tady použije VARIANT RECORD.
Zdrojový kód tedy bude vypadat takto:

const vodoretezec:array[0..2] of string=('sladka','slana','smisena');


type trida_zvirete = (ryba, ptak, savec);
     druh_biotopu = (travnaty, skalnaty, prales, poust);
     druh_vody = (sladka, slana, smisena);




Zvire = record
{Napred uvedeme obecne polozky, ktere budou u kazdeho zvirete}
cesky_nazev:string;
latinsky_nazev:string;


{A ted specificke informace. Pomoci prikazu CASE odlisime jednotlive variany.}
case Trida:trida_zvirete of
     Ryba:(voda:druh_vody; zivorodost:boolean);
     Ptak:(sirka_voliery, vyska_voliery:integer; zacatek_hnizdeni, konec_hnizdeni:string);
     Savec:(potrava:string; biotop:druh_biotopu);
     end;
{Tady je trochu zrada. Pouziva se tu jenom jedno END. To zaroven ukoncuje blok CASE i RECORD.
Proto neni mozne dat za specifickou informaci jeste nejakou obecnou.}  

A je to. Krásně jsme podchytili problematiku zoologických zahrad :-)
Vždycky je dobré, když logika programu kopíruje realitu, takový program je snazší na porozumění a není tak zmatečný jako řešení "hrubou silou".

A teď pozor! Neexistuje žádný mechanizus, který by určil, zda se zvířetem chceme pracovat jako s ptákem, rybou či savcem.
Proto je možný následující kód:

var drobecek:zvire;
begin
with drobecek do
   begin
   cesky_nazev:='Datel cerny';     {Pro neznale - to je ptak :-)  }
   latinsky_nazev:='Dryocopus martius';
   sirka_voliery:=10;              {To by mu mohlo stacit}
   end;


writeln(drobecek.sirka_voliery);   {samozrejme napise 10}


{A co je tohle?}
drobecek.voda:=slana;  {??? - ano, prekladac nevi, ze jsme se rozhodli, ze Drobecek je ptak}


writeln(vodoretezec[ord(drobecek.voda)]); {napise "slana"}
writeln(drobecek.sirka_voliery);          {napise 1. To znamena, ze prirazenim polozky VODA jsme zmenili}
                                          {polozku SIRKA_VOLIERY}  
Jednotlivé varianty totiž sdílí stejný kus paměti. A to je dobře, protože to přináší další možnosti využití.
Představme si, že chceme definovat typ OBDÉLNÍK.
Co to udělat takhle?

type Bod = record
     x,y:integer;
     end;


     Obdelnik = record
     case typ_souradnic:integer of
     0:(a,b:Bod);
     1:(ax,ay,bx,by:integer);
     end;  

S takto definovaným obdélníkem můžeme pracovat jak přes typ BOD, tak i přímo. Jak je zrovna libo.
Nebo jiný příklad:
registry = record
case i : integer of
     0 : (ax,f1,bx,f2,cx,f3,dx,f4,bp,f5,si,f51,di,f6,ds,f7,es,f8,flags,fs,gs : word);
     1 : (al,ah,f9,f10,bl,bh,f11,f12,cl,ch,f13,f14,dl,dh : byte);
     2 : (eax, ebx, ecx, edx, ebp, esi, edi : longint);
    End;  
Simulace registrů procesoru.

(Registry Fx samozřejmě ve skutečnosti neexistují. Jde o to, že pro účely deklarace musím nějak pojmenovat horní části 32bitových registrů, které nemají samostatná jména)

Open parameters

Pascal narozdíl od céčka neumožňuje tvorbu funkcí s proměnlivým počtem parametrů. Jedinou výjimkou jsou jak víte procedury a funkce

write, writeln, read, readln, inc, dec, halt

Poslední tři navíc zvládají pouze jeden nebo žádný parametr.
Open parameters (otevřené parametry) jsou jedním ze způsobů, jak toto omezení částečně překonat.
Podívejte se na definici procedury:
Procedure Menu(var polozky:array of string);  
Jak vidíte, tak se v definici neříká nic o rozsahu pole. Jak ale procedura pozná horní a dolní hranici pole?
Můžeme jí to jistě předat jako další parametry hlavičky. Ale proč? Pascal má mechanizmus, jak je detekovat.
Procedura může vypadat například takto:
Procedure Menu(var polozky:array of string);
var i:longint;
begin
for i:=Low(polozky) to High(polozky) do
    writeln(polozky[i]);
end;  
V programu ji použijeme takto:
var jmena_veznu:array[1..MAX_VEZNU] of string;
begin
...
Menu(jmena_veznu);
...
end;  

Vnořování parametrů

Další způsob, jak obejít pevný počet parametrů je technika vnořování parametrů.
Jestli jste někdy programovali pomocí Turbo vision, tak dobře víte, o co jde.
Využívá se toho, že argumentem procedury či funkce nemusí být jen obyčejná proměnná nebo natvrdo dané číslo, ale může to být
i název funkce.
Např.
Procedure Preved_na_zlomek(r:real;var citatel,jmenovatel:longint);  
můžeme volat takto:
Procedure Preved_na_zlomek(sin(60),c,j);  
A tento koncept můžeme rozvést takto:
Function Menu(polozky:string):integer;
begin
{Zda se to bizarni, ale v zakladni podobe teto techniky se nemusime vubec
starat o vykresleni menicka! Procedura Menu se stara pouze o klavesnici a
komunikaci s uzivatelem.
To nas momentalne, nezajima, tak se tomu nebudeme venovat...}
...
end;


Function NapisPolozku(s:string;dalsi:string):string;
begin
writeln(s);
end;  
Definice není zajímavá. Zajímavé je použití.
Menu(NapisPolozku('pampeliska',
     NapisPolozku('kopretina',
     NapisPolozku('rosnatka',
     NapisPolozku('konopi',''
                 )))));  
Efektní ne?
Má to ale jednu vadu. Položky se vypíší v obráceném pořadí.
Ovšem, asi takhle: "No a co?"

Nicméně, jestli je to na závadu, tak je asi sto a jeden způsob, jak zajistit výpis ve správném pořadí.
Určitě na nějaký příjdete.

Break a Continue

Často mě překvapuje, jak vzácně se s těmito příkazy (hlavně s Brake) setkávám v cizích
programech.
Break slouží k okamžitému opuštění jakéhokoliv cyklu.
Continue slouží k okamžitému skoku na podmínku cyklu.
V mých programech často používám konstrukce typu:
Function NajdiMezeru(s:string):byte;
var a:byte;
for a:=Length(s) downto 0 do
    if s[a]=' ' then Break;
NajdiMezeru:=a;
end;  
Hm... to možná nebyl nejpůsobivější příklad, ale je vidět, o co jde.
Dokonce používám i toto:
repeat
...
if Neco_se_stalo then Break;
...
until 1=2;  {Nesplnitelna podminka}  
Mám dojem, že Continue jsem zatím použil jenom několikrát, ale určitě je dobře vědět i o něm.

Procedurální proměnné

Je možné, že i poměrně zkušený programátor viděl některý z výše uvedených triků prvně. Má prostě jiný
styl a řeší takové situace jinak.
S procedurálními proměnnými se to má ale jinak. Na ně narazí každý, a pokud jste na ně zatím nenarazil, jste začátečník.
Ledaže byste byl ultraortodoxní vyznavač objektového programování. To je druhá možnost.

Řekněme, že píšete program, který umí pracovat v mnoha videorežimech s různými barevnými hloubkami a potřebujete rutinu, která nakreslí bod.
Je jasné, že procedura nakreslení bodu bude jiná pro 16 barevné režimy, jiná 256 barevné a jiná pro TrueColor.
Napíšete tedy tři různé procedury pro každou barevnou hloubku:
Procedure Pixel16(x,y:integer;barva:longint);
Procedure Pixel256(x,y:integer;barva:longint);
Procedure PixelTrueColor(x,y:integer;barva:longint);  

Vnitřky procedur nás nezajímají. Všimněte si ale, že chtějí stejné parametry.
To je základní předpoklad pro procedurální proměnné.
Podívejme se delší kus kódu:
Procedure Pixel16(x,y:integer;barva:longint);
Procedure Pixel256(x,y:integer;barva:longint);
Procedure PixelTrueColor(x,y:integer;barva:longint);


type Pixel=procedure(x,y:integer;barva:longint);
{stejne tak dobre by mohlo byt i
pixel=procedure(a,b:integer;c:longint);
Jmena parametru naprosto nehraji roli. Podstatny je jejich typ.}  

V programu (asi někde poblíž funkce na změnu videorežimu) pak bude něco jako:
case bitu_na_pixel of byte
   4:pixel:=Pixel16;
   8:pixel:=Pixel256;
  32:pixel:=PixelTrueColor;
end;  

Jsou tu dvě odlišnosti mezi Turbo pascalem a Freepascalem.
  1. V Turbo pascalu musí být všechny procedury, které mohou být přiřazeny k proměnné, kompilovány s direktivou {$F+}. Ve Freepascalu nemusí. Tam tahle direktiva nedělá nic a nemusíme se o to starat.
  2. V Turbo pascalu je příkaz přiřazení ten, jak jsem použil v příkladu, tedy: Pixel:=Pixel16; Ve FreePascalu záleží na módu překladače. Pokud máte zaškrtnutou možnost "Try to be TP compatible", tak se chová stejně. Pokud ne, tak se to zapisuje takhle: Pixel:=@Pixel16; (což je podle mě logičtější)
Procedurální proměnné jsou neskutečně mocný nástroj. Dá se sice do jisté míry nahradit technikami
objektového programování, ale na rozdíl od OOP jsou srozumitelné a jednoduché.

Dotaz:
Dá se z hlavního programu volat procedura v unitě?
-Jistě, od toho unity přece jsou.

A dá se z unity volat procedura v hlavním programu?
-Ano, právě pomocí okliky přes procedurální proměnné.

Já jsem měl například kdysi unitku pro práci s fonty. Byla tam procedura Nacti_Retezec.
Na obrazovce se prostě objevila řádka, kam se zadával text. Na některé klávesy byly pověšeny procedurální proměnné.
if klavesa=Tab then ZpracujTabulator;  
ZpracujTabulator byla procedurální proměnná, která normálně ukazovala na prázdnou proceduru, takže po zmáčknutí Tab se
nic nedělo.
Když jsem ale psal textovku, tak jsem v hlavním programu přiřadil
ZpracujTabulator:=UkazInventar;
a do unitky jsem nemusel ani sáhnout.
2006-11-30 | Laaca
Reklamy: