Pokud bychom nerozčlenili příklad z minulé lekce pomocí procedur, nevyznali bychom se v něm. Dnes se tedy podíváme mimo jiné i na to, jak zvýšit přehlednost a srozumitelnost programu.
Nejprve několik přehledných zásad
Zopakujte si rozdíl mezi procedurou a funkcí.
Procedura se definuje takto:
procedure NázevProcedury(Parametry); Definice a deklarace; begin Příkazy; end;
Definice a deklarace jsou stejné jako u programu, s jedinou výjimkou - nesmíme použít uses. (A neměli bychom definovat ani další procedury a funkce - sice to jde, ale je docela umění se v tom pak vyznat.) Potřebuje-li určitý datový typ pouze určitá procedura, je lepší nadefinovat ho přímo v ní. Proměnné, které se vyskytují pouze v těle (mezi beginem a endem) procedury, se takto definují téměř vždy. Tedy např. takto:
procedure Něco; type TohleJindeNenajdete = ^Seznam; {Seznam jsme definovali už v samotném programu} var Nic : TohleJindeNenajdete; Pomocna : string; I, J : integer; begin ... end;
Je ovšem zbytečné takto definovat proměnnou, která se v proceduře
nevyskytuje.
Proměnné nadefinované v dané proceduře jsou přístupné pouze v ní samotné.
V proceduře lze nadefinovat dokonce i proměnnou se stejným názvem jako má jiná
proměnná někde v hlavním programu. V proceduře pak pracujeme s proměnnou
procedury a mimo ní s proměnnou programu. Chceme-li přesto použít proměnnou programu
i v těle procedury, kde již máme jinou stejnojmennou proměnnou, můžeme to udělat
takto: NázevProgramu.NázevProměnné;
program StejnyNazev; var A:Integer; procedure Tudle; var A:Integer; {Pochopitelně může mít takováto proměnná i jiný typ} begin A:=100; writeln(StejnyNazev.A); writeln(A); end; begin A:=15; Tudle; writeln(A); readln; end.
Podívejme se blíže na parametry procedur. Pascal rozlišuje čtyři typy parametrů:
Parametry volané hodnotou se definují takto Název:Typ Typ
musí být již předem znám. (Místo Pole : array [1..2] of Integer musíme
tedy psát Pole : TPole, a předem mít nadefinováno TPole = array[1..2] of
Integer;)
Voláme-li daný parametr hodnotou, zajímá nás pouze hodnota a je nám jedno, jak
je zadána.
Máme-li tedy procedure Naskoc(A:integer); je jedno, použijeme-li Naskoc(2), nebo třeba Naskoc(D), kde D je libovolná proměnná typu Integer. Protože nás zajímá jen hodnota, je jasné, že změníme-li obsah parametru uvnitř procedury, navenek se to neprojeví.
Parametry volané odkazem se definují takto var Název : Typ Pochopitelně, že typ je znám dopředu. Na místo parametrů volaných odkazem lze dosadit pouze proměnnou. Navíc změna hodnoty uvnitř procedury bude patrná i vně, v samotném programu.
Parametry konstantní se definují takto const Název : Typ Překladač se k nim chová jako ke skutečným konstantám. (Místo nich můžeme použít obou předchozích typů. Pokud nikde nebudeme měnit hodnotu parametru, je výsledek stejný. Jsou-li ale použity konstantní parametry, je výsledná procedura o něco málo rychlejší.)
Parametry bez udaného typu lze volat pouze odkazem či jako konstantní. My se jim zatím vyhneme. Více o nich až v lekci o typových změnách a kouzlech s proměnnými.
Můžeme mít tedy třeba takovouto proceduru:
Ehm(a,b:integer;c:byte;var d,e:real;const f,g:char);
V samotném těle programu pak lze napsat například toto:
Ehm(1,5,6,nic,vic,'a','3'); Pokud jsme ovšem definovali proměnné nic a
vic jako reálné (na místo parametrů volaných odkazem lze dosadit jen proměnnou
!!!)
Existuje několik typů proměnných, které jdou používat pouze jako
parametry - jedná se o OpenString (Řetězec bez udané délky) a array (pole
bez udané délky). Použití je takovéto:
procedure NevimCo(Retezec : OpenString, Pole : array of NejakyTyp);
U takovýchto parametrů si musíme pomocí funkcí Length, High, Low... zjistit jejich skutečnou
délku...
Lze definovat i procedury, které využívají jiných procedura a funkcí. Můžeme dokonce definovat procedury, které využívají sami sebe. Jenom si musíme dát pozor, že tento cyklus (tzv. rekurze) někdy skončí.
Problém ovšem nastává, máme-li dvě procedury, z nichž první využívá
druhou a obráceně. V Pascalu se totiž smí používat jen to, co jsme již předem
nadefinovali. Jak z toho ven? Sdělíme o existenci dané procedury pomocí
procedure NázevA(parametry);forward; Poté dodefinujeme proceduru
NázevB (ve které používáme NázevA). A nakonec uvedeme samotné tělo procedury
s názvem NázevA (procedure NázevA; {už nemusíme vypisovat parametry...})
Jednoduchý příklad:
procedure NicNedela(A:integer);forward; procedure NecoDela(A:integer); begin If A > 0 then NicNedela(A-1); writeln(A); end; procedure NicNedela; begin If A < 0 then NecoDela(A); writeln(A); end;
Doufám, že teď je to již jasné.
Funkce se definují takto function Název(Parametry):VýslednýTyp; ... Výsledný typ nesmí být vytvořen uživatelem, lze použít pouze standardní pascalské typy. Následují deklarace a definice a nakonec samotné tělo.
O funkcích platí vše, co bylo řečeno o procedurách (typy parametrů ... direktiva forward). V těle samotné funkce se musí použít přiřazovací příkaz NázevFunkce:=NějakáHodnota; Poslední přiřazení dá výsledek funkce (Je ovšem krajně neetické použít více jak jedno takové přiřazení). Před i po mohou probíhat další příkazy, ty by měly přímo souviset s danou funkcí. Platí však, že funkce by měla skončit, jakmile známe výsledek. Velice často se používá rekurze.
function Factorial(N:byte):longint; begin if N >0 then Factorial:= N * Factorial(N-1) else Factorial:=1; end;
(Skutečně se při každém průběhu funkce přiřazuje jen jednou. Je-li N>1 používá se rekurze.)
Napište program, který vypočte s využitím známých vzorců pomocí rekurze kombinační číslo ( (n nad k) = (n-1 nad k) + (n-1 nad k-1)...). Příslušné vzorce si vyhledejte (programátor si musí umět sám najít potřebné informace) a nezapomeňte, že rekurze musí někdy skončit.
Některé funkce a procedury jsou natolik obecné, že se vyskytují ve více programech. Aby nás jejich neustále vypisování neobtěžovalo, je lepší umístit je do zvláštního souboru. (Tento způsob se také využívá tehdy, jestliže máme funkcí moc a výsledný program by byl velice dlouhý a nepřehledný). Takovéto procedury a funkce se umisťují do tzv. jednotek (units), které vypadají takto:
unit Název; interface Definice a Deklarace;
implementation Definice a Deklarace; begin Inicializační Část; end.
Unit musíme uložit do stejnojmenného souboru (Název.pas),
jinak nepůjde využít.
V části interface definujeme to, co by mělo být zjevné i mimo naší unitu
(procedury, funkce, konstanty, typy...). U procedur a funkcí uvádíme pouze
hlavičky (úplné)
V části implementation pak definujeme to, co potřebujeme pouze pro potřeby
dané unity (pomocné funkce, typy, konstanty, proměnné,...) Dále zde pak
dodefinujeme těla funkcí z interface(zde stačí psát zkrácenou hlavičku - bez
parametrů a výsledného typu). Je ovšem lepší psát hlavičku úplnou (implementation je pak přehlednější),
musíme ale dát pozor, abychom uvedli hlavičky přesně ve stejném tvaru jako v sekci interface.
Do Inicializační části napíšeme příkazy, které se provedou, když
jednotku načítáme do paměti (máme-li jednotku pro práci s grafikou, bylo
by dobré smazat obrazovku a nastavit grafický režim....). Neobsahuje-li náš
program žádnou inicializační část, můžeme begin vynechat (ale end.
zůstává)
Příklad - jednotka pro práci s komplexními čísly (pro ilustraci zkrácené hlavičky)
unit CPLX; {Obsahuje příkazy pro práci s komplexními čísly} interface type Komplex = record R:Real; I:Real; end; procedure Secti(A,B:Komplex;var Vysledek:Komplex); {A+B} procedure Odecti(A,B:Komplex;var Vysledek:Komplex); {A-B} procedure Znasob(A,B:Komplex;var Vysledek:Komplex); {A*B} procedure Del(A,B:Komplex;var Vysledek:Komplex); {A/B} implementation procedure Secti; begin Vysledek.R:=A.R+B.R; Vysledek.I:=A.I+B.I; end; procedure Odecti; begin Vysledek.R:=A.R-B.R; Vysledek.I:=A.I-B.I; end; procedure Znasob; begin Vysledek.R:=A.R*B.R-A.I*B.I; Vysledek.I:=A.R*B.I+A.I*B.R; end; procedure Del; var C:Real; begin Vysledek.R:=B.R; Vysledek.I:=-B.I; Vysledek:=Znasob(A,Vysledek); C:=Sqr(B.R)+Sqr(B.I); Vysledek.R:=Vysledek.R/C; Vysledek.I:=Vysledek.I/C; end; end.
Napíšeme-li nyní ve svém programu uses CPLX; (a je-li CPLX.pas uložen ve stejném adresáři), můžeme využívat nových příkazů Secti, Odecti, Znasob a Del; Rovněž můžeme použít typ Komplex (ten ovšem nejde vypsat pomocí Writeln...)
Jednotky mohou obsahovat v sekci uses další jednotky. Pokud by dvě jednotky odkazovaly na sebe navzájem, přesuneme v jedné z nich sekci uses až do oddílu implementation.
To pro dnešek stačí. Zkuste si napsat vlastní jednotku (libovolnou) a využít ji v některém ze svých programů. Pro ty otrlejší - napište jednotku pro práci s dynamickým seznamem pracovníků.