4. gyakorlat¶
operátor kiterjesztés¶
Az eddig ismert alap típusokra léteznek operátorok. Egy egész típusú változó operátora lehet pl. összeadás (+), kivonás (-), értékadás (=), inkrementálás (++). Ezeknek a jól megszokott hatásuk van a változó értékére. Az általunk megvalósított osztályokra azonban alapból nem működnek ezek az operátorok. A C++ azonban lehetőséget kínál rá, hogy saját típusra is értelmezzük az operátorokat, csak meg kell mondanunk, hogy mi történjen pl. összeadás esetén. Ezt operátor kiterjesztésnek hívjuk.
Vegyük alapul, hogyan működik pl. az összeadás operátor az egész számokra. Mit is jelent, ha leírjuk az a+b kifejezést:
1 |
|
Fontos észre venni, hogy az a
és b
értéke nem változik meg, és a kifejezés eredményének tárolására egy új ,,objektum'' jön létre (3+2 esetén 5, ami szintén egy egész), tehát van visszatérési értéke. Egész számok esetén ez egy egész szám lesz, tehát ha kiegészítjük a fentit a visszatérési értékkel, akkor a következő módon fog kinézni:
1 |
|
A saját osztályunkra nézve hasonlóan néz ki, csak meg kell határoznunk, hogy mit mivel szeretnénk összeadni és ennek mi lesz az eredménye. Ha vesszük a Kurzus
osztályt és hozzáadunk egy Hallgato
osztályt, akkor egy bővített kurzust kapunk, ami szintén egy Kurzus
.
Tehát a két paraméter: Kurzus
, Hallgato
(ami valójában konstans referencia lesz). A visszatérési érték típusa Kurzus
.
1 |
|
Mivel az operátor viselkedése szorosan kötődik a Kurzushoz (így fogalmaztuk meg) OOP szerint az osztályon végzett műveletet rakjuk be az osztályba magába. Ezt a metódust megírhatjuk mint a Kurzus
osztály tagfüggvénye. Ha tagfüggvény, akkor az első paraméter (Kurzus
) adott is, hiszen ha meghívjuk egy objektum operator+
függvényét, az első paraméter maga az objektum, így a paraméterlistából azt el is hagyhatjuk.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Ez hozzáadja a Kurzus
-hoz a kapott hallgatót, ha még elfér. Hasonló szemantikája van, mint az egész számoknál.
A használata a következő:
1 2 3 4 5 6 7 8 |
|
Az egészeknél ha egy változóhoz hozzá akarunk adni egy értéket az összeadást a következőképpen kell használnunk:
1 |
|
1 |
|
operator+=
-t definiáljuk felül.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Ez a megvalósítás meglehetősen hasonló, azonban fontos kiemelni néhány jelentős különbséget! Az első, hogy a visszatérési érték nem Kurzus
, hanem Kurzus&
és nem hozunk létre egy újabb Kurzus
típusú objektumot. Ennek oka a következő:
Mikor az operator+
-t használtuk, akkor az operandusok változatlanok maradtak, és létrejött egy új objektum, mely az összeget tárolta el és teljesen független a két operandustól. Ezt az alábbi ábra vizualizálja, melyen két Kurzus
objektumot összeadva lérrejön egy harmadik Kurzus
:
Ha ezt k1 = k1 + k2;
formában akarjuk használni, akkor akkor az eredeti változó objektuma felülíródik az eredmény objektummal. Ezt a kövezkező két ábra szemlélteti.
Ilyen esetekben jobb az operator+=
használata. Ennél már magában az operátorban benne van az értékadás, tehát az eredeti objektum módosul. Ezért is kell referenciával visszatérni, hiszen ugyan arról az objektumról beszélhetünk.
A hallgató hozzáadása is hasonlóan működik:
Fontos, hogy az eredeti Kurzus
módosult, a Hallgato
bekerült a kurzusra, de nem módosult. Ezért lehetett a paraméterben const &
.
Mivel az operátorokat függvényeknek vettük, ezért a meghatározásoknál nemcsak az operátor neve, hanem a paraméter típusa is meghatározza. A Kurzus
osztályban lehet két operator+=
, az egyik Hallgato
, a másik Kurzus
paraméterrel. Az így kiegészített Kurzus
osztály:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
A += operátor hasonlóan működik Kurzus
-ra, mint Hallgató
esetén:
Természetesen, a kurzushoz adott paraméter most sem módosul, így az lehet const &
.
indexer operátor¶
Az indexer operátor különleges operátornak tekinthető. Szemantikáját általában tömbökhöz kötjük, elemek eléréséhez (de tetszőleges értelmezést adhatunk neki). Ehhez az operator[](int)
metódust kell kiterjeszteni. Ha egy tömbben el akarunk érni egy elemet, azzal több dolgot is tehetünk. Egyszerűen lekérdezhetjük (módosítás nélkül):
1 |
|
Vagy az értéket módosíthatjuk:
1 |
|
Mivel az első esetben csak az értékre vagyunk kíváncsiak, nem kell tudnunk módosítani azt. A második esetben már a visszaadott értéket szeretnénk módosítani, vagyis igazából az int_array
objektumban lévő 2. indexen lévő elemet szeretnénk felülírni.
Azt már vettük, hogy ha egy függvény módosíthatja vagy sem az objektumot azt a const
kulcsszóval jelezhetjük. Az első esetben nem módosíthatjuk tehát használjuk a const
kulcsszót, a második esetben pedig nem.
Természetesen nem elég a metódust konstanssá tenni, hiszen ha a visszatérési értékben kiszivárog egy "kiskapu", akkor az objektumot elronthatjuk. Ennek kivédése érdekében a visszatérési értéket is máshogy kell megadnunk. Erről már szó volt a const
-ról szóló részben. Az indexer operátorok kiterjesztése a Kurzus
osztályra (annak éri meg, hiszen annak van tömbhöz köthető jellege):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Az első esetben egy referenciát adunk vissza, tehát ahova visszaadjuk, ott elérik az adott elemet pl. felül is írhatják az értékét. Mivel ezzel módosulhat a Kurzus
, ez a függvény nem is const
. A második esetben szintén referenciát adunk vissza, azonban ez most const &
tehát nem írható felül az értéke. Ez biztosítja, hogy akármit is teszünk, a Kurzus
nem fog megváltozni, így ez a függvény lehet const
.
++ / -- operátorok¶
A ++a
, a++
, --a
és a--
kifejezések mindenki számára ismerősek. A különbség a prefix és a postfix inkrementálás (dekrementálás) közt annyi pl. intekre nézve, hogy a prefix verzióban egy kifejezés kiértékelésekor már a megnövelt (csökkentett) érték van használva, míg postfix esetben a kifejezés kiértékeléskor még a nem megnövelt (csökkentett) értéket használjuk fel.
Mivel ezek is operátorok, ezeket is felül lehet definiálni. Azonban a pre s post verziók közt leírásban nem sok különbséget találunk, így valahogyan meg kell különböztetni azokat. Az operator kiterjesztése során a post verzióhoz egy plusz (nem használt) paramétert adunk, csupán a megkülönböztetés miatt.
Működésük miatt a visszatérési értékük sem teljesen egyezik meg. Nézzünk egy példát rá!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Hasznos a kettőt együtt megvalósítani s ekkor az egyiket a másik felhasználásával, hiszen ekkor hiba javításakor csak egy helyen kell módosítanunk.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
További operátor felüldefiniálás¶
Ahogy azt előzőekben láttuk, az operátor felüldefiniálás csak egy függvény / metódus megvalósítását jelenti. Ha egy osztálynak metódusként írunk meg egy operátort, akkor tudjuk, hogy az operátor első paraméterét elhagyhatjuk, hiszen az maga az objektum lesz. Természetesen, ha unáris (vagyis csak egy paramétert váró) operátorról van szó, akkor nem kell megadni semmit, hiszen csak az objektumra van szükség. Ilyen operátort pl. az operator++
, operator--
, operator!
stb.
operator!¶
A felkijáltójel használata gyakran a negálást jelenti. Ez egy paramétert vár, magát az objektumt. A Kurzus példánál maradva nézhetjük, hogy bizonyos kurzusok jóváhagyásosak. Ezt egy bool értékkel le tudjuk tárolni, azonban ha meg akarjuk változtatni, akkor akár egy negálással is megtehetjük. Erre nézzünk példát!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Konverziós operátorok¶
Sokszor konvertáljuk az adatokat és előfordulhat, hogy ezt egy általunk készített osztályra is meg kell tennünk. Ekkor írhatunk egy eljárást, ami mindig megcsinálja a konverziót, de kényelmesebb, ha meg tudjuk mondani, hogyan kell egy-egy adott konverziót megtenni.
Ha a Kurzus objektumunkat intté szeretnénk konvertálni, pl. a létszámmal legyen egyenlő a konverzió eredménye, akkor ezt megtehetjük egy konverziós operátorral.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
A fenti példán láthatjuk, hogy a kiíratásban unsigned típusra konvertáltuk a kurzusunkat. Ezt a Kurzusban definiált konverziós operátorral tettük meg. Fontos, hogy konverziós operátor esetében nem kell kiírni a visszatérési típust! Nem kell, hiszen a konverziós operátor neve egyértelűen meghatározza a visszatérési típust. Amire szeretnénk konvertálni, az lesz az eredmény. Eltérő típus esetében fordítási hibát kapunk.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Ebben a példában láthatjuk, hogy a konverziót nem kell explicit módon kiírnunk, automatikusan megtörténik. Ez azonban problémához is vezethet, hiszen lehet, hogy nem akartunk konvertálni, vagy éppen másik típusra akartuk volna. Erre megoldást az jelentene, ha a konverzió csakis az első módszerrel történhetne meg, vagyis csak akkor, ha kiírjuk (explicit módon). Ennek a megoldása, ha a konverziót ellátjuk az explicit taggal. Ekkor az automatikus konverziót letiltjuk, kötelezően ki kell írni a kívánt konverziót.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
A konverzió másik iránya¶
Eddig azt néztük meg, hogyan lehet egy már létező objektumból konvertálással egy értéket előállítani. A másik irány, hogy egy értékből az objektumot állítjuk elő. Erre példát a stringeknél láthattunk:
1 2 3 4 5 6 7 8 |
|
Ezt a saját osztályunkra is megtehetjük. Mivel érték alapján kell létrehoznunk objektumot a konstruktorhoz kapcsolódó részeket kell vizsgálnunk.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Látszik, hogy az unsigned értékkel tudjuk a k3 objektumot inicializálni, hiszen létezik hozzá konstruktor. Terészetesen előfordulhat, hogy ezt az autmatikus konverziót (konstruktor hívást) szeretnénk elkerülni. Ekkor a konstruktornak is megadhatjuk, hogy explicit lehessen csak meghívni.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Órai gyakorló feladatok¶
-
A múlt órán elkészült a Kurzus osztály. A hallgato-kurzus-1.cpp fájlban található az osztály megvalósítása, illetve van egy Hallgató osztály is.
- Nézzük meg, hogy milyen paraméterátadások vannak (const string&)
- Nézzük meg, hogy a metódus milyen módosítót kapott (const)
-
Valósítsuk meg, hogy a kurzushoz hozzá lehessen adni hallgatót
- A hozzáadáshoz a + operátort használjuk
- Valósítsuk meg a += operátort is
- A hibakezeléssel most nem kell foglalkozni, azaz ha a kurzus tele van, akkor csak írjuk ki a képernyőre, hogy nem sikerült felvenni a halgatót, és ne csináljunk semmit sem.
-
Valósítsuk meg a kurzusok "egyesítését"
- Össze lehetesen adni két kurzust, ahol a második kurzus hallgatóit hozzáadjuk az elsőhöz (a + operator alkalmazásával)
- Valósítsuk meg Kurzusokra a += operátort is
- A hibakezeléssel itt sem kell foglalkozni, azaz ha a második kurzus hallgatói nem férnek fel az elsőre, akkor csak adjunk hibaüzenetet
-
Valósítsuk meg az index operátorát is a Kurzus osztálynak
- Lehessen módosítani az adott elemet
- const objektumra is működjön, és ilyenkor nem módosítható módon adja vissza az i-edik elemet
- Valódi hibakezeléssel nem kell foglalkozni, elég kiírni a képernyőre, hogy nincs olyan indexű elem, és a 0. indexűt kell visszaadni (a 0. indexű biztosan létezik, legfeljebb "üres")
Megoldások: 2.-3. feladat, 4. feladat
Órai gyakorló feladatsor II¶
-
Legyen egy dokumentum osztály
- címmel (str)
- tartalommal (str, default param: "")
- szerzővel (str, default param: "anonym")
- megnyitasokkal (unsigned, kezdő érték 0)
-
Készíts hozzá gettert a tanult módon
- minden getter növelje a megnyitasokat eggyel. (Kivéve az azt lekérdező) (itt ugye muszáj, hogy mutable legyen)
- Valósítsd meg a pre és post incremental operátort
- növelje a megnyitasok számát eggyel (Mintha csak touch lenne vagy ilyesmi)
- Valósítsd meg a += operátort, mely egy stringet fűz a tartalomhoz
- Valósítsd meg a + operátort, mely egy stringet fűz a tartalomhoz, de azt csak egy saved_as című dokumentumba teszi bele. (save as.. funkcionalitás)
- A Dokumentum konvertálható legyen unsigned típusra, mely a tartalom hosszát adja eredményül.
- a konverzió ne történjen meg implicit módon!
- A Dokumentum negálása (operator!) megvalósítása (unsigned visszatérési típussal)
- üres dokumentum esetén Lorem ipsum.. szöveget írja bele
- bármilyen tartalom esetében törölje azt.
- visszatérési értékben az új tartalom hossza szerepeljen
Otthoni gyakorló feladatok¶
-
Készíts egy Meme osztály. Két string típusú adattagja van, láthatóságuk legyen privát. Nevük: szoveg, kep. Legyen hozzá egy két paraméteres publikus konstruktor, amivel beállítjuk az adattagokat. Írj gettereket is!
Részmegoldás
- A megoldás menete: https://youtu.be/TkY8Ua_G_iA
-
Készíts egy void print_meme() metódust, melynek paramétere egy ^^konstans^^ Meme referencia. Egy sorban kiírja a meme szövegét majd egy rákövetkező sorban a képet is. Hogyan kell módosítani az előbb megírt gettereket?
Részmegoldás
- A megoldás menete: https://youtu.be/sNxow_1KKCU
-
Készíts egy MemeGyujtemeny osztályt. Ez maximum tíz Memét tud letárolni egy tömbben. Mivel kell kiegészíteni a Meme osztályt, hogy működjön a tömb létrehozás?
Részmegoldás
- A megoldás menete: https://youtu.be/PtTbbojBDmA
-
Definiáld felül a & és a &= operátorokat a MemeGyujtemeny osztályban úgy, hogy a baloldali operandus egy MemeGyujtemeny legyen, a jobboldali pedig egy Meme. A MemeGyujtemeny tömbjében az első üres helyre kerüljön be az új meme. Ennek megfelelően módosítsuk úgy a MemeGyujtemeny osztályt, hogy le tudja tárolni, aktuálisan hány meme-t tárol!
- MemeGyujtemeny & Meme művelet esetén ne legyen módosítva egyik operandus sem, a visszatérési érték pedig egy lemásolt, bővített MemeGyujtemeny objektum legyen.
- MemeGyujtemeny &= Meme művelet esetén a bal oldali MemeGyujtemeny objektum módosuljon, ahhoz adjuk hozzá az új elemet. MemeGyujtemeny referencia legyen visszaadva.
- Ha nincs már hely a gyűjteményben, ne legyen hozzáadás, csak történjen meg a megfelelő típusok visszadaása (módosítás nélküli másolat illetve módosítás nélküli bal oldali operandus)
Részmegoldás
- A megoldás menete: https://youtu.be/gouhtl4Stfs
-
Definiáld felül az indexer [] operátort a MemeGyujtemeny osztályban úgy, hogy adja vissza az i-edik letárolt memét. Legyen konstans és nem konstans verziója is. Túlindexelés esetén legyen kiírva a "Hiba" szöveg a cout-ra és a 0-ás indexű meme legyen visszaadva.
- Hozz létre az osztályokon kívül egy void print_gyujtemeny függvényt, amely konstans MemeGyujtemeny paramétert vár és egy for ciklusban kiíratja a print_meme segítségével az összes letárolt memét. Gettert csak az aktuális meme szám lekérdezéséhez írj!
Részmegoldás
- A megoldás menete: https://youtu.be/VXMOez6NgH0
Teljes megoldás
- Kód: Gyakorló feladatsor
Otthoni gyakorló feladatsor II¶
-
Készíts egy Zene osztályt. Adattagjai: hossz (egész szám, másodpercben értve), nev. A nev default értéke legyen: "a-moll". Legyen az osztálynak egy print() metódusa, amely kiíratja cout-ra az értékeit. Legyen egy két paraméteres konstruktora, amely beállítja a két adattagot és legyen egy egy paraméteres is, amiben csak a hosszt lehet állítani (a nev pedig a deafult értékét kapja). Ne lehessen int-et impliciten Zene osztályra konvertálni.
Részmegoldás
- A megoldás menete: https://youtu.be/bjJHeHcIh1A
-
Legyen megvalósítva a Zene+int operátor, ami az előző Zene alapján készít egy új, megnövelt hosszúságú zenét. Az eredeti hosszhoz a paraméterben kapott értéket kell hozzáadni.
-
Legyen megvalósítva az int+Zene operátor, ami ugyanazt csinálja, mint a Zene+int operátor.
Részmegoldás
- A megoldás menete: https://youtu.be/0sHq6Xq_Eok
-
Legyen megvalósítva a Zene+=int operátor, mely a paraméterben kapott értékkel növeli a zene hosszát. Az aktuális Zene objektum legyen módosítva, ennek megfelelően Zene referencia legyen a visszatérési érték.
Részmegoldás
- A megoldás menete: https://youtu.be/Y_jFBAuiEJE
-
Legyen megvalósítva a Zene osztályra prefix és a postfix ++ operátor is, ami növeli 1-gyel a zene hosszát.
Részmegoldás
- A megoldás menete: https://youtu.be/Xp7Nq7BdvBs
Teljes megoldás
- Kód: Gyakorló feladatsor
- A megoldás menete: https://www.youtube.com/playlist?list=PLE8moZnI5dJvzb6AwJ1aC8Udo6zf_38o3