Függvények III.
Adattípusok
Tudjuk, hogy a változóknak különféle típusú adatokat adhatunk, pl. egész szám, karakterlánc, logikai érték, stb. Most röviden áttekintjük ezeket. A PHP-ben előforduló adattípusok láthatók az alábbi táblázatban:
| Adattípus | Név | Példák | Lecke |
|---|---|---|---|
| null | null | $x = null; | 11. |
| Logikai érték | bool | $x = true; $x = false; | 8. |
| Egész szám | integer | $x = 10; | 2. |
| Lebegőpontos szám | float | $x = 5.925; $x = 3.2e-4; $x = 85E6; | 2. |
| Karakterlánc | string | $x = "abc"; $x = 'abc'; | 2. |
| Tömb | array | $x = array(1, 2, 3); | 15. |
| Objektum | object | $x = new típusnév(...); | - |
| Erőforrás | resource | $x = speciális_függvény(...); | - |
Olyan is van benne ami még nem volt a tanfolyamon. A "Név" oszlopban a típus angol neve szerepel, erre akkor van szükség, ha valamilyen függvénnyel le akarjuk kérdezni egy változó típusát. A "Példák" oszlopban lehetséges értékadási módokat láthatunk, a "Lecke" oszlopban lévő linkek pedig arra a leckére mutatnak, ahol először volt szó az adott típusól.
Ha null "értéket" adunk egy változónak, azzal azt jelezzük, hogy az adott változó nem tárol semmit, ha meghívjuk rá az isset() függvényt, false értéket ad vissza.
Egy logikai érték csak true (igaz) vagy false (hamis) értékű lehet.
Az egész számokra már rengeteg példát láttunk az eddigi leckékben.
A lebegőpontos számok a tizedestörtek, csak annyi különlegesség van bennük, hogy tizedesvessző helyett tizedespontot kell írni értékadáskor (vagy bármilyen más kifejezésben). Van ezenkívül egy másik módszer is ilyen szám megadására, ahogy a táblázatban láthatjuk. A 3.2e-4 vagy 3.2E-4 jelentése például 3,2*10-4. Kis vagy nagy "e" betűt is használhatunk, így 10 hatványaként adhatjuk meg a számot. 3,2*10-4 jelentése egyébként 3,2/10000 de ha túl furcsának vagy nehézkesnek gondolod a tizedestörtek ezen megadási módszerét, nem kell használni, ez csupán egy lehetőség. Egyébként tizedestörteket még nem is használtunk a tanfolyamon, és nem is nagyon fogunk, mivel a PHP-t általában nem matematikai számításokra használják.
Karakterláncra is sok példát láttunk már, idézőjelek vagy aposztrófok között kell megadnunk. A különbség annyi, hogy idézőjelek között használhatunk escape karaktereket, melyekről még az 1. leckében volt szó.
Tömbökkel a következő leckében foglalkozunk, segítségükkel több azonos típusú változót rakhatunk egyetlen változóként kezelhető egységbe.
Objektumokkal a tanfolyamon nem fogunk foglalkozni, mivel ez már nem az "alapok" kategória. Ezekkel több, akár különböző típusú változót foglalhatunk egybe, így definiálhatunk tetszőleges, saját változótípusokat, a hozzá tartozó függvényekkel együtt. Kezelésük azonban viszonylag bonyolult. Például így létrehozhatunk egy dátum típust (mondjuk date néven), ami két egész számot (év, nap) és egy karakterláncot (hónap neve) tartalmaz.
Az erőforrás olyan típus, amit a PHP-vel nem tudunk közvetlenül kezelni. Általában függvények adják visszatérési értékként, és más függvényeknek kell paraméterként átadnunk, ha ki akarjuk belőle olvasni az adatokat. Például adatbázisok kezelésénél sokszor fordul elő ilyen adattípus.
Ha tudni akarjuk, hogy éppen milyen típusú változóval dolgozunk, akkor használhatjuk például az is_típusnév() alakú függvényeket. A típusnév helyére a fenti táblázat "Név" oszlopában látható szavak valamelyike kerülhet. Például:
$valtozo = "20";
if (is_string($valtozo)){
print "karakterlánc";
}
if (is_integer($valtozo)){
print "egész szám";
}
A példában létrehoztunk egy karakterláncot, amelyben számjegyek vannak ugyan, de mivel idézőjelek között van, ezért a típusa karakterlánc. Az is_típusnév() függvények paramétere a vizsgálni kívánt változó, visszatérési értéke pedig true, ha a változó típusa a típusnév-vel egyezik, különben false. A fenti példában az is_string($valtozo) true értéket ad vissza, az is_integer($valtozo) pedig false-t, így csak az első if ág fog végrehajtódni, a második nem.
Viszonylag ritkán van szükség arra, hogy ismerjük egy változó típusát, mivel a PHP elég lazán kezeli ezeket, például a karakterláncok és számok közötti átalakítás teljesen automatikus. Például:
$x = "23";
$y = $x * 5; // $x karakterlánc átalakul egész számmá
print $y; // $y egész szám átalakul karakterlánccá
if (is_string($y)){
...
}
Nyilván karakterláncot és számot értelmetlen összeszorozni, és kiírni csak karakterláncot lehet, de a PHP automatikusan elvégzi a megfelelő típusátalakításokat. Ezek az átalakítások viszont csak az adott kifejezésben történnek meg, így a fenti páldában az if ág nem fog végrehajtódni!
Az adattípusok kapcsán fontos megemlíteni a === és !== összehasonlító operátorokat. A három egyenlőségjel majdnem ugyanúgy működik mint a kettő (==), de az előbbi csak akkor ad igaz értéket, ha a két operandus típusa is megegyezik. Ennek az ellentéte a !== operátor. Például van egy egész számunk és egy karakterláncunk, amiben ennek a számnak a számjegyei vannak. Ilyen esetben már különböző eredményeket ad a két operátor. Például:
$szam = 72;
$string = "72";
if ($szam == $string){ // igaz
...
}
if ($szam === $string){ // hamis
...
}
if ($szam !== $string){ // igaz
...
}
Az első ág feltétele igaz, mivel itt a karakterlánc átkonvertálódik számmá, és így a két változó megegyezik. A második eset csak akkor lenne igaz, ha a két változó típusa és értéke is megegyezne. Mivel a típusuk különböző, a feltétel nem teljesül. A harmadik feltétel pedig akkor teljesül ha vagy a típusuk, vagy az értékük különbözik (vagy mindkettő), és mivel a típus különböző ez a feltétel igaz lesz. Viszonylag ritkán használatosak ezek az operátorok, bizonyos esetekben viszont szükség lehet rájuk, erre láthatunk majd példát a 18. leckében.
A lecke további részeiben három extra dolgot ismerünk meg a függvények témaköréből, de ezeket az egyszerűség és könnyebb megértés kedvéért a későbbiekben nem fogjuk használni. Ez azt jelenti, hogy a lecke további részei át is ugorhatók, de aki többet szeretne megtudni a függvények működéséről, illetve hasznos technikákat szeretne megismerni, annak mindenképp érdemes elolvasni a lecke hátralévő részét! A végén található házi feladatok is ezekhez kapcsolódnak.
Referenciák
A referencia egy változó másik neve. Ha páldául létrehozunk egy $szam nevű változót:
$szam = 15;
akkor erre a változóra természetesen a $szam névvel hivatkozhatunk, semmilyen más módon nem tudjuk elérni ezt a változót. Ha ezt csináljuk:
$szam = 15; $x = 15;
akkor két változó jött létre, amiknek semmi közük egymáshoz, hiába ugyanaz az értékük. Az egyik értékének megváltoztatása nyilván nem lesz hatással a másik értékére.
Referenciák létrehozásával el tudjuk érni, hogy egy változóra több névvel is hivatkozhatunk. Például van egy változónk:
$legnagyobb_arc = "Hörb";
De tegyük fel, hogy én $hulyegyerek néven is el szeretném érni ezt a karakterláncot! Ekkor az alábbit kell csinálni:
$legnagyobb_arc = "Hörb"; $hulyegyerek = &$legnagyobb_arc;
Itt a második sorban látható & jel egy operátor (vagy legalábbis felfogható annak), jelentése: referencia a $legnagyobb_arc változóra. Ennek az lesz a hatása, hogy ezentúl két különböző névvel is tudok hivatkozni ugyanarra a változóra. A két név egyébként teljesen egyenrangú. Mivel ugyanarra a változóra hivatkozik mindkettő, a következő kód a "nem Hörb" karakterláncot fogja kiíratni:
$legnagyobb_arc = "Hörb"; $hulyegyerek = &$legnagyobb_arc; $hulyegyerek = "nem Hörb"; print $legnagyobb_arc;
Vagyis "egyik" megváltoztatása hatással lesz a "másikra" is, mivel itt csak egyetlen változó van, csak van neki két neve. Első ránézésre ennek az egésznek csak akkor van értelme, ha csak játszadozni akarunk, vagy kínunkban már nem tudunk mit csinálni. A referenciákat valójában a fenti formában nem szokták használni, függvények írásakor vehetjük hasznukat. Többféleképpen is fel lehet őket használni függvényekben, most csak a paraméterátadás esetét vizsgáljuk.
Emlékezzünk vissza a függvényekről tanultak közül egy fontos dologra: a függvény paraméterének változtatása nem lesz hatással a paraméterként átadott változóra. Vagyis például az alábbi függvény nagyjából annyit ér, mintha nem is írtunk volna semmit:
function inc($x){
++$x;
}
Ez a függvény szeretné növelni eggyel a paraméterét. Az alábbi függvényhívás azonban nem csinál semmit a paraméterként átadott változóval:
$szam = 5; inc($szam); print $szam;
Mivel a függvény paraméterként csak a $szam változó értékét kapja meg (nem magát a változót), így a változtatások nem lesznek hatással az eredeti változóra. Így kiíratáskor még csak véletlenül sem a 6-os szám kerül kiírásra! Olykor azonban hasznos lehet, ha a függvény meg tudja változtatni a paraméterként átadott változót. Ezt úgy tudjuk elérni, hogy nem a változó értékét adjuk át, hanem egy változóra hivatkozó referenciát. Ehhez a függvénydefiníció paraméterlistáját módosítani kell:
function inc(&$x){
++$x;
}
$szam = 5;
inc($szam);
print $szam;
Csak a függvény definíciója módosult: a függvény nem a változó értékét fogadja paraméterként, hanem egy arra hivatkozó referenciát. A $szam változó ugyan a globális hatókörben van (lásd 10. lecke), a függvény mégis meg tudja változtatni, mivel az $x változó a függvénytörzsben ugyanerre a változóra hivatkozik. Figyeljük meg, hogy a függvény hívása ugyanúgy történt, mint a korábbi esetben, vagyis a függvény használatakor csak simán a változót kell átadnunk. Egyébként ha a függvény hívásakor próbálunk referenciát átadni inc(&$szam); módon, a PHP figyelmeztetést ad, hogy ez a módszer "elavult", és inkább a függvény paraméterlistáját változtassuk. Tehát a fent látható módszerrel tudjuk elérni, hogy a függvény megváltoztathassa a paraméterként átadott változót. Ezt azonban csak akkor érdemes használni, ha erre feltétlenül szükség van. Ha ugyanis referenciaként adunk át egy változót, akkor óvatosnak kell lenni a függvényen belül a változó használata során. Csak akkor használjunk referencia szerinti paraméterátadást, ha kifejezetten a paraméterként átadott változó megváltoztatása a célunk!
Alapértelmezett paraméterek
Olykor hasznos lehet, ha egy függvény paraméterlistájában lehetőségeket biztosítunk különféle extra szolgáltatások használatára. Például képzeljük el, hogy írtunk egy függvényt, ami egy tetszőleges (az egyszerűség kedvéért 10-nél kisebb) számrendszerbeli számot 10-es számrendszerbeli számmá konvertál. Például így:
convert("1001101001", 2);
Ez azt csinálja, hogy az első paramétereként megadott számsort (amit karakterláncként adunk át) átkonvertálja 10-es számrendszerbe. Nyilván meg kell mondani, hogy milyen számrendszerben van az eredeti szám, amit a második paraméter ad meg (a 1001101001 nyilván lehetne 10-es számrendszerbeli szám is). A fenti példában a függvény 617-et ad vissza, mivel ez az a szám, ami 2-es számrendszerben 1001101001. Ha azt szeretnénk, hogy a függvény más számrendszerekbe is tudjon konvertálni, akkor egy harmadik paraméterrel bővíthetjük a függvény definícióját, ahol a függvény hívója megadhatja, hogy milyen számrendszerbe akar konvertálni. Például:
convert("1001101001", 2, 8);
Így a függvény nem 10-es, hanem 8-as számrendszerbe konvertálná a megadott számot.
Ha azonban a függvény hívásakor mi mindig 10-es számrendszerbe akarunk konvertálni, akkor kényelmetlen lehet mindig megadni a 10-es számot paraméterként. Ha fontos számunkra a függvény használójának kényelme, megtehetjük a függvény definiálásakor, hogy harmadik paraméternek adunk egy alapértelmezett értéket. A függvény definíciójában ez így néz ki:
function convert($szamsor, $honnan, $hova = 10){
...
}
Látható, hogy a harmadik paraméternek értéket adtunk a paraméterlistában. Ez azt jelenti, hogy a függvény hívásakor elegendő csak az első két paramétert megadni, ez azzal lesz egyenértékű, mintha a harmadik paraméternek 10-est adtunk volna. Vagyis az alábbi két függvényhívás ugyanazt fogja eredményezni:
convert("1001101001", 2, 10);
convert("1001101001", 2);
A második változatban is 10-es számrendszerbe konvertálódik az első paraméter, mivel a harmadik paraméter alapértelmezett értéke 10. Több paraméternek is megadhatjuk az alapértelmezett értékét:
function convert($szamsor, $honnan = 2, $hova = 10){
...
}
Így a függvényt akár egy paraméterrel is hívhatjuk, feltéve hogy 2-es számrendszerből akarunk 10-esbe konvertálni. Fontos azonban, hogy csak a paraméterlista végén szereplő paraméterek kaphatnak alapértelmezett értéket. Ez azt jelenti, hogy alapértelmezett értékkel rendelkező paraméter után nem állhat olyan, aminek nincs alapértelmezett paramétere, mert ekkor a függvény hívásánál nem lehetne tudni, hogy melyik paraméter van megadva, és melyik nem. Vagyis az alábbi példa nem jó:
function convert($honnan = 2, $hova = 10, $szamsor){ // HIBÁS!
...
}
Például az exit() nevű beépített függvény is hasonlóan működik. A 11. leckében megtudtuk, hogy megadhatunk egy paramétert, amit kiír a program megszakítása előtt. Ezt például úgy lehet megvalósítani, hogy van egy paramétere, aminek az alapértelmezett értéke egy üres karakterlánc, és kilépés előtt ezt kiírja:
function exit($hiba = ""){
print $hiba;
// a program megszakítása...
}
Ez persze csak egy példa, nem biztos, hogy pont így van megvalósítva az exit függvény, de a fenti kóddal ugyanezt a hatást érjük el.
Rekurzió
Utolsó extraként egy érdekes lehetőséget nézünk meg, amit függvények írásakor használhatunk: a függvény meghívhatja saját magát. Ezt úgy kell elképzelni, hogy a függvény törzsében szerepel egy függvényhívás, ami nem egy másik, henem ugyanannak a függvénynek a hívása. Ezt nevezik rekurziónak.
Aki nem ismeri ezt a technikát, annak ez elsőre elég abszurdnak tűnik, mivel ha a függvény hívása során saját magát meghívja, akkor ennek soha nem lesz vége, vagyis ez olyasmi, mint egy végtelen ciklus. Nézzük például az alábbi őrültséget:
function rekurzio(){
rekurzio();
return;
}
Nyilván a fenti függvény meghívása nem lesz életünk legokosabb döntése. Mikor én kipróbáltam, le sem állt a szerver futása 30 másodperc múlva, ráadásul használhatatlanná vált a böngésző és a szerver is, amíg újra nem indítottam őket. Úgy tűnik hogy ez még egy mezei végtelen ciklusnál is rosszabb! Sejthető, hogy a rekurzió nem erre való, gondoskodni kell a függvényen belül a rekurzió leállításáról. Ezt általában valamilyen feltételvizsgálat végzi. Nézzünk egy példát arra, mikor juthat egyáltalán ilyesmi az eszünkbe, hogy a függvényben meghívjük önmagát!
A matematikában egy pozitív egész szám faktoriálisát a szám után írt felkiáltójellel jelölik, és jelentése: a szám szorzata az összes, nála kisebb pozitív egész szám szorzatával. Például 6 faktoriálisa: 6!=1*2*3*4*5*6, vagyis 720. Ennek az az értelme, hogy ennyiféleképpen rakhatunk sorba 6 objektumot (például 6 ember 720-féleképpen állhat sorba). Ha nem hiszed, próbáld ki! :P
Írjunk egy függvényt, ami visszaadja a paramétereként megadott szám faktoriálisát! Ha az első 5 szám faktoriálisát így írjuk egymás alá:
1! = 1
2! = 1*2
3! = 1*2*3
4! = 1*2*3*4
5! = 1*2*3*4*5
akkor látszik, hogy ezt egyszerűen megcsinálhatjuk egy ciklus segítségével.
function faktorialis($szam){
$fac = 1;
for ($n = 1; $n <= $szam; ++$n){
$fac *= $n;
}
return $fac;
}
Azonban a fenti listán megfigyelhetjük, hogy például 5! = (4!)*5. Általánosan is igaz:
n! = (n-1)! * n
Az az észrevétel, hogy egy szám faktoriálisa kiszámítható az előtte lévő szám faktoriálisa segítségével, azt jelenti, hogy a függvény n faktoriálisának kiszámításához meghívhatná saját magát (n-1)-re. Természetesen gondoskodni kell a rekurzió leállításáról úgy, hogy a függvény 1-re már ne tudja meghívni saját magát. Vagyis ha a paraméter 1, akkor visszatérünk az 1-es visszatérési értékkel:
function faktorialis($szam){
if ($szam == 1){
return 1;
}
else{
return faktorialis($szam-1) * $szam;
}
}
Nézzük meg, hogy például a 3-as számra meghívva a faktorialis() függvényt mi történik, a lényeg ezen már látszik. Az látható, hogy a faktorialis(3) függvényhíváshívás visszaadja a faktorialis(2) * 3 értéket. Ebben szerepel a faktorialis(2) hívás, ami visszaadja faktorialis(1) * 2 értéket, vagyis a faktorialis(3) függvény visszatérési értéke faktorialis(1) * 2 * 3. A faktorialis(1) hívás visszatérési értéke 1, így végeredményben a faktorialis(3) hívás az 1*2*3 értékkel fog visszatérni. Közben a $szam változó nem változott, csak a függvényhívások során mindig az előző függvényhívás során átadott paraméternél eggyel kisebb szám adódik át a függvénynek, egészen addig, amíg végül 1 adódik át, és akkor a függvény már nem hívja meg önmagát, hanem visszaad 1-et. Emiatt a rekurzív változat a ciklusossal ellentétben semmilyen segédváltozót (pl. $n vagy $fac) nem használ.
Elvileg minden rekurzió átalakítható ciklussá, így a rekurzió használata csak egy lehetőség, de sok esetben egyszerűbb kódot eredményez.
Házi feladat
1.) Írjunk egy olyan függvényt, ami kiszámítja az első paramétereként átadott szám faktoriálisát, majd az értéket (ahelyett hogy visszaadja return-nel) berakja a második paraméterként átadott változóba!
A feladatot érdemes a ciklusos faktoriális-függvény módosításával megoldani, mert a függvény paraméterlistája változott, így ez a rekurzióra is hatással lenne.
function faktorialis2($szam, &$ref){
$fac = 1;
for ($n = 1; $n <= $szam; ++$n){
$fac *= $n;
}
$ref = $fac;
}
Ellenőrizzük, hogy tényleg megváltozik-e a második paraméter! Ezt el lehet végezni például így:
$tarolo = 2; faktorialis2(5, $tarolo); print $tarolo;
A program a 120-as számot fogja kiírni.
2.) Írjunk egy olyan hatványozó függvényt, mint amilyen a 10. lecke végén található, annyi különbséggel, hogy használjunk benne rekurziót!
A 3. lecke végén látható, hogy 2 hatványait rekurzívan így lehet megadni: 2n+1 = 2n * 2. Ez az összefüggés tetszőleges alapra igaz, és ha n helyére (n-1)-et írunk: xn = xn-1 * x, vagy a függvényt használva: power(x, n) = power(x, n-1) * x. Ennek megfelelően a program:
function power($alap, $kitevo){
if ($kitevo == 1){
return $alap;
}
else{
return power($alap, $kitevo-1)*$alap;
}
}
Nyilván itt is gondoskodni kell a rekurzió leállításáról. Ez megoldható úgy, hogy amikor a kitevő 1, a függvény visszaadja az alapot. Látható, hogy a függvény majdnem ugyanúgy néz ki, mint a faktoriálist kiszámító függvény rekurzív változata.







