3. gyakorlat¶
Bíró¶
A félév során a Bírót használjuk a ZH -hoz, míg a Bíró2-n órai és gyakorló feladatok lesznek
Be kell regisztrálni a Bíróra és Bíró2-re is
Ismétlés¶
- karakterláncok tárolása
string
típusban - operátor felüldefiniálás
Objektumorientált programozás¶
Az objektumorientált programozás alapvetéseit már mindenki elsajátította a Programozás I. tantárgy keretein belül, illetve az előadáson ismét lesz (azonban ha valakinek mégis szükséges lenne az ott tanultak felelevenítése, az látogasson el ide).
Osztályok létrehozása¶
Osztályok létrehozása a class
kulcsszóval történik, az osztályunkat záró kapcsos zárójel után pontosvesszővel is le kell zárni (gyakori hiba, hogy ez lemarad).
1 2 3 |
|
Természetesen az osztályhoz tartozó adattagokat is deklarálhatjuk itt, a már ismert típusokkal.
1 2 3 4 5 6 7 8 9 |
|
Láthatóságok¶
A láthatóságok itt is léteznek, jelentésük azonos a Java esetében tanultakkal, azonban csak 3 darab van belőlük: public
, private
, protected
, nincs package private láthatóság (hiszen package-ek sincsenek C++-ban). A kulcsszavakat itt már nem kell minden egyes adattag, függvény elé kiírni, elegendő egyszer megadni a kívánt láthatóságot, majd kettőspont után felsorolni az adott láthatósághoz tartozó tagokat. Ha nem adunk meg semmit, akkor az osztályok esetében automatikusan private láthatóságot jelent.
A fentivel megegyező kód:
1 2 3 4 5 6 7 8 9 10 |
|
Egy láthatósági módosító nem csak egyszer szerepelhet a kódban, azonban általában a fejlesztés végén összevonásra kerülnek az azonos láthatóságú deklarációk, hogy egy láthatóság egyszer szerepeljen (de ez nem kötelező). Az alábbi kód érvényes C++ kód:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Getter/Setter¶
Ahogy tanultuk korábban, a private
láthatóság csak az osztályon belüli elérést tesz lehetővé, a protected
az osztályon kívül a leszármazott osztályok számára is biztosítja az elérést, a public
pedig teljesen nyilvános elérést jelent.
Az alapelvek itt is hasonlók, mint Javaban, vagyis jó volna, ha az objektumunk fontos adatait csak úgy a külvilágból nem módosítaná senki, csak és kizárólag ellenőrzött módon, az objektum által történjen változás az objektum állapotában (például, hogy egy Ember objektum ne lehessen -23891 éves).
Ehhez lehetőségünk van itt is getter és setter függvényeket írni, tartva az elnevezési konvenciókat a getter függvény a kurzuson get_adattag
, a setter pedig set_adattag
néven lesz használva (de előfordulhat az is, hogy a Javaból tanult camel-case változatot használják, azaz getAdattag
és setAdattag
az elnevezés).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
A getter és setter metódusok mellett természetesen tetszőleges további metódust is készíthetünk az osztályunkhoz.
Konstruktor¶
Természetesen itt is készíthetünk konstruktorokat az osztályainkhoz, amik az osztály példányosításakor fognak lefutni. Itt szokás inicializálni az adattagjainkat, valamint itt szokás dinamikusan memóriát foglalni. A konstruktor lehet paraméter nélküli (default), vagy pedig rendelkezhet tetszőleges számú paraméterrel. Azonos paraméter számú konstruktorból is lehet több (a típusok sorrendjének azonban mindenképpen különböznie kell). Ezeket azért tehetjük meg, mert C++ esetében a függvényeket és metódusokat többek közt (erről később) a nevük, paraméter számuk és paraméter típusuk határozza meg. Természetesen teljesen azonos "kinézetű" (adott scope-ban lévő, azonos nevű, azonos paraméterezésű) függvényből itt sem lehet több.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Default adattag értékek¶
C++11 óta itt is lehetőségünk van alapértelmezett értéket adni az adattagjainknak deklarációkor. Így megússzuk azt, hogy minden konstruktorban be kelljen állítani az adattagoknak értéket (ez főleg akkor jöhet jól, ha ugyanazt az értéket szeretnénk mindenhol beállítani, így ha mégis más alapértelmezett értéket szeretnénk beállítani, nem kell az összes konstruktort módosítani). Persze ettől még megtehetjük, ilyenkor a konstruktorban lévő érték felülírja az alapértelmezett értéket. Ez is ismerős lehet Javaból.
1 2 3 4 |
|
Default paraméter értékek¶
Az úgynevezett "boilerplate" (felesleges) kódsorok elkerülése érdekében, lehetőségünk van default paraméter értékeket is megadni a függvényeinknek. Ez akkor tud jól jönni, amikor nagyon hasonló függvényeket szeretnénk írni, amik csak a paraméterlistájukban különböznek (pl. ha nem adjuk meg a max. létszámot, akkor azt 25-nek veszi). Azért, hogy ne kelljen egy adott függvényt (a benne lévő potenciális hibával) annyiszor lemásolni, lehetőségünk van megadni a paramétereknek alapértelmezett értéket, amit a függvény fejlécében a paraméter neve után tehetjük meg egyenlőség jellel.
Ezzel kapcsolatban a legfontosabb szabály, hogy mindig csak és kizárólag az utolsó valamennyi paraméternek lehet alapértelmezett értéke. Ez persze nem zárja ki, hogy az utolsó összesnek legyen, de olyat nem lehet, hogy csak a 2. és 5. paraméternek adott alapértelmezett értéket. A default paraméterekkel kapcsolatos összes szabály itt érthető el.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
This¶
Ahogy a fenti példában is látszik van this
, mellyel az objektumunk adattagjait, metódusait érhetjük el. Ez egy pointer magára az objektumra (fontos, hogy nem az osztályhoz, hanem az objektumhoz tartozik). Mivel pointer, a ->
operátort használjuk. Természetesen használható a (*this).adattag
forma is, azonban a szebb kód érdekében ezt kevésbé használjuk. A this
használata hasznos lehet, ha egy paraméter formális neve megegyezik egy adattagunk nevével (további használatáról később lesz szó).
Példányosítás¶
Az elkészült osztályunkat ezt követően példányosíthatjuk, amelynek több módja is van (ezen az órán a legegyszerűbbel fogunk megismerkedni). Az objektum létrehozásához szükségünk van a típusdeklarációra, majd pedig a változónév megadására. Ezt követően pedig zárójelben jönnek a paraméterek. Ha paraméter nélküli konstruktort szeretnénk meghívni, akkor pedig TILOS a változónév után üres zárójelpárt tenni (nem változó definíció lesz, a jelenség neve "Most vexing parse", bővebben a Wikipédián olvashatsz róla).
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 |
|
Paraméter átadási módok¶
Az elkészült Kurzus osztályt paraméterként átadva, annak egy metódusát szeretnénk meghívni, ami módosítja az objektum állapotát (vagyis valamelyik adattag értékét megváltoztatja.) Ebben az esetben pointer szerint kell átadni a Kurzust, hogy a módosítások az eredeti objektumon történjenek.
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 48 49 50 51 52 53 54 55 |
|
Ahogy látszik is (és ahogy C-ben már tanultuk), az objektumnak a címét kell képezni, pointer szerint kell átvenni az objektumot, és utána úgy is kell használni (dereferencia, -> operátor)
Referencia¶
Mivel a pointerek használata nehézkes lehet, bonyolíthatja a kódot, hibaforrás is lehet, valamint használatuk esetén további ellenőrzéseket is be kellhet vezetni, ezért sokan nem szívesen használják azokat. Azért, hogy hasonló módon (eredeti objektum) tudjuk átadni a változót és a pointer dereferenciával se kelljen foglalkozni, használhatunk referenciákat. A referencia az eredeti objektumnak felel meg, csak másik scope-ban és más néven. Az eredeti objektumon dolgozik, nem kell dereferálni, nem lehet NULL
/ nullptr
értéke és az értéke nem módosítható (ellentétben azzal, amikor egy pointert másik címre állítunk).
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
|
Mint az ábrán látható, az átadott kurzusból nem egy másolat készül, hanem az eredetin hajtjuk végre a módosítást, tehát egy Kurzust adunk át. Eddig ezt pointerekkel tudtuk elérni. Nagyon fontos, hogy a Kurzus
és Kurzus*
külön típus.
Mivel a 2 külön típus, a függvény overloadolható referencia és pointer típussal is, azonban egyszerű Kurzus típussal nem (amennyiben készítettünk egy overloadot referencia típussal)!
Megengedett:
1 2 |
|
Megengedett:
1 2 |
|
Nem megengedett, mivel nem egyértelmű (hiszen mindkét függvénynek csak önmagában egy Kurzus objektumot adnánk át, így a fordító nem tudná, hogy melyik függvényt is szeretnénk meghívni, míg a pointer/referencia között átadáskor is teszünk különbséget):
1 2 |
|
Hiba: error: call of overloaded ‘foo(Kurzus&)’ is ambiguous
Konstruktor inicializáló lista¶
C++-ban a konstruktorban való adatinicializálásnak létezik egy preferált módja, ez pedig az inicializáló lista használata (initializer list). Ezt érdemes megtanulni, mert ez a leggyakrabban használt megoldás a konstruktoroknál (és bizonyos adattagokat más módon nem is lehet inicializálni).
1 2 3 4 5 6 7 8 9 10 |
|
A konstruktor fejléce után kettőspontot teszünk, majd pedig felsoroljuk az adattagokat és mögöttük zárójelben a kezdeti értéküket, amik például a konstruktor paraméterei is lehetnek.
Itt nem történik névütközés, mert az inicializáló listában mindig csak az osztály adattagjai lehetnek, a paraméter neve pedig elfedi a konstruktor scope-jában az adattag nevét, így a nev(nev)
például teljesen értelmes, hiszen a külső név csak adattag lehet, míg a zárójelben lévő érték pedig jelen esetben a paramétert jelenti. A fenti példa, amikor az adattagok és a paraméterek nevei különbözők:
1 2 3 4 5 6 7 |
|
Fontos megjegyezni, hogy minden esetben a deklaráció sorrendjében kerülnek végrehajtásra az inicializálások, nem pedig a konstruktor kódjában megadott sorrendben, majd ezt követően fut le a konstruktor törzse. Az alábbi kód hibás!!!
1 2 3 4 5 6 7 |
|
Nézzünk példát arra, hogy mi történik akkor, ha inicializáló lista nélkül szeretnénk a következő elemeket beállítani:
const
adattag
1 2 3 4 5 6 7 8 9 10 11 |
|
Helyesen:
1 2 3 4 5 6 |
|
- referencia
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Helyesen:
1 2 3 4 5 6 7 |
|
Delegating konstruktor¶
Megeshet, hogy két konstruktor működése elég hasonló, egyik kicsit több/másabb mint egy másik. Szerencsére itt is lehetőségünk van a kódmásolás elkerülésére, és meghívhatjuk az egyik konstruktorból a másikat. Ez hasonló lesz az inicializáló listához, annyi különbséggel, hogy az adattagok inicializálása helyett egy másik konstruktort is meghívunk (de ilyenkor már nem inicializálhatunk adattagot a konstruktor inicializáló listában). Ehhez annyit kell tennünk, hogy kiírjuk az osztály nevét, majd zárójelek között a paramétereket (mintha egy egyszerű metódushívás lenne). Megjegyzés: az itt látott megoldással lehet majd az örökölt osztály konstruktorát is meghívni.
1 2 3 4 5 |
|
Friend tagok¶
A friend tagok tekinthetők az OOP megszegésének, azonban sokszor szükség van használatukra.
Ha friend
tagot adunk az osztályunkhoz, akkor a barátként megjelölt elem hozzáfér a privát láthatóságú elemekhez is; ezért is tekinthető az OOP megtörésének.
Fontos, hogy a friend elem NEM az osztály része csupán egy kitüntetett különálló elem.
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
|
A friend kulcsszóról bővebben.
Órai gyakorló feladatok¶
Kurzusok¶
- Írjunk egy osztályt, amely egy Kurzust reprezentál.
- A Kurzusnak van neve (
string
típusú), kódja (string
) és maximális létszám, hogy hány hallgató veheti fel. - Ne legyenek publikusak az adattagok, de legyen hozzájuk getter metódus.
- Az osztálynak legyen egy konstruktora, ahol megadhatjuk a jellemzőket és a konstruktor inicializálja ezeket.
- Legyen egy
main
függvény is, ami bekéri a kurzus adatait, majd létrehozza azt, és kiírja a kurzus jellemzőit. - Megoldás
- A Kurzusnak van neve (
- A konstruktor paramétereinek a neve egyezzen meg az adattagok neveivel, és a this segítségével inicializáljuk az adattagokat.
- Default paraméter segítségével valósítsuk meg, hogy ha nem adják meg a max. létszámot, akkor az legyen 25.
- Megoldás
- Konstruktor inicializáló lista segítségével inicializáljuk az osztály adattagjait. Legyen 2 konstruktora az osztálynak (function overload), ahol a második esetében nem kell megadni a max. létszámot, és azt 25-re állítja. Megoldás.
- Inicializáljuk a maximális létszámot default inicializáció segítségével. Megoldás.
- A két konstruktor "működése" azonos, azért a 2 paraméteres konstruktor "hívja meg" a másik konstruktort (delegating konstruktor). Megoldás.
- Írjunk egy függvényt (ebben a példában ne a
Kurzus
osztály metódusa legyen), amelyik paraméterként (pointer) megkapja a kurzust, majd beolvas egy egész számot, és ennyivel megváltoztatja a kurzus max. létszámát. Ehhez aKurzus
osztályt is egészítsük ki a szükséges metódussal. Megoldás. - Valósítsuk meg a 6. feladatban szereplő függvényt referencia segítségével. Megoldás.
Mobiltelefon¶
- Készíts egy
SIMKartya
osztályt.- A SIM kártyának meg kell adni a számát létrehozáskor, amit később nem lehet megváltoztatni.
- Bárki lekérdezheti a számát.
- Megoldás
- Készíts egy
Mobiltelefon
osztályt.- A Mobiltelefonba be lehessen rakni egy SIM kártyát.
- Milyen kapcsolat van a mobiltelefon és a kártya között?
- Ki lehessen cserélni a kártyát.
- Ki lehessen venni a kártyát.
- Megoldás
- A Mobiltelefonba be lehessen rakni egy SIM kártyát.
- Legyen egy függvény, amelyik kicseréli a telefonban a kártyát.
- A paraméterek legyenek érték szerint átadva. Nézzük meg, hogy a hívás helyén valóban kicserélődött-e a telefonban a kártya.
- Megoldás
- Valósítsuk meg a cserét pointer segítségével.
- Mi történik, ha a kártyát nem pointerként adjuk át?
- Megoldás
- Mutassuk be ugyanezt a cserét referencia használattal.
- Mi változott? Vizsgáljuk meg a használat és a hívás helyét is.
- Megoldás
- Készítsd egy
Szolgaltato
osztályt.- Egy már létező SIM kártyának csak a szolgáltató változtathatja meg a számát.
- Megoldás
Otthoni gyakorló feladatok¶
-
Készíts egy Immunrendszer osztályt, melynek két egész szám adattagja van, melyek privát láthatóságúak. Az egyik adattag neve
vedelem
, a másiktamadas
. Lehessen az osztályt default konstruktorral inicializálni, amikor avedelem
és atamadas
értéke is 10. Lehessen úgy is inicializálni az osztályt, hogy mindkét adattag értéke paraméterből jön.Részmegoldás
- A megoldás menete: https://youtu.be/bFVpXXS7-eE
-
Készíts egy Virus osztályt, melynek két privát adattagja van:
nev
(string) éstamadas
(int). Legyen olyan konstruktora, amivel mindkét adattagot lehet inicializálni és olyan is, amivel csak a nevet, a tamadas érték pedig ilyenkor legyen 10.Részmegoldás
- A megoldás menete: https://youtu.be/sOq1Hbk7Zxk
-
Valósítsd meg a vírus immunrendszer elleni támadását! Legyen az Immunrendszer osztálynak egy
tamadast_elszenved
publikus metódusa, melynek egyetlen paramétere egy Vírus objektum. Ha a vírus támadas értéke nagyobb, mint az immunrendszeré, akkor csökkenjen az immunrendszer védelem értéke eggyel. A védelem érték ne csökkenjen nulla alá. Ha eléri a nullát vagy az alá akarnánk csökkenteni, legyen kiírva a standard outputra: "a [vírus neve] gyozott."Részmegoldás
- A megoldás menete: https://youtu.be/07SCxXj09y8
-
Definiáld felül a
<<
operátort, amelynek baloldali operandusa egy immunrendszer, jobboldali operandusa pedig egy vírus és ugyanazt csinálja, mint atamadast_elszenved
.Részmegoldás
- A megoldás menete: https://youtu.be/WXer34fYuho
Teljes megoldás
- Kód: Gyakorló feladatsor
- A megoldás menete: https://www.youtube.com/playlist?list=PLE8moZnI5dJsXvhqQn6fWXpoyHPkBDpuq