4. hét: Objektumok memóriakezelése

Gera Dóra · 2019.02.27.

Heti kiegészítő feladatok

Felkészülés

1. Verem osztály

A mai feladat egy ehhez hasonló Verem osztályt írni, ami tetszőlegesen sok egész számot tud tárolni. Két fő művelete van: egy elemet bele lehet rakni, és mindig a legutolsót lehet elérni (LIFO)

A belső adatszerkezetre – a konzultáción elhangzottól eltérően – most egyszeresen láncolt listát fogunk használni, és elrejtjük a dinamikus memóriakezelést az objektum belsejében. Rendelkezésre áll a Prog1-ből ismerős lista típus:

struct ListaElem {
    int adat;
    ListaElem *kov;
};

Hozd létre ennek segítségével a Verem osztályt! Milyen adattagja(i) lesz(nek)? Írd meg a default konstruktorát (paraméter nélküli), ami egy üres listát hoz létre!

Tipp

Elég egyetlen ListaElem* típusú adattag, amit a default konstruktor NULL-ra állít.

2. Tagfüggvények

Írd meg a következő tagfüggvényeket:

  • berak: a paraméterében kapott számot berakja a verembe a listába való befűzéssel
  • kivesz: visszatér a legutoljára belerakott inttel, és kiveszi a veremből, azaz kifűzi a listából
  • meret: visszaadja, hogy milyen nagy a verem (hány elem van a listában)
  • ures: megmondja, hogy üres-e a verem

Ha az új adatot mindig a lista elejére teszed, akkor nem kell mindig végigiterálni az egész listán, és kitörölni is a legelső elemet kell. A memóriafoglalásnál és a felszabadításnál használj new és delete hívásokat!

3. Kiírás

Írj egy olyan globális operátort, amivel a Verem kiíratható lesz egy std::cout-ra!

Verem v1;
// v1 feltöltése intekkel
std::cout << v1 << std::endl; 
Tipp

A globális függvény nem láthatja az osztály privát adattagjait, de hívhatja publikus tagfüggvényeit. Az osztálynak legyen egy kiir(std::ostream&) függvénye, amit az operator<< meghívhat.

4. Destruktor

Mivel az új számok berakásakor dinamikusan foglalunk memóriát, ezért ezt valamikor fel is kell szabadítani. Írd meg a destruktort, hogy ne legyen memóriaszivárgás!

5. Másoló konstruktor

Mit csinálna a következő sor? Helyesen működik?

Verem v1;
// v1 feltöltése intekkel
Verem v2 = v1; 

A fordító által generált másoló konstruktor most csak az egyetlen pointer adattagunkat másolná le, és a két Verem objektum ugyanazokra a listaelemekre mutatna:

copy1

Nekünk viszont arra van szükségünk, hogy minden veremnek saját listája legyen:

copy2

Ehhez le kell másolni az egész listát. Írd meg a másoló konstruktort! Segítségképp egy előre megírt listamásoló függvény C-ben:

ListaElem *lista_masol(ListaElem *eleje)
{
    ListaElem *ujeleje = NULL;
    ListaElem *ujvege = NULL;
    for (ListaElem *iter = eleje; iter != NULL; iter = iter->kov)
    {
        ListaElem *uj = (ListaElem*)malloc(sizeof(ListaElem));
        uj->adat = iter->adat;
        uj->kov = NULL;
        if (ujvege != NULL)
            ujvege ->kov = uj;
        ujvege = uj;
        if (ujeleje == NULL)
            ujeleje = uj;
    }
    return ujeleje;
}

A memóriafoglalást írd át C++-osra!

6. Értékadó operátor

Ha nekünk kell megírni egy osztálynak a másoló konstruktorát és destruktorát, akkor valószínűleg az értékadó operátor is kelleni fog.

Írd meg az értékadó operátort! Mi a függvény visszatérési értéke? Legyen láncolható, és figyelj az önértékadásra is!

7. Kódduplikáció

Itt az alábbi kódrészlet:

ListaElem *uj = new ListaElem();
uj->adat = iter->adat;
uj->kov = NULL;

Meg lehet-e valósítani, hogy egy sorban történjen a memóriafoglalás és az új objektum adattagjainak inicializálása? Mivel kell kiegészíteni a ListaElem osztályt?

Tipp

A new ListaElem() utasítás meghívja a ListaElem default konstruktorát, amit a fordító írt nekünk. Írj egy két paraméteres konstruktort a ListaElem-nek, majd javítsd ki a new hívásokat a kódodban.

Nézd át a másoló konstruktor, destruktor és az értékadó operátor tartalmát. Írj egy lista_masol és egy lista_felszabadit függvényt, hogy megszüntesd a kódduplikációt!

Tipp

A két függvényt csak a Verem osztály tagfüggvényei használják, ezért felesleges hogy globálisak legyenek, tedd ezeket a Verem static függvényeivé!

A ListaElem most egy olyan osztály, amit nem akarunk a Verem nélkül használni, és emiatt nem kell láthatónak lennie a főprogramból. Elég, ha az osztály adattagjait és függvényeit csak a Verem ismeri, ezért az egész típus definícióját beemelhetjük a Verem osztályon belülre. Legyen a ListaElem osztály privát, és figyelj oda a sorrendre: nem vehetsz fel egy ListaElem* típusú adattagot előbb, mint az osztály deklarációja.

Amikor egy osztályon belül egy másik osztályt definiálunk, azt nested class-nak hívjuk, azaz belső osztálynak, erről még lesz szó a félév során, de előre is olvashatsz a témában a 6. fejezetben.

megoldas.cpp letöltése