2.Prednaska

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

úlohy | cvičenie


For-cyklus

Pozrite sa na program, v ktorom sme nejaký príkaz zapísali úplne rovnako viackrát:

procedure Form1.Button1Click(Sender: TObject);
begin
  Image1.Canvas.TextOut(Random(300), Random(200), 'Hello');
  Image1.Canvas.TextOut(Random(300), Random(200), 'Hello');
  Image1.Canvas.TextOut(Random(300), Random(200), 'Hello');
  Image1.Canvas.TextOut(Random(300), Random(200), 'Hello');
  Image1.Canvas.TextOut(Random(300), Random(200), 'Hello');
end;

Vo väčšine programovacích jazykoch existujú konštrukcie (hovoríme im cyklus), pomocou ktorých sa jednoducho zapíše opakovanie nejakých príkazov. Začneme tzv. cyklom s pevným počtom opakovaní. Jeho zápis v Pascale (syntax) je:

for premenná := hodnota1 to hodnota2 do
  iba jeden príkaz;

alebo

for premenná := hodnota1 to hodnota2 do
begin
  viac príkazov;
end

Zapíšeme predchádzajúci príklad pomocou tejto konštrukcie a vysvetlíme, ako funguje:

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
begin
  for I := 1 to 5 do
    Image1.Canvas.TextOut(Random(300), Random(200), 'Hello');
end;

náhodne 5-krát Hello

Programová konštrukcia for-cyklus v tomto príklade označuje, že jeden príkaz (výpis textu) sa zopakuje 5-krát. For-cyklus na to používa tzv. počítadlo cyklu (niekedy sa tomu hovorí aj riadiaca premenná cyklu). Je to nejaká premenná, v ktorej si program postupne počíta prechody cyklu. Na začiatku sa do tejto premennej priradí štartová hodnota cyklu (hodnota1, t.j. 1), potom sa bude tejto premennej táto hodnota postupne zvyšovať o jedna, až kým nadobudne koncovú hodnotu cyklu (hodnota2, t.j. 5). Pre každú túto hodnotu (teda presne 5-krát) sa vykoná telo cyklu, t.j. jeden príkaz Image1.Canvas.TextOut(Random(300), Random(300), 'Hello');

Pre for-cykly platí, že koncová hodnota hodnota2 by nemala byť menšia ako štartová hodnota1, lebo inak sa telo cyklu nevykoná ani raz. Zrejme, ak sú tieto dve hodnoty rovnaké, telo cyklu sa vykoná len raz.

Ešte pripomíname, že premenná cyklu musí byť už zadeklarovaná. Táto premenná ešte pred príkazom cyklu je "obyčajnou" premennou a môžeme ju používať na ľubovoľné účely. Počas cyklu sa stáva počítadlom a nesmie sa nijakým spôsobom do nej priraďovať. Po skončení cyklu (až po záverečný end) je to opäť obyčajná premenná.

Ak chceme vo for-cykle vykonať viac ako jeden príkaz, použije zápis s konštrukciou begin a end. Ukazuje to nasledovný príklad:

procedure Form1.Button1Click(Sender: TObject);
var
  I, X, Y: Integer;
begin
  for I := 1 to 9 do
  begin
    X := Random(300);
    Y := Random(200);
    Image1.Canvas.Brush.Color := clYellow;
    Image1.Canvas.Ellipse(X - 10, Y - 10, X + 10, Y + 10);
  end;
end;

9 náhodných žltých kruhov

Program nakreslí na náhodné pozície 9 žltých kruhov. V tomto cykle sa postupne vykonajú príkazy tela cyklu a to 9-krát. Ak si uvedomíme, že počas každého prechodu cyklu bude mať počítadlo (premenná I) príslušnú hodnotu. Teda pri prvom prechode hodnotu 1, pri druhom 2 atď. a pri poslednom hodnotu 9. Túto premennú môžeme v tele cyklu používať ale nemeniť, preto ju vypíšeme do nakreslených kruhov:

procedure Form1.Button1Click(Sender: TObject);
var
  I, X, Y: Integer;
begin
  for I := 1 to 9 do
  begin
    X := Random(300);
    Y := Random(200);
    Image1.Canvas.Brush.Color := clYellow;
    Image1.Canvas.Ellipse(X - 10, Y - 10, X + 10, Y + 10);
    Image1.Canvas.TextOut(X - 3, Y - 8, IntToStr(I));
  end;
end;

9 náhodných kruhov s číslami

Premennú cyklu môžeme využiť aj na umiestňovanie kruhov v ploche. Do X a Y nebudeme priraďovať náhodné hodnoty, ale hodnoty vypočítame pomocou premennej I. Y bude konštantné, X vypočítame tak, aby sa kruhy nakreslili tesne vedľa seba. Napr. prvý kruh nech má X = 30, druhý kruh 50, ďalší 70 a každý ďalší o 20 viac (lebo priemer je 10 a kruh je posunutý o veľkosť dvoch priemerov). Môžeme to zapísať takýmto výrazom:

procedure Form1.Button1Click(Sender: TObject);
var
  I, X, Y: Integer;
begin
  for I := 1 to 9 do
  begin
    X := I * 20 + 10;
    Y := 100;
    Image1.Canvas.Brush.Color := clYellow;
    Image1.Canvas.Ellipse(X - 10, Y - 10, X + 10, Y + 10);
    Image1.Canvas.TextOut(X - 3, Y - 8, IntToStr(I));
  end;
end;

9 kruhov v rade

Pozrime príkazy v tele cyklu. Dva z nich sú priraďovacie príkazy a stále (9-krát) sa priraďujú tie isté hodnoty. Takéto príkazy môžeme vytiahnuť pred cyklus, lebo pre každý prechod majú mať rovnaké hodnoty. Napr.

procedure Form1.Button1Click(Sender: TObject);
var
  I, X, Y: Integer;
begin
  X := 30;
  Y := 100;
  Image1.Canvas.Brush.Color := clYellow;
  for I := 1 to 9 do
  begin
    Image1.Canvas.Ellipse(X - 10, Y - 10, X + 10, Y + 10);
    Image1.Canvas.TextOut(X - 3, Y - 8, IntToStr(I));
    X := X + 20;
  end;
end;

V tomto príklade nepočítame X pomocou premennej cyklu I, ale pred cyklom sme do nej priradili počiatočnú hodnotu 30 a pri konci cyklu sme túto hodnotu zvýšili o 20, takže v ďalšom prechode už bude X o 20 väčšie.

V ďalšom príklade ukážeme spôsob, ako sa niečo vypisuje do grafickej plochy, pričom by tento výpis mal ísť do viacerých riadkov.

procedure TForm1.Button1Click(Sender: TObject);
var
  I, Y: Integer;
begin
  Y := 0;
  Image1.Canvas.Font.Height := 20;
  Image1.Canvas.Font.Name := 'Consolas';
  for I := 1 to 10 do
  begin
    Image1.Canvas.TextOut(30, Y, Format('%3d%5d%5d', [I, I * I, I * I * I]));
    Y := Y + 20;
  end;
end;

tabuľka druhých a tretích mocnín

Všimnite si použitie premennej Y, ktorej sme ešte pred cyklom priradili počiatočnú hodnotu 0 a po každom prechode cyklu sme túto premennú zvýšili o 20 (Y:=Y+20). Tiež je zaujímavá malá modifikácia %d parametra vo funkcii Format. Namiesto %d píšeme %3d alebo %5d, ktoré teraz označujú šírku vypisovanej hodnoty, t.j. počet znakov, na ktoré sa zadané číslo vypisuje. Pri tomto výpise sa číslo doplní medzerami zľava.

Niekedy budeme riešiť úlohu, ktorú vieme zapísať bez cyklu (niekedy je to dosť zdĺhavé), ale ak ju treba zapísať cyklom, môžeme mať s tým ešte problémy. Pozrite tento program, ktorý kreslí niekoľko úsečiek pod sebou:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Image1.Canvas.Pen.Width := 3;
  Image1.Canvas.Pen.Color := clGreen;
  Image1.Canvas.Line(50, 20, 300, 20);
  Image1.Canvas.Line(50, 40, 270, 40);
  Image1.Canvas.Line(50, 60, 240, 60);
  Image1.Canvas.Line(50, 80, 210, 80);
  Image1.Canvas.Line(50, 100, 180, 100);
  Image1.Canvas.Line(50, 120, 150, 120);
  Image1.Canvas.Line(50, 140, 120, 140);
  Image1.Canvas.Line(50, 160, 90, 160);
  Image1.Canvas.Line(50, 180, 60, 180);
end;

9 úsečiek kreslených bez cyklu

Radi by sme riadok s kreslením úsečky Line(X1, Y1, X2, Y2) opakovali v nejakom for-cykle. Vidíme, že sa nakreslilo 9 úsečiek. Pre každú z nich sa nejako mení Y1 aj X2 aj Y2. Predpokladajme, že to budeme riešiť pomocou cyklu for I := 1 to 9 do. Vidíme, že Y1 aj Y2 je 20-násobok I. X2 sa stále zmenšuje, pričom pre I=1 je to 300, pre I=2 je to 270, pre I=3 je to 240, atď. Teda vidíme, že sa opakuje tento jeden príkaz:

  Image1.Canvas.Line(50, 20 * I, 330 - 30 * I, 20 * I);

Program prepíšeme pomocou for-cyklu:

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
begin
  Image1.Canvas.Pen.Width := 3;
  Image1.Canvas.Pen.Color := clGreen;
  for I := 1 to 9 do
  begin
    Image1.Canvas.Line(50, 20 * I, 330 - 30 * I, 20 * I);
    Repaint;
    Sleep(500);
  end;
end;

9 úsečiek cyklom

Na tomto príklade ukazujeme, že pri zápise časti programu pomocou cyklu môžeme využiť to, že niekoľko krokov rozpíšeme bez cyklu a hľadáme, čo sa v ňom opakuje a čo pritom bude treba prepočítať. Do tela cyklu sme pridali dvojicu príkazov Repaint; Sleep(500); - tieto slúžia na mierne spomalenie výpočtu a označujú: okamžite vykresli grafickú plochu a potom pozdrž beh programu o pol sekundy. Príkaz Sleep(ms) pozdrží o ms mikrosekúnd, preto 500 označuje 500/1000 = 1/2 sekundy. Tento príkaz používame len v úvodných prednáškach kvôli názornosti, neskôr sa naučíme správny spôsob spomaľovania behu, napr. pre animáciu.


Farby

Môžeme predpokladať, že všetky farby v počítači sú namiešané z troch základných farieb: červenej, zelenej a modrej (model RGB, teda Red, Green, Blue). Farba závisí od toho, ako je v nej zastúpená každá z týchto troch farieb. Zastúpenie jednotlivej farby vyjadrujeme číslom od 0 do 255 (zmestí sa do jedného bajtu), napr. žltá farba vznikne, ak namiešame 255 červenej, 255 zelenej a 0 modrej. Ak budeme zastúpenie každej farby trochu meniť, napríklad 250 červenej, 240 zelenej a hoci 100 modrej, stále to bude žltá, ale v inom odtieni. Na skladanie farieb máme k dispozícii funkciu RGBToColor, ktorej zadáme tri čísla od 0 do 255 a ona vytvorí príslušnú farbu. Napr. niektoré zo známych preddefinovaných farieb majú takéto vyjadrenie:

  clBlack  = RGBToColor(0, 0, 0)
  clRed    = RGBToColor(128, 0, 0)
  clGreen  = RGBToColor(0, 128, 0)
  clBlue   = RGBToColor(0, 0, 255)
  clGray   = RGBToColor(128, 128, 128)
  clYellow = RGBToColor(255, 255, 0)
  clWhite  = RGBToColor(255, 255, 255)

V skutočnosti sú farby v počítači zakódované v štvorbajtovej hodnote – rovnako ako celé čísla Integer. Pascal dovoľuje pracovať s týmito farbami, ako keby to boli celé čísla. Ak predpokladáme, že rôznych farieb v počítači je 256*256*256 (čo je 16777216), tak by sme náhodnú farbu mohli generovať takýmto zápisom:

  Image1.Canvas.Brush.Color := Random(256 * 256 * 256);

Toto môžeme zapísať aj pomocou funkcie RGBToColor, prípadne môžeme "namiešať" naozaj ľubovoľnú farbu podľa našich predstáv. Môžeme generovať náhodné farby s rôznymi vlastnosťami, napr.

  RGBToColor(Random(255), Random(255), Random(255))  // úplne náhodná farba
  RGBToColor(0, 0, Random(255))                      // odtiene modrej (od čiernej po modrú)
  RGBToColor(Random(255), 0, Random(255))            // náhodná fialová (červená až modrá)

Nasledujúci program nakreslí vedľa seba tri farebné kruhy s rôznymi odtieňmi červenej, zelenej a modrej farby. Každé zatlačenie tlačidla jemne zmení tieto odtiene.

procedure TForm1.Button1Click(Sender: TObject);
begin
  Image1.Canvas.Brush.Color := RGBToColor(128 + Random(128), 0, 0);
  Image1.Canvas.Ellipse(50 - 50, 150 - 50, 50 + 50, 150 + 50);
  Image1.Canvas.Brush.Color := RGBToColor(0, 128 + Random(128), 0);
  Image1.Canvas.Ellipse(150 - 50, 150 - 50, 150 + 50, 150 + 50);
  Image1.Canvas.Brush.Color := RGBToColor(0, 0, 128 + Random(128));
  Image1.Canvas.Ellipse(250 - 50, 150 - 50, 250 + 50, 150 + 50);
end;

3 náhodne zafarbené kruhy

Zaujímavý efekt dosiahneme, keď meníme farby tak, že postupne prechádza jedna farba do druhej. Napr. Začneme s maximálnou červenou, červenú postupne znižujeme a pritom zvyšujeme zelenú, na záver ostane vidieť už len zelená.

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
begin
  for I := 0 to 255 do
  begin
    Image1.Canvas.Pen.Color := RGBToColor(255 - I, I, 0);
    Image1.Canvas.Line(I, 0, I, Image1.Height);
    // Repaint;
  end;
end;

postupný prechod od jednej farby k druhej

Ak do cyklu vložíme príkaz Repaint;, program sa trochu spomalí hoci sme tam ani nevložili Sleep(...);.


Podmienený príkaz

Pri programovaní často narazíme na situácie, keď potrebujeme, aby sa nejaké príkazy vykonali len pri splnenej podmienke. Prípadne neskôr uvidíme, že aj cyklus môže bežať nie pevný počet krát, ale len kým je splnená nejaká podmienka. Najjednoduchší spôsob vytvárania podmienok je použitie relačných operácií: <, <=, >, >=, <>, =. Pomocou nich môžeme porovnávať nejaké dve číselné hodnoty, napr.

  X < 150
  Y > Image1.Height div 2
  X + Y > 200

Pri takýchto výrazoch predpokladáme, že relačné operátory majú nižšiu prioritu ako všetky aritmetické operácie. Z matematiky už poznáme aj niektoré logické operácie ako

  • not - negácia podmienky
  • and - musí platiť súčasne
  • or - musí platiť aspoň jedna z podpodmienok

Keď zhrnieme skúsenosti z matematiky pri vyhodnocovaní aritmetických výrazov a pridáme pravidlá Pascalu pre relačné a logické operácie, dostaneme takúto tabuľku:

unárne not
násobenie a delenie *   /   div   mod   and
súčet a rozdiel +   -   or
relačné =   <   <=   >   >=   <>

Najvyššiu prioritu má logická negácia not. Trochu nižšiu majú násobenie, delenie a logický súčin and. Ak je v jednom výraze viac operátorov z rovnakej úrovne, tak sa väčšinou vo výraze vyhodnocujú zľava doprava. Samozrejme, že budeme využívať okrúhle zátvorky, aby sme toto poradie vyhodnocovania ovplyvnili podľa našich predstáv. Pozrime sa na zložitejší výraz:

  X < 100 or Y >= 50

Podľa tabuľky priorít vidíme, že ako prvé sa bude vyhodnocovať or, t.j. 100 or Y, potom sa výsledok tejto operácie porovná, či je väčší ako X a ďalej by sa to porovnávalo s 50 ... Zrejme sme tu neočakávali, že by sa mal vyhodnocovať práve takto (Pascal by aj tak hlásili chybu, že sa to nedá vyhodnotiť). Správne to teda malo vyzerať takto:

  ( X < 100 ) or ( Y >= 50 )

Teraz sa naučíme tzv. príkaz vetvenia (podmienený príkaz). Jeho zápis v Pascale (syntax) je:

if podmienka then
  príkaz1
else
  príkaz2;

Podobne ako for-cyklus, aj tento príkaz v sebe obsahuje nejaké iné príkazy a v tomto prípade sú dva: 1 a príkaz2. Vnorený príkaz príkaz1 sa vykoná jedine vtedy, keď výsledok podmienky je pravdivý inak sa vykoná len vnorený príkaz príkaz2. Teda vždy sa vykoná len jeden z týchto dvoch príkazov a to buď iba príkaz1, ak bola podmienka splnená alebo iba príkaz2, ak bola podmienka nesplnená.

Prvý program, v ktorom použijeme podmienený príkaz, po zatlačení tlačidla Button1 vygeneruje 1000 bodov X, Y na náhodných pozíciách a ak bude X menšie ako 150, nakreslí modrú bodku veľkosti 10 inak bude červená:

procedure TForm1.Button1Click(Sender: TObject);
var
  I, X, Y: Integer;
begin
  Image1.Canvas.Pen.Width := 10;
  for I := 1 to 1000 do
  begin
    X := Random(Image1.Width);
    Y := Random(Image1.Height);
    if X < 150 then
      Image1.Canvas.Pen.Color := clBlue
    else
      Image1.Canvas.Pen.Color := clRed;
    Image1.Canvas.Line(X, Y, X + 1, Y);
  end;
end;

1000 modrých a červených bodiek

Pridáme ešte jednu podmienku: bodky budú žlté, ale len keď je Y > 100 a inak nech ostanú modré alebo červené. Tento zápis ale obsahuje chybu:

var
  I, X, Y: Integer;
begin
  Image1.Canvas.Pen.Width := 10;
  for I := 1 to 1000 do
  begin
    X := Random(Image1.Width);
    Y := Random(Image1.Height);
    if X < 150 then
      Image1.Canvas.Pen.Color := clBlue
    else
      Image1.Canvas.Pen.Color := clRed;
    if Y > 100 then
      Image1.Canvas.Pen.Color := clYellow
    else
      // nemeň farbu - nechaj ju nastavenú, ako bola
    Image1.Canvas.Line(X, Y, X + 1, Y);
  end;
end;

1000 modrých a červených bodiek - bez žltej

Problémom je použitie druhého if-príkazu. Totiž tento príkaz, ak je Y > 100 nastaví farbu pera na žltú, inak vykoná jeden príkaz za else. Lenže komentár sa nepovažuje za príkaz a preto príkazom za else je až nakreslenie bodky (pomocou Line). Preto sa žlté bodky vôbec nekreslili: v podmienenom príkaze sa buď nastavila farba (zbytočne) alebo kreslila bodka.

Uvedieme niekoľko správnych riešení, napr. v else-vetve naozaj nič nevykonáme:

    if Y > 100 then
      Image1.Canvas.Pen.Color := clYellow
    else ;
      // nemeň farbu - nechaj ju nastavenú, ako bola

Bodkočiarka za else označuje koniec príkazu vo vetve, t.j. za slovom else je prázdny príkaz. Iný zápis:

    if Y > 100 then
      Image1.Canvas.Pen.Color := clYellow
    else
    begin
      // nemeň farbu - nechaj ju nastavenú, ako bola
    end;

Ešte jeden dobrý zápis, v ktorom sme otočili podmienku:

    if Y <= 100 then
    else
      Image1.Canvas.Pen.Color := clYellow;

Najlepšie riešenie bude použitie neúplného podmieneného príkazu, t.j. variantu príkazu, ktorý nemá else vetvu:

if podmienka then
  príkaz;

Ak za príkaz v prvej vetve zapíšeme bodkočiarku, označíme, že else-vetva už nebude, ale za ním už nasleduje nejaký ďalší príkaz. Správne riešenie žltých bodiek by mohlo vyzerať takto:

var
  I, X, Y: Integer;
begin
  Image1.Canvas.Pen.Width := 10;
  for I := 1 to 1000 do
  begin
    X := Random(Image1.Width);
    Y := Random(Image1.Height);
    if X < 150 then
      Image1.Canvas.Pen.Color := clBlue
    else
      Image1.Canvas.Pen.Color := clRed;
    if Y > 100 then
      Image1.Canvas.Pen.Color := clYellow;
    Image1.Canvas.Line(X, Y, X + 1, Y);
  end;
end;

1000 modrých, červených a žltých bodiek

V ďalšom príklade nakreslíme na náhodných pozíciách 20 štvorčekov, pričom sa budú striedavo zafarbovať žltou a zelenou farbou.

var
  I, X, Y: Integer;
begin
  for I := 1 to 20 do
  begin
    X := Random(Image1.Width);
    Y := Random(Image1.Height);
    if I mod 2 = 1 then
      Image1.Canvas.Brush.Color := clYellow
    else
      Image1.Canvas.Brush.Color := clGreen;
    Image1.Canvas.Rectangle(X, Y, X + 20, Y + 20);
  end;
end;

striedavo zafarbené žlté a zelené štvorce

Keby namiesto I mod 2 = 1 bola napr. podmienka I mod 3 <> 0, tak sa budú striedať dva žlté štvorce a jeden zelený.

Aj samotná podmienka môže testovať nejakú náhodu, napr. nech približne 10% (každá desiata) bodiek je väčších a inak zafarbených.

var
  I, X, Y: Integer;
begin
  for I := 1 to 1000 do
  begin
    X := Random(Image1.Width);
    Y := Random(Image1.Height);
    if Random(10) = 0 then
    begin
      Image1.Canvas.Pen.Color := clBlue;
      Image1.Canvas.Pen.Width := 10;
    end
    else
    begin
      Image1.Canvas.Pen.Color := clRed;
      Image1.Canvas.Pen.Width := 4;
    end;
    Image1.Canvas.Line(X, Y, X + 1, Y);
  end;
end;

10% modrých kruhov

Všimnite si, že sme museli použiť dvojice príkazov begin a end, nakoľko chceme aby sa aj v jednej aj v druhej vetve príkazu if vykonávalo viac príkazov.


Podmienený cyklus

Najprv program, ktorý nakreslí tesne vedľa seba 20 zväčšujúcich sa štvorcov. Veľkosti: 10, 20, 30, 40, 50, ... Keďže štvorcov má byť 20, použijeme for-cyklus, napr. takto:

var
  I, A, X: Integer;
begin
  X := 10;         // x-ová súradnica ľavého okraju štvorca
  A := 10;         // veľkosť štvorca
  for I := 1 to 20 do
  begin
    Image1.Canvas.Rectangle(X, 200, X + A, 200 - A);
    Inc(X, A);     // to isté ako X := X + A;
    Inc(A, 10);    // to isté ako A := A + 10;
  end;
end;

zväčšujúce sa štvorce vedľa seba

Vidíme, že sa zobrazilo len 7 štvorcov. Ostatné sa kreslili zbytočne, lebo sú (aspoň čiastočne) mimo grafickej plochy. Počet týchto štvorcov závisí od šírky grafickej plochy, takže bolo by dosť náročné tento počet dopredu vypočítať, aby for-cyklus prebehol menej krát.

Väčšina programovacích jazykov má aj ďalšiu konštrukciu cyklu, tzv. cyklus s podmienkou. Takýto cyklus nemá dopredu daný počet prechodov (ako je to vo for-cykle), ale pred každým prechodom cyklu sa skontroluje dopredu daná podmienka a telo cyklu sa vykoná len keď je podmienka splnená. Zápis takéhoto cyklu v Pascale (syntax) je:

while podmienka do
  iba jeden príkaz;

alebo

while podmienka do
begin
  viac príkazov;
end

Prepíšeme predchádzajúci príklad so štvorcami použitím while-cyklu:

var
  A, X: Integer;
begin
  X := 10;         // x-ová súradnica ľavého okraju štvorca
  A := 10;         // veľkosť štvorca
  while X + A < Image1.Width do
  begin
    Image1.Canvas.Rectangle(X, 200, X + A, 200 - A);
    Inc(X, A);     // to isté ako X := X + A;
    Inc(A, 10);    // to isté ako A := A + 10;
  end;
end;

zväčšujúce sa štvorce while-cyklom

Vidíme, že kreslenie štvorcov sa zastavilo v správnom momente: ďalší kreslený štvorec by už presahoval okraj grafickej plochy. Pozrime sa, ako sa postupne vyhodnocoval tento program (predpokladáme, že šírka grafickej plochy je 304, ? označuje nedefinovanú hodnotu):

premenná A premenná X podmienka X + A < Image1.Width
begin ? ? ?
X := 10; 10
A := 10; 10
while X + A < Image1.Width   True
kresli štvorec
Inc(X, A); 20
Inc(A, 10); 20
while X + A < Image1.Width   True
kresli štvorec
Inc(X, A); 40
Inc(A, 10); 30
while X + A < Image1.Width   True
kresli štvorec
Inc(X, A); 70
Inc(A, 10); 40
while X + A < Image1.Width   True
kresli štvorec
Inc(X, A); 110
Inc(A, 10); 50
while X + A < Image1.Width   True
kresli štvorec
Inc(X, A); 160
Inc(A, 10); 60
while X + A < Image1.Width   True
kresli štvorec
Inc(X, A); 220
Inc(A, 10); 70
while X + A < Image1.Width   True
kresli štvorec
Inc(X, A); 290
Inc(A, 10); 80
while X + A < Image1.Width   False
end;

Nasledujúci program môže byť poučný vo viacerých veciach:

var
  I: Integer;
begin
  I := 37;
  while I > 0 do
    Dec(I);
  Image1.Canvas.Font.Height := 30;
  Image1.Canvas.TextOut(10, 10, IntToStr(I));
end;

test premennej po while-cykle

While-cyklus obsahuje jediný príkaz: zníženie premennej I o 1. Zrejme po 37 prechodoch cyklu I dosiahne 0 a cyklus skončí. Čo sa stane, keď dáme bodkočiarku za slovo do?

var
  I: Integer;
begin
  I := 37;
  while I > 0 do;
    Dec(I);
  Image1.Canvas.Font.Height := 30;
  Image1.Canvas.TextOut(10, 10, IntToStr(I));
end;

Program sa zacyklí, lebo stále testuje podmienku, či je I väčšie ako 0, pritom vykonáva prázdny príkaz: medzi do a ; je prázdny príkaz, ktorý nerobí nič. Takýto program môžeme zastaviť buď v operačnom systéme v správcovi úloh, alebo v prostredí Lazarus tlačidlom Zastaviť (resp. Ctrl-F2).

Zaujímavá situácia vznikne, keď namiesto Dec() zapíšeme Inc(), t.j. namiesto znižovania pôvodnej hodnoty 37 až na 0, budeme túto hodnotu zvyšovať - na prvý pohľad by sa zdalo, že donekonečna. Lenže celočíselná aritmetika nám dovolí ísť len do čísla 2147483647. Keď túto hodnotu zvýšime o 1, buď program spadne na pretečení, alebo nám dá nezmyselný výsledok číslo -2147483648 (to je najmenšie možné celé číslo). To, ako to dopadne závisí od toho, čí máme alebo nemáme zapnutú kontrolu pretečenia (ako v prvej prednáške o 13!).

var
  I: Integer;
begin
  I := 37;
  while I > 0 do
    Inc(I);
  Image1.Canvas.Font.Height := 30;
  Image1.Canvas.TextOut(10, 10, IntToStr(I));
end;

chybová správa

výsledok po pretečení

Všimnite si, že v oboch prípadoch tento program relatívne dlho trval - vyše 2 miliardy krát sa robil príkaz Inc(I).

Keď už vieme ako funguje while-cyklus, ukážme, ako Pascal v skutočnosti interpretuje for-cykly. Takýto for-cyklus

  for premenná := hodnota1 to hodnota2 do
  begin
    príkazy;
  end;

Pascal na to použije pomocnú premennú (nazvali sme ju Koniec), do ktorej si najprv vypočíta koncovú hodnotu. Potom priradí počiatočnú hodnotu cyklu do premennej cyklu. Teraz naštartuje while-cyklus, v ktorom kontroluje, či premenná cyklu neprevyšuje koncovú hodnotu. V tele cyklu najprv vykoná všetky príkazy z tela for-cyklu a na záver zvýši premennú cyklu:

  Koniec := hodnota2;
  premenná := hodnota1;
  while premenná <= Koniec do
  begin
    príkazy;
    Inc(premenná);
  end;

Z tejto realizácie vidíme, že for-cyklus, ak je hodnota1 menšia alebo rovná hodnota2, prejde presne hodnota2 - hodnota1 + 1 krát. Napr. takýto cyklus

  N := 10;
  for I := 1 to N do
  begin
    Image1.Canvas.TextOut(10, Y, IntToStr(I));
    if I > 1 then
      N := 4;
    Inc(Y, 20);
  end;

Tento cyklus prejde vždy 10-krát, aj keď sme v tele cyklu zmenili hodnotu premennej N, ktorá je aj koncovou hodnotou cyklu. Ďalším dôsledkom je aj to, že na výpočet štartovej a koncovej hodnoty môžeme použiť aj premennú, ktorá sa stane riadiacou premennou cyklu. Ukazuje to nasledujúci príklad:

var
  I, Y: Integer;
begin
  Y := 0;
  Image1.Canvas.Font.Height := 20;
  I := 37;
  for I := I - 1 to I + 1 do
  begin
    Image1.Canvas.TextOut(10, Y, IntToStr(I));
    Inc(Y, 20);
  end;
end;

použitie premennej cyklu

Uvedomte si, že takýto spôsob zápisu programu je ťažšie čitateľný a takéto konštrukcie neodporúčame používať.


Vstupný riadok

Do formuláru aplikácie môžeme vložiť aj komponent vstupný riadok - s vnútorným menom Edit1. Komponent vyberieme zo štandardnej palety a vložíme niekam do formuláru - vtedy dostane meno Edit1. Pre nás bude najdôležitejšie nastavenie Text, ktoré môžeme meniť nielen v Inšpektore objektov ale aj v programe (v programe použijeme Edit1.Text). Týmto nastavením sa vieme dostať k textu, ktorý sa momentálne nachádza v editovateľnej časti komponentu. Počas behu programu používateľ môže do tohto riadku písať, resp. ho opravovať a program môže tento text ďalej spracovať.

Nasledujúci program po zatlačení tlačidla Button1 text v komponente Edit1 vypíše na náhodnú pozíciu, pričom zmení na náhodnú aj veľkosť písma:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Image1.Canvas.Font.Height := Random(30) + 10;
  Image1.Canvas.TextOut(Random(250), Random(180), Edit1.Text);
end;

náhodne veľký text

náhodne veľký text

Zrejme sme zatlačili tlačidlo viackrát a menili sme aj text vo vstupnom riadku.

Vstupný riadok môže slúžiť aj na zadávanie čísel, ktoré potom ďalej môžeme spracovať programom. Hodnotu Edit1.Text ale nie je možné priradiť do celočíselnej premennej. Túto textovú hodnotu musíme najprv prekonvertovať na celé číslo. Použijeme štandardnú funkciu StrToInt() (je to vlastne opačná funkcia k IntToStr(), ktorá konvertuje celé číslo na text). Ilustruje to nasledujúci príklad:

procedure TForm1.Button1Click(Sender: TObject);
var
  Cislo: Integer;
begin
  Cislo := StrToInt(Edit1.Text);
  Image1.Canvas.Font.Height := 30;
  Image1.Canvas.TextOut(10, 10, Format('zadal si číslo %d', [Cislo]));
end;

prevod textu na číslo

správa o chybe

Keď sme druhýkrát stlačili Button1, zadali sme text, ktorý nie je správnym textom celého čísla - program spadol a vypísal správu.

Napíšeme program, ktorý zo vstupného riadku prečíta celé číslo a vypíše, či je záporné, nulové alebo kladné:

procedure TForm1.Button1Click(Sender: TObject);
var
  A: Integer;
begin
  A := StrToInt(Edit1.Text);
  Image1.Canvas.Font.Height := 30;
  if A < 0 then
    Image1.Canvas.TextOut(10, 10, Format('%d je záporné', [A]))
  else
    if A = 0 then
      Image1.Canvas.TextOut(10, 10, Format('%d je nulové', [A]))
    else
      Image1.Canvas.TextOut(10, 10, Format('%d je kladné', [A]));
end;

zisťuje celé číslo

zisťuje celé číslo

Všimnite si dva vnorené podmienené príkazy if. Formátovali sme ich podľa pascalovských pravidiel, t.j. vnorené príkazy sú odsunuté vpravo o dve medzery. Keďže pri programovaní aj náročnejších úloh sa veľmi často objavujú vnorené if-príkazy a často je to postupnosť aj viac vnorení, v Pascale (aj v iných programovacích jazykoch) sa to zvykne zapisovať aj takto (tzv. zápis else-if):

  if A < 0 then
    Image1.Canvas.TextOut(10, 10, Format('%d je záporné', [A]))
  else if A = 0 then
      Image1.Canvas.TextOut(10, 10, Format('%d je nulové', [A]))
  else if A = 1 then
      Image1.Canvas.TextOut(10, 10, Format('%d je rovné jedna', [A]))
  else
    Image1.Canvas.TextOut(10, 10, Format('%d je kladné', [A]));

Operácie mod a div môžeme použiť na rozklad čísla na cifry. Nasledujúci príklad ukazuje, ako zistíme poslednú cifru a ako odstránime túto poslednú cifru, t.j. vytvoríme číslo bez poslednej cifry:

procedure TForm1.Button1Click(Sender: TObject);
var
  Cele: Integer;
begin
  Cele := StrToInt(Edit1.Text);
  Image1.Canvas.Font.Height := 30;
  Image1.Canvas.TextOut(10, 10, Format('zadal si %d', [Cele]));
  Image1.Canvas.TextOut(10, 40, Format('posledna cifra = %d', [Cele mod 10]));
  Image1.Canvas.TextOut(10, 70, Format('bez poslednej cifry = %d', [Cele div 10]));
end;

posledná cifra čísla


Euklidov algoritmus


Tento algoritmus je jeden z najznámejších a najstarších algoritmov. Datuje sa okolo 300 rokov pred n. l. Je to vlastne matematický predpis, ako zistiť najväčší spoločný deliteľ (NSD) dvoch čísel, napr A a B:

  • ak sa obe čísla rovnajú, táto hodnota je hľadané NSD
  • ak je A > B, tak A znížime o B
  • ak je A < B, tak B znížime o A
  • toto opakujeme, kým nenájdeme NSD

Zrejme obe hodnoty A aj B musia byť väčšie ako 0, inak sa tento návod môže zacykliť. Zapíšme to pomocou while-cyklu, pričom obe hodnoty prečítame z dvoch vstupných riadkov Edit1 a Edit2:

procedure TForm1.Button1Click(Sender: TObject);
var
  A, B, A0, B0: Integer;
begin
  Image1.Canvas.Rectangle(Image1.ClientRect);
  Image1.Canvas.Font.Height := 20;
  A := StrToInt(Edit1.Text);
  B := StrToInt(Edit2.Text);
  A0 := A;
  B0 := B;
  if (A > 0) and (B > 0) then
  begin
    while A <> B do
      if A > B then
        Dec(A, B)
      else
        Dec(B, A);
    Image1.Canvas.TextOut(10, 100, Format('NSD(%d, %d) = %d', [A0, B0, A]));
  end
  else
    Image1.Canvas.TextOut(10, 100, Format('NSD(%d, %d) = nedef', [A, B]));
end;

najväčší spoločný deliteľ
najväčší spoločný deliteľ

Samotný výpočet Euklidovho algoritmu je v programe vyznačený červenou. Premenné A0 a B0 sú pomocné len kvôli výpisu v TextOut.

Pozrime sa na výpočet NSD(1000, 3): program najprv 333-krát odpočíta 3 od prvej premennej A, ktorá mala hodnotu 1000. Takto dostávame výsledok 1. Lenže k tomuto istému sa vieme rýchlejšie dostať pomocou zvyšku po delení, t.j. A := A mod B; Takto opravený algoritmus dáva rovnaké výsledky ale je výrazne efektívnejší:

    while B > 0 do
    begin
      Pom := B;
      B := A mod B;
      A := Pom;
    end;

Neskôr uvidíme tento algoritmus zapísaný pomocou rekurzie.


späť | ďalej