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
cesky_nazev:string;
latinsky_nazev:string;
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;
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';
latinsky_nazev:='Dryocopus martius';
sirka_voliery:=10;
end;
writeln(drobecek.sirka_voliery);
drobecek.voda:=slana;
writeln(vodoretezec[ord(drobecek.voda)]);
writeln(drobecek.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
...
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;
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);
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.
- 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.
- 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.