Útmutató a 7. laborhoz
Laborfeladatok megoldásokkal
A feladat az előadáson bemutatott alakzatos modellezési példa elemzése és kiegészítése. A laborfoglalkozás végén 5 db fájlt kell feltöltenie — egy részük már félig kész van — a Jporta rendszerbe (Alakzatlab, 7. önellenőrző feladat), hogy megkapja a labor elvégzéséért járó pontot! Legalább az első öt tesztesetet (ELEKESZULT >= 5) hibátlanul meg kell oldania! A feladatok feltöltésére a laborgyakorlatot követő szombat 06:00-ig van lehetősége.
- Töltse le a laborgyakorlathoz előkészített projektet: https://git.ik.bme.hu/Prog2/labor_peldak/lab_07!
- Elemezze, értse meg a programot, ami lényegében az 5. előadáson bemutatott síkidomokat modellező példa módosított változata. Kezdetben a modell csak szakaszt és kört tartalmaz, amit később bővíteni fogunk. Az új osztályok kipróbálását a főprogramban definiált ELKESZULT makróval lehet vezérelni. A megértést segíti a mellékelt osztálydiagram:
Az előadás végén demózott SDL_alakzat példával ellentétben ez a program nem használ grafikát. Minden alakzathoz defináltunk egy globális inserter operátort (operator<<), ami kiírja az adott alakzat adatait egy adatfolyamra. A rajzol() tagfüggvények grafikus megjelenítés helyett ezt az insertert hívják.
A enum nem osztály, de ez most lényegtelen.A Szin osztályt pedig lecseréltük egy enum-ra. Figyelje meg, hogy a csere nem jelentett az osztályokban semmilyen módosítást!
- Alapesetben (ELKESZULT==0) a program létrehoz egy szakaszt és egy kört, majd különböző módokon kiírja azok adatait ill. "felrajzolja" azokat. Futtassa a programot! Ezt az eredményt várta? Amennyiben nem, elemezze a kódot újra! Szükség esetén használja a fejlesztőeszköz debug funkcióját illetve kérjen segítséget a laborvezetőtől.
- Cserélje ki a
virtual void rajzol() const = 0;
kódsort avirtual void rajzol() const { std::cout << "Rajzol" << std::endl; }
sorra az alakzat.h fájtban! Fordítsa le újra, és futtassa a programot! Mi tapasztal?
- Most törölje a virtual kulcsszót, majd fordítson, futtasson újra! Mi tapasztal?
- Legyen a függvény újból virtuális! Most törölje a const kulcsszót, majd fordítson, futtasson! Mi tapasztal?
- Állítsa vissza rajzol() tagfüggvény eredeti állapotát (const, tisztán virtuális), majd bővítse a modellt háromszöggel! A háromszög legyen létrehozható három pontból. A rajzol() tagfüggvénye a szakaszhoz hasolóan egyetlen sort írjon ki, amiben megjelenik a "Rajzol: Haromszog" szöveg, a három csúcspont koordinátája és az alakzat színe is. Valósítsa meg a hozzá tartozó globális insertert és a rajzol() tagfüggvényt. Az osztály deklarációit a haromszog.h, az implementációt, pedig a haromszog.cpp fájlba írja.
Állítsa az ELKESZULT makró értékét 1-re, hogy az új osztály működését is ki tudja próbálni! Fordítson, futtasson!Amennyiben saját projektfájlt használ, ne felejtse el hozzáadni a projekthez az új fájlokat! - Megvalósítottunk egy Ellipszis osztályt is az ellipszis.h fájlban. Az egyszerűség kedvéért az ellipszist a Kor osztályból származtattuk, ami geometriai szempontból vitatható, de most nem ez a lényeg. A kipróbálásához állítsa az ELKESZULT makró értékét 2-re!
Vegye észre, hogy a "kirajzolásnál" az ellipszis körként rajzolódik ki! Pedig a rajzol() tagfüggvény jól van megvalósítva! Az inserter is jól ír ki!
Ez egy tipikus hiba, amit szinte mágnesként vonz a "legyen minden tagfüggvény inline és egy fájlban" gondolat. A dolog kezelhető, de meg kell érteni! - Az ellipszis.h fájlban class definíciója elé készítsen egy prototípust az inserter függvényhez! Ez a gyakorlatban azt jelenti, hogy másolja az inserter függvény első sorát a class elé és a nyitó kapcsos zárojelet cserélje pontosvesszőre.
- Az alakzat_main-ben egy tömbbe gyűjtöttük össze a különböző alakzatok pointereit, ami tulajdonképpen egy heterogén kollekció tárolójának felel meg. A példa egyszerűsége miatt az objektumok nem a dinamikus memóriában jöttek létre. A valós esetekben ez csak ritkán van így. Az objektumok a dinamikus tárban keletkeznek és gondoskodni kell a felszabadításról is. Célszerűen egy "okos" tárolóra bízzuk a dinamikusan létrehozott objektumokat azzal, hogy kezelje, szükség esetén törölje azokat.
Készítsünk a síkidomokhoz egy "okos" Rajztabla osztályt, amiben max. 100 különböző alakzat tárolható! Mivel a síkidomokat egységesen szeretnénk kezelni, ezért heterogén kollekcióként tároljuk, ahogyan azt a 6. előadás EventList osztályánál is láthattuk. Esetünkben a közös ős az Alakzat. Legyen a rajztáblának:- size() tagfüggvénye, ami megadja, hogy héyn alakzat van a táblán,
- felrak() tagfüggvénye, ami felrak a rajztáblára egy tetszőleges alakzatot (pointert),
- rajzol() tagfüggvénye, ami "kirajzolja" összes alakzatot,
- mozgat() tagfüggvénye, ami a táblán levő alakzatok origóját elmozdítja egy d vektorral,
- torol() tagfüggvénye, ami "letörli" a táblát (minden objektumot megszüntet).
- A rajztabla.h fájlban elkészítettük az osztály deklarációját, ami még nem végleges. Az egyszerűség kedvéért fix méretű tömbben tároljuk a pointereket. A konstruktort és a size() tagfüggvényt inline megvalósítottuk, ami inicializálja a darabszámot. Az alapértelmezett másoló konstruktort, és az értékadás műveletet privátként felüldeklaráltuk, mivel az alapértelmezett változat nem megfelelő. Így ezek nem lesznek érhetőek kívülről. Ha nem valósítjuk meg, akkor belülről sem! (c++11-ben van más megoldás, de ez is tökéletesen megfelelő.)
class Rajztabla { class Rajztabla { static const size_t MAXDB = 100;///< legegyszerűbb így belső (egész) konstanst felvenni egy osztályban. Alakzat* tabla[MAXDB]; ///< tároló size_t db; ///< aktuális darabszám /// "Elrejtett" másoló konstruktor és értékadó operátor. /// Az alapértelmezett változat nem jó, ezért ne legyen elérhető. Rajztabla(const Rajztabla& rhs); Rajztabla& operator=(const Rajztabla& rhs); public: Rajztabla() :db(0) { } /// Visszadja, hogy hány síkidom van a táblán /// @return - darabszám size_t size() const { return db; } /// Alakzatot tesz a rajztáblára /// @param ap - pointer az alakzatra void felrak(Alakzat *ap); /// Kirajzolja az összes alakzatot void rajzol() const; /// Az összes alakzatot elmozdítja /// @param d - eltolás vektora void mozgat(const Pont& d) const; #if ELKESZULT >= 5 /// letörli a táblát, eltávolítja az objektumokat void torol(); #endif // ELKESZULT /// Lehet, hogy kell destruktor is. };
- Vegye ki a projektből az alakzat_main.cpp fájlt és tegye helyére az alakzat2_main.cpp fájlt. Ez a főprogram már dinamikusan hozza létre az alakzatokat és a rajztáblára "bízza" az objektumokat, azaz a rajztábla fogja kezelni, és megszüntetni is azokat. Fordítsa le a kódot, ami alapesetben (ELESZULT==3) csak létrehoz egy Rajztabla objektumot, de nem használja.
- Valósítsa meg Razjtabla osztály tagfüggvényeit a torol() kivételével a rajztabla.cpp fájlban!
Állitsa az ELKESZULT makró értékét 4-re. Fordítson és futtasson!
Memóriaszivárgást kell tapasztalnia, hiszen az alapértelmezett destruktor nem "felügyelte" az objektumokat, amit rábíztunk. - Valósítsa meg a torol() tagfüggvényt és a destruktort is! Gondolja meg, hogy a két funkciónak van közös része/feladata. Ellenőrizze, hogy van-e memóriaszivárgás!
- Állítsa az ELKESZULT makró értékét 5-re. Fordítson és futtasson! Könnyen lehet, hogy most elszáll a programja. Ellenőrizze a darabszámokat!
- Készítsen insertert a Rajztabla osztályhoz, ami kiírja a táblán levő síkidomok számát! Állítsa az ELKESZULT makró értékét 6-ra. Fordítson és futtasson!
- Ha elégedett az eredménnyel, akkor töltse fel az elkészített illetve módosított haromszog.h, haromszog.cpp, rajztabla.h rajztabla.cpp, ellipsis.h fájlokat a Jporta rendszerbe (7. labor önellenőrző feladat), hogy megkapja a labor elvégzéséért járó pontot! Legalább ELKESZULT==5 szintig el kell jutnia.
Szorgalmi feladat
- Bővítse a modellt Teglalap osztállyal! Az egyszerűség kedvéért feltételezhető, hogy a téglalapok oldalai párhuzamosak a koordináta tengelyekkel, így elegendő két szemközti csúcspont koordinátáit tárolni. Állítsa az ELKESZULT makró értékét 7-re. Fordítson és futtasson!
Jó munkát!
Szeberényi Imre