Preštudujte si: Podprogramy
Portál: | amos.ukf.sk |
Kurz: | Programovanie 1 |
Kniha: | Preštudujte si: Podprogramy |
Vytlačil(a): | Hosťovský používateľ |
Dátum: | streda, 4 decembra 2024, 08:38 |
1. Čo sú podprogramy?
Väčšina programovacích jazykov umožňuje vytvárať
určité ucelené a relatívne samostatné časti programov, ktoré sa označujú
ako podprogramy. Každý podprogram
obvykle predstavuje algoritmus, ktorý je možné vykonávať samostatne alebo v
závislosti na iných prvkoch programu. Správne používanie dokáže výrazne
zrýchliť ako tvorbu, tak i ladenie programu.
Podprogramy používame ak:
- sa v programe vyskytuje úplne rovnaká postupnosť
príkazov viackrát na rôznych miestach. Je bez diskusie, že opakovanie tých
istých postupností príkazov vedie k nehospodárnemu využívaniu prostriedkov
(zbytočne sa zväčšuje veľkosť výsledného súboru a to ako aplikácie, tak i zdrojového kódu) a
na druhej strane predstavuje nočnú moru pre tých, ktorí majú zdrojový kód
neskôr ladiť – opakovaním rovnakého kódu sa program stáva neprehľadným a každú
úpravu i opravu chyby treba realizovať na všetkých miestach, kde sa táto
postupnosť vyskytuje (častým javom je, že raz sa zabudne zapísať jednu zmenu na
jednom mieste, potom ďalšiu na inom a napokon sa programátor čuduje, prečo
sa program správa „divne“ a náhodne).
- v programe sa viackrát vyskytuje podobná postupnosť
príkazov odlišujúca sa iba parametrami.
- potrebujeme zvýšiť prehľadnosť programu a
priblížiť jeho zápis “ľudskému” riešeniu. Veľmi často programátor postupuje pri
vytváraní programu tak, že najprv určí postupnosť vykonania jednotlivých
podprogramov slovne, a až potom ich „kóduje“, napr.:
VycistiMriezku;
NacitajHodnotyZoSuboru;
NajdiNajmensiPrvok;
Vypis;
Výhodou takéhoto postupu je, že okrem
zrozumiteľného zápisu algoritmu si ujasní vlastné myšlienkové pochody ešte
predtým, ako začne písať samotný kód.
- riešenie je formulované rekurzívne, t.j. celkové riešenie získame tak, že pojem je opísaný
pomocou “samého seba”, napr. n!=n*(n-1)! alebo an=an-1+d. Tejto problematike sa budeme
podrobnejšie venovať v samostatnej kapitole.
2. Procedúry
Napíšte
program, ktorý bude prostredníctvom klikania na tlačidlá vykonávať matematické
operácie pre zadané dvojice čísel. Po výpočte zobrazí výsledok a vyčistí
obsah editov. Históriu výpočtov uchovávajte v listboxe.
V prostredí Delphi pracujeme
s podprogramami (a konkrétne s procedúrami) už od začiatku. Možno ste
si to neuvedomili, no všetky príkazy, ktoré sme doposiaľ zapisovali, sa
vkladali do tela udalostnej procedúry (obvykle šlo o udalosť kliknutia na
tlačidlo) patriacej formuláru, na ktorom bol umiestnený komponent, ktorého
udalosť sme využívali.
Procedúry patriace formuláru sú umiestnené
v samostatnom súbore – unite. Každý unit pozostáva z dvoch základných
častí:
- interface (rozhranie) – obsahuje
zoznam použitých unitov (iných), z ktorých sa v aktuálnom formulári
využívajú komponenty i podprogramy, ďalej zoznam typov a v rámci
definovaného typu pre formulár (TFormx) i zoznam podprogramov,
ktoré sú preň vytvorené. Sekciu končí deklaračná časť.
- implementation – implementačná časť
obsahuje zdrojový kód podprogramov definovaných v časti interface.
Obr.
Zdrojový kód formulára/unitu
Pri vytváraní vlastného podprogramu ho budeme aspoň
spočiatku viazať na formulár (unit), v ktorom ho vytvárame – zjednoduší sa
nám prístup ku komponentom (pokiaľ sa na ne budeme v rámci podprogramu
odvolávať) a zlepší sa prehľadnosť kódu. Pri zápise v implementačnej
časti budeme pred názov vkladať jeho vlastníka (napr. TForm1), do interface ho budeme vkladať ako súčasť triedy TForm1, už bez vlastníka.
Procedúra na vyčistenie Editov potom môže vyzerať podpobne ako na obrázku.
Obr.
Procedúra VycistiEdity
Samotný výpočet odohrávajúci sa napr. pri kliknutí
na tlačidlo zabezpečujúce sčítanie môže vyzerať nasledovne:
var a,b,vysledok:integer;
riadok:string;
begin
a:=StrToInt(Edit1.Text);
b:=StrToInt(Edit2.Text)
vysledok:=a+b;
Label1.Caption:=IntToStr(vysledok);
riadok:=Edit1.Text+’+’+Edit2.Text+’=’+IntToStr(vysledok);
Listbox1.Items.Add(riadok);
VycistiEdity;
end;
3. Lokálne a globálne premenné
Napíšte procedúru, ktorá nájde v poli prvok
s najväčšou hodnotou.
V programe je
v prvom rade potrebné hodnoty do poľa načítať, na to by však naše
doterajšie vedomosti mali postačovať. V ďalšom kroku budeme
v programe volať procedúru na výpočet maxima. Získanú hodnotu umiestnime
do premennej max
a v tele hlavného programu (v Delphi v procedúre
obsluhujúcej udalosť kliknutia na tlačidlo) vypíšeme.
var max:integer;
pole:array[1..20]
of integer;
procedure NajdiMaximum;
var i:integer;
begin
max:=pole[1]; {na
zaciatku za maximum prehlasime 1. prvok pola}
{budeme prechadzat pole od dalsieho prvku}
for i:=2 to 20 do {ak najdeme vacsiu hodnotu, prehlasime ju za
max}
if pole[i]>max then
max:=pole[i];
end;
{*************** hlavny program *******************}
begin
NacitajHodnotyDoPola;
{nacita, prip. nageneruje hodnoty do pola}
NajdiMaximum; {po vykonani tohto riadku bude v premennej max}
{umiestnena
maximalne hodnota pola}
WriteLn(max);
end;
Ak sa lepšie zahľadíte
na prezentovaný kód, určite si všimnete, že premenné sú deklarované na dvoch
miestach – na začiatku programu sú samostatne umiestnené max a pole, v rámci procedúry NajdiMaximum zasa premenná i.
Premenné deklarované na
začiatku programu sú prístupné a použiteľné na ľubovoľnom mieste programu
počas jeho behu a označujeme ich ako globálne.
Premenné definované v tele podprogramu vznikajú až pri volaní podprogramu
a môžeme k nim pristupovať len v rámci podprogramu,
v ktorom sú deklarované. Po ukončení podprogramu sa z pamäte uvoľňujú
– označujeme ich ako lokálne.
V Delphi sú globálne premenné deklarované
v časti interface. Pokiaľ definujete premennú v udalostnej procedúre,
je prístupná len v jej tele, iné podprogramy (ktoré sú z procedúry
volané) k nej prístup nemajú.
Globálne premenné sa
v rámci nášho programu využili na uloženie hodnôt poľa (pracovali sme
s ním v procedúre pre načítanie hodnôt i pri samotnom
vyhľadávaní) a na „vynesenie“ hodnoty max z procedúry NajdiMaximum.
Vo všeobecnosti platí,
že v programe je potrebné používať minimálne množstvo globálnych
premenných, pretože pri väčších programoch znižujú prehľadnosť
a podprogramy strácajú univerzálnosť. Ak chceme použiť ten istý
podprogramu na viacerých miestach, je potrebné mať k dispozícii naplnené
globálne premenné, ktoré do podprogramu vstupujú i globálne premenné
prostredníctvom ktorých údaje z podprogramu vystupujú.
4. Funkcie
Okrem procedúr ponúkajú programovacie jazyky
i podprogramy, ktoré ako výsledok svojej činnosti vracajú hodnotu.
Označujeme ich ako funkcie. Funkcie
ako také poznáme už z matematiky (abs, sin, cos a pod.), mnohé máme
k dispozícii i v programovacích jazykoch (Length, Copy, IntToStr
a pod.) a dokážeme vytvoriť i vlastné. Funkcia má vo
všeobecnosti podobu
Výsledok funkcie získame v rámci tela programu
prostredníctvom priraďovacieho príkazu, napr.
max:= VratMaximum;
Funkcia vracia takú hodnotu, aká je v rámci
jej tela priradená názvu funkcie. Vysvetlenie najlepšie nasledujúci poskytne
príklad.
var pole:array[1..20] of integer;
max:integer;
function NajdiMaximum:integer; {funkcia
vrati hodnotu typu integer}
var i,mm:integer;
begin
mm:=pole[1];
for i:=2 to 20 do if pole[i]>mm
then mm:=pole[i];
NajdiMaximum:=mm; {priradenie „vynesie“ ziskanu hodnota z
funkcie}
end;
{*************** hlavny program *******************}
begin
NacitajHodnotyDoPola;
max:=NajdiMaximum; {do
premennej max sa priradi hodnota ziskana}
{vo funkcii}
ShowMessage(max);
end;
V tele funkcie sme
namiesto premennej max (z
riešenia prostredníctvom procedúry) použili premennú mm. Iný názov sme zvolili len
s didaktických dôvodov – aby sa čitateľovi neplietli názvy. Ak by sme
v tele funkcie NajdiMaximum deklarovali premennú s rovnakým názvom ako je premenná globálna (max), došlo by k prekrytiu – v tele funkcie by sa
zmeny realizovali len na lokálnej premennej, globálna by zostávala
nepovšimnutá.
NacitajHodnotyDoPola;
Funkcia NajdiMaximum vráti hodnotu, ktorá sa
použije ako argument pre príkaz ShowMessage.
5. Parametre podprogramov
Rovnako ako algoritmy
vďaka možnosti zadávať rôzne vstupné údaje, dokážu riešiť úlohu pre rôzne
vstupné hodnoty, i vhodne napísané podprogramy dokážeme využiť tak, aby
úlohu riešili pre rôzne vstupy. Jeden zo spôsobov, akým možno do podprogramu
„poslať“ hodnoty sme už prezentovali – spracúvali sme údaje uložené
v globálnej premennej.
Ak však chceme, aby
podprogram predstavoval samostatný a nezávislý celok (vďaka čomu by bolo
jeho použitie univerzálne) a aby sme sa nemuseli zaoberať neustálym
sledovaním správaneho obsahu používaných globálnych premenných, potrebujeme
použiť iný aparát.
Funkcie i procedúry
umožňujú využívať tzv. formálne
parametre. Formálny parameter je
špeciálna lokálna premenná, ktorej hodnota sa nastaví pri volaní funkcie
a v momente spustenia tela podprogramu už obsahuje hodnotu
príslušného typu. Počas vykonávania kódu môžeme do nej priraďovať hodnoty
a pracovať s ňou ako s obyčajnou premennou, no po ukončení
podprogramu sa z pamäte uvoľní a zmeny hodnoty sa neprejavia. Všeobecný
zápis vyzerá napr. nasledovne:
procedure Nazov1(par1,par2:typ1;par3:typ2);
{procedure Vypis(a,b:integer;c:string)}
function Nazov2(par1,par2:typ2;par3:typ2):typ3;
{function Vypocet(a,b:integer;c:string):integer}
pričom parametre
rovnakého typu oddeľujeme čiarkou, v prípade použitia premenných viacerých
typov použijeme na oddelenei skupín bodkočiarku.
Volanie podprogramu môže
vyzerať:
vysledok:=Nazov2(a,b,c); {vysledok:=Vypocet(a,10,’test’)}
Napíšte podprogram, ktorý pre zadané n vypíše do prvého
riadku jednu hviezdičku, do druhého dve atď. až po n-tý riadok (v Delphi
vkladajte riadky do Listboxu).
procedure Hviezdy(n:integer);
var i:integer;
riadok:string;
begin
riadok:=’’; {obsahuje
pocet hviezd, ktore treba vypisat}
for i:=1 to n
do begin
riadok:=riadok+’*’; {v kazdom
riadku sa prida jedna *}
Listbox1.Items.Add(riadok); {vypis}
end;
end;
Premenná n obsahuje počet riadkov, ktoré sa majú spracovať
a využíva sa v podprograme len na čítanie. Vykonanie procedúry by sme
mohli zabezpečiť napr. ako Hviezdy(4);.
*
**
*
**
***
*
**
***
****
Úlohu vyriešime dvoma
spôsobmi:
- v hlavnom
programe zavoláme procedúru na vykreslenie trojuholníkov postupne so
zväčšujúcim sa parametrom,
- napíšeme podprogram,
ktorý bude vytvárať strom na základe zadania jeho výšky
begin
for i:=1 to 4
do Hviezdy(i);
end;
Procedúra sa volá najprv
s hodnotou 1 (t.j. vykreslí sa jedna *), potom s parametrom 2
(vykreslí sa trojuholník s prvým riadkom obsahujúcim jeden znak
a druhým dva znaky atď.).
Druhá možnosť je
zaujímavejšia – v procedúre Strom budeme volať procedúru Hviezda vďaka čomu získame pomerne univerzálnu procedúru:
procedure Strom(n:integer);
var i:integer;
begin
for i:=1 to n
do Hviezda(i);
end;
Rovnako ako do procedúr, môžeme i do funkcií
posielať parametre. Tento prístup je pre nás v určitej podobe známy
už z matematiky.
Napíšte
funkciu, ktorá pre dve zadané hodnoty vráti väčšiu z nich. Ak sú rovnaké,
návratová hodnota bude jedna z nich.
function Max(a,b:integer):integer;
begin
if a>b then Max:=a {ak je a vacsie, Max vrati a}
else Max:=b; {inak je b vacsie a Max vrati b}
end;
Do funkcie posielame dve celé čísla
a výsledok bude tiež celočíselný, vo funkcii dôjde k ich porovnaniu
a na základe neho sa do názvu funkcie priradí väčšia hodnota.
Volanie môže mať podobu:
vysledok:=Max(10,20) alebo vysledok:=Max(a,b)
prípadne vysledok:=Max(Max(10,20),30)
pričom jednoznačne najzaujímavejšie je posledné
volanie. V ňom sa najprv nájde hodnota vnorenej funkcie Max(10,20). Výsledok z nej sa potom použije na ďalšej úrovni
a zavolá sa Max(20,30), ktorá vráti skutočný
a správny výsledok.
Napíšte
funkciu, ktorá pre zadané hodnoty a, n vráti an.
Do podprogramov sme doposiaľ posielali len
konkrétne hodnoty, ktoré sa v ich tele spracúvali. Podprogram ich vykonal,
prípadne ak šlo o funkciu, vrátil výsledok. V praxi sa však často
stretneme s prípadmi, keď od podprogramu potrebujeme vrátiť viac ako jednu
hodnotu alebo potrebujeme upraviť obsah premennej, či poľa.
Použitie globálnych parametrov predstavuje obvykle
zbytočné komplikovanie a prekombinovanie kódu, pretože máme
k dispozícii aparát, ktorý nám umožní ovplyvniť iným spôsobom.
Dve triedy
súťažia v zbere papiera. Evidujte odovzdané množstvá jednotlivých žiakov prostredníctvom
poľa. Zistite, ktorá trieda nazbierala viac papiera. Zistite, v ktorej triede
bol vyšší priemerna žiaka (v prípade Delphi načítavajte údaje z Listboxu
a ako jeden z parametrov pošlite do procedúry názov listboxu, pričom
jeho typ bude TListbox).
A tu sa dostávame do situácie, keď pre
načítanie hodnôt do oboch polí použijeme prakticky rovnaký postup (načítavanie
postupnosti ukončené nulou), čo nám ponúka možnosť využiť podprogram. Okrem
toho by sme v rámci podprogramu mohli už pri načítaní zistiť priemery
i celkové nazbierané množstvo.
Vzhľadom na množstvo údajov, ktoré vyžadujeme, nám
funkcia vracajúca ako výsledok jedinú hodnotu, určite nepostačí. Navyše by bolo
vhodné, aby sme si údaje uložené do poľa i zapamätali.
Podprogramy, s ktorými sme sa doteraz stretli
pracovali s parametrami volanými
hodnotou. Do podprogramu vstupovali prostredníctvom lokálnych premenných
hodnoty, s ktorými sa pracovalo, a ktoré sa po ukončení podprogramu
z pamäte uvoľnili.
Pokiaľ chceme, aby sa zmeny v premenných uchovali
i po ukončení podprogramu, potrebujeme ich upraviť na parametre volané adresou. Pamäť pre parametre volané hodnotou sa
vytvára tak, aby bola po ukončení podprogramu uvoľnená. Pokiaľ je parameter
volaný adresou, nemôže byť na jeho pozícii konkrétna hodnota, ale vždy len
názov premennej. Pri inicializácii podprogramu sa preň nevytvorí nové pamäťové
miesto, ale manipuluje sa s tým pamäťovým miestom, na ktorom je uložená
príslušná premenná – t.j. mení sa ako hodnota formálneho parametra v podprograme,
tak i hodnota premennej v tele programu (obe premenné ukazujú na
rovnaké miesto). Po ukončení podprogramu sa pamäťové miesto neuvoľnuje, pretože
pri jeho inicializácii nevzniklo.
Obr. Postup
pri parametroch volaných hodnotou a adresou
Kľúčovým slovom, ktoré definuje parameter ako
volaný adresou je var. Použitie v podprograme
môže vyzerať ako v nasledujúcom príklade:
{na to, aby sme mohli pouzit pole ako parameter
podprogramu,}
{potrebujeme ho definovat ako typ}
var trieda1,trieda2:TZoznam;
sucet1,sucet2:integer; {sucty
za triedu}
priemer1,priemer2:real; {priemery
za triedu}
{parametre pred ktorymi sa nachadza “var” menia v
podprograme hodnoty}
{premennych, ktore su pri volani umiestnene na rovnakych miestach }
procedure Operacia(var trieda:TZoznam;var
suc:integer;var priem:real);
var pocet,hodnota:integer;
begin
pocet:=0;
suc:=0; {inicializacia premennych}
repeat {opakuje,
kym sa nezada hodnota 0}
Writeln(‘Zadaj ziaka c.’,pocet+1); {preco
pocet+1?}
ReadLn(hodnota); {nacita sa hodnota}
if
hodnota>0 then begin {ak je nenulova}
inc(pocet); {zvysi
sa pocet zapojenych ziakov}
trieda[pocet]:=hodnota; {zapamata sa v poli}
suc:=suc+hodnota; {zvysi
sa sucet za triedu}
end;
until
hodnota=0; {koniec nacitavacieho
cyklu}
priem:=suc/pocet; {vypocita
sa priemer}
end;
begin
Operacia(trieda1,sucet1,priemer1); {do
premennej trieda1 sa ulozi}
{zoznam hodnot pre ziakov,
do}
{sucet1 hodnota, ktora je}
{v procedure vedena ako
sucet a}
{detto priemer1}
Operacia(trieda2,sucet2,priemer2); {analogia}
if
sucet1>sucet2 then WriteLn(‘Viac
nazbierala 1. trieda’)
else WriteLn(‘Viac nazbierala 2.
trieda’);
if
priemer1>priemer2 then WriteLn(‘Lepsi
priemer ma 1. trieda’)
else WriteLn(‘Lepsi priemer ma 2.
trieda’);
end;
Procedúra naplní polia
pre prvú i pre druhú triedu, zároveň do premenných sucet1, priemer1 a sucet2, priemer2 získavame prostredníctvom
formálnych parametrov vypočítané hodnoty.
Ošetrite program tak, aby bral do úvahy i rovnosť
výsledkov.
Formálne parametre sú
všetky parametre vystupujúce v tele podprogramu (volané hodnotou
i adresou). Postup popísaný na formálnych parametroch sa
v podprograme aplikuje na objekty určené pri volaní. Označujeme ich ako skutočné parametre. Pre jednotlivé
volania podprogramu môžeme dosadzovať rôzne skutočné parametre a tým je postup,
zostavený a napísaný jedenkrát, použiteľný univerzálne.
Napíšte podprogram, ktorý vymení hodnoty dvoch
premenných.
Napíšte
podprogram, ktorý porovná maximálne hodnoty v dvoch poliach.
Pre zadané pole zistite jeho
minimum a maximum.
Napíšte
podprogram, ktorý pre zadaný názov súboru a meno človeka prehľadá súbor
a vypíše či a akoľkokrát sa v ňom človek nachádza (súbor môže
obsahovať len zoznam mien, napr. návštevníkov budovy).
Napíšte
podprogram, ktorý zaokrúhli číslo na zadaný počet desatinných miest. Číslo
posielajte ako parameter volaný adresou.
Napíšte
funkciu, ktorá pre zadané číslo zistí, či ide o prvočíslo. Odpoveďou bude
hodnota áno/nie (true/false).
6. Mechanizmus volania podprogramu
Keď sa objaví v programe volanie podprogramu:
- zapamätá sa návratová adresa (kam sa bude treba vrátiť)
- vytvoria sa lokálne premenné procedúry (s nedefinovanou hodnotou) a
formálne parametre s hodnotami nastavenými podľa vstupov
- prenesie sa riadenie programu do tela podprogramu
- vykonajú sa všetky príkazy podprogramu
- zrušia sa lokálne premenné
- riadenie sa vráti za miesto v programe, odkiaľ bol podprogram volaný
Kvôli potrebe vykonania všetkých týchto operácií môže byť rýchlosť
programu s podprogramami o čosi nižšia ako bez ich použitia, pri
súčasných procesoroch však ide o mizivé a prakticky nezmerateľné
zdržanie.