Múltkori számunkban nem volt programtervezés rovat, s ezek szerint nem is hiányoltátok annyira, hogy billentyűt ragadjatok s írjatok nekem. Kérem aki rendszeres olvasója ezen rovatnak eresszen el egy eMail a PC-XUSER@freemail.c3.hu-ra s ígérem előbb olvassa el a következő programtervezés cikket, mint bárki más.

Nos utoljára a léptetéses ciklust tárgyalgattuk most a rutinok használatát, jelentőségét vesszük nagyítónk alá.

Elmélet

Mire jó a rutin, mikor használjuk ?

  1. A programot - elvileg - úgy kell megírni, hogy adott egy nagy probléma. Ezt rész problémákra bontjuk, amelyek már külön egységként kezelhetőek. Tehát finomítjuk a problémát. A rész problémákat most külön - külön vesszük nagyítónk alá, s megpróbáljuk mindig az egyiket megoldani. Egy részprobléma megoldása hasonlóan történik: ezt is annyi részproblémára bontjuk, ami már megegyezik a programozási nyelv alaputasításaival. (amíg le nem írható if then, for, … alap egységekkel). Ezt a folyamatot lépésenkénti finomításnak nevezik. A program részegységeinek elkülönítésére – is – szolgálnak a rutinok. Minden főbb problémát és annak megvalósítását, ha szükséges külön rutinba tehetjük.
  2. Persze nem csak a program felépítésénél használhatjuk előnyösen, hanem általában vannak olyan feladatok – művelet sorozatok, amit többször is használunk egy program megírása során. Ilyenek az adatok beolvasását és ellenőrzését végző programrészek melyeket érdemes egy rutinba foglalni, hogy ezek után csak a rutint meghívva egy sorral elvégezhessük a beolvasást, megkímélve magunkat a beolvasó algoritmus sokszori begépelésétől. (s persze a programunk is rövidebb lesz jó pár byte-tal.)
  3. A 2. Pontból adódóan a rutinoknak mindig olyan nevet kell (saját jól felfogott érdekünkben) adni, mely utal az általa végrehajtott tevékenység sorozatra.
Most hogy jól körül írtuk miért használjuk, akkor lépjünk a tettek mezejére, s vegyünk egy ismerős példát (ezt a példát én is főiskolán hallottam, ahol előadáson sokan alszanak), nos:

Íme egy reggeli készülődés számítástechnikus által megfogalmazott stuktogrammja:
 

 
Nos ezt a programot kellene optimalizálnunk méghozzá úgy, hogy a kétszer előforduló kódrészeket egy rutinba foglaljuk. A rutinokat stuktogrammal az alábbi módon jelöljük:

Bővebben:

Így a feladatunk leegyszerűsödött, s így fog kinézni:
 
Magának a REGGEL rutinnak meg itt található a kifejtése:
 

Nos remélem e példa megfogott mindenkit, most inkább gyakorlatibb vizekre evezzünk.

Tegyük fel billentyűzetről kell beolvasni bizonyos változókat. Ahhoz, hogy ezt megtegyük el kell készíteni egy beolvasó rutint. A beolvasó rutin saját – lokális változóiba olvassa be az adatokat (lokális változó = csak a rutin kezdetétől annak végéig használható változó. A rutin meghívásával foglalódik le számára hely a memóriában, s a rutinból történő kilépéskor megszűnik létezni.). Az adatokat vissza kell adni a meghívó kódrészlet számára, vagy onnan kell átvenni adatokat amivel a rutin dolgozik. Tehát a rutinnak létre kell hozni valamilyen adat – változó csere lehetőséget, hiszen NEM adhatunk meg globális változókat (Az egész program minden része által elérhető változót mert azzal csak mi gabalyodnánk meg. Elfogadott tány, hogy minden rutin, s maga a főprogram is csak lokális változókkal dolgozik.) az adatcserére. Az egyik helyen így, a másik helyen meg úgy kellene használni a globális változót, s csak bénázhatnánk vele, hogy kinullázzuk, meg minden. Így az lesz a legtisztább, ha a fent említettek szerint csak lokális változókkal dolgozunk.

Lássuk a példát a rutinban használt változókra, s az őt meghívő kód által elért lokális változókat. Felhívnám a figyelmet, hogy egy egyszerű példát veszünk csak mely adatot ad át a rutinnak, de vissza nem kér – nem kap semmit.

Adjuk át a rutinnak A és B egész típusú változót. A rutin kiszámítja ezeknek a számtani közepét:

 
Láthatjuk, a főprogramban deklaráltunk egy x, y valós változót. Majd ezeknek értéket olvastunk be. Az x, és y változó által definiált értékeket adjuk át paraméterként a Szamtani közepet számító rutinnak. Ez a rutin az értékeket A és B nevű változókban kapja meg. Így ő belűl már A és B-vel tud számolni. Ez az A és B változó reprezentálja a kívülről kapott értékeket melyek jelen esetben az x és y változóban vannak. Mivel most csak értéket adtunk át meghívhatnánk a Szamtani() rutint úgy is, hogy a paramétere két konkrét szám: Szamtani(2,4) így a képernyőre (2+4)/2 az-az 3-at írna ki. Az x, y változókra csak a szám beolvasás miatt volt szükség.

Azért hangsúlyozom ezt ennyire ki, mert a későbbiekben másképp is használjuk a paraméterátadást mégpedig úgy, hogy nem csak a rutinnak adunk paramétert, hanem ugyan azon változón keresztül vissza is várunk kiszámított értéket.
 
Mielőtt a dolgok elébe szaladnánk, lássuk a szabványosított stuktogramm beli definíciót a paraméter átadásra.

 
Aktuális (hívó)   Formális (szubrutin)
X ó  A
Y ó  B
 
Most, hogy a definíciót megbeszéltük lássuk a szubrutin fajtáit felhasználásuktól függően.
Két különböző módon használhatjuk a szubrutinokat: Láttuk az eljárás stuktogrammját, de függvényét még nem. Megoldandó probléma, hogy hogyan és hol jelezzük a visszaadott értéket. Íme:
 
Nyílván való, hogy a visszaadott érték típusának meg kell egyeznie azzal a típussal, ahova a függvényt behelyettesítjük. Tehát ahol karakteres típust várunk el, oda nem helyettesíthetünk be numerikus értéket visszaadó függvényt.
 
Gyakorlati rész
 
Mivel eddig csak a stuktogrammokról, meg egyéb elméleti témákról volt szó, most evezzünk a megvalósítás vizére s lássuk, hogyan történik mindez Pascal ill. C++ nyelvben: A fent elmondottak szerint az eljárást procedure-ának, a függvényt function-nek nevezhetjük. Először lássuk egy program képletes felépítését:

procedure JoKisNeveLegyen( parameter1 : tipus; parameter2: tipus; …)
begin
  utasítások …
end;

function EzEgyProba( parameter1 : tipus; …): visszatérési_tipus;
begin
  utasítások …
  EzEgyProba:= visszatérési érték. {így adunk visszatérési értéket a függvénynek!}
end;

BEGIN {a főprogram itt kezdődik}

END.
 
Nos általánosságokban így kell felépíteni, lássuk a konkrét megadást:
 
function beolvas_karaktert(szoveg: string): Char;
var c: Char;
begin
  Write(szoveg); ReadLn(c);
  beolvas_karaktert:=c;
end;

procedure Kiir(szoveg1, szoveg2: String; karakter: Char);
begin
  WriteLn(szoveg1, ’ ’, karakter);
  WriteLn(szoveg2, ’ ’, karakter);
end;

var
  s1, s2: String;
  c : Char;

BEGIN {a főprogram itt kezdődik}
  s1:=’Kérem a beolvasandó karaktert: ’;
  c:=beolvas_karaktert(s1);
  s1:=’Ezt a karaktert éppen most olvastuk be: ’;
  s2:=’Ezt a karaktert éppen most írtuk ki: ’;
  Kiir(s1, s2, c);
END.

Mit csinál a program ?

A fenti programot jelentősen lehet egyszerűsíteni, így tegyük meg:

function beolvas_karaktert(szoveg: string): Char;
var c: Char;
begin
  Write(szoveg); ReadLn(c);
  beolvas_karaktert:=c;
end;

procedure Kiir(szoveg1, szoveg2: String; karakter: Char);
begin
  WriteLn(szoveg1, ’ ’, karakter);
  WriteLn(szoveg2, ’ ’, karakter);
end;

BEGIN {a főprogram itt kezdődik}
  Kiir(’Ezt a karaktert éppen most olvastuk be: ’,
  'Ezt a karaktert éppen most írtuk ki: ’,
  beolvas_karaktert(’Kérem a beolvasandó karaktert: ’));
END.
 
Nos lőn csoda nem is volt szüksége a főprogramnak változókra. Hisz ahogy fent elmondottam, ilyen érték szerinti paraméterátadásnál közvetlen konstansként is megadhatjuk a paramétert. A kiírandó karaktert, amit az eljárásnak adunk át nem szükséges külön változóba másolni, hanem a karakter beolvasó függvényt közvetlenül elhelyezhetjük az eljárás paramétereként, hisz annak visszatérési értéke akár egy konkrét szám helyettesítődik oda be.
 
A Pascal-os megvalósítás után lássuk a C gyönyöreit:

#include<stdio.h>
#include<conio.h>
 
void main()
{
  void JoKisNeveLegyen(int, int, char);
  char beolvas_karakter();
  clrscr(); // kepernyotorles
  char c;
  c=beolvas_karakter();
  JoKisNeveLegyen(1, 2, c);
}
 
char beolvas_karakter()
{
  char tempc;
  printf("\nKérek be egy karaktert: ");
  tempc=getche();
  return(tempc);
}

 

void JoKisNeveLegyen(int szam1, int szam2, char karakter)
{
  printf("\nA beolvasott karakter: %c", karakter);
  printf("\nAz első szám: %d", szam1);
  printf("\nA második szám: %d", szam2);
}
 
Nos magyarázkodjunk egy kicsit:

 Ahogy a Pascal-ban is lehetett egyszerűsíteni, így a C-ben is lássuk a forráskódot:

#include<stdio.h>
#include<conio.h>
 
void main()
{
  void JoKisNeveLegyen(int, int, char);
  char beolvas_karakter();
  clrscr(); // kepernyotorles
  JoKisNeveLegyen(1, 2, beolvas_karakter());
}

char beolvas_karakter()
{
  printf("\nKérek be egy karaktert: ");
  return(getche());
}

void JoKisNeveLegyen(int szam1, int szam2, char karakter)
{
  printf("\nA beolvasott karakter: %c", karakter);
  printf("\nAz első szám: %d", szam1);
  printf("\nA második szám: %d", szam2);
}

Ahogy a forráskód mutatja itt még egyszerűbb dolgunk volt, mint Pascal-ban hisz ugyanúgy a függvény behelyettesíthető bárhova, de a függvény visszatérési értékét megadhatjuk közvetlenül a RETURN( )-be ágyazott újabb függvénnyel mely a billentyűt olvassa be ( getche() ). Pascal-ban külön segédváltozóra volt szükség, hogy visszaadhassuk a beolvasott karaktert.

(Persze ha Pascal-ban lenne (vagy készítenénk) egy olyan függvényt, melynek visszatérési értéke a beolvasott karakter, akkor a fenti beolvas_karaktert() függvény visszatérési értékét egy közvetlen egyenlőséggel megadhatnánk.
Beolvas_karaktert:=ReadChar; (ahol readchar egy általunk készített másik függvény).)

Nos ennyi mára, aki érdeklődőbb (vagy nem értett valamit) elereszthet egy eMailt a PC-XUSER@freemail.c3.hu-ra.

 
 Bérczi László
eMail:PC-XUser@FREEMAIL.C3.HU, Subject: "ProgTerv rovat"
BELA@MI.STUD.PMMFK.JPTE.HU