Az alprogram fogalma
Kissé pongyolán fogalmazva alprogramnak nevezhetünk minden olyan kódrészletet, amelyek valamilyen módon elkülönülnek a fôprogramtól, és így akár többször egymás után is meghívhatóak. Az alprogramok elhelyezkedhetnek a fôprogramot is tartalmazó fájlban, bár gyakran találkozhatunk azzal az esettel is, amikor az alprogramok külön fájlban kapnak helyet. Amikor meghívunk egy alprogramot, akkor a fôprogram végrehajtása egy idôre megszakad és helyette az alprogram kódja fut. Amikor az alprogram befejezôdik, akkor a vezérlés visszatér a fôprogramhoz és a következô sorban található utasítás hajtódik végre. A REXX-ben két fajta alprogramot különböztetünk meg, az eljárást és a függvényt. A függvény tulajdonképpen nem más, mint egy speciális eljárás, amely értéket ad vissza. A REXX eljárások minden esetben egy címkével kezdôdnek és a RETURN utasítással fejezôdnek be. A címke tulajdonképpen az eljárás neve, így ez alapján lehet az eljárást a fôprogramból meghívni, a CALL utasítással:
/* Példaeljárás */ CALL peldaeljaras EXIT peldaeljaras: SAY 'Ez itt a példaeljárás!' RETURN
Ha kíváncsiak vagyunk arra, hogy a vezérlés átadása pontosan hogyan történik, akkor gépeljük be a példaprogramot és kapcsoljuk be a nyomkövetést a CALL elé beírt TRACE 'R' utasítással. Fontos tudni, hogy (ha csak nem rendelkezünk másként) az alprogramokban és a fôprogramban használt változók a program bármelyik részében elérhetôek (globálisak) maradnak. Tekintsük például az alábbi programot, amely kilistázza a gépen elérhetô meghajtókat:
/* Meghajtók kilistázása */
CALL SetLocal
DO driveID = C2D('C') TO C2D('Z')
drive = D2C(driveID) || ':'
CALL tesztdrive
END
SAY 'Ezen a gépen elérhetô meghajtók:'
SAY meghajtok
CALL EndLocal
EXIT
tesztdrive:
IF meghajtok = 'MEGHAJTOK' THEN meghajtok = ''
IF Directory(drive'\') = drive'\' THEN
meghajtok = meghajtok drive
RETURN
A fôprogram végigmegy az összes betûn C-tôl Z-ig és minden esetben meghívja a tesztdrive eljárást, amely megnézi, hogy az adott betûhöz tartozik-e meghajtó. A példából jól látszik, hogy a fôprogramban bevezetett változót (drive) egy az egyben lehet használni az alprogramban is. Ugyanez vonatkozik a meghajtok változóra, amely az alprogramban kerül elôször használatra, a fôprogram végén viszont mégis simán hivatkozhatunk rá.
Paraméterek átadása
Az alprogramok meghívásakor nem csak a vezérlést, hanem paramétereket is átadhatunk a meghívott programrészletnek. A paramétereket az alprogram meghívásakor egyszerûen csak fel kell sorolnunk az alprogram neve után vesszôkkel elválasztva, mivel így automatikusan átadásra kerülnek. Az alprogramban aztán az ARG, vagy a PARSE ARG utasítások valamelyikével, vagy a beépített Arg függvény meghívásával juthatunk a meghíváskor továbbított adatokhoz. Ha például egy alprogramot az alábbi módon hívunk meg, akkor mindhárom paraméter (egy szám, egy karakterlánc és egy karakter) átadásra kerül:
CALL teszteles 5, 'egy hosszú karakterlánc', 'c' EXIT teszteles: PARSE ARG szam, lanc, meghajto SAY szam lanc meghajto RETURN
Egy másik megoldás lehet az átadott paraméterek "befogására" az Arg függvény alkalmazása. Ha az Arg-ot paraméter nélkül hívjuk meg, akkor válaszul megkapjuk, hogy hány darab paraméterrel hívták meg a kérdéses alprogramot. Ha egy sorszámot adunk meg argumentként, akkor a függvény visszatérési értéke a sorszámhoz tartozó paraméter lesz. Az Arg segítségével az elôbbi példaprogram-részlet az alábbi módon is megoldható:
teszteles: szam = Arg(1) lanc = Arg(2) meghajto = Arg(3) SAY szam lanc meghajto RETURN
Érdemes észben tartani, hogy OS/2 alatt a CALL utasítással maximum 20 darab paramétert adhatunk át.
Alprogramok tervezése
Alprogramokat nem csak akkor használhatunk, amikor ismétlôdô kódrészleteket kell használnunk, hanem akkor is, ha egy hosszabb programot áttekinthetô módon, strukturáltan, azaz részfolyamatokra bontva akarunk megírni. Szinte minden programra alkalmazható a hármas felosztás elve. Ez azt jelenti, hogy a program elsô része a környezet inicializálását végzi, a második része a tulajdonképpeni feladat végrehajtását, míg a harmadik és egyben utolsó rész a futás utáni "rendrakást" végzi el:
PARSE ARG parameterek CALL Inicializacio parameterek CALL A_feladatok_elvegzese CALL Romeltakaritas EXIT
Természetesen több alprogramot is definiálhatunk, mint a fent említett három, ha azt a program által megvalósított feladat is úgy kívánja. Nem szabad azonban túlzásokba esni, mert a CALL és RETURN utasítások végrehatása is idôt igényel, ezért ha az alprogram csak néhány szerkezetbôl áll, akkor azokat inkább ne különítsük el.
Saját alprogramok megvalósítása
Ha elkészültünk a program logikai felosztásával, akkor elkezdhetjük gyártani az alprogramokat. A program késôbbi visszafejtését nagyon megkönnyíti, ha az alprogramoknak mûködésükre utaló nevet adunk. Nagyon fontos, hogy az alprogramokat a fôprogramot lezáró EXIT utasítás után helyezzük el, különben az összes alprogram végrehajtásra kerül, amely nem az EXIT után áll! Ha már megvan az alprogram neve és helye, akkor nincs más hátra, mint az alprogram törzsének megírása. A kódot minden esetben a RETURN utasítás zárja le, különben a vezérlés átugrik a soron következô alprogramra. Sokszor alkalmazott stílusbeli fogás, hogy az alprogramokat bekezdéssel írják, mivel ekkor jól elkülönül a fôprogram az alprogramoktól. Gyakran visszatérô szokás az is, hogy az alprogram magyarázó szöveggel kezdôdik, amely röviden leírja, hogy mit is csinál a soron következô kódrészlet.
Ahogy a program növekszik, elôbb vagy utóbb szembe fogjuk magunkat találni azzal a problémával, hogy az alprogramok alapesetben minden változót látnak és használhatnak. Ha például véletlenül azonos nevû számlálókat definiálunk különbözô programrészletekben, akkor elôfordulhat, hogy az egyik kódrészlet módosítja a másik kódrészletben használt értéket, ami aztán misztikus, általában nehezen felderíthetô hibákhoz vezethet. Természetesen kikerülhetjük a problémát azzal, ha ügyelünk arra, hogy csak egyedi változóneveket használjunk. Ez azonban nagy programok esetén szinte majdnem biztos, hogy nem fog mindig sikerülni, akármennyire ügyelünk is. Sokkal jobb megoldás, ha a PROCEDURE instrukcióval láthatatlanná tesszük az alprogramok számára az alprogramon kívül használt változókat. A PROCEDURE szót rögtön az alprogram címkéje után kell megadni:
/* Példaeljárás */ tesztrutin: PROCEDURE ARG szo IF TRANSLATE(szo) = 'OS/2' THEN SAY 'OS/2 rulez!' ELSE SAY 'Rossz a megadott szó!' RETURN
Elôfordulhat az is, hogy az alprogramnak szüksége van arra, hogy mégiscsak elérjen néhány, az alprogramon kívül használt változót. Ebben az esetben a kívánt változókat a PROCEDURE után megadható EXPOSE kulcsszó után kell felsorolni és ekkor az alprogram képes lesz ezeket is kezelni. ™sszetett változók esetén elég a tôrrészt megadni, hogy az ebbôl a tôbôl levezetett összetett változó is használható legyen. Most pedig lássunk egy példát a PROCEDURE és az EXPOSE használatára. Az alábbi példaprogram két alprogramot valósít meg, amelyek segítségével a bekért számokat grafikus formában jeleníti meg:
/* Grafikusan jelenítjük meg a bevitt számokat */
SAY 'Gépeld be a kirajzolandó számokat'
SAY 'vagy nyomj ENTERT-t, ha kész vagy!'
SAY ''
legnagyobb = 0
CALL szambevitel
DO i = 1 TO szamok.0
CALL rajzol szamok.i
END
EXIT
/* Ez az alprogram kéri be a számokat. */
/* Az alprogram számára látható alprogramon kívüli változók a */
/* legnagyobb és az összes, a szamok. tôbôl képzett összetett változó. */
szambevitel: PROCEDURE EXPOSE legnagyobb szamok.
DO i = 1 UNTIL szam = '' /* Ez egy másik i!!! */
SAY 'Szám:'
PULL szam
IF szam <> '' THEN
DO
szamok.i = szam
legnagyobb = MAX(szam, legnagyobb)
END
END
szamok.0 = i - 1
RETURN
/* Kirajzolja a grafika egy sorát. */
/* Egyedül a legnagyobb változó látszik. */
rajzol: PROCEDURE EXPOSE legnagyobb
ARG ertek
i = (ertek * 100) % legnagyobb % 2 /* Egy csillag 2%-ot jelent */
SAY Right(ertek, 5) Copies('*', i) /* Ez pedig egy harmadik i!! */
RETURN
Az elsô alprogram a szamok. összetett változóban tárolja el a bevitt számokat és megkeresi a legnagyobbat. Mind a legnagyobb szám, mind pedig a szamok. összetett változók láthatók az alprogram számára (EXPOSE), mivel ezekre szükség van a program többi részében is. A fôprogram aztán végigmegy az összes bevitt számon és meghívja a rajzol eljárást, amely kirajzolja a grafika egy sorát. Figyeljük meg, hogy három helyen is definiáltuk az i számlálót, ez azonban nem jelent problémát, mivel ezek láthatatlanok egymás számára az alprogramokat "leárnyékoló" PROCEDURE utasításoknak köszönhetôen.
Függvények
Már korábban is volt róla szó, hogy a függvények az alprogramok speciális fajtái. Azért speciálisak, mert az eljárásoktól eltérôen kötelezôen megadandó visszatérési értékkel rendelkeznek. A meghívás módjában is van különbség, mivel a függvény neve után rögtön a zárójelek közé zárt argumenteknek kell következni. Függvényt használhatunk minden olyan helyen, ahol egyébként bármilyen érvényes REXX konstanst is használhatunk. A függvények bizonyos fokig ismerôsök kellenek, hogy legyenek, mivel a tanfolyam során eddig is használtuk már a REXX beépített függvényeit. Az eljárásokkal teljesen megegyezô módon hozhatjuk létre a függvényeket is, vagyis mindig egy címkével kezdjük a függvényt és kötelezôen RETURN utasítással zárjuk le. Ami az eljárásoktól eltérô az az, hogy a RETURN-nek minden esetben valamilyen értéket is meg kell adni, amely aztán egyben a függvény visszatérési értéke lesz:
SAY 'A kettô négyzete 'negyzet(2)'.' EXIT /* Négyzetre emelô függvény */ negyzet: PROCEDURE ARG szam RETURN szam * szam
Elôfordulhat, hogy nincs szükségünk a függvény visszatérési értékére. Ekkor a CALL utasítással érdemes meghívni a függvényt:
CALL...
A függvény visszatérési értéke azonban "a fû alatt" ekkor és visszaadódik, is mindig elérhetô lesz az elôre definiált result változóban. A négyzetre emelô programrészletet például az alábbi módon is megírhattuk volna:
CALL negyzet 2 SAY 'A kettô négyzete 'result'.' EXIT /* Négyzetre emelô függvény */ negyzet: PROCEDURE ARG szam RETURN szam * szam
Rekurzív függvényhívások
Nagyon sokszor lehet programokat egyszerûsíteni rekurzív függvényhívásokkal. Ez azt jelenti, hogy a függvény saját magát hívja meg. Természetesen ilyenkor arra is ügyelni kell, hogy a program ne kerüljön ezáltal végtelen ciklusba. Rekurzív hívással tipikusan jól megoldható problémák a faktoriális számítás vagy például a legnagyobb közös osztó megkeresése. Nézzük meg ez utóbbit egy példa kapcsán! Az euklédeszi algoritmus alapján úgy számíthatjuk ki két szám legnagyobb közös osztóját, ha megkeressük kisebbik szám és az eredeti számok különbségének a legnagyobb közös osztóját. Ezt a folyamatot pedig addig ismételjük, amíg a kisebbik szám nulla nem lesz, mivel akkor megkaptuk a legnagyobb közös osztót a nagyobbik szám személyében. A folyamatot az alábbi módon önthetjük programkódba:
/* A legnagyobb közös osztó kiszámítása */ lnko: PROCEDURE ARG u, v IF v = 0 THEN RETURN u ELSE RETURN lnko(v, ABS(u - v))
Figyeljük meg, hogy az lnko függvény hogyan hívjuk meg önmagát a program végén, ha a v még nem nulla! Amikor viszont a v nulla lesz, akkor nem történik több rekurzív hívás, így nem kerülünk végtelen ciklusba. Ha az IF v = 0 feltétel nem lenne, akkor ez a programrészlet egészen addig hívogatná önmagát, amíg meg nem szakítanánk a programot, vagy amíg a REXX erôforrások el nem fogynának.
Külsô REXX programok meghívása
A fôprogramból külsô REXX alprogramokat is meghívhatunk. Ez tulajdonképpen azt jelenti, hogy az egyik REXX programból minden további nélkül meg lehet hívni a másikat. A külsô programnak nincsen hozzáférése a fôprogramban használt változókhoz, vagyis a külsô program ugyanolyan jogokat élvez, mint egy belsô eljárás, amelynek neve után megadtuk a PROCEDURE kulcsszót EXPOSE változók felsorolása nélkül. A külsô eljárás neve megegyezik az eljárást tartalmazó fájl nevével, mínusz a fájl kiterjesztése.
Keresési sorrend
Láttuk, hogy a meghívott függvények, eljárások lehetnek a REXX-be beépítettek, saját gyártmányúak, vagy akár külsô programok is. Fontos, hogy megismerjük az eljárások keresési sorrendjét, mivel ezen ismeret hiányában kellemetlen meglepetések érhetnek bennünket, ha több, ugyanolyan nevû eljárás is létezik a programon belül, vagy kívül.
Amikor egy alprogramot meghívunk egy REXX programban, akkor a REXX elôször a belsô címkéket veszi sorra a keresés során. Ha idézôjelek közé tesszük az alprogram nevét, akkor ez a lépés kimarad! Ha nem találta meg a keresett alprogramot, akkor a beépített REXX függvények között folytatja a keresés. Ha ez sem vezet sikerre, akkor a következô lépés a betöltött külsô függvénykönyvtárak sorra vétele. A függvénykönyvtárakról a 16. leckében lesz bôvebben szó. Ha itt sem szerepel a kérdéses alprogram, akkor azokat a külsô fájlokat keresi a REXX, amelynek a neve megegyezik a hívott alprogram nevével, a kiterjesztése pedig a futtatott program kiterjesztésével. Ha ilyen sem lenne, akkor a .CMD kiterjesztéssel próbálkozik, mint alapértelmezett kiterjesztés. Ha az elôzôekben felsorolt lépések mindegyike kudarccal végzôdik, akkor hibajelzést kapunk.
REXX GYÍK:
K1. Hogyan lehet egy függvénynek egynél több visszatérési értéke?
V1. A RETURN utasítással sajnos csak egy értéket lehet visszaadni. Ha viszont egy olyan karakterláncot adunk vissza, amelyben összetoldottuk a visszaadandó értékeket, akkor így kikerülhetjük ezt a limitációt. Természetesen a visszatérési értéket megfelelô kezelésnek kell alávetni a fôprogramban, hogy visszanyerjük a kívánt adatokat. Egy másik kerülô út, ha a PROCEDURE EXPOSE segítségével a visszaadandó változókat globálissá tesszük, így azok az alprogramban és a fôprogramban egyaránt elérhetôek lesznek.
Gyakorlatok:
1. Készítsen alprogramok felhasználásával egy olyan programot, amely bekér egy egész számot és kiszámítja a számok összegét 1-tôl a bekért számig.
2. Módosítsa a fenti programot oly módon, hogy az a számok négyzetét adja össze.
| Kádár Zsolt 1998. 06. 23. | [ Elôzô lecke | Következô lecke | Tartalom ] |