4.Prednaska

Z Pascal
Prejsť na: navigácia, hľadanie
4. Prednáška

úlohy | cvičenie


Podprogramy

Podprogramom nazývame pomenovanie nejakého algoritmu alebo nejakej časti programu. Podprogram potom môžeme vyvolať pomocou tohto mena ľubovoľný početkrát. Pomocou podprogramov môžeme riešiť aj väčší problém jeho rozdelením na podúlohy - hovoríme tomu, že úlohu riešime zhora nadol. V Pascale môžu byť podprogramy dvoch typov:

  • procedúry - postupnosť príkazov
  • funkcie - postupnosť príkazov pričom výsledkom je nejaká hodnota

Podprogramy sa najčastejšie používajú vtedy, keď

  • nejaká časť programu sa opakuje na viacerých miestach - my ju zapíšeme len raz a používame viackrát;
  • nejaká časť programu kvôli čitateľnosti alebo logike algoritmu je vytiahnutá do samostatnej časti - podprogramu - čím ju môžeme napr. lepšie odladiť;
  • nejakú časť programu naprogramoval nejaký iný programátor - zabalil to do procedúry - a my to len používame;
  • ak budeme potrebovať nejaký problém riešiť pomocou rekurzie, musíme na to použiť podprogram;
  • napr. aj grafické príkazy pre Canvas (ako Rectangle, Ellipse, TextOut, ...) sú tiež podprogramy, ktoré niekto dávnejšie naprogramoval a my ich môžeme používať;



Deklarácia procedúry


Keď chceme nejaké príkazy zabaliť do procedúry, musíme túto procedúru zadeklarovať (podobne ako deklarujeme premenné). Na niektorom mieste programu teda zapíšeme

procedure meno;
  ...      // lokálne deklarácie pre procedúru, napr. deklarácie premenných
begin
  ...      // telo procedúry
end;

Najlepšie to uvidíme na príklade, v ktorom na náhodnú pozíciu budeme vypisovať nejaký text. Zadefinujeme procedúru Vypis, pričom ju vnoríme do TForm1.Button1Click:

procedure TForm1.Button1Click(Sender: TObject);
 
  procedure Vypis;
  begin
    Image1.Canvas.TextOut(Random(300), Random(200), "Pascal");
  end;
 
begin
  Image1.Canvas.Font.Height := 30;
  Vypis;
  Image1.Canvas.Font.Color := clBlue;
  Vypis;
  Image1.Canvas.Font.Color := clRed;
  Vypis;
end;

volanie podprogramu



Volanie procedúry


Podprogram sme zadefinovali raz a použili - volali - sme ho trikrát. Je veľmi dôležité správne pochopiť, ako pracuje počítač pri volaní podprogramu (pri vykonávaní programu sa príde na riadok s menom podprogramu):

  1. zapamätá sa návratové miesto (v počítači je to tzv. návratová adresa), kam sa bude treba vrátiť po skončení podprogramu
  2. prenesie sa riadenie programu do tela podprogramu (za príslušný begin)
  3. vykonajú sa všetky príkazy podprogramu (až po koncový end)
  4. riadenie sa vráti za miesto v programe, odkiaľ bol podprogram volaný – t.j. na návratové miesto (návratová adresa) - napr. na riadok Image1.Canvas.Font.Color := clBlue;


V ďalšom príklade ukážeme, ako sa niekedy riešia úlohy metódou zhora nadol. Napr. na náhodných pozíciách chceme nakresliť 100 štvorcov a kruhov, pričom kruhov je približne desatina. Najprv zapíšeme hrubú schému programu, v ktorej niektoré časti (detaily) budú ešte v komentároch:

var
  I, X, Y: Integer;
begin
  // zmaž graficku plochu
  for I := 1 to 100 do
  begin
    // priprav súradnice X, Y
    if Random(10) = 0 then          // približne jeden z desiatich
      // nakresli modrý kruh
    else
      // nakresli červený štvorec
  end;
end;

Použime ideu podprogramov a komentáre nahraďme procedúrami, t.j. volaniami procedúr. Samotné podprogramy musíme zadefinovať ešte pred programom. My ich vložíme medzi deklarácie premenných a začiatočným begin:

procedure TForm1.Button1Click(Sender: TObject);
var
  I, X, Y: Integer;
 
  procedure Zmaz;
  begin
    Image1.Canvas.Brush.Color := clWhite;
    Image1.Canvas.FillRect(Image1.ClientRect);
  end;
 
  procedure PripravXY;
  begin
    X := Random(Image1.Width);
    Y := Random(Image1.Height);
  end;
 
  procedure KresliKruh;
  begin
    Image1.Canvas.Brush.Color := clBlue;
    Image1.Canvas.Ellipse(X - 20, Y - 20, X + 20, Y + 20);
  end;
 
  procedure KresliStvorec;
  begin
  end;
 
begin
  Zmaz;                     // zmaž graficku plochu
  for I := 1 to 100 do
  begin
    PripravXY;              // priprav súradnice X, Y
    if Random(10) = 0 then  // priblizne jeden z desiatich
      KresliKruh;           // nakresli modrý kruh
    else
      KresliStvorec;        // nakresli červený štvorec
  end;
end;

volanie podprogramov

Vysvetlime, čo je tu zapísané:

  • zadefinovali sme 4 podprogramy: Zmaz, PripravXY, KresliKruh, KresliStvorec
  • druhá procedúra PripravXY nastavuje hodnoty premenných X a Y, ktoré sa potom používajú v ďalších dvoch procedúrach KresliKruh a KresliStvorec - tieto dve premenné sú definované ešte pred definíciou všetkých štyroch procedúr preto, aby ich tieto "videli"
  • pri zatlačení tlačidla Button1 sa postupne tieto podprogramy volajú, niektoré viackrát



Lokálne premenné podprogramu


Ukážeme, ako fungujú lokálne premenné v našich nových podprogramoch. Napíšeme podprogram, ktorý nakreslí 10 rôznofarebných vpísaných kruhov s polomermi 40, 36, 32, ..., 8, 4. Využije for-cyklus, v ktorom sa počítadlo I nebude zvyšovať, ale znižovať. Namiesto slova to v cykle zapíšeme downto:

procedure TForm1.Button1Click(Sender: TObject);
   
  procedure VpisaneKruhy;
  var
    I, X, Y: Integer;
  begin
    X := Random(Image1.Width);
    Y := Random(Image1.Height);
    for I := 10 downto 1 do
    begin
      Image1.Canvas.Brush.Color := Random(256 * 256 * 256);
      Image1.Canvas.Ellipse(X - 4 * I, Y - 4 * I, X + 4 * I, Y + 4 * I);
    end;
  end;
 
begin
  VpisaneKruhy;
  VpisaneKruhy;
  VpisaneKruhy;
  VpisaneKruhy;
  VpisaneKruhy;
end;

vpísané kruhy

Po stlačení tlačidla sa päťkrát zavolá procedúra VpisaneKruhy, t.j. nakreslí sa 5 skupín vpísaných kruhov. Pozrime sa, ako sa teraz zmení mechanizmus volania podprogramu:

  1. zapamätá sa návratové miesto (v počítači je to tzv. návratová adresa), kam sa bude treba vrátiť po skončení podprogramu
  2. vytvoria sa lokálne premenné procedúry (so zatiaľ nedefinovanou hodnotou) - u nás sú to premenné I, X a Y - neskôr uvidíme, že tu sa vytvoria aj parametre
  3. prenesie sa riadenie programu do tela podprogramu (za príslušný begin)
  4. vykonajú sa všetky príkazy podprogramu (až po koncový end)
  5. zrušia sa lokálne premenné - "zabudnú" sa premenné I, X a Y
  6. riadenie sa vráti za miesto v programe, odkiaľ bol podprogram volaný – t.j. na návratové miesto (návratová adresa) - na riadok s nasledujúcim volaním VpisaneKruhy

Ak by sme chceli volania našej pocedúry VpisaneKruhy urobiť napr. vo for-cykle, mohli by sme to zapísať takto:

procedure TForm1.Button1Click(Sender: TObject);
   
  procedure VpisaneKruhy;
  var
    I, X, Y: Integer;
  begin
    X := Random(Image1.Width);
    Y := Random(Image1.Height);
    for I := 10 downto 1 do
    begin
      Image1.Canvas.Brush.Color := Random(256 * 256 * 256);
      Image1.Canvas.Ellipse(X - 4 * I, Y - 4 * I, X + 4 * I, Y + 4 * I);
    end;
  end;
 
var
  I: Integer;
begin
  for I := 1 to 10 do
    VpisaneKruhy;
end;

Uvedomte si, že lokálna premenná I vo vnútri procedúry VpisaneKruhy je iná (vznikla pri volaní tejto procedúry a potom hneď bola zrušená) ako premenná I, ktorá sa používa ako počítadlo pre 10-násobné volanie tejto procedúry.

Ďalším vylepšením tohto programu budeme meniť počet vpísaných kruhov v skupinách. Pridáme ešte jednu premennú, v ktorej nastavíme počet kruhov v cykle:

procedure TForm1.Button1Click(Sender: TObject);
var
  N: Integer;
   
  procedure VpisaneKruhy;
  var
    I, X, Y: Integer;
  begin
    X := Random(Image1.Width);
    Y := Random(Image1.Height);
    for I := N downto 1 do
    begin
      Image1.Canvas.Brush.Color := Random(256 * 256 * 256);
      Image1.Canvas.Ellipse(X - 4 * I, Y - 4 * I, X + 4 * I, Y + 4 * I);
    end;
  end;
 
var
  I: Integer;
begin
  N := 5;
  for I := 1 to 4 do
    VpisaneKruhy;
  N := 10;
  for I := 1 to 4 do
    VpisaneKruhy;
end;

Program najprv nakreslí 4 skupiny vpísaných kruhov, ktoré obsahujú po 5 kruhov. Potom nakreslí ďalšie 4 skupiny kruhov, ale teraz už každá po 10 kruhov.



Viditeľnosť identifikátorov


Pre Pascal platia tieto pravidlá:

  • identifikátor (premennej, podprogramu, typu, objektu, ...) sa môže v programe používať, až od toho miesta nižšie, kde bol zadeklarovaný
  • štandardné deklarácie (štandardné typy, funkcie, podprogramy) – sú definované tak, ako keby boli ešte pred samotným programom, hovoríme, že sú v 0. úrovni
  • prekrytie identifikátora s rovnakým menom je zakázané na tej istej úrovni, ale je povolené vo vnorených úrovniach
    • napr. var Real: Integer; - čo je programátorsky veľmi škaredé a robiť to radšej nebudeme
  • identifikátor je viditeľný len na svojej úrovni a tiež vo všetkých pre neho vnorených úrovniach (kde ale nebol predefinovaný)
  • keď zadefinujeme procedúru ako súčasť napr. TForm1.Button1Click, tak táto je automaticky na vyššej úrovni (je vnorená) ako náš "program" – ona vidí deklarácie vo vnútri TForm1.Button1Click (ak boli pred ich definíciou) ale samotný program nevidí dovnútra procedúry
    • procedúra VpisaneKruhy vidí premennú N, ale program nevidí premenné X a Y definované v tejto procedúre VpisaneKruhy

V celom našom programe:

  • na 0. úrovni sú všetky štandardné identifikátory, napr. typy Integer, Boolean, TColor, alebo konštanta MaxInt, alebo podprogramy napr. Rectangle, FillRect a štandardné funkcie ako Random, Sin, Sqrt
  • na 1. úrovni je definícia formulára TForm1 ale aj procedure TForm1.Button1Click(Sender: TObject); do ktorej píšeme náš program
  • na 2. úrovni je premenná N a procedúra VpisaneKruhy
  • na 3. úrovni sú všetky tri lokálne premenné v procedúre VpisaneKruhy: I, X a Y



Globálne premenné a procedúry



Všetky doteraz definované procedúry (napr. Vypis vypisoval text 'Pascal', KresliKruh, VpisaneKruhy) sme zapísali vo vnútri procedúry Button1Click. Keby sme potrebovali tú istú procedúru (napr. VpisaneKruhy) použiť aj v Button2Click, museli by sme ju znovu definovať (vo vnútri Button2Click), lebo všetko čo zadefinujeme v Button1Click vidí len tento Button1Click a nik iný. Procedúry môžeme definovať aj mimo procedúr ako Button1Click - môžeme ich definovať na tzv. globálnej, t.j. 1. úrovni ešte pred samotnými procedúrami Button1Click a Button2CLick. Hoci teraz sa bude s grafickou plochou pracovať trochu komplikovanejšie. Napr.

procedure VpisaneKruhy;
var
  I, X, Y: Integer;
begin
  X := Random(Form1.Image1.Width);
  Y := Random(Form1.Image1.Height);
  for I := 10 downto 1 do
  begin
    Form1.Image1.Canvas.Brush.Color := Random(256 * 256 * 256);
    Form1.Image1.Canvas.Ellipse(X - 4 * I, Y - 4 * I, X + 4 * I, Y + 4 * I);
  end;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
begin
  for I := 1 to 10 do
    VpisaneKruhy;
end;

Teraz musíme v procedúre VpisaneKruhy pred každé použitie Image1 pripísať Form1. Na druhej strane, takúto procedúru teraz môžeme využiť v celom zvyšnom programe.

V príklade o štvorcoch a kruhoch (KresliKruh a KresliStvorec) sme videli použitie premenných X a Y, ktoré sa zadefinovali ešte pred týmito procedúrami tak, aby ich obe tieto procedúry videli. Podobne to môžeme urobiť aj s globálnymi procedúrami: keď zadeklarujeme nejaké premenné ešte pred takouto globálnou procedúrou, tak ich uvidí aj ona. Aj tieto premenné budú na globálnej úrovni a uvidí ich nielen táto globálna procedúra, ale aj celý zvyšok programu. Ukážeme to na príklade pohybujúceho sa štvorca. Každé zatlačenie tlačidla zmaže grafickú plochu, zväčší globálnu premennú X o 5 a nakreslí farebný štvorec tak, že jeho ľavý horný roh bude na súradniciach (X, 100). Každé zatlačenie tlačidla nakreslí tento štvorec o kúsok posunutý vpravo:

var
  X: Integer;
 
procedure KresliStvorec;
begin
  Form1.Image1.Canvas.Brush.Color := clBlue;
  Form1.Image1.Canvas.Rectangle(X, 100, X + 30, 130);
end;
 
procedure Zmaz;
begin
  Form1.Image1.Canvas.Brush.Color := clWhite;
  Form1.Image1.Canvas.FillRect(Form1.Image1.ClientRect);
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  Zmaz;
  X := X + 5;
  KresliStvorec;
end;

Všimnite si, že aj procedúra Zmaz je tu definovaná ako globálna. V tomto programe využívame to, že premenná X je globálna a teda má už pri štarte programu nulovú hodnotu.

V tejto ukážke využívame globálne premenné a procedúry, hoci skúsení programátori sa snažia minimalizovať používanie globálnych premenných. Úlohu s pohybujúcim sa štvorcom by sme ale bez globálnej premennej zatiaľ vyriešiť nevedeli. Až neskôr sa naučíme mechanizmy, vďaka ktorým nebudeme musieť definovať žiadne nové globálne premenné.


Parametre procedúr

Pri volaní podprogramu môžeme do neho poslať aj nejaké hodnoty, aby sme nemuseli rôzne špeciality nastavovať v globálnych premenných (napr. počet vpísaných kruhov): v podprograme zadefinujeme špeciálne lokálne premenné, tzv. parametre, do ktorých sa pred samotným volaním priradia (inicializujú) hodnoty parametrov.

V súvislosti s parametrami rozlišujeme dva dôležité pojmy:

  • parameter v hlavičke procedúry - popisuje (lokálnu) premennú, v ktorej sa dozvedáme parametre procedúry
    • hovorí sa tomu aj formálny parameter
  • vstupná hodnota parametra - sa zapisuje pri volaní procedúry a svojim typom musí zodpovedať typu parametra
    • niekedy sa tomu hovorí argument alebo skutočný parameter

Procedúru s parametrami zapíšeme

procedure meno(parameter1: typ1; parameter2: typ2);
  ...      // lokálne deklarácie
begin
  ...      // telo procedúry
end;

Procedúry môžu mať jeden aj viac parametrov. Ak ich je viac, oddeľujeme ich bodkočiarkami.

Procedúra s parametrami X a Y - umiestnenie kruhu:

procedure TForm1.Button1Click(Sender: TObject);
   
  procedure VpisaneKruhy(N: Integer);   // N je parameter
  var
    I, X, Y: Integer;
  begin
    X := Random(Image1.Width);
    Y := Random(Image1.Height);
    for I := N downto 1 do
    begin
      Image1.Canvas.Brush.Color := Random(256 * 256 * 256);
      Image1.Canvas.Ellipse(X - 4 * I, Y - 4 * I, X + 4 * I, Y + 4 * I);
    end;
  end;
 
var
  I: Integer;
begin
  for I := 1 to 4 do
    VpisaneKruhy(5);          // skutočný parameter 5 => nakreslí sa 5 vpísaných kruhov
  for I := 1 to 4 do
    VpisaneKruhy(10);         // skutočný parameter 10 => nakreslí sa 10 vpísaných kruhov
end;

Podprogram môže mať aj viac parametrov, buď rovnakého alebo rôznych typov. Napíšme procedúry, ktoré nasreslia kruh s daným stredom a polomerom a farebný štvorec s danou veľkosťou strany na náhodnej pozícii:

procedure TForm1.Button1Click(Sender: TObject);
 
  procedure Kruh(X, Y, R: Integer);   // X, Y a R sú parametre
  begin
    Image1.Canvas.Brush.Color := clWhite;
    Image1.Canvas.Ellipse(X - R, Y - R, X + R, Y + R);
  end;
 
  procedure Stvorec(Velkost: Integer; Farba: TColor);   // Velkost a Strana sú parametre
  var
    X, Y: Integer;
  begin
    X := Random(Image1.Width - Velkost);
    Y := Random(Image1.Height - Velkost);
    Image1.Canvas.Brush.Color := Farba;
    Image1.Canvas.Rectangle(X, Y, X + Velkost, Y + Velkost);
  end;
 
begin
  for I := 1 to 10 do
    Kruh(I * 10, 100, I * 3);
  for I := 1 to 10 do
    Stvorec(10, clRed);
end;

Program nakreslí 10 kruhov v jednom rade a 10 červených štvorcov na náhodných pozíciách.

Upravme mechanizmus pri volaní procedúry:

  1. zapamätá sa návratové miesto (návratová adresa), kam sa bude treba vrátiť po skončení podprogramu
  2. vytvoria sa
    • lokálne premenné procedúry (s nedefinovanou hodnotou)
    • vytvoria sa aj parametre ako lokálne premenné, ktoré sú v tele podprogramu naozaj obyčajnými lokálnymi premennými, ale sú inicializované vstupnými hodnotami parametrov
  3. prenesie sa riadenie programu do tela podprogramu
  4. vykonajú sa všetky príkazy podprogramu
  5. zrušia sa lokálne premenné (teda aj parametre - samozrejme, že sa tu nezrušia vstupné hodnoty parametrov)
  6. riadenie sa vráti za miesto v programe, odkiaľ bol podprogram volaný

Všetky procedúry, ktoré sme vytvárali doteraz, boli vnorené do procedúry TForm1.Button1Click, t.j. využili sa, keď sme zatlačili tlačidlo Button1. Teraz chceme definovať procedúru, ktorá nebude vnorená, ale bude globálna - bude na definovaná na 1. úrovni a teda ju uvidia všetky neskôr definované podprogramy (napr. aj FormCreate, Button1Click, Button2Click, ...).


Funkcie

Funkcia je taký typ podprogramu, ktorý niečo počíta a nakoniec vráti nejakú hodnotu. S touto hodnotou ďalej pracujeme vo výrazoch, v testoch, v priradeniach a pod. Pascal má veľa dopredu zadefinovaných (preddefinovaných) funkcií, ktoré sme už používali od prvých stretnutí s programovaním. Napr. funkcie

  • Abs, Sqr, Random
  • IntToStr, IntToHex, FloatToStr, Format
  • RGBToColor
  • SizeOf, Low, High, Ord
  • Trunc, Round, Sin, Cos, Sqrt


Funkcie majú väčšinou jeden alebo viac parametrov a zapíšeme ich

function meno(parameter1: typ1; parameter2: typ2): typ_výsledku;
  ...      // lokálne deklarácie
begin
  ...      // telo funkcie
end;

Keď má funkcia viac parametrov, oddeľujeme ich bodkočiarkami.

Funkcia sa podobá na procedúru s jedným veľmi dôležitým rozdielom: musíme nejako určiť, aká bude výsledná hodnota funkcie. V tele funkcie máme k dispozícii špeciálnu premennú Result – túto nedeklarujeme, pretože to za nás spravil Pascal. Táto špeciálna premenná sa správa rovnako ako obyčajná lokálna premenná:

  • je rovnakého typu ako typ funkcie,
  • na začiatku má nedefinovanú hodnotu,
  • hodnota, ktorú má Result pri skončení podprogramu sa zapamätá a vráti sa ako hodnota funkcie.

Vysvetlime to na tomto jednoduchom príklade:

function Mocnina(X: Integer): Integer;
begin
  Result := X * X;
end;

Tento zápis znamená:

  • definujeme funkciu (podprogram) s menom Mocnina,
  • podprogram má jeden celočíselný parameter X,
  • výsledkom funkcie bude tiež celé číslo,
  • telo tejto funkcie sa skladá z jediného priraďovacieho príkazu: do špeciálnej premennej Result priraďujeme výsledok funkcie, teda druhú mocninu parametra X.

Teraz, ak by sme chceli použiť túto funkciu, zapíšeme napr.:

procedure TForm1.Button1Click(Sender: TObject);
 
  function Mocnina(X: Integer): Integer;
  begin
    Result := X * X;
  end;
 
var
  I, Sucet: Integer;
begin
  Sucet := 0;
  for I := 1 to 10 do
    Sucet := Sucet + Mocnina(I);
  Memo1.Lines.Append(Format('súčet = %d', [Sucet]));
end;

Tento prvý testovací program 10-krát zavolal túto novú funkciu Mocnina a pri každom tomto volaní vypočítal príslušnú mocninu čísla a výsledok pripočítal k doterajšiemu súčtu. Každé volanie funkcie Mocnina beží podobne ako už poznáme mechanizmus volania procedúr. Tento mechanizmus volania procedúr upravíme tak, aby fungoval pre funkcie. Teda pri volaní funkcie sa postupne:

  1. zapamätá sa návratové miesto, t.j. riadok programu, kam sa bude treba vrátiť po skončení funkcie;
  2. vytvoria sa všetky zadeklarované lokálne premenné funkcie – tieto majú nedefinovanou hodnotou;
    • aj parametre sú lokálne premenné, preto sa na tomto mieste vytvoria aj všetky parametre (premenné) a do nich sa priradia príslušné vstupné hodnoty
    • okrem toho sa automaticky vytvorí špeciálna lokálna premenná Result, ktorá je rovnakého typu ako typ funkcie – zatiaľ má nedefinovanú hodnotu;
  3. prenesie sa riadenie programu do tela podprogramu (za príslušný begin) a vykonajú sa všetky príkazy podprogramu (až po koncový end);
  4. zrušia sa lokálne premenné – a teda sa zrušia aj parametre;
    • pri tom sa zapamätá hodnota špeciálnej premennej Result;
  5. riadenie sa vráti za miesto v programe, odkiaľ bol podprogram volaný – t.j. na návratové miesto – sem sa dosadí zapamätaný výsledok funkcie.

Takže, čo sa naozaj stane v počítači, keď program príde na riadok, kde je volanie funkcie:

  Sucet := Sucet + Mocnina(I);

Z bodov mechanizmu volania funkcie vidíme, že

  1. zapamätá sa pozícia tohto riadka programu, aby sa sem výpočet vedel vrátiť (tzv. návratové miesto);
  2. vytvoria sa všetky lokálne premenné: funkcia Mocnina má jeden parameter X – to je prvá lokálna premenná, okrem toho sa automaticky vytvorí aj druhá lokálna premenná Result,
    • premenná X dostáva hodnotu vstupnej premennej I, teda 1;
    • premenná Result má zatiaľ nedefinovanú hodnotu;
  3. vykoná sa jediný príkaz tela funkcie, teda do premennej Result sa priradí hodnota X * X, t.j. v Result je teraz 1;
  4. zapamätá sa výsledok funkcie (obsah premennej Result) a všetky lokálne premenné sa zrušia (X aj Result);
  5. pokračuje sa v rozrobenom priraďovacom príkaze, z ktorého sa skočilo do funkcie Mocnina – premennej Sucet sa pripočíta 1 (výsledok volania funkcie Mocnina(1)).



Príklady funkcií



Zapíšeme funkciu Min, ktorá vráti menšiu z dvoch celočíselných hodnôt:

function Min(A, B: Integer): Integer;
begin
  if A < B then
    Result := A
  else
    Result := B;
end;

Logická funkcia JeCifra, ktorá vracia True, ak je vstupný parameter cifrou:

function JeCifra(Znak: Char): Boolean;
begin
  if (Znak >= '0') and (Znak <= '9') then
    Result := True
  else
    Result := False;
end;

Túto istú funkciu zapíšeme elegantnejšie:

function JeCifra(Znak: Char): Boolean;
begin
  Result := (Znak >= '0') and (Znak <= '9');
end;

Celočíselná funkcia, ktorá vracia hodnotu načítanej cifry (inak vráti -1):

function Cifra(Znak: Char): Integer;
begin
  if JeCifra(Znak) then
    Result := Ord(Znak) - Ord('0')      // Ord('0') = 48
  else
    Result := -1;
end;

Logická funkcia, ktorá vracia True, ak je vstupný parameter písmenom:

function JePismeno(Znak: Char): Boolean;
begin
  Result := (Znak >= 'a') and (Znak <= 'z') or (Znak >= 'A') and (Znak <= 'Z');
end;

Logická funkcia Odd, ktorá vracia True, ak je celé číslo, ktoré je jej parametrom, nepárne (pozn.: Odd je už preddefinovaná ako štandardná funkcia):

function Odd(Cislo: Integer): Boolean;
begin
  Result := Cislo mod 2 = 1;
end;

Celočíselná funkcia, ktorá vracia súčet prvých N celých čísel:

function Suma(N: Integer): Integer;
begin
  Result := 0;         // zrejme by fungovalo aj Result := N*(N+1) div 2;
  while N > 0 do
  begin
    Result := Result + N;
    Dec(N);
  end;
end;

Príklad s počítaním mocniny čísla. Najprv uvedieme nie úplne správne riešenie:

function Mocnina(X, K: Integer): Integer;
begin
  Result := 1;
  while K > 0 do
  begin
    Result := Result * X;
    Dec(K);
  end;
end;

Táto funkcia občas vráti nesprávny výsledok, pričom my nevieme, či momentálny vypočítaný výsledok je dobrý alebo zlý. Preto pridáme ďalší parameter (typu var), v ktorom vrátime informáciu, či nepretiekol výsledok:

function Mocnina(X, K: Integer; var OK: Boolean): Integer;
var
  Hranica: Integer;
begin
  Result := 1;
  Hranica := MaxInt div X;
  while (K > 0) and (Result <= Hranica) do
  begin
    Result := Result * X;
    Dec(K);
  end;
  OK := K = 0;
end;

Funkcia Random2 – náhodné číslo zo zadaného intervalu:

function Random2(A, B: Integer): Integer;
begin
  Result := Random(B - A + 1) + A;
end;

Funkcia Dist2 – vzdialenosť dvoch robotov:

function Dist2(Robot1, Robot2: TRobot): Real;
begin
  Result := Sqrt(Sqr(Robot1.X-Robot2.X) + Sqr(Robot1.Y-Robot2.Y));
  // alebo Result := Robot1.dist(Robot2.X, Robot2.Y);
end;

Funkcia JePrvocislo zistí, či je dane číslo prvočíslom. Najprv jednoduché, ale veľmi neefektívne riešenie:

function JePrvocislo(Cislo: Integer): Boolean;
var
  I: Integer;
begin
  Result := False;
  I := 2;
  while I < Cislo do
  begin
    if Cislo mod I = 0 then
      Exit;
    Inc(I);
  end;
  Result := True;
end;

Trochu vylepšená verzia tejto funkcie kontroluje výrazne menej potenciálnych deliteľov, a teda je o kúsok efektívnejšia:

function JePrvocislo(Cislo: Integer): Boolean;
var
  I: Integer;
begin
  Result := False;
  if (Cislo > 2) and (Cislo mod 2 = 0) or (Cislo < 2) then
    Exit;
  I := 3;
  while I * I <= Cislo do
  begin
    if Cislo mod I = 0 then
      Exit;
    Inc(I, 2);
  end;
  Result := True;
end;

Ďalšie námety:

  • celočíselná funkcia, ktorá počíta NSD dvoch prirodzených čísel Euklidovým algoritmom
  • procedúra SKRAT(a, b, p, q) s celočíselnými parametrami (b<>0), ktorá skráti zlomok a/b na základný tvar p/q
  • dve prirodzené čísla sa nazývajú "priateľské", ak je každé z nich rovné súčtu všetkých deliteľov druhého bez neho samého (také sú napr. čísla 220 a 284); vypíšte všetky páry "priateľských" čísel, ktoré nepresahujú zadané prirodzené číslo


  • suma radu, prvočísla, fibonnaci,
  • graf funkcie
  • pozor - ak je volaná ako procedúra


Premenné parametre

Máme procedúru, ktorá vráti pomocou var-parametra minimum z dvoch čísel:

procedure Min(A, B: Integer; var Vysledok: Integer);
begin
  if A < B then
    Vysledok := A
  else
    Vysledok := B;
end;

Používanie takto definovanej procedúry nie je veľmi pohodlné: vždy treba mať k dispozícii premennú, do ktorej dostávame výsledok výpočtu - v tomto prípade minimum z dvoch celých čísel.


- predpríprava na funkcie, funkcie, ktoré vrátia viac hodnôt, - riešenie kvadratickej rovnice (jeden parameter je počet real riešení, ďalšie 2 sú riešenia) - akokeby funkcia vymení hodnotu dvoch premenných - upravený postup pri volaní


Začneme takouto úlohou: napíšeme program, v ktorom sa 2 roboty budú neustále pohybovať po svojich kruhových dráhach. Súčasne s nimi sa tretí robot bude neustále snažiť byť presne v strede medzi nimi (ako keby bol v strede gumenej nite, ktorá je pripevnená na týchto dvoch robotoch) Vytvoríme si pomocnú procedúru PocitajStred, ktorá z dvoch robotov Robot1 a Robot2 vypočíta súradnice stredu (SX, SY) pre tretieho robota:

procedure TForm1.Button1Click(Sender: TObject);
var
  Robot1, Robot2, Robot3: TRobot;
  StredX, StredY: Real;
 
  procedure PocitajStred;
  begin
    StredX := (Robot1.X + Robot2.X) / 2;
    StredY := (Robot1.Y + Robot2.Y) / 2;
  end;
 
begin
  Robot1 := TRobot.Create(80, 10, 89);
  Robot2 := TRobot.Create(380, 200, 0);
  PocitajStred;
  Robot3 := TRobot.Create(StredX, StredY, 0);
  Robot3.PC := clRed;
  while True do
  begin
    Robot1.Fd(4);
    Robot1.Rt(3);
    Robot2.Fd(3);
    Robot2.Lt(2);
    PocitajStred;
    Robot3.SetXY(StredX, StredY);
    Wait(10);
  end;
end;

...


V tomto príklade sme vytvorili procedúru PocitajStred bez parametrov, lebo táto "vidí" aj na roboty Robot1, Robot2 aj na premenné StredX a StredY. Tieto premenné sú pre ňu globálne (nie sú to jej lokálne premenné). Tento spôsob, keď procedúra používa globálne premenné, nie je vždy použiteľný, lebo niekedy môžeme potrebovať vypočítať stred iných dvoch robotov a výsledok dať do iných dvoch premenných, alebo samotnú procedúru chceme mať "globálnu" a táto nebude vidieť na potrebné premenné (lokálne v našej procedúre Button1Click).

Úlohu preprogramujeme tak, aby sa nepoužili "globálne" premenné, ale parametre:

procedure TForm1.Button1Click(Sender: TObject);
 
  procedure PocitajStred(Robot1, Robot2: TRobot; SX, SY: Real);
  begin
    SX := (Robot1.X + Robot2.X) / 2;
    SY := (Robot1.Y + Robot2.Y) / 2;
  end;
 
var
  Robot1, Robot2, Robot3: TRobot;
  StredX, StredY: Real;
begin
  Robot1 := TRobot.Create(80, 10, 89);
  Robot2 := TRobot.Create(380, 200, 0);
  PocitajStred(Robot1, Robot2, StredX, StredY);
  Robot3 := TRobot.Create(StredX, StredY, 0);
  Robot3.PC := clRed;
  while True do
  begin
    Robot1.Fd(4);
    Robot1.Rt(3);
    Robot2.Fd(3);
    Robot2.Lt(2);
    PocitajStred(Robot1, Robot2, StredX, StredY);
    Robot3.SetXY(StredX, StredY);
    Wait(10);
  end;
end;

Takto zapísaný program nebude fungovať, lebo parametre SX a SY sú "lokálne" premenné, ktoré sú inicializované vstupnými hodnotami parametrov a po skončení procedúry sa ich hodnoty "zabúdajú". Doteraz známy mechanizmus parametrov nám v tomto prípade nepomôže - potrebujeme niečo nové.


Parametre v Pascale


Existujú rôzne prístupy k informáciám - predstavme si napr. diár, pre ktorý majú rôzne osoby rôzne prístupové práva:

  • vlastník - zápis, čítanie
  • 1. sekretárka - prístup k duplikátu - čítanie, modifikácia kópie
  • 2. sekretárka - len čítanie
  • ostatní - žiadny prístup

Typy prístupov k informáciám:

  • úplný - čítanie, zápis
  • len čítanie
  • len zápis
  • úplný prístup k duplikátu
  • žiaden

Poznáme štandardné procedúry na prácu so súbormi:

  • procedúra Read(Subor, X)
    • prístup k súboru '"Subor'" na čítanie
    • k premennej "'X'" na zápis (pôvodný obsah premennej nás nezaujíma)
  • procedúra Write(Subor, X)
    • prístup k súboru '"Subor na zápis
    • k premennej "'X"' na čítanie

3 typy parametrov v Pascale

  • prístup k duplikátu - hodnotové parametre, volanie hodnotou (mali sme doteraz)
  • úplný prístup - premenné parametre, volanie adresou (var parametre)
  • prístup len na čítanie - konštantné parametre (hodnota parametra sa nezmení) (const parametre)

V predchádzajúcom príklade sme procedúre PocitajStred poslali všetky parametre ako hodnotové, t.j. použili volanie hodnotou. Čo sa teda stane:

  • vypočítajú sa vstupné hodnoty parametrov, s ktorými je procedúra volaná (v tomto prípade Robot1, Robot2 a momentálne hodnoty StredX a StredY)
  • odovzdá sa riadenie procedúre, pričom sa pre ňu vytvoria lokálne premenné Robot1, Robot2, SX, SY a do týchto sa priradia hodnoty, ktoré sa pri volaní vypočítali
  • s robotmi (grafickými perami) nebude problém, lebo nepotrebujeme meniť hodnoty robotov, problém bude s premennými SX a SY:
    • tu by sa nám viac hodil úplný prístup k týmto premenným, t.j. potrebovali by sme volanie adresou
    • v Pascale sa to zapíše rezervovaným slovom var pred príslušným parametrom

Korektné riešenie predchádzajúcej úlohy:

  procedure PocitajStred(Robot1, Robot2: TRobot; var SX, SY: Real);
  begin
    SX := (Robot1.X + Robot2.X) / 2;
    SY := (Robot1.Y + Robot2.Y) / 2;
  end;

Teraz zhrňme všetky doterajšie poznatky o tom ako pracuje počítač pri volaní procedúry. Keď sa objaví v programe meno procedúry (musela byť definovaná v deklaráciách), ide o volanie tejto procedúry, t.j.

  1. zapamätá sa návratové miesto (kam sa bude treba vrátiť)
  2. vytvoria sa lokálne premenné procedúry:
    • naozajstné lokálne premenné dostávajú nedefinovanú hodnotu
    • aj hodnotové parametre sú lokálne premenné - len sú inicializované vstupnými hodnotami parametrov (duplikát)
    • premenné parametre sa nikde nevytvárajú - sú to len dočasné nové mená premenných, ktoré sú vstupnými (skutočnými) parametrami
  3. prenesie sa riadenie programu do tela podprogramu
  4. vykonajú sa všetky príkazy podprogramu
  5. zrušia sa lokálne premenné - teda aj prípadné nové hodnoty hodnotových parametrov
    • pre premenné parametre sa len zrušia ich dočasné mená
  6. riadenie sa vráti za miesto v programe, odkiaľ bol podprogram volaný

Ukážme si teraz inú verziu tejto úlohy, kde jeden z robotov chodí po obvode štvorca a druhý po kružnici. Všimnite si, že procedúru PocitajStred sme vytiahli pred Button1Click, takže sa stala globálnou:

procedure PocitajStred(Robot1, Robot2: TRobot; var SX, SY: Real);
begin
  SX := (Robot1.X + Robot2.X) / 2;
  SY := (Robot1.Y + Robot2.Y) / 2;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  Robot1, Robot2, Robot3: TRobot;
  StredX, StredY: Real;
  I: Integer;
begin
  Robot1 := TRobot.Create(300, 250, 27);
  Robot2 := TRobot.Create(200, 200, 0);
  PocitajStred(Robot1, Robot2, StredX, StredY);
  Robot3 := TRobot.Create(StredX, StredY, 0);
  Robot3.PC := clRed;
  while True do
  begin
    for I := 1 to 30 do    // skúste iný počet opakovaní, napr. 20
    begin
      Robot1.Fd(4);
      Robot2.Fd(3);
      Robot2.Lt(2);
      PocitajStred(Robot1, Robot2, StredX, StredY);
      Robot3.setxy(StredX, StredY);
      Wait(10);
    end;
    Robot1.Rt(90);   // skúste iné uhly: 120, 144 a pod.
  end;
end;
...



späť | ďalej