Operátorok II.
Logikai operátorok
A legutóbbi anyag utolsó példájában továbbfejlesztettük a számokat egymás alá író programot. Ott volt olyan probléma, hogy 10 és 100 közé eső számok esetén fusson le egy ág (konkrétan egy else if ág, de ez most mindegy). Ez úgy volt megoldva, hogy az előtte lévő ág benyelte a 10 alatti számokat, így elég volt csak az $n < 100 esetet vizsgálni a feltételben. Természetesen előfordulhat hogy egy magányos ág esetén kell ilyen összetett feltételeket vizsgálni, most erre nézünk példát.
Mit tehetünk akkor, ha azt akarjuk egy feltételben vizsgálni, hogy egy szám kétjegyű-e? Tegyük fel hogy csak pozitív egész számokkal találkozhatunk és ezek az $n változóba kerülnek! A matekórák alapján (amennyiben bejártunk ezekre) arra gondolhatnánk, hogy a következő program pontosan úgy fog viselkedni, ahogy mi szeretnénk:
if (10 <= $n < 100){ // Hibás!
print "Kétjegyű!";
}
Sajnos nem ez a helyzet! A feltételben lévő kifejezés a PHP nyelvtani szabályai szerint értelmetlen.
Indoklás (akit érdekel): A kifejezés hasonlít például az $a + $b - $c kifejezésre, két azonos szinten lévő operátort (ugyanolyan szorosan kötnek) használunk egy kifejezésben zárójelezés nélkül. Az utóbbi esetében az aritmetikai operátorokra vonatkozó szabály alapján jobbról balra hajtódnak végre a műveletek, vagyis az ($a + $b) - $c kifejezéssel egyenértékű. Az összehasonlító operátorokra viszont nem határoztak meg végrehajtási sorrendet, így a 10 <= $n < 100 és hozzá hasonló kifejezések zárójelezés nélkül nyelvtani hibának számítanak.
Ha kipróbáljuk, akkor meg is kapjuk a jól megérdemelt hibaüzenetünket! Az ilyen összetett feltételes kifejezéseket tehát nem úgy értelmezi a PHP, mint ahogy általában szoktuk. Ezt az összetett feltételt azonban elő tudjuk állítani két egyszerű feltétel segítségével: $n >= 10 és $n < 100. A két feltételnek egyszerre kell teljesülnie, tehát először megvizsgáljuk hogy az első teljesül-e, és ha igen, akkor megvizsgáljuk a másodikat. A program egy beágyazott elágazás lesz:
if ($n >= 10){
if ($n < 100){
print "Kétjegyű!";
}
}
Most else ágak nem kellenek, mert ha valamelyik feltétel nem teljesül, akkor biztos nem kétjegyű a szám. Ezzel még nincs is különösebb probléma, viszont a PHP lehetővé teszi, hogy összetett feltételeket fogalmazhassunk meg egyetlen if vagy else if ág után, vagy ciklusfeltételben. Persze ezt nem úgy tesszük, ahogy a fent lévő hibás példában láttuk, hanem azon az alapelven, hogy egyszerű feltételes kifejezésekből rakjuk össze az összetett kifejezést:
Például az if ($n >= 10 és $n < 100) alakban írjuk a feltételbe, csak az "és" szó helyett olyan logikai operátort használunk, aminek ugyanaz a jelentése. A PHP-ben négyféle logikai operátor van:
operátor-jelölés kulcsszavas jelölés Logikai ÉS: && and Logikai MEGENGEDŐ VAGY: || or Logikai KIZÁRÓ VAGY: xor Logikai NEM: !
Mint látható, az első kettőnek kétféle jelölésmódja is van, így mindenki kénye-kedve szerint választhat közülük. Én a továbbiakban az operátor-jelölést (&& és ||) fogom használni, mivel más nyelvekben ezt szoktam meg, akinek nem tetszik, attól elnézést kérek! (A függőleges vonalat általában az AltGr+W billentyűkombinációval lehet előállítani)
A logikai és, megengedő vagy és kizáró vagy operátorokat két feltételes kifejezés közé, a nem operátort a kifejezés elé kell rakni. Legyen A és B két logikai kifejezés! Ekkor az operátorok jelentése a következő:
A && B akkor igaz, ha A és B is igaz
A || B akkor igaz, ha A és B közül legalább az egyik igaz
A xor B akkor igaz, ha A és B közül pontosan az egyik igaz
!A akkor igaz, ha A hamis (vagyis ha A ellentéte igaz)
Ez azt jelenti, hogy a megengedő vagy igaz akkor is, ha A és B egyaránt igaz, a kizáró vagy viszont csak akkor ad igaz értéket, ha csak A vagy csak B igaz. Itt az "igaz" szó alatt természetesen a true logikai értéket kell érteni. Itt az ideje hogy példákat nézzünk rájuk!
// Logikai és:
if ($n >= 10 && $n < 100){
print "Kétjegyű!";
}
Ez a fent vizsgált probléma megoldása logikai operátorral. Akkor fut le a print utasítás, ha az $n >= 10 és az $n < 100 feltétel is teljesül.
// Logikai megengedő vagy:
if ($szo == "igen" || $szo == "nem"){
print $szo;
}
Ha a $szo karakterlánc "igen" vagy "nem" értékű, akkor kiírjuk. Mivel egyszerre mindkettő nem lehet, ezért itt kizáró vagy-ot is használhattunk volna.
// Logikai kizáró vagy:
$n = 0;
while ($n < 8 xor $n > 5){
print $n;
++$n;
}
Ezeket az operátorokat persze ciklusok feltételében is lehet használni. A fenti példának nem sok értelme van, csak a kétféle vagy operátor közti különbséget szeretném megmutatni vele. A fenti esetben (xor operátort használva) az alábbi kimenetet adja: 012345. Azért áll meg 5-nél, mert amikor $n értéke 6, akkor mindkét feltétel teljesül, és ilyenkor a xor hamis logikai értéket eredményez. Ha ||-t használtunk volna, akkor végtelen ciklus lenne, mivel ez akkor is igaz értéket eredményez, ha mindkét feltétel teljesül.
// Logikai nem:
if (!isset($n)){
$n = 1;
}
A nem operátorra nehéz értelmes példát mutatni az eddig tanult eszközökkel, mivel mindegyik összehasonlító operátornak van ellentéte: például a !($n == 5) kifejezés egyenértékű az $n != 5 kifejezéssel. A fenti példában lévő isset() cucc igaz értéket ad, ha az utána lévő kerek zárójelek közé írt változó létezik, hamisat ha nem létezik. Vagyis a fenti példa létrehozza az $n változót, ha az nem létezik, és kezdőértéket ad neki.
A fenti példákban sehol sem használtam zárójeleket, mivel az összehasonlító operátorok a logikai operátoroknál szorosabban kötnek, de ha nem vagyunk biztosak ilyenekben, inkább zárójelezzünk. Ezekkel az eszközökkel már tetszőlegesen bonyolult feltételes kifejezéseket kreálhatunk, mivel több logikai operátort is használhatunk egyszerre. Például:
if ($n < 0 || ($n > 5 && $n < 10)){
// ...
}
Ez a feltétel akkor teljesül, ha $n vagy negatív szám, vagy 5 és 10 közötti. Ilyen esetekben viszont érdemes megfelelően zárójelezni. Bár a && és || operátorok nem ugyanolyan szorosan kötnek, de nekem például fogalmam sincs hogy melyik a szorosabb, úgyhogy zárójeleztem.
Érdekességképpen nézzük meg az alábbi példát:
if ((A && (!B)) || ((!A) && B)){
// ...
}
Itt A és B valamilyen feltételes kifejezés, azért rövidítettem őket, hogy áttekinthetőbb legyen. Mikor teljesül az if után írt feltétel? Ha szavakkal akarjuk felolvasni, nyilván akkor teljesül, ha "vagy A igaz és B hamis, vagy B igaz és A hamis". Ez röviden azt jelenti, hogy A és B közül pontosan az egyik igaz. Ez viszont ugyanaz, mint a kizáró vagy (xor). Ez azt jelenti, hogy a &&, || és ! operátorok alkotják a logikai alapműveleteket, ebből a háromból bármilyen bonyolult feltétel kifejezhető. Tulajdonképpen a xor csak kényelmi szempontok miatt került a PHP-be, a legtöbb programozási nyelvben nem szerepel.
Értékadó operátorok
Ugyancsak kényelmet támogatónak tűnhetnek azok az operátorok, amiket most fogok megmutatni, de valójában nem emiatt vezették be őket. Az értékadó operátorok közül egyet már ismerünk, ez az egyenlőségjel (=). Főleg ciklusmagokban gyakran fordul elő, hogy a már értékkel rendelkező változót növelni, csökkenteni, stb. kell bizonyos értékkel. Azt már tudjuk, hogy az eggyel való növelésre és csökkentésre a ++ és -- operátorok alkalmasak. Ha például 2-vel akarjuk növelni $n értékét, az $n = $n + 2 alakú kifejezéssel próbálkozhatunk, de ehelyett használhatjuk a += operátort: $n += 2. Minden aritmetikai operátorhoz tartozik egy értékadó operátor, melyek jelentése a következő:
$a += $b // jelentése: $a = $a + $b $a -= $b // jelentése: $a = $a - $b $a *= $b // jelentése: $a = $a * $b $a /= $b // jelentése: $a = $a / $b
Vagyis a += operátor az utána írt értékkel növeli, a -= az utána írt értékkel csökkenti az elé írt változót. A *= és /= operátorokat ritkábban szokták használni. Hogy megértsük, miért nem kényelmi szempontok miatt léteznek ezek az operátorok, nézzük az alábbi példát:
$a = 5; $n = (9 * $a) + 7; $n = $n + 3; $n += 3;
Itt az $n változónak az $a segítségével adunk értéket, viszonylag bonyolult módon. A 3. sorban a "klasszikus" módszert használtuk $n növelésére. Ebben a kifejezésben az $n változó kétszer szerepel, és mivel a számítógép nem gondolkodik előre, kétszer fogja kiértékelni (kiszámítani az $n értékét), majd utána hozzáad a jobb oldali értékhez 3-at, és ezt rakja az $n változóba. A 4. sorban viszont csak egyszer szerepel, így csak egyszer kell kiértékelni $n-t, tehát ezt az utasítást gyorsabban fogja végrehajtani. Hasonló okok miatt van ++ és -- operátor is.
Viszonylag hasznos értékadó operátor a .= ami az összefűző operátor után írt egyenlőségjel, értelmezése az imént megismertekéhez hasonló:
$a .= $b // jelentése: $a = $a.$b
Álatlában akkor használható, amikor egy ciklus segítségével fokozatosan építünk fel egy karakterláncot, pl.:
$k = "";
$n = 1;
while ($n < 10){
$k .= ($n.", ");
++$n;
}
print $k;
Ebben a példában a kezdetben üres karakterlánchoz minden cikluslépésben hozzáfűzzük $n értékét és egy vesszőt. A kimenet az alábbi lesz:
1, 2, 3, 4, 5, 6, 7, 8, 9,
Modulus operátor
Még egy érdekes operátort megvizsgálunk, mert bizonyos esetekben hasznos lehet. A négy alapműveleten kívül van még egy aritmetikai operátor, ami a maradékos osztást végzi el, ezt modulus (vagy modulo) operátornak hívják, és jele pedig a százalékjel: %. Erre azért van szükség, mert csak nehezen (ciklussal) lehet kifejezni a négy alapművelet felhasználásával. Két operandusa van, mindkettő egész szám és az eredmény is egész.
Általános esetben az $a % $b kifejezés az $a / $b osztás maradékát adja. Például 7 % 2 = 1 mert 7 osztva 2-vel az 3, maradék 1. Vagy például 5 % 8 = 5 mert a hányados 0, maradék 5. Itt mindig egész számokban kell gondolkodni! Általában akkor vesszük hasznát, ha el akarjuk egy számról dönteni, hogy osztható-e egy másikkal. Ha pl. $a osztható $b-vel, akkor $a % $b = 0, ha nem osztható, akkor nullától különböző az eredmény.
Ezt az operátort egy kicsit bonyolultabb példán keresztül mutatom be. Készítsünk egy programot, ami megvizsgálja egy számról, hogy prímszám-e! Prímszám az olyan egész szám, ami csak 1-gyel és önmagával osztható. Most az egyszerűség kedvéért csak pozitív számokkal foglalkozzunk! Érdemes kihasználni azt a tényt, hogy egy szám sosem osztható a felénél nagyobb számokkal (kivéve önmagát). Vagyis a tesztelést pl. egy $a szám esetében elegendő 2-től $a/2-ig végezni (1-re és önmagára sem kell, mivel minden szám osztható 1-gyel és önmagával). A feladat egy lehetséges megoldása:
$szam = 101;
$prim = true;
$x = 2;
while ($x <= ($szam / 2)){
if ($szam % $x == 0){
$prim = false;
}
++$x;
}
// Az eredmény a $prim változó értékétől függ:
if ($prim == true){
print $szam." prímszám!";
}
else{
print $szam." nem prímszám!";
}
Ez egy jó példa arra is, hogy mikor érdemes egy változónak logikai értéket adni. Kezdetben feltételezzük a számról hogy prím, de amint kiderül az ellenkezője (osztható egy számmal), megváltoztatjuk a logikai értéket. Érdemes a programot kipróbálni különböző számokra, jelen esetben 101 prímszám. Ha egy kicsit figyelmesen végignézzük a programot észrevehetjük, hogy nem a legjobb (pedig én írtam...)! Ugyanis amikor kiderült, hogy a szám nem prím, a ciklus fut tovább, és az összes ezutáni számot is megvizsgálja. Így a program feleslegesen dolgozik, a legjobb az lenne, ha megszakítanánk a ciklust, amint kiderült hogy a szám nem prím. Ennek a problémának a megoldása az egyik házi feladat része lesz (nehogy már csak én dolgozzak! :D).
Házi feladat
1.) Gyűjtsük össze egy karakterláncba (pl. vesszővel elválasztva) 1-től 100-ig az összes olyan számot, ami osztható 2-vel, 3-mal és 5-tel is! Használjunk logikai operátort a feltételvizsgálatban!
(Megoldás: 30, 60, 90)
Az oszthatóság vizsgálata - ahogy az előző leckében megtudtuk - a modulus operátorral lehetséges: $a osztható $b-vel, ha $a % $b = 0. Ennek megfelelően a program egy lehetséges megoldása:
$szamok = "";
$n = 1;
while ($n <= 100){
if (($n % 2 == 0) && ($n % 3 == 0) && ($n % 5 == 0)){
$szamok .= ($n.", ");
}
++$n;
}
A karakterlánc eleinte üres, majd egy ciklussal belerakosgatjuk azokat a számokat, melyek teljesítik a feltételt (oszthatók 2-vel, 3-mal és 5-tel is). Itt a karakterlánc feltöltésére érdemes a .= operátort használni.
2.) Írjunk programot, ami kiírja az összes 1000-nél kisebb prímszámot (megint csak pozitív számokkal foglalkozzunk)! Használhatjuk a fenti példaprogramot a vizsgálatra. Próbáljuk meg megoldani a fent említett problémát, vagyis hogy ha a szám osztható valamivel, akkor ugrasszuk ki a ciklusból a programot! Figyeljük meg a különbséget, hogy mennyivel gyorsabb lett a program! Ha nem vehető észre, akkor állítsunk be 1000-nél nagyobb számot.
Itt tulajdonképpen csak az előző leckében szereplő példaprogramot kellett berakni egy ciklusba annyi változtatással, hogy a ciklus végén magát a számot írjuk ki, ha prím, ha nem prím akkor meg nem csinálunk semmit. A ciklusból pedig úgy lehet a legegyszerűbben kiugrani, ha hamissá tesszük a ciklusfeltételt.
$szam = 1;
while ($szam < 1000){
$prim = true;
$x = 2;
while ($x <= ($szam / 2)){
if ($szam % $x == 0){
$prim = false;
$x = $szam; // "kiugrasztás" a ciklusból
}
++$x;
}
if ($prim == true){
print $szam.", ";
}
++$szam;
}







