A Perl programnyelvet tárgyaló sorozatunk a nyolcadik részéhez érkezett. Az előző néhány részben megismerkedhettünk a fájl- és könyvtárkezeléssel, különböző fájlvizsgálatokkal, a dátum és időkezeléssel, stb. Most megnézzük, hogyan hozhatunk létre többdimenziós tömböket, referenciákat, szó lesz a változók érvényességi tartományairól, és a modulokról is.

Mit is nevezünk többdimenziós tömbnek? Már eddig is találkoztunk tömbökkel, azonban ezek mind egy dimenziósak voltak, kulcs-érték párokból álltak. A számokkal indexelt tömbök esetén (@), mint azt neve is mutatja, a kulcsok növekvő számok voltak - 0, 1, 2, 3, ... -, a hash-ek (%) esetén a számokat sztringek váltották fel, tehát ezek alapján lehetett elérni az értékeket. A többdimenziós tömböket tömbökből álló tömböknek is nevezhetném. Tehát ahhoz, hogy elérjünk egy elemet, két kulcsot is meg kell adni, először, hogy melyik tömbből akarom kiválasztani, másodszor, hogy az elsőnek kiválasztott tömb melyik elemére van szükségem. Mindkét tömb lehet számokkal indexelt, és hash is, így jön össze a négyféle változat, tehát megkülönböztetünk tömbökből álló tömböket, tömbökből álló hash-eket, hash-ekből álló tömböket, és hash-ekből álló hash-eket. Lássunk egy egyszerű példát!

%pontok = (   "Anna" => {     "fizika" => "84",     "matek" => "91",     "irodalom" => "57"   },   "Vera" => {     "fizika" => "39",     "matek" => "58",     "irodalom" => "89"   },   "Imre" => {     "fizika" => "77",     "matek" => "93",     "irodalom" => "76"   ) );

Itt egy hash-ekből álló hash-t hoztam létre. A hivatkozás egy elemre az alábbi módon történhet:

print $pontok{'Anna'}->{'irodalom'}; #kiírja, hogy 57

Ebből a -> rész el is hagyható. Ez alapján a fenti tömböt így is létrehozhattam volna:

$pontok{'Anna'}{"fizika"} = "84"; $pontok{'Anna'}{"matek"} = "91"; $pontok{'Anna'}{"irodalom"} = "57"; $pontok{'Vera'}{"fizika"} = "42"; #és így tovább...

Ugyanez nevek nélkül:

$pontok[0]{"fizika"} = "84"; $pontok[0]{"matek"} = "91"; $pontok[0]{"irodalom"} = "57"; $pontok[1]{"fizika"} = "42"; #és így tovább...

Ebben az esetben tehát már nem adtuk meg a tanulók neveit, csak egy számot, és szögletes zárójeleket használtunk. Tehát ez már egy hash-ekből álló tömb. Most készítsünk el egy kis programot, ami az elsőnek létrehozott tömb alapján kiszámolja egy adott tárgy átlagát! A forrás:

$targy = 'fizika'; $tanulok = 0; foreach $nev (keys %pontok) {   $tanulok++;   $ossz += $pontok{$nev}{$targy};   print "$nev: $pontok{$nev}{$targy}\n"; } $atlag = int($ossz / $tanulok); print "--\nátlag: $atlag";

Megadtuk, hogy melyik tárgyra vagyunk kíváncsiak, majd a tanulók számát 0-ra állítottuk -e változó növelésével fogjuk számolni a tanulók számát listázáskor. Ez után a %pontok tömbön úgy mentünk végig egy foreach ciklus segítségével, mintha nem is lenne többdimenziós, azonban a ciklus belsejében már a megfelelő hivatkozást használtuk. A kimenet:

Imre: 77 Vera: 39 Anna: 84 -- átlag: 66

Most pedig beszéljünk a referenciákról, más néven mutatókról. A referencia egy olyan változó, amely egy másik változóra mutat. Kétféle referenciát különböztetünk meg, a puhát - más néven szimbolikus -, és a keményet - valós -. A különbség, hogy a valós egy másik változó tartalmára mutat, tehát egy memóriacímet tárol, ahol az elérhető. Ezzel szemben a szimbolikus csak egy másik változó nevére mutat, amin keresztül elérhető a tartalom -ergo nem magára a tartalomra. Létrehozásuk az alábbi módon történik:

$szoveg = "akármi"; $valos_ref = \$szoveg; #valós referencia $szimb_ref = 'szoveg'; #szimbolikus referencia

Most írassuk ki az értéküket!

print $szimb_ref . "\n"; print $valos_ref;

A kimenet:

szoveg SCALAR(0x1765194)

Ez nem a várt eredmény. A szimbolikus referencia esetén a változó értéke a "szoveg" volt, és ez került itt kiírásra is, a másik esetén pedig az érték az a hely volt, ahol a tartalom megtalálható a memóriában - tehát az $szoveg helye. A referenciákat máshogy kell meghívni:

print ${$szimb_ref} . "\n"; print ${$valos_ref};

Mely egyenértékű az alábbival, ugyanis a kapcsos zárójelek elhagyhatók:

print $$szimb_ref . "\n"; print $$valos_ref;

A kimenet így már megfelelő:

akármi akármi

Nem csak skalárokhoz lehet referenciát készíteni, hanem a többi adattípushoz is, tehát tömbökhöz, hash-ekhez, szubrutinokhoz, sőt, még fájlmutatókhoz is! Nézzünk meg egy referenciát, amely egy tömbre hivatkozik!

@szamok = (345, 436, 34636, 243, 12); $tomb_ref = \@szamok; print $$tomb_ref[1]; #kiírja, hogy 436

Mint látható, hasonlóan használjuk, mint a skalárra hivatkozó referenciát. Természetesen itt is készíthetünk valós -mint a példában- és szimbolikus referenciát. Most tekintse meg, hogyan járunk el akkor, ha a referenciára nem, mint skalárra -tehát csak egy elemre-, hanem a tömb egészére van szükségünk:

@szamok = (345, 436, 34636, 243, 12); $tomb_ref = \@szamok; foreach $szam (@$tomb_ref) {   print $szam . "\n"; }

Mint látható, ekkor a $ jelet le kell cserélni @-ra, tehát ugyanúgy, mintha nem is referenciával dolgoznánk, a különbség csak annyi, hogy a @ után szükséges még egy $ jel a visszahivatkozáshoz, mert ha az nem lenne, akkor valami ilyesmi eredményt kapnánk:

@szamok = (345, 436, 34636, 243, 12); $tomb_ref = \@szamok; print $tomb_ref;

Ami egy ilyen kimenetet generál:

ARRAY(0x17651b8)

Ezek után nem hinném, hogy gondot okozhat egy hash referencia elkészítése:

%allatok = (   "hal" => "fekete tetra",   "madár" => "veréb",   "kutya" => "erdélyi kopó" ); $ref = \%allatok; print $$ref{'hal'} . "\n\n"; #kiírja, hogy fekete tetra #kulcs-érték párok listázása: foreach $kulcs (keys %$ref) {   print "$kulcs => $$ref{$kulcs}\n"; }

Ugyanígy készítünk referenciát szubrutinokhoz:

sub udvozles {   print "Hello!"; } $sub_ref = \&udvozles; &$sub_ref;

Most készítsünk szimbolikus szubrutint. Az alábbi példa napszaktól függő köszöntést állít elő:

%koszont = (   "este" => "koszont_e",   "reggel" => "koszont_r",   "napkozben" => "koszont_n" ); $ora = (localtime(time))[2]; if ( ( $ora < 5 ) or ($ora > 18) ) {   $kulcs = "este"; } elsif ( $ora < 9 ) {   $kulcs = "reggel"; } else {   $kulcs = "napkozben"; } &{$koszont{$kulcs}}; exit; sub koszont_e {   print "Jó estét!"; } sub koszont_r {   print "Jó reggelt!"; } sub koszont_n {   print "Jó napot!"; }

Tudom, sokkal egyszerűbben is meg lehetett volna oldani, de így legalább bemutatásra került egy példában több lehetőség is. A program elején egy hash-ben tároltuk a szubrutinok neveit, majd a $ora skalárba tettük a jelenlegi időpontból az órák számát. Ettől függően deklaráltuk a $kulcs változó értékét, majd egy sorban egyszerre választottuk ki a %koszont-ből a szubrutin nevét, és hívtuk azt meg. Kicsit túlbonyolítottnak tűnhet - mert az is -, azonban remekül látható, hogy referenciák segítségével milyen rugalmasan hívhatunk meg szubrutinokat, vagy hivatkozhatunk tömbökre és tömbelemekre, változókra.

Mint már említettem, akár fájlmutatókhoz is készíthetünk referenciát. Ekkor a globális adattípust kell használni (*), ha valódi referenciát szeretnénk. Az alábbi program egy előre megadott fájlba ír egy előre megadott mutató használatával:

$mutato = \*FAJL; $fajl = "adatok.txt"; open($mutato, ">>$fajl"); print $mutato "Adatok írása a(z) $fajl állományba..."; close($mutato);

A most következő példa pedig a @ARGV tömbből [lásd 5. rész] veszi a fájlkezelőt a kimenethez:

$mutato = \*{shift @ARGV}; open(FAJL,">>adatok.txt"); print $mutato "A program kimenete....\n"; close(FAJL);

A program kimenete a meghívásnál megadott első paraméterben meghatározott mutatóra fog íródni. Ha a programot az alábbi módon hívjuk meg, akkor kiírja az üzenetet a képernyőre:

perl prgnev.pl STDOUT

Ha pedig így, akkor az adatok.txt-be fogja írni:

perl prgnev.pl FAJL

A változók érvényességi tartományai

Most pedig beszéljünk a változók érvényességi tartományairól. Ezt a változó típusa adja meg, mely lehet globális - ez az alapértelmezett -, és lokális/helyi, melyeknek további két fajtája van, a lexikus és a dinamikus.

Eddig globális változókkal dolgoztunk, ami azt jelenti, az összes változó elérhető volt a program bármely részéről. Az alábbi program egy előre deklarált változót használ fel egy szubrutinban, majd egy szubrutinban deklaráltat a programban:

$globalis = "változó tartalma"; &alprg; print $szubrutinban; #$kiírja, hogy változó tartalma exit; sub alprg {   $szubrutinban = $globalis; }
 

Ebből remekül látszik, hogy a skalár még egy szubrutinból is elérhető volt, és a szubrutinban létrehozott változó a főprogramban is elérhető maradt. Igen kényelmes így használni a változókat, azonban nem mindig szükséges, hiszen nagyon sokra csak ideiglenesen van szükség, a programnak csak egy bizonyos részében, máshol nem. A globális változók ekkor is a memóriában maradnak, tehát foglalják a helyet. Ez legtöbbször szubrutinokban, ciklusokban, és elágazásokban fordul elő, ilyenkor érdemes lokális változókat használni. Mint már említettem, kétféle lokális változótípust különböztetünk meg. A dinamikus változó elérhető minden szubrutinból, amit azon a szubrutinon belül hívtunk meg, ahol a változót létrehoztuk. Az ilyen típust a local előtag használatával kell deklarálni:

sub alprg {   local $szam = 33;   &szamkiir; } sub szamkiir {   print $szam; } &alprg; #kiírja, hogy 33 &szamkiir; #nem ír ki semmit

Mivel az alprg() szubrutinban lokális változót hoztam létre, az innen meghívott szamkiir() alprogram kiírta a számot, viszont utána, amikor a főprogramból hívtam meg, már nem írta ki, mivel ekkor már nem létezett a változó. Ha az alprg()-ben globális változót hoztam volna létre, akkor a $szam kétszer került volna kiírásra.

A dinamikus változóval szemben a lexikus csak ott érhető el, ahol deklaráltuk, tehát az onnan hívott szubrutinban már nem. Ilyen változó a my-jal hozható létre:

sub alprg {   my $szam = 33;   &szamkiir; #nem ír ki semmit   print $szam; #kiírja, hogy 33 } sub szamkiir {   print $szam; } &alprg; &szamkiir; #nem ír ki semmit

Mint látható, itt már a szamkiir() szubrutin egyáltalán nem írt ki semmit, még akkor sem, ha az alprg() szubrutinból hívtuk meg. A kiírás csak az alprg() szubrutinban működött.

Perl-ben lehetőségünk van ún. modulok készítésére is. Ezekben a gyakran használt szubrutinokat lehet összegyűjteni, és azokat ezzel több helyről is használni. Az itt használt változók külön táblában tárolódnak, ezért nem fognak "összegabalyodni" a program változóival. Nem csak a modul szubrutinjait, hanem a változóit is el lehet érni $modulneve::változó formában. Célszerű az így elkészített modulokat egy külön fájlban tárolni, és azokat a use segítségével meghívni azon programokban, amelyekben szükség van rá. Egy egyszerű példa - itt egy fájlban a programmal együtt:

$sugar = 5; $magassag = 8; $terfogat = &szamitasok::henger_terfogat($sugar, $magassag); print "V = $sugar ^ 2 * $szamitasok::pi * $magassag = $terfogat"; exit; package szamitasok; sub henger_terfogat {   $pi = 3.1415926535897932;   return $_[0]**2 * $pi * $_[1] ; }

Egy szamitasok nevű modult hoztam létre, amiben egy szubrutint készítettem, melyben deklarálásra kerül egy $pi változó, a ? értékének első néhány tizedesjegyével, majd a visszatérési értéknél néhány műveletet végez el a kapott két paraméterrel. Ennyi lenne a modul, a programban két változót deklaráltam, majd azokat megadva paraméterként a modul visszatérési értékét egy harmadikba olvastam be, végül kiírtam a számítás menetét, és a végeredményt. Jól látható, hogy a modul szubrutinjára való hivatkozás &szamitasok::henger_terfogat(paraméterek) formában történt, az $pi változó értékéhez pedig $szamitasok::pi módon jutottam hozzá.

Akár több modult is egymásba ágyazhatunk, ekkor $modul1::modul2::valtozo formában történik a hivatkozás. Maga a program mindig a main modulban indul, tehát az egyébként $sugar-nak írt skalárt akár $main::sugar formában is írhatnánk.

Ha külső fájlban szeretnénk elhelyezni a modult, akkor a fájl neve legyen a package utasításnál megadott név .pm kiterjesztéssel, a fenti esetben "szamitasok.pm". A fő programban csak az use() utasítást kell kiadni, megadva a modul nevét, .pm kiterjesztés nélkül. Ebben az esetben valahogy így:

use szamitasok;

Itt kell szót ejtenem a @INC tömbről, melyben azok az elérési utak vannak felsorolva, amelyekben a Perl keresi a modulokat, ezért ha nem az alapértelmezett hely(ek)en tároljuk ezeket, akkor meghívás előtt ki kell adni egy push @INC, "/eleresi/ut/a/modulokhoz"; utasítást (hozzácsatoljuk a tömbhöz az új elérési utat).

A modulon belül további utasításokat lehet megadni, amelyek a modul betöltődésekor, ill. kitöltődésekor fognak végrehajtódni. Ezeket a BEGIN { … } és az END { … } -el tehetjük meg:

&modul::szubrutin; exit; package modul; BEGIN {   print "A modul betöltôdött.\n"; } sub szubrutin {   print "A szubrutin lefutott.\n"; } END {   print "A modul kitöltôdött.\n"; }

A kimenet:

A modul betöltôdött. A szubrutin lefutott. A modul kitöltôdött.

Mint látható, a program futásakor azonnal végrehajtódott a BEGIN utasításblokk, a futás végén pedig az END.

Végül lássunk egy összetettebb példát, mely a bekért adatok alapján egy külső modul segítségével kiszámolja egy henger néhány adatát!

A szamitasok.pm tartalma:

package szamitasok; BEGIN {   $pi = 3.1415926535897932; } sub henger_terfogat {   local $alapterulet = &kor_terulet($_[0]);   return $alapterulet * $_[1] ; } sub henger_felszin {   local $alapterulet = &kor_terulet($_[0]);   local $alapkerulet = &kor_kerulet($_[0]);   return 2 * $alapterulet + $alapkerulet * $_[1] ; } sub kor_terulet {   return $_[0]**2 * $pi; } sub kor_kerulet {   return 2 * $_[0] * $pi; } 1;
 

A henger.pl tartalma:

push @INC, "."; use szamitasok; print "Adja meg egy henger alapjának a sugarát!\n"; $sugar = <STDIN>; chomp($sugar); print "Adja meg a henger magasságát!\n"; $magassag = <STDIN>; chomp($magassag); print "-" x 40 . "\n"; print "A henger alapkerülete: " . &szamitasok::kor_kerulet($sugar) . "\n"; print " alapterülete: " . &szamitasok::kor_terulet($sugar) . "\n"; print " felszíne: " . &szamitasok::henger_felszin($sugar, $magassag) . "\n"; print " térfogata: " . &szamitasok::henger_terfogat($sugar, $magassag) . "\n"; exit;

A modulban négy szubrutin található, melyek némelyike egy másikat is használ, valamint van egy BEGIN rész is, amelyben deklarálásra kerül az $pi változó. A programban a modul betöltése után a szükséges adatok kerülnek bekérésre - a henger alapkörének sugara, valamint a test magassága -, majd a modul segítségével az adatok kiszámításra kerülnek, és kiíródnak. A program a modullal együtt megtalálható itt megtalálható.