08. fejezet: Eljárások és függvények

Egy összetettebb programot nehéz megérteni az elejétől a végéig. Ezért fejlesztés során a feladatot több részfeladatra bontjuk, és ezeket egymástól függetlenül készítjük el. Így elegendő csak egy-egy részletre koncentrálnunk. Továbbá ha egy gyakori feladat megoldását elkészítettük, azt a program több pontján is fel tudjuk használni, így időt is megtakarítunk (ez a kód újrahasznosítása).
Az eljárások és függvények a program részben önálló részei. A függvény annyiban több az eljárásnál, hogy visszaad valamilyen értéket (lásd a 3. fejezetet).  Ilyen pl. a sqr(...) függvény, melynek eredménye egy szám (a paraméter négyzete).  A Pascal legtöbb utasítása eljárás: ilyen pl. a writeln(...), mely kiírja a paramétert. Minden eljárás és függvény egy adott feladatot lát el.
Az eljárásokat és függvényeket felhasználásuk előtt deklarálni kell a deklarációs részben, ez a főprogram begin-je elé kerül.

Egyszerű eljárás

Nézzük a következő programot.
BEGIN
  writeln('3+2=');

  write('nyomj entert a megoldáshoz'); readln;

  writeln('5.');

 
writeln('5*7=');
  write('nyomj entert a megoldáshoz'); readln;
  writeln('35.');
END.

Látható, hogy a várakozás enterre több helyen is előfordul. Most csináljunk egy eljárást erre a feladatra.
Procedure varj;
  begin
    write('nyomj entert a megoldáshoz');
    readln;
  end;

BEGIN
  writeln('3+2=');
  varj;
  writeln('5.');
  writeln('5*7=');
  varj;
  writeln('35.');
END.

A főprogramunk rövidebb és áttekinthetőbb lett. Figyeld meg, hogy az eljárás törzse a főprograméhoz hasonlóan egy összetett utasítás, de nem pont, hanem pontosvessző van a végén. A főprogram az eljárást a nevével indítja, ezt az eljárás hívásának nevezzük.

Paraméteres eljárás

Gondoljunk egy eljárásra (legyen a neve csik), amely - jelekből vízszintes vonalat hoz létre. Az eljárás paramétere, hogy milyen széles legyen a vonal: csik(10) eljáráshívás pl. egy 10 karakter szélességű vonalat húz. Az eljárásban lesz egy for-ciklus. Ennek ciklusváltozóját az eljárás deklarációs részében adjuk meg.
Procedure csik(sz:integer);
  var i:integer;
  begin
     for i:=1 to sz do write('-');
    writeln;
  end;

BEGIN
  csik(10);
  csik(20);
  csik(10);
END.

Az eljárás hívásakor a paraméterként átadott szám bekerül az eljárás sz változójába, az első híváskor így az eljárás törzsében sz=10.
Egy eljárásnak több paramétere is lehet, például: Procedure valami(a,b:integer; x:real); Ilyenkor eljáráshíváskor a megfelelő sorrendben kell megadni az értékeket, ebben a példában valami(1,2,0.11).

Az eljárások általános felépítése tehát:

Procedure eljárásnév(paraméterlista);
   az eljárás saját deklarációs része
   Begin
     az eljárás utasításai {más néven az eljárás törzse}
   End;

Lokális és globális változók

A legutóbbi példaprogramban az i változó az eljárás hívásakor keletkezik, és az eljárás lefutása után megszűnik. Ezért ha a főprogramban hivatkozunk rá, hibaüzenet lesz az eredmény (ismeretlen azonosító). Az ilyen változók neve lokális, mert csak az eljáráson belül léteznek. Lokális minden, az eljárásban deklarált változó, és az eljárás paraméterei is lokális változóként viselkednek
Mi történik, ha a főprogramban is van ilyen nevű változó? Ebben az esetben az eljárás indulásakor a Pascal elmenti a főprogram változóját, az eljáráson belül csak a lokális változó lesz érvényben, majd az eljárás lefutásakor a főprogram visszakapja a saját változóját.

VAR a,b:integer;

Procedure Valami;
   Var a:integer;
   Begin
     a:=5; {lokális változó}
     b:=7; {a fõprogram globális változója}
   End;

BEGIN
   a:=1;
   b:=2;
   Valami;
   Writeln(a,b);
END.

Ebben a programban az a és b változókat az eljárás előtt deklaráltuk, ezek tehát az eljáráson belül láthatóak. Viszont az eljárás lokális a változója a főprogram a változója helyébe lép az eljárás futása közben (azt "eltakarja").
A b változó a főprogramé, és az eljárásnak nem lép helyébe lokális változója. Az ilyen változók neve globális: ezek a főprogram eljárásokban is használható változói.
A program által kiírt számok: 1,7. Az eljárás csak a saját lokális a változóját módosítja, a főprogramé változatlan, viszont a b az eljárásban globális.

Mire jó mindez? Az eljárások általában egy önálló részfeladatot oldanak meg. Megírásuk után el is feledkezhetünk arról, hogyan működnek, csak az a fontos, mire valók. Ezért nem jegyezzük meg a bennük használt segédváltozók nevét, és nem baj, hogy máshol is szerepelhetnek ilyen nevű változók.

Összegzésként: az eljárásban általában minden változó legyen lokális, kivéve, ha a feladat kifejezetten a főprogram egyes változóinak módosítása.

Függvények

A függvény majdnem olyan, mint egy eljárás, csak rendelkezik visszatérési értékkel. Az eljáráshoz képest meg kell adnunk a visszatérési érték típusát, és a függvény törzsében valahol magát az értéket.

A következő példa elkészíti a köbre emelő függvényt, az sqr mintájára. Ennek a függvénynek egy paramétere van (amit köbre emelünk).
Function kob(a:real):real;
   Begin
     kob:=a*a*a;
   End;

BEGIN
   writeln(kob(65));
END;

A függvény törzsében látható értékadás nem értékadás valójában, hanem a visszatérési értéket adja meg. Tehát:

Function függvénynév(paraméterlista):típus;
   a függvény saját deklarációs része
   Begin
     ...
     függvénynév:=visszatérési érték;
     ...
   End;

A következő példa az egész számok hatványozását mutatja be egy kétparaméteres függvénnyel (a függvénynek akárhány paramétere lehet, de visszatérési értéke csak egy!). A hatványozást többszöri szorzással végzi el.
Function hatv(x,n:integer):integer;
  var i,p:integer;
  begin
    p:=1;
    for i:=1 to n do p:=p*x;
    hatv:=p;
  end;

BEGIN
  if hatv(2,3)=8 then writeln('Működik!');
END.

Felmerülhet a kérdés, hogy mi szükség volt a p változóra, miért nem használtuk hatv-ot. Vigyázat, hatv nem változó! A hatv:=hatv*x jobb oldala függvényhívás, itt a függvény meghívhatja saját magát.

Változóparaméterek

Keressük meg a hibát a következő programban, amely két változó értékét felcserélő eljárást használna!
Procedure Csere(a,b:integer);
  Var m:integer;
  Begin
    m:=a; a:=b; b:=m;
  End;

VAR p,q:integer;

BEGIN

  p:=8;
  q:=4;
  Csere(p,q);
  Writeln(p,q);
END.

A program a 8,4 számokat fogja kiírni, hiszen az eljárás a főprogram eredeti p,q változóját nem módosította, csak a lokális a,b értékét cserélte fel. A Pascal ilyen esetben érték szerinti paraméterátadást használ, vagyis az a,b változók értéke az átadott két paraméter (szám) lesz.

Lehet azonban cím szerinti paraméterátadást használni. Cseréljük ki a program első sorát erre, és működni fog:
Procedure Csere(var a,b:integer);

A különbség a paraméterlistát megelőző var. Ebben az esetben eljáráshíváskor nem használhatunk értékeket, csak változókat. Az eljárásban létrejövő paraméterek ugyanazokra a memóriacímekre fognak mutatni, mint a hívásban szereplő változók. Vagyis ami a,b változókkal történik az eljárásban, az történik a főprogram p,q változóival. (Használhatunk vegyesen "sima" és változóparamétereket is.)
Így működik a Pascal readln(változó) utasítása is, az átadott változó értékét módosítja.

Feladatok

30. Készíts függvényt, mely a két átadott egész szám közül a nagyobbikat adja vissza! Vagyis a következő programrészlet elé írva az jól fog működni.
...
BEGIN
  writeln(max(2,7)); //7-et ír ki
END.
megoldás
31. Egészítsd ki a következő programrészletet a megfelelő eljárással, amely az adott karaktert adott számszor írja ki!
...
BEGIN
  write('B');  //Brrrrrrrrrrrr! Nagyon hiiiiideg van!
  ism('r',12);
  write('! Nagyon h');
  ism('i',5);
  writeln('deg van!');
END.
megoldás
32. Most készíts függvényt ugyanerre a célra! A feladata előállítani az adott karaktert adott számszor tartalmazó stringet. Segítségként megadom a függvény elejét is.
Function ism(c:char;n:integer):string;
...
BEGIN
  write('B',ism('r',12),'! Nagyon h',ism('i',5),'deg van!');
END.
megoldás
33. Készíts eljárást, mely a paraméterként átadott egész típusú változót a következő páros számra kerekíti!
...
VAR a:integer;
BEGIN
  a:=53;
  parit(a);
  writeln(a); //a értéke 54 lesz
  parit(a);
  writeln(a); //a értéke marad 54
END.
  megoldás
Comments