Elágazások II.
A switch szerkezet
Miután megtudtuk, hogy többféle ciklus is létezik a PHP-ben, talán nem olyan nagy meglepetés, hogy elágazásból is többféle van. Elvileg már kétfélét ismerünk, az if-else és az else-if szerkezeteket. Előbbivel kétfelé tudjuk ágaztatni a program végrehajtását (vagy egy magányos if ág létrehozásával feltételesen tudunk utasításokat végrehajtani), utóbbi alkalmas kettőnél több irányú elágazások létrehozására. Most megtanulunk még egy típust, amivel szintén többirányú elágazást készíthetünk.
Gyakran előfordul, hogy egyetlen változó értékét állandók halmazával szeretnénk összehasonlítani, és ez alapján elágazást létrehozni. Tegyük fel például, hogy ki szeretnénk íratni a PHP-vel, hogy a hét melyik napja van (lehetőleg magyarul). Már említettem, hogy a date() függvénnyel ezt ki lehet deríteni, csak utána kell járni, hogy milyen speciális karaktert kell használni az első paraméterében. Erre alkalmas például a "w" karakter:
$nap = date("w", time());
Így a $nap változóba egy egész szám kerül 0 és 6 között. A 0 jelenti a vasárnapot, az 1 a hétfőt, a 2 a keddet, ... a 6 a szombatot. Ha az a célunk, hogy kiírjuk a nap nevét, akkor egy else-if szerkezetet használhatunk az alábbi módon:
if ($nap == 0){
print "vasárnap";
}
else if ($nap == 1){
print "hétfő";
}
...
else if ($nap == 6){
print "szombat";
}
A ... helyén persze a többi ág van, remélem mindenki oda tudja képzelni. Látható, hogy itt egy változót ($nap) hasonlítunk össze állandók halmazával (0, 1, 2, 3, 4, 5, 6). Erre a feladatra áttekinthetőbb kódot írhatunk, ha a switch-szerkezetet használjuk. Ennek a formája egy kissé eltér az eddigiektől, általános esetben így néz ki:
switch (kifejezés){
case érték_1:
utasítások
break;
case érték_2:
utasítások
break;
...
case érték_n:
utasítások
break;
}
A switch szó után kerek zárójelek közé kell írnunk azt a kifejezést, amit különféle állandókkal szeretnénk összehasonlítani (jelen esetben a $nap változó). Ezután kapcsos zárójelek között hozzuk létre az egyes ágakat. Egy case szó után írjuk azt az állandót, amivel összehasonlítjuk a kifejezést, majd utána jön egy kettőspont, és az utasításlista. Az a case ághoz tartozó utasításlista fog lefutni, amelyiknél a case után írt érték megegyezik a kifejezés értékével. Figyelem: itt az utasításlistát nem határolják kapcsos zárójelek. Kezdete a case után írt kettőspont, végét pedig a break utasítás zárja.
Nézzük, hogy néz ki a fenti elágazás switch-szerkezettel:
switch ($nap){
case 0:
print "vasárnap";
break;
case 1:
print "hétfő";
break;
...
case 6:
print "szombat";
break;
}
Itt a $nap változó értéke sorban összehasonlítódik a 0, 1, stb számokkal, és amint az egyikkel egyezik, az utána lévő utasítások végrehajtódnak egészen a break utasításig, majd kiugrik a switch szerkezetből. Ha egyik case utáni érték sem egyezik, akkor nem történik semmi.
A break utasítás valójában nem arra szolgál, hogy jelezze az adott case ág végét, hanem hogy kiugorjunk a switch szerkezetből. Ha valamelyik ág végéről hiányzik a break, akkor a következő ág utasításai is végrehajtásra kerülnek, függetlenül a következő ág case után írt értékétől. A switch szerkezet működését tehát úgy kell elképzelni, hogy a case címkék mutatják a program számára a lehetséges belépési pontokat, és a program ott lép be a szerkezetbe, ahol a case után írt érték megegyezik a switch után lévő kifejezés értékével. Innentől a program addig hajtja végre az utasításokat, amíg nem találkozik egy break utasítással, vagy a szerkezet végére nem ér. Ez tehát eltér az eddig látott szerkezetek működésétől. De mire jó az, ha valahol elhagyjuk a break utasítást? Előfordulhat, hogy több különböző érték esetén is ugyanazokat az utasításokat kell végrehajtani. Ilyenkor egyszerűen azt csinálhatjuk, hogy felsoroljuk a case ágakat, majd utána írjuk a hozzájuk tartozó utasításokat. Például ha azt akarjuk vizsgálni, hogy hétvége van-e, azt így tehetjük meg:
switch ($nap){
case 0:
case 6:
print "hétvége van";
break;
case 1:
case 2:
case 3:
case 4:
case 5:
print "hétköznap van";
break;
}
A switch szerkezetben is van olyan ág, ami az else ágnak felel meg, vagyis akkor fut le, ha egyik case ág sem teljesült. Ezt a default kulcsszó jelöli. A fenti példa ezért így is írható:
switch ($nap){
case 0:
case 6:
print "hétvége van";
break;
default:
print "hétköznap van";
break;
}
A default után is kettőspontot kell írni, és utolsóként kell szerepelnie a szerkezetben. A break szempontjából viszont ez is ugyanúgy viselkedik, mint a többi case ág, vagyis ha a default előtt nincs break utasítás, akkor az utána írt utasítások szintén végrehajtódnak. Valójában az utolsó ág után felesleges break utasítást rakni, mivel a szerkezet végén a program úgyis kilép belőle.
A kerek zárójelek közé és a case címkék után bonyolultabb kifejezések is írhatók, a lényeg, hogy a case után írt kifejezésnek ismert értékűnek kell lennie. Például:
switch ($valasz.$szamsor){
case "igen123":
...
break;
case "nem321":
...
break;
...
}
Látható, hogy a szerkezet karakterláncok esetén ugyanúgy működik. A switch szerkezetet általában akkor érdemes használni, ha egy változó értékét egész számok, vagy karakterláncok halmazával szeretnénk összehasonlítani.
Ciklusvezérlő utasítások
A ciklus vezérlését nem csak a ciklusváltozóval illetve a vezérlő résszel befolyásolhatjuk. Van két utasítás, melyet ciklusmagon belül használva kiléphetünk a ciklusból, vagy indíthatjuk a ciklusmag következő lefutását.
A break utasítás nem csak a switch-ből való kilépésre használható, alkalmas ciklusból való kilépésre is! Bárhol elhelyezhetjük egy cikluson belül, ha végrehajtódik, akkor a ciklus utáni első utasításra ugrik a program. Így egy while (true){...} alakú ciklusból is ki tudunk lépni. Több egymásba ágyazott szerkezet esetén a break utasítás a legbelső while, for vagy do ciklusból vagy switch szerkezetből ugraszt ki:
$szam = 23;
for ($x = 2; $x <= ($szam / 2); ++$x){
if ($szam % $x == 0){
print "Nem prím!";
break;
}
}
Ez a szerkezet megállapítja a $szam változóról, hogy prímszám-e. Egy hasonló program esetén korábban úgy léptünk ki a ciklusból, hogy hamissá tettük a ciklusfeltételt. Ezt nem mindig tudjuk megvalósítani, ráadásul a break utasítás egyszerűbb is, és látszik hogy a szándékunk a ciklusból való kilépés (egy értékadásra ránézve ez nem mindig ilyen nyilvánvaló).
Az előző példában tehát a for ciklusból fog kiugrasztani a break. Több egymásba ágyazott ciklus esetén csak a legbelső ciklusból tudunk így kiugrani. Ha egyszerre többől is ki akarunk lépni, a break-nek meg kell adnunk egy paramétert (pozitív egész számot), ami azt jelenti, hogy hány egymásba ágyazott ciklusból illetve switch szerkezetből akarunk kiugrani. Példa:
$n = 1;
while (true){
switch ($n){
case 3:
break 1;
case 5:
print "vége";
break 2;
default:
print $n.", ";
}
++$n;
}
Ezt a paramétert úgy kell használni, hogy egyszerűen a break után írjuk egy szóközt kihagyva. A paraméter alapértelmezett értéke 1, így a break 1; utasítás a break; utasítással egyenértékű, vagyis a fenti példában az csak a switch-ből ugrik ki. A fenti program első ránézésre egy végtelen ciklus, de valójában nem, mivel a break 2; utasítás nem csak a switch szerkezetből, hanem a while ciklusból is kiugrik.
A program az alábbi kimenetet hozza létre: 1, 2, 4, vége
Az 1, 2 és 4 számok esetén ugyanis a default ág hajtódik végre, ami kiírja a számot. A 3-as szám esetén viszont kiugrunk a switch-ből és folytatódik tovább a ciklus, amikor pedig elérjük az 5-öt, kiugrunk az egész ciklusból.
Hasonló utasítás a continue, amit azonban csak ciklusokban használhatunk. Ez nem a ciklusból való kilépésre való, hanem a ciklusmag további részeinek átugrására. Vagyis a continue végrehajtásakor a program a ciklusmag végére ugrik. Újra megvizsgálódik a ciklusfeltétel, és ha igaz, elkezdődik a ciklusmag újbóli végrehajtása. Például:
$szam = 23;
$x = 2;
while ($x <= ($szam / 2)){
if ($szam % $x != 0){
++$x;
continue;
}
print "Nem prím!";
break;
}
Ez a program ugyanazt csinálja, mint az egyik fenti (megállapítja hogy a $szam változó prímszám-e), csak kicsit máshogy áll hozzá a problémához. Az előző program úgy gondolkodott, hogy sorban megvizsgáljuk a $szam-nál kisebb számokat és ha $szam osztható vele, akkor kiderült hogy nem prím, és kiugrunk a ciklusból. Az utóbbi program szintén sorbaveszi a változónál kisebb számokat, viszont azt mondja, hogy ha a $szam nem osztható vele, akkor nézzük meg a következőt.
A continue utasítás itt arra szolgál, hogy (miután növeltük $x értékét) hajtsuk végre a ciklus következő lépését. Ha az összes számot megvizsgáltuk, akkor a continue hatására megvizsgálódik a ciklusfeltétel, de mivel az hamis, így a ciklus véget ér. Persze ha az if utáni feltétel nem teljesül, az azt jelenti hogy a szám nem prím, és ilyenkor kiugrunk a ciklusból.
A kérdés az, hogy mi van akkor, ha for ciklust használunk, ugyanúgy mint az előbb. Mi történik, ha így írjuk át a fenti programot:
$szam = 23;
for ($x = 2; $x <= ($szam / 2); ++$x){
if ($szam % $x != 0){
continue;
}
print "Nem prím!";
break;
}
A problémát az okozza, hogy vajon növekszik-e $x értéke, ha belépünk az if ágba? Fontos, hogy a continue nem a ciklust kezdi újra, hanem a ciklusmag végére ugrik! Mivel a for ciklus változtató része (3. rész, jelen esetben a ++$x utasítás) a ciklusmag lefutása után hajtódik végre, ezért a continue utasítás után növekedni fog a változó értéke. Vagyis a program két utóbbi változata egyenértékű!
Látható tehát, hogy for ciklus magjában (ha jól írjuk meg a feltételét) nem kell foglalkozni a ciklus vezérlésével akkor sem, ha continue-t használunk.
A continue utasításnak szintén adhatunk egy paramétert (pontosan ugyanúgy, mint a break esetén), amivel megadhatjuk, hogy több egymásba ágyazott ciklus esetén melyiknek a végére akarunk ugrani:
for ($n = 1; $n <= 4; ++$n){
print "<br />".$n.": ";
for ($m = 1; $m <= 5; ++$m){
if ($n == 2){
continue 2;
}
print $m.", ";
}
}
Ez a program kiírja 1-től 5-ig a számokat vesszővel elválasztva több sorban egymás után, viszont a 2. sort kihagyja. A kimenet ilyen lesz:
1: 1, 2, 3, 4, 5,
2:
3: 1, 2, 3, 4, 5,
4: 1, 2, 3, 4, 5,
Ebben az esetben a continue 2; utasítás nem a belső, hanem a külső ciklus végére ugrik. A programot persze meg lehet csinálni máshogy is, de már fáradok, úgyhogy ez házi feladat lesz!
Házi feladat
1.) Írjunk függvényt, ami visszaadja az aktuális dátumot ilyen formában: 2009. január 11.
A date() függvényben az "m" adja meg a hónap sorszámát, de vigyázz, mert ha egyjegyű, akkor nullát rak elé!
Mivel az egyjegyű számok előtti nullák esetleg problémát okozhatnak (ha egész számokként dolgozunk velük), illetve a date() függvény visszatérési értéke amúgy is egy karakterlánc, így a date("m", ...) által visszaadott értéket az alábbi karakterláncok halmazával fogjuk összehasonlítani: "01", "02", ... "12". Így kézenfekvőnek tűnik egy switch szerkezet írása a hónap nevének meghatározására. A feladat egy lehetséges megoldása:
function datum(){
$ido = time();
$ev = date("Y", $ido);
$honap = date("m", $ido);
$nap = date("d", $ido);
switch ($honap){
case "01": $honap = "január"; break;
case "02": $honap = "február"; break;
// ...
case "12": $honap = "december"; break;
default: $honap = "?"; break;
}
return $ev.". ".$honap." ".$nap.".";
}
Itt az egyes case ágakat egy sorba írtam, mivel így szerintem egy kicsit áttekinthetőbb. Ebben az esetben ugyanis mindegyik ágban csak egy értékadás szerepel, és egy break utasítás.
2.) Egyszer egy fórumban találtam ezt a PHP kódot (azt nem írom le melyikben, mert nem akarok lejáratni senkit):
function szamolo($szam1, $szam2, $muvelet){
$vegeredmeny = $szam1 $muvelet $szam2 ;
}
szamolo(23, 10, +);
Az utolsó sor a függvény hívására mutat egy példát. Szegény gyerek azt sem értette, hogy miért kap hibaüzenetet. Próbáljuk helyrepofozni a kódot, hogy úgy működjön, ahogy az alkotója szerette volna (mondjuk a négy alapműveletre). Nyilván az volt a cél, hogy a függvény elvégezze a műveletet az első két paraméterével, majd visszaadja az eredményt. A függvényhívást is ki kell javítani, mert operátort nem adhatunk át paraméterként!
A függvényhívás esetén érdemes a harmadik paramétert karakterláncként átadni:
szamolo(23, 10, "+");
Így a függvény definíciója lehet például ilyen:
function szamolo($szam1, $szam2, $muvelet){
switch ($muvelet){
case "+": return $szam1 + $szam2;
case "-": return $szam1 - $szam2;
case "*": return $szam1 * $szam2;
case "/": return $szam1 / $szam2;
default: return "?";
}
}
Itt a break utasítás használata fölösleges, mert a return az egész függvényből kiugrik.
3.) Írjuk át az utolsó példaprogramot úgy, hogy nem használjuk a continue utasítást!
A program kiírta 1-től 5-ig a számokat vesszővel elválasztva több sorban egymás után, a 2. sort pedig kihagyta. A kimenet:
1: 1, 2, 3, 4, 5,
2:
3: 1, 2, 3, 4, 5,
4: 1, 2, 3, 4, 5,
A continue utasítás nélkül például úgy lehet megírni a programot, hogy az if-es feltételvizsgálatot a belső cikluson kívülre rakjuk, így a belső ciklus csak akkor fut le, ha nem a 2. sorban vagyunk.
for ($n = 1; $n <= 4; ++$n){
print "<br />".$n.": ";
if ($n != 2){
for ($m = 1; $m <= 5; ++$m){
print $m.", ";
}
}
}
Az így megírt program ebben az esetben talán kicsit egyszerűbb is lett. A continue utasítást nem is szokták gyakran használni, és általában csak hosszú ciklusok esetén van értelme.







