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: piatok, 19 apríla 2024, 23:34

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.

V Delphi sme s podprogramami pracovali už od začiatku – každé kliknutie na tlačidlo, resp. každá udalosť si generovala samostatný podprogram – procedúru.

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:

procedure TForm1.Button1Click(Sender:TObject);
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é
maxpole, 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

 function NazovFunkcie:typ;

 pričom typ predstavuje niektorých z jednoduchých, prípadne i štruktúrovaných typov (pole).
Výsledok funkcie získame v rámci tela programu prostredníctvom priraďovacieho príkazu, napr.


mojText:=IntToStr(cislo);
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.

 Upravte úlohu na hľadanie maxima v poli tak, aby maximálny prvok poľa vrátila funkcia.

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;

Úlohu sme modifikovali, v tele funkcie sme do jej názvu priradili hodnotu, ktorú požadujeme vrátiť ako výsledok. Tento bude priradením do premennej max použitý v ďalšom behu programu.

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

Globálnu premennú max by sme dokonca mohli v našom riešení i vynechať a telo programu upraviť na:

NacitajHodnotyDoPola;
       ShowMessage(NajdiMaximum);

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

 Nazov1(a,b,c);             {Vypis(10,20,’test’)}
 vysledok:=Nazov2(a,b,c);       {vysledok:=Vypocet(a,10,’
test’)}

 pričom ako parameter môžeme použiť konkrétnu hodnotu alebo premennú, z ktorej sa hodnota prečíta (manipulácia s ňou nemá zatiaľ žiaden vplyv na obsah premennej, z ktorej sa prečítala).

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);.

 Napíšte podprogram, ktorý na základe procedúry z predchádzajúcej úlohy vykreslí polovicu vianočného stromu v podobe ako na obrázku:

*
**
*

**
***
*
**
***
****

Ú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é n vráti n!
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:

 Type TZoznam=array[1..30] of integer;    
{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;

{********* hlavny program **********}

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, priemer1sucet2, 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:

  1. zapamätá sa návratová adresa (kam sa bude treba vrátiť)
  2. vytvoria sa lokálne premenné procedúry (s nedefinovanou hodnotou) a formálne parametre s hodnotami nastavenými podľa vstupov
  3. prenesie sa riadenie programu do tela podprogramu
  4. vykonajú sa všetky príkazy podprogramu
  5. zrušia sa lokálne premenné
  6. 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.