Kihagyás

9. gyakorlat

Ismétlés

  • Dinamikus memóriakezelés
  • Destruktor
  • Copy constructor
  • Assignment operátor

Öröklődés

Az objektumorientáltság egyik alappillére az öröklődés, azaz egy adott osztály speciális esetekre bontása; a meglévő tulajdonságok, funkciók kibővítése, pontosítása. Ahhoz, hogy megtudjuk nézni, hogyan zajlik az öröklődés és milyen lehetőségeket rejt a nyelv, kell egy alap osztály. Biztosan mindenkinek ismerős az absztrakt és interface kulcsszó, azonban erre később térünk ki, most egy szokványos osztályból származtatunk.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Hallgato {
    std::string neptun;
protected:
  std::string nev;
  unsigned felvett_oraszam;
public:
  Hallgato(const std::string& nev = "John Doe", unsigned felvett_oraszam = 0) :
        nev(nev), felvett_oraszam(felvett_oraszam) {
            neptun = nev + std::to_string(felvett_oraszam);
        }

  const std::string & get_nev() const { return nev; }
  const std::string & get_neptun() const { return neptun; }
  unsigned get_felvett_oraszam() const { return felvett_oraszam; }

  void orakat_hallgat() const {
      std::cout << "Orat latogat a Hallgato: " << neptun << std::endl;
  }
};

Ahogy látszik egy Hallgato osztályt hoztunk létre. Az neptun adattag private, így csak ez az osztály érheti el, azonban a felvet_oraszam és a nev már nem a megszokott private láthatóságú, hanem protected. Protected tagokat a leszármazott típusok is elérhetik, ahogy azt korábban is láthattuk már. A felvett_oraszam, nev a leszármazott típusokban is közvetlenül változtatható, azonban az neptun (egyetemi azonosító) kód minden példány privát tulajdonsága, kívülről nem elérhető, nem módosítható.

Egy PhD hallgató ugyan olyan hallgató mint bárki más, csupán az órák hallgatása mellet órát is tarthat, azaz bővíti az eredeti típust, annak egy speciális esete.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class PhD_hallgato : public Hallgato {
  unsigned oktatott_oraszam;
public:
  PhD_hallgato(const std::string nev, unsigned felvett_oraszam, unsigned oktatott_oraszam) :
    Hallgato(nev, felvett_oraszam),
    oktatott_oraszam(oktatott_oraszam) {}

  unsigned get_oktatott_oraszam() const { return oktatott_oraszam; }

  void orat_tart() const {
      std::cout << "Orat tart a PhD_hallgato" << std::endl;
  }
};

Ezzel létrehoztunk egy Hallgato osztályból leszármaztatott PhD_hallgato típust. Ezt úgy képzelhetjük el, mintha egy objektum gömb köré egy nagyobb burkot helyeznénk el:

leszarmazott

A fenti öröklődés példában a fontosabb részek:

Öröklődés láthatóság

1
class PhD_hallgato : public Hallgato {

Az öröklődést a kettőspont után adjuk meg, azonban nagyon fontos, hogy milyen láthatósággal. Ahogy itt megadtuk, az öröklődés public. Ez azt jelenti, hogy bármit is kapott az ős osztálytól a gyerek osztály azt maximum public elérhetőséggel örökli. Természetesen ez nem változtat sokat, azonban ha protectedet írnánk, már a public elemek nem ugyanúgy viselkednének. Ha private-ot írunk vagy nem írunk semmit, akkor maximum private láthatósággal örökli az elemeket (tehát ami az ősben public, protected volt, ilyenkor priváttá "válik" az örökölt osztályban).

Egy kisebb példán szemléltetve:

 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
#include <iostream>
class Base {
    int priv;
protected:
    int prot;
public:
    int pub;
};

class Publ_child : public Base {
public:
    void goo() { /*std::cout << priv << std::endl;*/ }  // priv az osben privat, a gyerek osztala sem lathatja
};

class Prot_child : protected Base {

};

class Priv_child : Base { // -> class Priv_child : private Base {
public:
    void goo() {
        std::cout << prot << " " << pub << std::endl;   // A private modon orokles nem a gyerek osztalytol
        // rejti el az elemeket, hanem mintha beleirnank az adattagokat, csak mostmar private mezpűobe kerulne mind.
        // Azaz innen elerheto az, ami a Base-ben nem private!
    }
};

int main() {
    Base b;
    std::cout << b.pub << std::endl;
    // std::cout << b.prot << std::endl; // forditasi hiba, inaccessible
    // std::cout << b.priv << std::endl; // forditasi hiba, inaccessible
    Publ_child publ_c;
    std::cout << publ_c.pub << std::endl;   // Mivel a public maradt public, ez elerheto
    // std::cout << publ_c.prot << std::endl; // Meg mindig nem elerheto, sem a priv
    Prot_child prot_c;
    // std::cout << prot_c.pub << std::endl;   // Mar ez sem erheto el, hiszen a pub adattag ebben az osztalyban mar protected.

    Priv_child priv_c;
    priv_c.goo();   // Lathato, az osztaly azonban eleri amit megorokolt!
}

Látható, hogy az öröklődés láthatósága nem a leszármazott osztály elől rejt el elemeket, hanem "abba bekerülve" az adattagnak a láthatóságát módosítja. Ha private is a láthatóság, az osztály eléri. Ez azonban csak az olyan adattagokra vonatkozik, melyeket az öröklődés által elérne. Vagyis az Ős eredetileg is private adattagjait nem érhetjük el a leszármazott osztályokból.

Ős osztály konstruktora

Ahogy a fenti ábrán is látható, a leszármazott osztály tartalmazza az ős osztályt, tehát ha a leszármazott osztályt teljes mértékben inicializálni akarjuk, akkor a benne lévő őst is inicializálni kell. Ha nincsen default inicializálás (default konstruktor), akkor meg kell adni, hogy milyen módon / melyik konstruktorral legyen az ős inicializálva. Ezt a következő módon tettük meg a PhD_hallgato esetében:

1
2
3
PhD_hallgato(const std::string nev, unsigned felvett_oraszam, unsigned oktatott_oraszam) :
    Hallgato(nev, felvett_oraszam),
    oktatott_oraszam(oktatott_oraszam) {}

Látható, hogy az inicializáló lista első "inicializáló" eleme egy Hallgato konstruktor hívása. Ezzel tudjuk megadni, hogy milyen adatokat adunk át az ős résznek. Fontos, hogy mindig az ős konstruktorokat kell hívni először, azután lehet csak leszármazott saját adattagjait beállítani!

Ős osztály tagfüggvényei

Azt láttuk, hogyan kell az ősosztály-beli adatokat inicializálni. Láttuk, hogy a private elemeket nem érhetjük el. Azonban a konstruktorban átadott információkat - ha nem is érjük el közvetlenül - nem veszítjük el, hiszen ha létezik olyan ősosztály-beli metódus (pl.: getterek), ami a privát adatot használja, akkor azt a metódust használhatjuk leszármazott osztályban is (feltéve, hogy nem privát). Az alábbiakban bemutatjuk, hogy PhD_hallgato egyik tagfüggvényében az örökölt Hallgato-beli orakat_hallgat metódust is meg tudjuk hívni (ez a metódus pedig a Hallgato osztály privát adatával dolgozik).

 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
class Hallgato {
    std::string neptun;
protected:
  std::string nev;
  unsigned felvett_oraszam;
public:
  Hallgato(const std::string& nev = "John Doe", unsigned felvett_oraszam = 0) :
        nev(nev), felvett_oraszam(felvett_oraszam) {
            neptun = nev + std::to_string(felvett_oraszam);
        }

  const std::string & get_nev() const { return nev; }
  const std::string & get_neptun() const { return neptun; }
  unsigned get_felvett_oraszam() const { return felvett_oraszam; }

  void orakat_hallgat() const {
      std::cout << "Orat latogat a Hallgato: " << neptun << std::endl;
  }
};

class PhD_hallgato : public Hallgato {
  unsigned oktatott_oraszam;
public:
  PhD_hallgato(const std::string nev, unsigned felvett_oraszam, unsigned oktatott_oraszam) :
    Hallgato(nev, felvett_oraszam),
    oktatott_oraszam(oktatott_oraszam) {}

  unsigned get_oktatott_oraszam() const { return oktatott_oraszam; }

  void orat_tart() const {
      std::cout << "Orat tart a PhD_hallgato" << std::endl;
  }

  void phd_orakat_hallgat() const {
      std::cout << "Most a PhD hallato ment be egy orara" << std::endl;
      Hallgato::orakat_hallgat();
  }
};

int main() {
    PhD_hallgato phd("Meme Joe", 5, 3);
    phd.phd_orakat_hallgat();
}

Látható, hogy meg kell adni, melyik az a rész ahol van a számunkra fontos metódus, jelen esetben (ős osztály) Hallgato. C++ esetén, ha meg akarjuk adni, hogy honnan és mit szeretnénk elérni, akkor a scope operátort kell használnunk. Ez a ::. Vagyis a Hallgato-ból szeretnénk az orakat_hallgat metódust használni.

Polimorfizmus

Polimorfikus hívás esetén lehetőségünk van általánosan az ős osztályról beszélni, majd különféle leszármazott osztályokat átadni, hiszen azok is megfelelnek az ős osztály típusának, csupán részletesebbek, bővebbek.

A Hallgato és PhD_hallgato esetében írhatunk függvényeket, melyek Hallgato__t várnak, és __PhD_hallgato__t kapnak. Ehhez olyan metódusra van szükségünk ami megtalálható az ősben, ha a leszármazott ezt felüldefiniálja, annak lehet más működése. Ilyen lehet például az __orakat_hallgat metódus, hiszen eddig külön metódust írtunk a PhD_hallgato-nak, mely meghívta az ősosztály-beli elemet. Ezt felül tudjuk definiálni, hogy PhD_hallgato esetében másként viselkedjen.

Felüldefiniálás esetében meg kell egyezzen:

  • metódus neve
  • metódus paraméterezése
  • metódus qualifierek
  • visszatérési típus
 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
67
#include <iostream>
class Hallgato {
    std::string neptun;
protected:
  std::string nev;
  unsigned felvett_oraszam;
public:
  Hallgato(const std::string& nev = "John Doe", unsigned felvett_oraszam = 0) :
        nev(nev), felvett_oraszam(felvett_oraszam) {
            neptun = nev + std::to_string(felvett_oraszam);
        }

  const std::string & get_nev() const { return nev; }
  const std::string & get_neptun() const { return neptun; }
  unsigned get_felvett_oraszam() const { return felvett_oraszam; }

  void orakat_hallgat() const {
      std::cout << "Orat latogat a Hallgato: " << neptun << std::endl;
  }
};

class PhD_hallgato : public Hallgato {
  unsigned oktatott_oraszam;
public:
  PhD_hallgato(const std::string nev, unsigned felvett_oraszam, unsigned oktatott_oraszam) :
    Hallgato(nev, felvett_oraszam),
    oktatott_oraszam(oktatott_oraszam) {}

  unsigned get_oktatott_oraszam() const { return oktatott_oraszam; }

  void orat_tart() const {
      std::cout << "Orat tart a PhD_hallgato" << std::endl;
  }

  void orakat_hallgat() const { // megegyezo metodus az osbelivel
      std::cout << "Most a PhD hallato ment be egy orara" << std::endl;
  }
};

void ertek(Hallgato h) {
    h.orakat_hallgat();
}

void pointer(Hallgato* hp) {
    hp->orakat_hallgat();
}

void referencia(Hallgato& hr) {
    hr.orakat_hallgat();
}

int main() {
    Hallgato hallgato("Papa Joe", 8);
    PhD_hallgato phd("Meme Joe", 5, 3);
    phd.orakat_hallgat();   // phd lefutas
    hallgato.orakat_hallgat(); // hallgato lefutas

    std::cout << "Hallgato atadasa a fuggvenyeknek:" <<std::endl;
    ertek(hallgato);    // hallgato lefutas
    pointer(&hallgato); // hallgato lefutas
    referencia(hallgato); // hallgato lefutas

    std::cout << "PhD_allgato atadasa a fuggvenyeknek:" <<std::endl;
    ertek(phd); // hallgato lefutas
    pointer(&phd); // hallgato lefutas
    referencia(phd); // hallgato lefutas
}

Azt láthatjuk, hogy ha PhD_hallgato-t hoztunk létre, valóban annak az orakat_latogat metódusát meghívva a felüldefiniált hívás fut le, azonban a függvényeknek átadva nem tudtuk polimorfikusan használni. Ennek a magyarázata a típusok kezelésében rejlik. Hallgato átadása során nincsen probléma, ezért a PhD_hallgato esetét nézzük meg részletesebben!

Érték szerinti átadás

Ebben az esetben statikusan megmondtuk a rendszernek, hogy Hallgato típust kell várnia és amikor a PhD_hallgato-t adtuk át, a rendszer másolatot készített azonban statikusan csak a Hallgato adatait másolta le.

polimorf_copy_nem_virtualis

Pointer szerinti átadás

Pointer szerinti átadáskor nem másolat készül, hanem megmondjuk, hogy a kapott címre menve találunk egy adott típusú elemet, jelen esetben Hallgato__t, hiszen statikusan megadtuk, hogy ott azt kapunk, akkor is, ha az egy __PhD_hallgato objektum része. Így nem adtunk információt, hogy azt a dereferált elemet még több minden kiegészíti, pl. a felüldefiniált metódus, csak az alap Hallgato részt dereferálja a függvény.

polimorf_pointer_nem_virtualis

Referencia szerinti átadás

Azt már sokszor láttuk, hogy a referencia nagyon hasznos, azonban nagyon hasonlít a pointerhez és most sem viselkedett attól eltérően. Az eredeti objektumot érte el, ami statikusan egy Hallgato.

polimorf_referencia_nem_virtualis

Látható, hogy az eddig ismert módon nem tudjuk megoldani a polimorfikus hívást. A magyarázat minden esetben az, hogy statikusan (tehát nem futásidőben) megadtuk, hogy milyen típust fog használni az adott függvény. A megoldás az lenne, ha lenne egy jelzés az objektumon, hogy ha Hallgao típust is vár pl. a pointer ne csak azt dereferálja, hanem figyeljen oda arra is, hogy PhD_hallgato típust kapot, azt máshogy kell kezelje. Ez a virtualizáció. Ekkor van egy táblázat, melyben megvannak adva objektumok és az, hogy hogyan is viselkednek egyes metódusaik meghívásakor. C++ esetében nem alapértelmezett ezen tábla használata, mivel például erőforrás igényesebb, de megadható, hogy a megjelölt osztályok és azok leszármazottjai esetében használja. Ahhoz, hogy egyes metódusok virtuálisan tudjanak működni meg kell azokat jelölni a virtual kulcsszóval (ezt elegendő az ősosztályban megtenni).

Virtualizáció

Ahogy láttuk a virtualizáció nem alapértelmezés. Nézzük meg az átalakított példát ahol már van virtualizált metódus is!

 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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <iostream>
class Hallgato {
    std::string neptun;
protected:
  std::string nev;
  unsigned felvett_oraszam;
public:
  Hallgato(const std::string& nev = "John Doe", unsigned felvett_oraszam = 0) :
        nev(nev), felvett_oraszam(felvett_oraszam) {
            neptun = nev + std::to_string(felvett_oraszam);
        }

  const std::string & get_nev() const { return nev; }
  const std::string & get_neptun() const { return neptun; }
  unsigned get_felvett_oraszam() const { return felvett_oraszam; }

  // A virtual kulcsszó ezt a metodust virtualisnak jeloli
  virtual void orakat_hallgat() const {
      std::cout << "Orat latogat a Hallgato: " << neptun << std::endl;
  }

  void nem_virtual() const {
      std::cout << "Ez a Hallgatoban ami nem virtual" << std::endl;
  }
};

class PhD_hallgato : public Hallgato {
  unsigned oktatott_oraszam;
public:
  PhD_hallgato(const std::string nev, unsigned felvett_oraszam, unsigned oktatott_oraszam) :
    Hallgato(nev, felvett_oraszam),
    oktatott_oraszam(oktatott_oraszam) {}

  unsigned get_oktatott_oraszam() const { return oktatott_oraszam; }

  void orat_tart() const {
      std::cout << "Orat tart a PhD_hallgato" << std::endl;
  }

  // Ha egy metodus virtualisnak lett jelolve onnantol a teljes oroklesi lancban lefele virtualis lesz
  // Nem kotelezo kiirni
  /*virtual*/ void orakat_hallgat() const { // megegyezo metodus az osbelivel
      std::cout << "Most a PhD hallato ment be egy orara" << std::endl;
  }

  void nem_virtual() const {
      std::cout << "Ez a PhD_hallgatoban ami nem virtual" << std::endl;
  }
};

void ertek(Hallgato h) {
    h.orakat_hallgat();
    h.nem_virtual();
}

void pointer(Hallgato* hp) {
    hp->orakat_hallgat();
    hp->nem_virtual();
}

void referencia(Hallgato& hr) {
    hr.orakat_hallgat();
    hr.nem_virtual();
}

int main() {
    Hallgato hallgato("Papa Joe", 8);
    PhD_hallgato phd("Meme Joe", 5, 3);
    phd.orakat_hallgat();   // phd lefutas
    hallgato.orakat_hallgat(); // hallgato lefutas

    std::cout << "Hallgato atadasa a fuggvenyeknek:" <<std::endl;
    ertek(hallgato);    // hallgato lefutas
    pointer(&hallgato); // hallgato lefutas
    referencia(hallgato); // hallgato lefutas

    std::cout << "PhD_allgato atadasa a fuggvenyeknek:" <<std::endl;
    ertek(phd); // hallgato lefutas
    pointer(&phd); // phd hallgato lefutas
    referencia(phd); // phd hallgato lefutas
}

Természetesen csak a megjelölt metódusok viselkednek polimorfikusan (virtualizáció szerint), így például a nem_virtual nevű metódus nem viselkedik polimorfikusan. Az is látszódik, hogy érték szerinti átadásnál a virtuális metódus sem viselkedett polimorfikusan. Ez a fent is említett érték szerinti másolás miatt történt így.

Ha polimorfikusan akarunk típusokat kezelni, az alábbiakat kell tennünk:

  • virtual kulcsszó a kívánt metódusokra
  • referencia vagy pointer használata

Override

A virtuális felüldefiniálás feltételei miatt kisebb elírások is ahhoz vezethetnek, hogy nem a megfelelő metódust írjuk felül. Ennek kivédésére meg tudjuk jelölni a felüldefiniálandó metódust, hogy a rendszer leellenőrizze, valóban a megfelelő metódus prototípust alkalmaztuk-e. Ez az override kulcsszó. Ha olyan metódust látunk el ezzel a kulcsszóval, melynek nincsen virtuális megfelelője valamelyik ős osztályban az öröklődési láncban, akkor fordítási hibát kapunk.

 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
#include <iostream>
class Hallgato {
    std::string neptun;
protected:
  std::string nev;
  unsigned felvett_oraszam;
public:
  Hallgato(const std::string& nev = "John Doe", unsigned felvett_oraszam = 0) :
        nev(nev), felvett_oraszam(felvett_oraszam) {
            neptun = nev + std::to_string(felvett_oraszam);
        }

  const std::string & get_nev() const { return nev; }
  const std::string & get_neptun() const { return neptun; }
  unsigned get_felvett_oraszam() const { return felvett_oraszam; }

  // A virtual kulcsszó ezt a metodust virtualisnak jeloli
  virtual void orakat_hallgat() const {
      std::cout << "Orat latogat a Hallgato: " << neptun << std::endl;
  }

  virtual void virtual_method() const {
      std::cout << "Ez a Hallgatoban ami nem virtual" << std::endl;
  }
};

class PhD_hallgato : public Hallgato {
  unsigned oktatott_oraszam;
public:
  PhD_hallgato(const std::string nev, unsigned felvett_oraszam, unsigned oktatott_oraszam) :
    Hallgato(nev, felvett_oraszam),
    oktatott_oraszam(oktatott_oraszam) {}

  unsigned get_oktatott_oraszam() const { return oktatott_oraszam; }

  void orat_tart() const {
      std::cout << "Orat tart a PhD_hallgato" << std::endl;
  }

  void orakat_hallgat() const override { // override jeloles
      std::cout << "Most a PhD hallato ment be egy orara" << std::endl;
  }

  void virtual_method() override {  // hibas, mert a method definicioja nem ugyan az. A nev megegyezik, de a const jelzo nem
      std::cout << "Ez a PhD_hallgatoban ami nem virtual" << std::endl;
  }
};

Feladatok

Gyakorló feladatok

  1. Hozz létre egy Jarmu osztályt, aminek egy privát string uzemanyag adattagja van publikus getterrel.

    Megoldás
  2. Legyen egy halad() függvénye a járműnek, ami kiírja, hogy a jármű halad és az üzemanyag típusát.

    Megoldás
  3. Hozz létre egy Auto osztályt, ami a Jarmu osztályból származik és van egy privát unsigned sebesseg adattagja publikus getterrel.

    Megoldás
  4. Az Auto definiálja felül a halad() fgv-t, az eredeti szöveg helyett írja ki, hogy az autó [sebesseg] km/h-val halad és [uzemanyag]-t fogyaszt.

    Megoldás
  5. Hozz létre egy Hajo osztályt, ami a Jarmu osztályból származik és van egy privát float sebesseg adattagja publikus getterrel.

    Megoldás
  6. A Hajo is definiálja felül a halad() fgv-t, az eredeti szöveg helyett írja ki, hogy a hajó [sebesseg] csomóval halad és [uzemanyag]-t fogyaszt.

    Megoldás
  7. Írj egy függvényt ami Jarmu tipusu objektumot vár és meghívja a kapott objektum halad() metódusát, majd hívd meg a létrehozott osztályok egy-egy példányával.

    Megoldás
  8. Gyakorló feladatsor

    • Hozzunk létre egy allat osztályt!

      • Az állat egyetlen attribútuma, hogy novényevo-e (novenyevo:bool) a színe. Írj paraméteres konstruktort az osztályhoz, mely kiírja a standard outputra, hogy "allat letrehozva".
      • Írj destruktort, mely kiírja: "allat torolve"
    • Hozzunk létre egy macska osztályt, ami az allat osztályból származik publikusan.

      • Legyen a macskanak neve, amit létrehozáskor meg kell adni
      • A novenyevo adattag legyen false-ra állítva.
      • A konstruktor és a destruktor is irja ki, hogy a macska létrejött/törlődött a nevével együtt.
    • Legyen az állatban egy void taplalkozik() metódus

      • Ha az állat novenyevo írja ki: legel, ha nem az írja ki: vadaszik.
      • A macska osztályban ez legyen kiirva a taplalkozik metódusban: X macska egeret vadaszik (ahol X a macska neve)
    • Legyen egy függvény, amelyik allat típust vár, és azon is nézzük meg a taplalkozik metódus meghívását.

      • Mi történik, ha különböző paraméterátadási módokat alkalmazunk (érték szerinti, paraméter, pointer)?
    • Példányosíts dinamikusan egy macskat, de a pointer allat legyen. Töröld! Mit tapasztalsz?

    Megoldás

Utolsó frissítés: 2020-11-13 06:38:42