5. Procedury a funkce Procedura je samostatně odladěný algoritmus, v programu může být volána vícekrát. Dvojí terminologie - rozlišujeme procedury a funkce nebo procedury obecné a procedury funkční.
5.1 Hierarchická struktura programu Každý složitější mechanizmus je třeba řídit hierarchickým způsobem. Hierarchické řízení spočívá v zavedení systému vrstev tak, že vyšší vrstvy pracují s obecnějšími informacemi zatímco nižší vrsrvy se zabývají detaily. Hierarchická struktura programu úzce souvisi s postupným návrhem programu metodou shora dolu. Tato metoda spočívá v opakovaném rozkladu složitějších problémů na dílčí problémy při současném rozkladu abstraktních příkazů (řešících složitější problémy) na příkazy méně abstraktní (řešící dílčí problémy). Abstraktní příkazy vytváříme jako volání procedur a funkcí. Př. Sestavte program, který přečte vstupní text a zjistí maximální počet znaků na řádku.
Základem je algoritmus pronalezení největšího čísla: begin maxpocet:=0; while not eof do begin "vypočti délku řádku - početzn" if pocetzn > maxpocet then maxpocet:=pocetzn; end; writeln(maxpocet); end. Přidáme řešení dílčího problému: begin pocetzn:=0; while not eoln do begin read(zn); pocetzn := pocetzn + 1; end; readln; end;
Použití abstraktního příkazu oddělíme od popisu jeho provedení ve výsledném programu tak, že zavedeme proceduru delkaradku. program radky; var zn : char; pocetzn, maxpocet : integer; procedure delkaradku; begin pocetzn:=0; while not eoln do begin read(zn; pocetzn := pocetzn + 1; end; readln; end; {delka radku} begin maxpocet:=0; while not eof do begin delkaradku; if pocetzn > maxpocet then maxpocet:=pocetzn; end; writeln(maxpocet); end.
Dalším přínosem použití procedur je zkrácení zápisu programu - činnost, která se opakuje vícekrát je zapsána jen jednou a zvýšení čitelnosti programu při použití vhodných jmen procedur.
5.2 Deklarace a volání procedur Deklarace procedur se uvádějí v deklarační části bloku za úsekem deklarací proměnných (v TP kdekoliv v deklarační časti). Deklarace procedury se skládá z hlavičky ukončené středníkem; hlavička může obsahovat seznam vstupních a výstupních parametrů, a těla procedury, kterému mohou předcházet lokální deklarace. Tělo je složený příkaz, ukončený středníkem. procedure soucet(a,b:integer;var c:integer); var s:integer; begin s:=a+b; c:=s; end; Na rozdíl od globálních objeků, které jsou zavedeny v deklarační části bloku programu a existují po celou dobu provádění programu, lokální objekty procedury existují pouze po dobu provádění této procedury. Je-li procedura vyvolána několikrát, její lokální proměnné vznikají pokaždé znovu a na začátku každého vyvolání mají nedefinované hodnoty. Př. Program pro výpočet hodnoty x^n, n N
program testmocniny; var zakl, vysl : real; exp : integer; procedure mocnina; var x :real; n : integer; begin x:=zakl; n:=exp; vysl:=1; while n > 0 do begin while not odd(n) do begin n:=n div 2; x:=sqr(x); end; n:=n-1; vysl:=vysl * x; end; end; {mocnina} begin {testmocniny} read(zakl,exp); mocnina; writeln(zakl,’**’,exp:4,’=’,vysl); end.
Identifikátor lokální proměnné se může zhodovat s identifikátorem již dříve deklarované globální proměnné - v tom případě lokální proměnná zastíní globální proměnnou. Komunikace procedury s okolím prostřednictvím nelokálních (globálních) proměnných neni vhodná a používá se jen výmečně. Procedura může být volána z hlavního programu nebo z jiné procedury. Komunikace s procedurou se provádí pomocí vstupních a výstupních parametrů.
5.3 Parametry volané hodnotou a odkazem Parametry procedury se definují v hlavičce seznamem specifikací formálních parametrů. Jednotlivé specifikace se oddělují středníkem, každou specifikací se zavádí jeden nebo několik parametrů určitého typu. Rozlišujeme parametry volané hodnotou a odkazem. Př.: procedure proc(fx,fy : integer; var fz : real); fx,fy - formální parametry volané hodnotou fz - formální parametr volaný odkazem Pro vyvolání procedury s parametry slouží příkaz procedury, v němž je za identifikátorem procedury uveden seznam skutečných parametrů: proc(x,y,z);
5.3 Parametry volané hodnotou a odkazem Jednotlivé skutečné parametry se v tomto seznamu oddělují čárkou a jejich počet musí souhlasit s formálními parametry. Formální parametr volaný hodnotou představuje v těle procedury lokální proměnnou, které je na začátkuprocedury přiřazena hodnota skutečného parametru. Formální parametr volaný odkazem představuje v těle procedury vždy tu konkrétní proměnnou, která je určena skutečným parametrem a jejíž adresa umístění v paměti se předá na začátku procedury. Parametrem volaným hodnotou předáváme proceduře vstupní hodnoty, parametrem volaným odkazem se z procedury předávají výstupní hodnoty (je to parametr vstupně - výstupní).
program testmocniny; var zakl, vysl : real; exp : integer; procedure mocnina(x:real; n:integer; var v:real); begin v:=1; while n > 0 do begin while not odd(n) do begin n:=n div 2; x:=sqr(x); end; n:=n-1; v:=v * x; end; end; {mocnina}{intpower - math.pas} begin {testmocniny} read(zakl,exp); mocnina(zakl,exp,vysl); writeln(zakl,’**’,exp:4,’=’,vysl); end.
Přípustným skutečným parametrem pro parametr volaný hodnotou je libovolný výraz, jehož hodnota je kompatibilní vzhledem k přiřazení s typem formálního parametru. Přípustným skutečným parametrem pro parametr volaný odkazem je pouze proměnná, jejíž typ je totožný s typem formálního parametru. Př.: mocnina(1+1,1+1,v); procedure proc(x : integer; var y,t : real; z : boolean);... var se vztahuje pouze k jedné deklaraci... x,z - volané hodnotou, y,t - volané odkazem Typ formálního parametru musí být buď standardní nebo pojmenovaný - v hlavíčce procedury se nesmí vyskytnout nepojmenovaný typ... např. array[1..10] of real. Řídící proměnná cyklu v proceduře musí být lokální (neplatí v TP ale je to vhodné).
Př. Program pro zjištění, zda jsou dvě čtvercové matice záměnné, tj. platí-li a*b=b*a program zamene_matice; type matice=array[1..10,1..10] of real; var i,j,k,m : integer; a,b,c,d : matice; zamene : boolean; procedure cti_mat(m : integer; var a : matice); var i,j : integer; begin for i := 1 to m do for j := 1 to m do read(a[i,j]); end;{cti_mat}
procedure tisk_mat(m : integer; a : pole); var i,j : integer; begin for i := 1 to m do begin for j := 1 to m do write(a[i,j]); writeln; end; end; {tisk_mat} procedure nas_mat(m:integer; a,b : matice; var c:matice); var i,j,k : integer; s : real; begin for i := 1 to m do for j := 1 to m do begin s := 0; for k := 1 to m do s := s + a[i,k] * b[k,j]; c[i,j] := s; end; end; {nas_mat}
begin zamene := true; read(m); cti_mat(m,a); cti_mat(m,b); nas_mat(m,a,b,c); nas_mat(m,a,b,d); for i := 1 to m do for j := 1 to m do if c[i,j]<>d[i,j] then zamene := false; tisk_mat(m,a); tisk_mat(m,b); tisk_mat(m,c); tisk_mat(m,d); if zamene then writeln('matice jsou zamene'); end.
5.4 Deklarace a volání funkcí Funkce je algoritmus, jehož provedením se vypočte hodnota určitého typu. Deklaruje se podobně jako procedura. Deklarace se skládá z hlavičky funkce, zakončené středníkem a těla funkce zakončeného středníkem: function identifikátor(seznam_formálních_parametrů):typ_funkční_hodnoty; lokalní_deklarace begin příkazy end;
- typ funkce nesmí být strukturovaný typ - specifikace parametrů mají stejný tvar a význam jako v případě procedur - v těle funkce se musí vyskytovat přiřazovací příkaz, kterým se přiřazuje hodnota identifikátoru funkce a jehož provedením se definuje výsledná funkční hodnota - volání funkce - předepisuje se výrazem, který nazýváme zápisem funkce a který má tvar: identifikátor(seznam_skutečných_parametrů); - přípustnost skutečných parametrů, jejich substituce za formální parametry a způsob provedení těla funkce při jejím volání je stejný jako u procedur Rozdíl mezi procedurou a funkcí - vyvolání procedury je příkaz, zápis funkce je výraz označující hodnotu, která je výsledkem provedení těla funkce. Zápis funkce tedy musí být použit vždy v nějaké konstrukci, která předepisuje, co se s výslednou funkční hodnotou provede.
Př.: function uvnitr_kruhu(x,y,xs,ys,r:real):boolean; begin if sqr(x-xs)+sqr(y-ys)<=sqr(r) then uvnitr:=true else uvnitr:=false; end; begin... if uvnitr_kruhu(x[i],y[i],sx,sy,r) then function faktorial(n:integer):longint; var i : integer; v : longint; begin v:=1; for i:=2 to n do v:=v*i faktorial:=v; end;... writeln(faktorial(5));
Př.: Skalární součin type vektor = array[1..100] of real; function skalarni_soucin(n:integer;a,b:vektor):real; var i:integer; s:real; begin s:=0; for i:=1 to n do s:=s+a[i]*b[i]; skalarni_soucin:=s; end;
5.5 Rekurzivní procedury a funkce Pascal povoluje volat procedury a funkce rekurzivně, tzn. že nové volání procedury nebo funkce nastane ještě dříve, než je předchozí volání ukončeno. Rozlišujeme dva druhy rekurze = přímá a nepřímá. Přímá rekurze = procedura volá sama sebe. Nepřímá rekurze = procedura p1 volá proceduru p2, procedura p2 volá proceduru p3,..., procedura pn volá p1. Speciální případem nepřímé rekurze je rekurze vzájemná = procedura p1 volá proceduru p2 a procedura p2 volá proceduru p1. Pomocí rekurzivních procedur lze řešit problémy, při jejichž rozkladu na podproblémy vznikne dílčí problém, který je podobný původnímu problému, ale v jistém smyslu je jednodušší.
Př.: Rekurzivní výpočet faktoriálu (max n je 16) function faktorial(n:integer):longint; begin if n=0 then faktorial:=1 else faktorial:=n*faktorial(n-1); end; Př.:Výpočet n-tého členu Fibonacciho posloupnosti function fib(n:integer):integer; begin if n=0 then then fib:=0 else if n=1 then fib:=1 else fib:=fib(n-1) + fib(n-2); end;
Každá rekurzivní funkce musí obsahovat podmínky, které vedou k ukončení rekurze. Rekurzivní výpočty jsou neefektivní - velké nároky na paměť pro lokální proměnné, ale vedou k jednoduchému zápisu složitějších problémů. Výpočet fibonacciho posloupnosti vede pro n=5 k 15 volání funkce. Pokud je parametrem rekurzivní procedury pole, je vhodné předávat je odkazem.
Př.: Fibonacciho posloupnost nerekurzivně function fib(n:integer):integer; var i,x,y,z:integer; begin if n=0 then fib:=0 else begin i:=1; x:=1; y:=0; while i<n do begin i:=i+1; z:=y; y:=x; x:=y+z; end; fib:=x; end; význam proměnných : x... f i, y... f i-1, z... f i-2
Př.: Vzájemná rekurze procedure p(var n:integer); forward; procedure q(var n:integer); begin n:=n div 2; p(n); writeln('p - ',n); end; procedure p; begin n:=n+1; q(n); writeln('q - ',n); end;