Második lecke

2. lecke: Vezérlési szerkezetek




A Pascal a struktúrált programozást erősen támogató nyelv. A struktúrált nyelvekben 3 féle vezérlési szerkezet van.

1. szekvencia
2. szelekció
3. iteráció

Az elsőt már ismerjük. A szekvencia nem más mint amikor utasításokat adunk meg egymás után. Ilyenkor a program végrehajtása az első soron kezdődik és halad sorban (szekvenciálisan). Ez nem valami nagy szám. Nézzük meg a másodikat. Szelekció, azaz elágazás. Tegyük fel, az a feladat, hogy eldöntsük egy számról kisebb-e mint 100. Íme a kód:


  Program Elagazas;         { Programunk neve. }
  Uses crt;                 { Crt unit csatolása. }
  
  var szam  : Integer;                        { Változó deklarálása (létrehozása). }

  BEGIN                                       { Főprogram kezdete. }
    ClrScr;
    Write('Add meg a számot: ');
    ReadLn(szam);                             { Bekérjük a számot. }
    if (szam<100) then                        { Ha kisebb 100-nál akkor }
      Write('Ez a szám kisebb mint 100.')
    else                                      { Különben }
      write('Ez a szám nem kisebb mint 100.');
    ReadKey;
  END.                                        { Főprogram vége. }

Az elágazást az "if (feltétel) then utasítás [else utasítás]" szerkezettel lehet megvalósítani. Az if kulcsszót egy feltétel követi. Ha a feltétel igaz, akkor végrehajtja a then utáni utasítást. Itt csak egy utasítás szerepelhet. Az else ág nem kötelező. Ha mégis van, akkor kerül ide a vezérlés, ha a feltétel nem teljesül. Itt is egy utasítás állhat. Fontos megemlíteni, hogy az else előtti utasítás végén nem állhat ';'. Úgy lehetne fordítani ezt, hogy: Ha (feltétel igaz) akkor utasítás_1 különben Utasítás_2;.

Mi a helyzet akkor, ha nem csak egy utasítást szeretnénk végrehajtani. Pl. legyen az a feladat, hogy miután eldöntöttük kisebb-e mint 100 kiszámoljuk mennyivel kisebb és kiírjuk azt is. Ha nem kisebb, megmondjuk mennyivel nagyobb.


  Program Elagazas;         { Programunk neve. }
  Uses crt;                 { Crt unit csatolása. }
  
  var szam  : Integer;      { Változó deklarálása (létrehozása). }

  BEGIN                     { Főprogram kezdete. }
    ClrScr;
    Write('Add meg a számot: ');
    ReadLn(szam);
    if (szam<100) then
    begin
      WriteLn('Ez a szám kisebb mint 100.');
      Write(100-szam,'-al kisebb 100-nál.');
    end                                         { Itt nincs ';'.}
    else
    begin
      WriteLn('Ez a szám nem kisebb mint 100.');
      Write(szam-100,'-al nagyobb 100-nál.');
    end;                                        { Itt viszont van ';'. }
    ReadKey;
  END.                                          { Főprogram vége. }

Ha valamit begin end; közé teszünk, akkor az 1 utasításnak számít. Itt se felejtsük el, hogy az else elé nem szabad ';'-t tenni. Viszont az else ágban az end után kellett a pontosvessző.

Van egy másik módszer is elágazások készítésére. Legyen most az a feladat, hogy beírunk egy számot és ha kisebb mint 10 akkor kiiratjuk azt szöveggel.


  Program elag_2;
  Uses crt;

  var szam : Integer;

  BEGIN
       ClrScr;
       Write('Add meg a számot:');
       ReadLn(szam);
       case (szam) of
            0 : write('Nulla');
            1 : write('Egy');
            2 : write('Kettő');
            3 : write('Három');
            4 : write('Négy');
            5 : write('Öt');
            6 : write('Hat');
            7 : write('Hét');
            8 : write('Nyolc');
            9 : write('Kilenc'){ Ide sem kell pontosvessző, hiszen else következik. }
            else write('Ezt a számot nem tudom kiírni betűkkel');
       end;                    { A "case of"-ot egy end;-nek kell zárnia. }
       ReadKey;
  END.

A működése nagyon egyszerű. A case után megadott változó értéke ha megegyezik az alatta felsorolt értékek egyikével végrehajta az utána álló utasítást. Itt is csak egy utasítás állhat, de mint az if-nél lehetőség van a begin end; páros használatára. Az else ág itt sem kötelező, de ha van, akkor kerül ide a vezérlés ha egyik értékkel sem talált megegyezést. Fontos megemlíteni, hogy a számok (a kettőspont előtt) nem lehetnek változók. Csakis konstansok. Talán az is feltűnt, hogy mit keres ott az az end. Nincs hozzá begin. Ez az end; a case-t zárja le.

Jöhet a harmadik, iteráció. Azaz ciklusok, más szóval valaminek az ismétlése. Ennek több módja is van. Nézzük az elsőt! Legyen az a feladat, hogy megkérdezzük a felhasználót, hányszor szeretné látni a szöveget, majd ki is írjuk annyiszor.


  Program forciklus;
  Uses crt;

  var i : Integer;      { Ciklusváltozó. }
      meddig : Integer; { Hányszor szeretnék ismételni. }

  BEGIN
       ClrScr;
       Write('Hányszor szeretnéd látni: ');
       ReadLn(meddig);
       for i:=1 to meddig do         { Ismételd 1-től az előbb bekért számig. }
       begin
            WriteLn('Helló Világ!');
       end;
       ReadKey;
  END.

A for ciklus egy előírt lépésszámú ciklus. Amint látható, használ egy változót, aminek a neve most i. Ennek az i-nek a "for" után értékül adjuk az 1-et. A "to" után megadjuk meddig szeretnénk növelni i-t. Ez volt a ciklus feje. Most következik a ciklus magja, vagyis amit ismételni fog. Jelen esetben a szöveg kiírása. A Pascal minden egyes iteráció (azaz ismétlés) után növeli 'i' értékét 1-el. Az ismétlődés addig tart amíg 'i' el nem éri a "to" után megadott értéket. Ha 'i'-t 1-ről indítjuk akkor pontosan annyiszor fog ismételni, amennyit megadtunk a "to" után. Miután befejezte a ciklus a működését 'i'-értéke a "to" után megadott értékkel lesz egyenlő.

Természetesen 'i'-t nem kötelező 1-ről indítani. Lehet akár 100-ról is. A "to" után pedig 105-öt adok meg. Ez esetben hányszor fog lefutni?? Egy kis fejszámolás...... Remélem nem dőltél be!! Természetesen 6-szor. Miért?? Mert a ciklus lefut 100-ra, 101-re, 102-re, 103-ra, 104-re, és 105-re. Ha ezt megszámolod, akkor 6db. Ugye az előbb 1-től indultunk. Most pedig 100-tól 105-ig vagyis, (ha kivonunk 100-at) 0-tól 5-ig. Íme meg is van az eltérés oka. Ez a bizonos 0-tól kezdünk nem pedig 1-től "dolog" gyakran meg tudja zavarni a kevésbé tapasztalt programozni tanulót. De te ne hagyd magad, figyelj rá oda!

Ha a "for" képes 'i'-t növelni, vajon csökkenteni tudja? Igen, tudja. Ekkor nem "to"-t kell írni, hanem "downto"-t. Nyílván ekkor a dolgok megfordulnak, és az 'i' kezdőértékének kell nagyobbnak lenni. Ha nem így van, akkor is elindul majd programunk, de nem fog lefutni a ciklusmag egyszer sem. Lássunk erre is egy példát! Készítsünk el egy űrhajó kilövését időzítő programot, vagyis számoljunk 10-től vissza 0-ig!


  Program _downto;
  Uses crt;

  var i : integer;   { Ciklusváltozó. }

  BEGIN
       ClrScr;
       for i:=10 downto 0 do
       begin
            WriteLn(i);
       end;
       ReadKey;
  END.

Itt a kimenet a követező lesz:

  10
  9
  8
  7
  6
  5
  4
  3
  2
  1
  0

Sajnos nem másodpercenként történik a kiírás, hanem olyan gyorsan amilyen gyorsan csak tudja a géped. Valószínüleg csak annyit látsz, hogy miután elindult a program ott lettek a számok. De a "downto"-s ciklus működik és most csak ez számít.

Most jöhet egy másik ciklusfajta, a feltételes ciklus. Ezt akkor használjuk, amikor nem tudjuk pontosan hányszor szeretnénk ismételni valamit. Tegyük fel be akarunk kérni 10-től kisebb számot. Nem tudhatjuk, hogy a felhasználó hányszor fog marhaságot beírni. Mindaddig ismételni kell, amíg hibás az amit beírt. Feltételes ciklusból kettő is van a Pascalban. Van olyan ami elől tesztel és van olyan ami hátul. Az előltesztelős ciklus magja lehet, hogy nem fut le egyszer sem. Ha a feltétel már az elején nem teljesül, akkor be sem lép ciklusba. Ezzel ellentétben a hátultesztelős ciklus lefuttatja a magját egyszer, majd csak utána ellenőrzi a feltétel teljesülését. Itt a ciklusmag 1-szer mindenféleképpen lefut. Nézzük meg először az előltesztelőset!


  Program _while;
  Uses crt;

  var szam : Integer;

  BEGIN
       ClrScr;
       szam := 11;                { Így teszem igazzá a while-ban megadott feltételt. }
       while (szam >= 10 ) do     { Ismételd, ha a feltétel igaz. }
       begin                      { Ciklus magja. }
            Write('Adj meg egy 10-től kisebb számot: ');
            ReadLn(szam);
       end;
  END.

A "while" kulcsszó után itt is (hasonlóan az "if"-hez) egy feltételt kell megfogalmazni. A ciklus mindaddíg ismétlődik amíg a feltétel igaz! A szam változó értéke a létrehozása után automatikusan nulla lesz. (A C-vel ellentétben a Pascal ad kezdőértéket a változóknak.) A nulla ugye kisebb 10-től, így a feltétel nem teljesülne és nem futna le egyszer sem a ciklusmag, nem kérnénk be egy számot sem a felhasználótól. Ezért atdam értékül neki 11-et. Látszik, hogy a for ciklussal ellentétben itt nem változik automatikusan semminek sem az értéke. A for ciklus előbb utóbb véget fog érni. A while nem biztos. Könnyen lehet olyan feltételt kitalálni ami mindíg teljesül. Ezeket a ciklusokat hívják végtelen ciklusoknak. Végtelen ciklusból is több féle létezik, nem térek most ki erre. Gondolom mondanom sem kell, hogy ha egy program sosem ér véget, akkor az rossz. Figyelj oda rá, hogy ez ne forduljon elő. A "for"-ral ellentétben a while ciklus befejezéséről gondoskodni programozó feladata.

Említettem egy másik feltételes ciklust, a hátultesztelőset. Írjuk meg ugyanezt a programot most ezzel.


  Program _repeat;
  Uses crt;

  var szam : Integer;

  BEGIN
       ClrScr;
       repeat { Ismételd... }
             Write('Adj meg egy 10-től kisebb számot: ');
             ReadLn(szam);
       Until (szam < 10); { mindaddig amíg a feltétel hamis. }
  END.

A ciklust a repeat (foglalt szó) vezeti be, de itt a feltétel a ciklus magja után van. Másik fontos eltérés, hogy itt a ciklus addig ismétlődik, amíg a feltétel hamis. Mivel a ciklus magja egyszer mindenképp lefut, fölösleges a szam változónak értéket adni. Az első vizsgálat úgyis csak azután történik, miután már a felhasználó adott neki értéket. A két program működését tekintve teljesen megegyezik. Látható, hogy a két ciklusfajta számos esetben felcserélhető. De akkor melyiket használjuk? Mindíg azt, amelyik jobban illeszkedik az adott feladathoz. Jelen esetben most a repeat-el megspóroltunk egy értékadást, ezért ez jobbnak tűnik. Talán azt is észrevetted, hogy most nem használtunk begin end; párost a ciklusmag megadásánál. Nem is kell, hiszen a mag mindíg a repeat until szavak közé kerül, így pontosan tudja a Pascal, mettől meddig tart az.

Ezzel meg is tárgyaltuk mindhárom vezérlési szerkezetet, mégegyszer, mik is voltak azok? Szekvencia, szelekció, iteráció. A Pascalban elégazásokat és ciklusokat nem csak egymás után lehet használni. Azokat egymásba is lehet ágyazni. Lehet olyat csinálni, hogy egy for-ban van egy if, azon belül egy while stb... Így meglehetősen bonyolult kód készíthető. Fontos, hogy ne féljünk ezektől. Amikor látsz egy kódot, próbáld meg azt fejben futtatni, (de vigyázz a végtelen ciklusokkal, nehogy éhen halj!!). Sok gyakorlás után már nem is fog olyan nehéznek tűnni. Itt fontos a sok szót kiemelnem. Ha ez nehezedre esik, vagy csak egyszerűen nincs hozzá kedved, akkor csak egy megoldás van. Ne foglalkozz programozással, ugyanis nem neked való! Ezt most nem viccből mondtam. Sajnos tele van az egyetem olyan emberekkel akik saját bevallásuk szerint sem szeretnek ilyenekkel bajlódni. A programozást egyfajta kényszernek tartják amit az iskola talált ki pusztán azért, hogy őket szivassák. Szóval, ha úgy érzed nincs kedved a sok tanuláshoz, gyakorláshoz, nincs kedved sokszor napokig agyalni valami elvont kézzel megfoghatatlan baromságon, akkor a programozás nem neked való. Ez persze nem baj, biztosan találsz valami mást ami tetszeni fog. De érdemes már az elején tisztázni, programozni megtanulni nem könnyű, sőt nagyon is nehéz.

Ha még nem vettem volna el a kedved és úgy döntesz folytatod, akkor elárulok egy titkot. Nem is annyira titok ez, hanem inkább tabu a programozók közt. Létezik egy negyedik vezérlési szerkezet is. A feltétel nélküli ugrás. Ez azonban egy "gonosz" szerkezet! Annyira képes megbonyolítani a kódot, hogy azt a készítőjén kívül nem érti meg senki. Nagyobb programokat már nem egy ember készít, hanem 10,20, sőt akár több száz emberből álló csapat. Olyankor létszükség, hogy a másik is értse mit csánál az ember programja. Ezért van az, hogy a programozók nem használják ezt az eszközt. Egyébként matematikusok bizonyították, mindent amit meg lehet csinálni a 4 vezérlési szerkezettel (azaz feltétel nélküli ugrást is használva), azt meg lehet csinálni 3-al is (azaz a feltétel nélküli ugrás nélkül). Így ez feleslegessé vált. Ennek ellenére néha még ma is fel-fel merül. Ugyanis a nyelvekből nem vették ki. A Pascalban is megtalálható. Ezért írtam az elején, hogy a Pascal támogatja a struktúrált programozást. Lehet benne struktúrálatlanul is programozni. Lássunk erre is egy példát!


  Program _goto;
  Uses crt;

  var i : Integer;

  label ide_ugorj;

  BEGIN
       ClrScr;
       for i:=1 to 10 do
       begin
            WriteLn('Ez még az első ciklus.');
            if (i=3) then
            begin
                 goto ide_ugorj;
            end;
       end;
       for i:=6 to 10 do
       begin
            WriteLn('Ez már a második ciklus.');
            if (i>1000) then
            begin
  ide_ugorj:     WriteLn('i értéke nagyobb mint 1000.');
            end;
       end;
       ReadKey;
  END.

Íme a kimente:

  Ez még az első ciklus.
  Ez még az első ciklus.
  Ez még az első ciklus.
  i értéke nagyobb mint 1000.
  Ez már a második ciklus.
  Ez már a második ciklus.
  Ez már a második ciklus.
  Ez már a második ciklus.
  Ez már a második ciklus.
  Ez már a második ciklus.
  Ez már a második ciklus.

Hogy is van akkor ez? Maga a feltétel nélküli ugrás a goto (XY nélkül!!). Ahhoz hogy ukorjunk meg kell mondani azt is, hogy hova. Erre valóak a címkék. Most egy ilyen cimke (label) van aminek az a neve, hogy ide_ugorj. Miután a program elején létrehoztuk, bármelyik sor elé odatehetjük, felcimkézve azt. A goto utasítás kiadásakor meg kell adni melyik cimkére szertnénk ugrani.

Talán furcsáljuk a kimenetet. Az első ciklusnak 10-szer kellett volna lefutni, e helyett csak 3-szor futott. Ez érthető, hiszen amikor i elérte a 3-at beléptünk az if-be és ugrottunk. Méghozzá pont arra a sorra ami kiírja nekünk az "i értéke nagyobb mint 1000." szöveget, nem törődve azzal, hogy egy olyan feltételben van ami sohasem teljesülhet. Ráadásul a második ciklusnak csak 5-ször kellett volna futnia, helyette lefutott 7.5-szer, viszont csak 7-szer írta ki az "Ez már a második ciklus." szöveget. Miért? A goto-val sikeresen beugrottunk egy ciklus közepébe. Így a fejben az i változó 6-ra állítása nem történik meg, továbbra is marad 3. Az első Write után ugrottunk, így az sem hajtódik végre, megtévesztően csak 7-szer lesz kiírva a szöveg, holott a ciklus magja hét és félszer futott le. Na pont az ilyen ocsmányságok elkerülése miatt nem használja szinte senki sem a goto-t.

De akkor miért van benne? Mint említettem minden goto-s programot meg lehet írni goto nélkül is. Vannak viszont olyan feladatok amikor sokkal gyorsabb programot lehet írni goto-val, mint nélküle.

A következő leckében megismerkedünk a logikai műveletekkel, igazságtáblákkal, precedenciával. Ha valamit nem értettél, zavarosnak találsz, írd meg a fórumba.

Vissza