4. hét: Objektumok memóriakezelése
Gera Dóra · 2019.02.27.
Heti kiegészítő feladatok
Felkészülés
- Jegyzet 3. fejezete
- InfoC - láncolt listák emlékeztető
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.
Megoldás
class Verem {
ListaElem* eleje;
public:
Verem(): eleje(NULL) {}
};
Í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ésselkivesz
: visszatér a legutoljára belerakott inttel, és kiveszi a veremből, azaz kifűzi a listábólmeret
: 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!
Í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.
Megoldás
void Verem::kiir(std::ostream &os) const {
ListaElem *p;
for (p = eleje; p != NULL; p = p->kov)
os << p->adat << " ";
}
std::ostream& operator<<(std::ostream &os, Verem const& v) {
v.kiir(os);
return os;
}
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:
Nekünk viszont arra van szükségünk, hogy minden veremnek saját listája legyen:
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!
Megoldás
Verem::Verem(Verem const& masik) {
eleje = lista_masol(masik.eleje);
}
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!
Megoldás
Verem& Verem::operator=(Verem const& masik) {
if (this != &masik) {
while (eleje != NULL) {
ListaElem *tmp = eleje->kov;
delete eleje;
eleje = tmp;
}
eleje = lista_masol(masik.eleje);
}
return *this;
}
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!
Megoldás
struct ListaElem {
int adat;
ListaElem *kov;
ListaElem(int adat, ListaElem *kov): adat(adat), kov(kov) {}
};
ListaElem *uj = new ListaElem(iter->adat, NULL);
Nézd át a másoló konstruktor, destruktor és az értékadó operátor tartalmát! Ha van benne kódduplikáció, írj egy lista_masol
és egy lista_felszabadit
függvényt, hogy megszüntesd!
Megoldás
void lista_felszabadit(ListaElem* eleje) {
while (eleje != NULL) {
ListaElem *tmp = eleje->kov;
delete eleje;
eleje = tmp;
}
}
Verem::~Verem() {
lista_felszabadit(eleje);
}
Verem& Verem::operator=(Verem const& masik) {
if (this != &masik) {
lista_felszabadit(eleje);
eleje = lista_masol(masik.eleje);
}
return *this;
}
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
private, 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.