Jak si usnadnit práci

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í.

Procedury

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ů:

  1. Parametry volané hodnotou
  2. Parametry volané odkazem
  3. Parametry konstantní
  4. Parametry bez udaného typu

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

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.

Unity

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ů.