játékfejlesztés.hu
FórumGarázsprojectekCikkekSegédletekJf.hu versenyekKapcsolatokEgyebek
Legaktívabb fórumozók:
Asylum:    5448
FZoli:    4892
Kuz:    4455
gaborlabor:    4449
kicsy:    4304
TPG:    3402
monostoria:    3284
DMG:    3172
HomeGnome:    2919
Matzi:    2520

Pretender:    2498
szeki:    2440
Seeting:    2306
Geri:    2188
Orphy:    1893
Joga:    1791
Bacce:    1783
MaNiAc:    1735
ddbwo:    1625
syam:    1491
(C++) DirectX programozás 11. - Objektumorientált programozás 2005.10.29 05:10


Ebben a cikkben egy új fába vágnám a fejszémet, ugyanis DirectX alatt nem árt tudni, mi is az az objektumorientált programozás. Fel lehet tenni a kérdést: miért olyan népszerû ez a modern és misztikusan hangzó programozási módszer? Elõször is nem válaszolnám meg ezt a kérdést, ugyanis a válaszhoz ismernünk kell magát az elvet, amit késõbb ismerünk meg. Tudniillik még, hogy az objektumorientalt C++ modernnek modern, de semmiképp sem új! Azaz az elsõ használható változat már 1985-ben megjelent, amit aztán továbbfejlesztettek, az akkori 1.2v C++ már a múltté.

Mielõtt mérlegelnénk az objektumorientált nyelvek elõnyeit, meg kell ismertnünk magát a fogalmat! Kezdetben voltak a sima programnyelvek, mint a Basic, a Pascal és a C. Aki ismeri a Basicet(ki ne ismerné), az tudja, hogy azt ki is ejthetjük a sorból, hiszen nem lehet együtt emlegetni pl. a Pascallal. A Pascal nyelvet oktatási célokra találták ki, ám könnyû használhatósága miatt széles körben elterjedt, szintaxisát ugyancsak kibõvítették, és a Pascal is objektumorientálttá vált(Object Pascal). Objektumorientált programnyelv a Pascal szintaxist használó Delphi is. Régen, aki C-ben programozott, az kihúzta magát, mert "én húde tudok programozni", pedig programozni C alatt hasonló volt mint Pascal alatt, csupán más szintaxissal. Aki pedig szöveges programot írt, az úgyis Pascal-t használt, mert az sokkal egyszerûbb volt. Magát a C-t csupán a profiknak volt érdemes használni.

Aztán jött egy új nyelv, a C++. Hangsúlyozom, hogy új nyelv, hiszen a C-vel csak szintaxisában egyezett, illetve felülrõl kompatibilis volt a C nyelvvel. Fõ újítása az objektumorientáltság volt, ami jelentõsen megkönnyítette a programozást az újrafelhasználható elemek révén. Tehát már ki is szivárgott az elsõ információ: a programot ezentúl modulokban írjuk, nem pedig csak úgy vaktában... Ezeknek a moduloknak a neve: osztály.

Nézzük mi a viszonya az osztályokban a függvényeknek és az adatoknak! Pascalban vagy C-ben létrehoztunk változókat, aztán mûveleteket hajtottunk rajtuk végre. Vagy globálisan hoztunk létre változót, vagy lokálisan, ettõl függõen átadogattuk õket a függvényeknek, vagy csak simán hivatkoztunk rájuk a függvényekbõl... Ez a C++-al megszûnt, pontosabban támogatja az ilyen programozást a C-vel való kapcsolata miatt, de a fõ vezérelv az objektumorientáltság lett. Azaz legyen egy objektumunk. Ez az objektum tartalmazzon az objektumot leíró adatokat, melyeket az objektum létrehozásakor inicializálunk, aztán el is felejtjük õket, legalábbis az objektumon kívûlrõl! Tehát ezek után az objektumon kívûlrõl csak függvények útján kommunikálunk az objektummal. Ezt hívják adatrejtés elvének. Lássuk, miért is jó ez: egyrész így illetéktelen programrész nem nyúlhat bele a változóinkba, ami esetleg galibát okozna, pontosabban az objektumunk helytelen mûködését. A másik pedig a hibakeresés: nem kell sorról sorra végignézni a programunkat, hogy hol a hiba, hiszen az objektumunkat addig tesztelhetjük, amíg tökéletes nem lesz, majd azután beleépítjük a programunkba. Ez a program pedig nem tudja elrontani az objektumunkat(mármint ha az objektumot helyes írtuk meg).

Az objektumok pontos mûködésérõl, az öröklõdésrõl, a beágyazásról, a virtuális függvényekrõl majd már a gyakorati részben írok, addig pedig meg lehet barátkozni a gondolattal, hogy a nehezen megtanult C nyelvrõl most új vizekre evezünk!


Alapok

Lássuk akkor, hogy is kell programozni objektumorientáltan! Hangsúlyozom, hogy csak az alapdolgokat mutatom be, aki feltétlenül meg akar érteni minden részletet, az vehet valamilyen könyvet a C++ programozásról, úgyis annyi létezik már. Én mindig azt mondom, hogy az iagzi tudást könyvbõl lehet megszerezni, vagy méginkább egy egyetemen! Szóval hajrá, irány tanulni!

Az elsõ és legfontosabb dolog: nem kell header fájl!!! Mint az elõzõ cikkben (elõzõ bekezdésben - HG) említettem, az objektumorientált programozás része a C++ nyelvnek, tehát az alapvetõ nyelvi elemek megtalálhatók. Ugye mindenki ismeri a struct felhasználói típust? Nos, azt is ellátták az objektumorientáltsághoz szükséges eszközökkel, de mi az osztály típust fogjuk használni. Hogy miért? Mert sokkal jobban beleillik az adatrejtés elvébe, azaz minden tag alapban kívûlrõl nem elérhetõ. De talán akkor kezdjünk is bele, és késõbb talán jobban megértjük, miért is fontos az objektumorientált programozás.

Hogyan hozunk létre egy osztályt? Mindenekelõtt kell egy prototípus, hiszen mit akarunk addig létrehozni? Lássuk, hogyan hozunk létre egy struktúra prototípust!

struct vmi {

};

Most nézzük az osztály prototípust!

class vmi {

};

Ugyan az, csak a kulcsszó tér el! Vigyázzunk a pontosvesszõvel, ugyanis ide is kell!!! Namost, akkor lássuk, mit is tudunk ezzel az osztállyal csinálni, mit is lehet beírni a zárójelek közé! Az elsõ legfontosabb dolog az, hogy megismerjük az adatrejtés elvét!

Régen ugye voltak a struktúrák, amikbe csak változókat lehetett deklarálni, de függvényeket nem! Ott természetesen kívûlrõl kellett látni ezeket a változókat, mert belülrõl mi látta volna õket? Ott nem volt semmilyen függvény. Itt most lehetnek, tehát nem szükséges, sõt sokszor fontos, hogy ne is lássák a változóinkat kívülrõl! Ekkor ezeket a változókat csak az osztályon BELÜLI függvényekkel tudjuk megváltoztatni. Akkor lássuk az elérést módosító kulcsszavakat:

private - privát elérés, azaz csak a mi osztályunkon belülrõl látszik

public - nyilvános elérés, azaz az osztályon kívül is látszik

protected - privát elérés kívülrõl, azonban az örökölt osztályok láthatják(errõl majd késõbb)

Nézzünk erre egy példát: egy anyuka már 50 éves, tehát nem akarja hogy más is tudja az életkorát, azonban a fõ méretei 90-60-90, tehát azokkal pedig csak büszkélkedni tud. Lássuk ezt C++-ban:

class anyuka {

private:
int kor;

public:

int csipo,derek,mell;

};

Látszik, hogy attribútumszerûen megadjuk az adatrejtés típusát, majd kettõspont után írjuk a változóinkat. Egy ilyen kijelölt rész a következõ adatrejtési kulcsszóig tart. Megjegyzem, hogy az osztály típusban a tagok, mint az elõbb említettem, alapban private elérésû, míg struktúrában alapban public elérésûek.

Szóval ha ezt mind értjük, akkor nézzük meg nagyon röviden mi lehet egy osztályban:

  1. adatelérést szabályzó kulccsszavak
  2. változók, mutatók, akármilyen eddig ismert változótípus, tömbök, stb. FONTOS, hogy a változóknak nem lehet kezdõértéket adni!
  3. függvények töménytelen mennyiségben, ha private kulcsszó alatt van, akkor természetesen az sem érhetõ el kívûlrõl
  4. lehetnek még különbözõ öröklõdést, adatrejtést,stb. szabályzó kulcsszavak(ezekrõl késõbb)
Nézzünk akkor egy ilyen osztályt, amiben vannak függvények és változók is:

class Szamlalo {
private:
int akt;
public:
void Inicializal(int honnan=0) { akt=honnan; }
void Novel() { akt++; }
int Leker() { return akt; }
};

Akkor lássuk az újdoságokat! Azt észrevehetjük, hogy ez egy komplett, mûködõ, bár a tudásához képes méretes egy osztály, szóval egy egyszerû, bár borzasztóan rossz példa. De mondhatnám kitenyésztettnek, mert sok minden benne van, ami kell. Mit is csinál? Ha létrehozzuk az osztályt, és inicializáljuk, akkor kapunk egy szamlalót, mely az inicializálás paramétereként megadott számtól számol elõre minden Novel() parancsra! Az is feltûnõ, hogy itt már a deklarációban benne vannak a definiciók, azaz mindegyik függvényt megadtam! Természetesen ezeket külön is meg lehet adni, a hatókör feloldása operátorral(::), de arról majd késõbb, ugyanis itt nagyon egyszerû függvényekrõl van szó.

Nézzük az adatrejtést: a változóknak nem közvetlenül adunk értéket, hanem az osztály függvényein keresztül! Ez azért fontos, hogy megbízható legyen a programunk futása! Gonduljuk el, hogy ez a számláló egy nagyobb program része! Mondjuk éppen már kész is van, csak azt szeretnénk, hogy ez a program ne legyen tele hibával! Tehát ennek alapfeltétele, hogy a számlálónk jó legyen. Ez természetesen jó, de mi van, ha a program a számlálónk akt változóját átírja? A számláló ugrik egy nagyot, mi pedig kershetjük a hibát mindenhol, ez pedig nehéz. De a program NEM TUDJA ÁTÍRNI az akt változót, mert az privát elérésû! Tehát biztosak lehetünk benne, hogy a számláló mûködik, máshol kell keresni a hibát!

Következõ: lássuk hogyan adunk meg függvényeket! Ugyanúgy, ahogy osztályon kívûl. Csupán arra kell figyelni, hogy ne legyen az osztály nevével azonos nevû változónk vagy függvényünk! Az Inicializal(...) talán nem ismerjük az alapértelmezett érték trükköt(nem trükk, szintaktika). Ez azt jelenti csupán, hogyha nem adunk meg semmilyen paramétert a függvénynek, akkor a honnan nulla értéket fog felvenni. Ez mûködik többre is, pl.:

int vissza(int a, float k, double t=4.5f,char e=3);

de ez nem jó: int vissza(int a=0, float k, double t=4.5f, char e=3);

ugyanis az ilyen paramétereknek mindig a paraméterlista végén kell lenni, nem lehet utánnuk alapértelmezett érték nélküli paraméter! Akkor gondolom minden szép és jó, lássuk, hogyan hivatkozunk az osztály tagjaira!

Szamlalo szaml;
int a;

szaml.Inicializal(); //vagy szaml.Inicializal(67);
szaml.Novel();
szaml.akt=67; //Ez HIBÁS!!!
a=szaml.Leker();

Tehát elõször létrehoztam a Szamlalo egy példányát(igy csináljuk struktúránál is!), majd egy a változót. Gondolom az osztály tagjainak elérése egyértelmû. Természetesen a szaml.akt=67; sor hibás, mert az akt az osztály privát tagja. Ha nyilvános tagja lenne, akkor tökéletes értékadás történne.

Mi van akkor, ha az osztályt dinamikusan akarom létrehozni? Semmi, tegyük az alábbiakat:

Szamlalo *szaml;

szaml=new Szamlalo;
szaml->Inicializal();

Tehát elõször mutatót hozok létre, majd a new paranccsal(ami ugyancsak a C++-hoz tartozik) létrehozom az osztályt. A -> jelet már DirectX-ben megszokhattuk, nos ezért. Ugyanis a szaml->Inicializal() ugyan az, mint a (*szaml).Inicializal(); csak a C szintaktika egyszerûsíti.

Mivel a DirectX is osztályokra épül ,ezért a DirectDraw-ban lévõ mutatózgatást most nézzük meg, miért fontos! Lássuk a programot, szigorúan csak a dinamikus osztálylétrehozás szempontjából, és persze a függvényeket leegyszerûsitve:

LPDIRECTDRAW7 lpDD; //IDirectDraw7* lpDD; ->ezt csinálja a makró(azaz az LPDIRECTDRAW7 igazából mutatót kreál)

HRESULT DirectDrawCreateEx(...,IDirectDraw7** vmi,...) {

...

(*vmi)=new IDirectDraw7;

...

}

Tehát mindig egy globális függvénnyel indítunk, mely létrehozza nekünk a megfelelõ osztályt. Remélem érthetõ, bár ez csak egy kis kitérés volt...

Egy dolog maradt hátra, méghozzá a hatókör feloldása operátor. Ez két kettõspont egymás után. Így az osztályon kívûlre is tudunk definíciót helyezni, de annak mindig látnia kell az eredeti deklarációt! Lássuk erre példát, például a számlálónk definícióit helyezzük az osztályon kívûlre! Ekkor az operátor bal oldalán mindig az osztály neve áll, a jobb oldalán a függvény neve!

class Szamlalo {
private:
int akt;
public:
void Inicializal(int honnan=0);
void Novel();
int Leker();
};

void Szamlalo::Inicializal(int honnan) {akt=honnan;}

void Szamlalo::Novel() {akt++;}

int Szamlalo::Leker() {return akt; }

Szóval akkor az alapok készen vannak, ennyihez nem mellékelek forráskódot.


Konstruktor, destruktor

Mik is ezek a rejtélyes nevek? Konstruktor annyit jelent, mint építõ, a destruktor pedig romboló. Körülbelül. De mire jók?

Konstruktor

A konstruktor arra jó, hogy inicializáljuk az osztályunk egyik példányát(a példány maga a változó, vázlatosan pl. int a; ,ekkor a az integer egyik példánya). Ezt azért hozták létre, mert ugye privát tagokat kívûlrõl nem tudunk inicializálni. Ezért létezik egy konstruktor, mely ezeket a változókat olyan állapotba állítja, amilyenbe mi akarjuk. Ráadásul a konstruktor a mi külön parancsunk nélkül fut le(még akkor is, ha külön nem csinálunk konstruktort, akkor az alapértelmezett fut le).

A konstruktor akkor fut le, ha az osztály példánya létrejön... De mikor jön létre az osztály egy példánya? Két módon jöhet létre: statikusan vagy dinamikusan. Ezek egyszerûek, nezzük a statikusat:

Osztaly valtozonev; //Ezt úgy értsétek mint pl.: int a;

Azaz mikor egyszerûen létrehozunk egy Osztaly típusú változót. Tehát ekkor fut le, mi mégse kértük. Ez azt jelenti, hogy mindig lefut, ha van. Az elõzõ részben lévõ példáinkban nem volt, szóval ott nem. Fontos hangsúlyozni, hogy NEM a konstruktor foglal memóriát az objektum példányának, hanem jelen esetben a fordító! Vagy pedig mi, dinamikus esetben:

Osztaly *mutato;

mutato=new Osztaly;

Ekkor a new paranccsal jön létre és a new parancs fogja a konstruktort lefuttatni! Tehát MINDIG new paranccsal foglaljunk memóriát, mert ez már a C++ része! Hiába próbálkozunk a malloc vagy a calloc paranccsal, azok nem futtatják le a konstruktort! Szóval felejtsük el ezeket az õsrégi tákolmányokat most(persze hasznosak, de nem most!). Akkor példálozzunk! Hogy is csinálunk konstruktort? A konstruktor neve mindig azonos az osztály nevével, azaz errõl tudjuk, hogy konstruktor! A konstruktornak NINCS visszatérési értéke! Az esetleges paraméterekrõl majd késõbb szólok.

class Anya {
private:
int gyerekekszama;
public:
Anya() { gyerekekszama=0; }

};

Fontos, hogy a konstruktornak nyilvános elérésûnek kell lenni! Ekkor az

Anya elsoanyuka;

példányosítás után a gyerekekszama nem ismeretlen értékû, hanem nulla lesz! Így tehát könnyedén tudunk inicializálni egy osztálypéldányt. Fontos, hogy nem osztályt inicializálunk, hanem a példányt!

Konstruktorok paraméterezése

Nézzük az alábbi létrehozást:

Anya *mutato;

mutato=new Anya;

Ez azonos az alábbival:

mutato=new Anya();

Ez pedig azt jelenti, hogy a konstruktor hordozhat paramétereket is. Alapértelmezett konstruktor mindig van, és az paraméter nélküli!!! Ha mi egy akármilyen konstruktort létrehozunk, akkor az már nincs többé! Tekintsük az alábbi kódot:

class Anya {
private:
int gyerekekszama;
public:
Anya(int szam) { gyerekekszama=szam; }
};

Tehát létrehozásnál beállíthatjuk a gyerekek számát. Ekkor viszont az alapértelmezett létrehozás elveszik, azaz nem mondhatjuk azt, hogy:

Anya vmi;

mert a fordító azt fogja mondani, nincs alapértelmezett konstruktor, azaz nincs Anya() konstruktor! Tehát létrehozni most így tudjuk:

Anya vmi(7);

vagy

Anya *mutato;

mutato=new Anya(6);

De mi van, ha szeretnénk alapértelmezett konstruktort? Akkor azt is megadunk, és a fordító majd a megfelelõ paraméterezésût hívja meg:

class Anya {
private:
int gyerekekszama;
public:
Anya() {gyerekekszama=0;}
Anya(int szam) {gyerekekszama=szam;}
};

Ekkor viszont akármelyik módon létrehozhatjuk, csak figyeljünk, hogy az a konstruktor hívódik-e meg, amelyiket akarjuk! A több konstruktor egyébként a magyar szakirodalom túlterhelt konstruktornak nevezi(az angol overload után). Szerintem ez nagyon nyelverõszakolás...

Destruktor

Akkor most már tudjuk mi az a destruktor: mikor az osztály megszûnik, akkor elõtte lefut. Mikor szûnik meg egy osztály? Statikusan létrehozott osztály akkor szûnik meg, ha a fordító felszabadítja. A dinamikusan létrehozott osztály pedig a

delete peldany;

parancsra hívja meg a destruktort. Itt is fontos, hogy nem a destruktor szabadítja fel a memóriát, hanem a fordító, vagy éppen a delete parancs. Csak elõtte meghívja a destruktort! A free() parancs NEM hívja meg a destruktort! Destruktor csak egy lehet, ez pedig az alábbiként néz ki:

class Anya {
private:
int gyerekekszama;
public:
~Anya();
};

Anya::~Anya() {gyerekekszama=0; }

Azaz egy ~ jel után az osztály neve. Most nem akartam okos példát találni, de gondolom értjük a lényegét, azaz ha pl. létrehoztunk egy nagy 1000 elemû dinamikus tömböt, akkor azt itt felszabadíthatjuk.

A destruktornak nincs visszatérési értéke és nem lehet paramétere.

Akkor nézzünk egy ilyen dinamikus tömbös példát(jóval komplexebb példa az Ogg lejátszásnál van!):

//Deklarációk
class Tomb {
private:
int *tomb;
int elemekszama;
public:
Tomb(int);
~Tomb();

void Beir(int,int);
int Leker(int);
int LekerElemSzam() { return elemekszama; }
};

//Definíciók

Tomb::Tomb(int elemszam)
{
tomb=new int[elemszam];
elemekszama=elemszam;
}

Tomb::~Tomb()
{
delete[] tomb;
}

void Tomb::Beir(int melyik, int mit)
{
if(melyik>elemekszama) ; //hibakezelés
tomb[melyik]=mit;
}

int Tomb::Leker(int melyik)
{
return tomb[melyik];
}

A tomb=new int[elemszam] ugyancsak C++ parancs, azaz helyet foglal elemszam darab egésznek, a delete[] tomb pedig felszabadítja. Vigyázat! A delete tomb csak az elsõ elemet szabadítja fel!

Ez volt egy tömb körülményes, de tanulságos megvalósítása!


Öröklõdés

Na, akkor lássuk a leghíresebb tulajdonságát az objektum-orientált programozásnak... Az öröklõdés fõleg a kezdõ C++ programozónak jelent problémát, mert nem teljesen érthetõ... Ugyanis sokszor úgy vezetik be, hogy van egy apuka, meg egy gyereke. Namost, az apukából örököl a gyerek, így ez lesz, meg az lesz. Én inkább másból indulnék ki, és ezt az apuka-gyerek kapcsolatot a végén fejteném ki teljesen, mikor már minden érthetõ lesz... Kezdjünk bele!

Csináljunk egy primitívek rajzolásához szükséges osztályt! Itt a primitív olyan geometriai alakzatokat jelent, mint a kör, háromszög, poligon, stb. Lássuk, hogy a primitívekben mi a közös! Mindegyik például pontokból áll. Legyen mondjuk ez a vizsgálódás tárgya! Szóval kellenek pontok! Csináljunk pontokat, bár ezek nem igazán kapcsolódnak most az öröklõdéshez:

struct pont {
double x,y;
};

Akkor vegyük azt, hogy csinálunk egy teljesen általános primitív osztályt, mely tartalmazni fogja a primitívünk pontjait, területét és kerületét! Lássuk:

class cPrimitiv {
protected:
double terulet,kerulet;
pont *pontok;
int pontokszama;
public:
cPrimitiv(int);
~cPrimitiv();

double Terulet() {return terulet; }
double Kerulet() {return kerulet; }
};

Tehát tároljuk a primitív területét és pontjait protected eléréssel, ami azt jelenti, hogy az öröklõ osztályból public elérésûek, kívülrõl pedig private elérésüek. A konstruktor csupán a pontok számát kéri paraméternek. A pontokat dinamikus tömbben fogjuk tárolni, amelyet a konstruktor hoz létre, aminek a definíciója:

cPrimitiv::cPrimitiv(int psz)
{
pontokszama = psz;
pontok = new pont[psz];
}

Ez létre is hozta a tömböt. A destruktor pedig ezt felszabadítja:

cPrimitiv::~cPrimitiv()
{
delete[] pontok; //ez itt szögletes zárójel space nélkül: [ ]
}

Oké. Akkor kész is, van egy primitívünk, melynek a területérõl és kerületérõl nem tudunk, hiszen ahhoz tudni kell a primitív geometriájáról! Viszont erre a kódra, osztályra lehet már építeni különbözõ geometriájú alakzatokat. Akkor csináljunk egy négyzetet! Ehhez azt használjuk, hogy már van egy primitívünk. Ezért olyan osztályt csinálunk, ami nagyon egyszerû lesz, ugyanis ez az osztály örökölni fogja a primitív osztályunk függvényeit! És annyira, hogy teljesen a sajátja lesz, tehát nem a primitív osztályunké! Mert a primitív osztályt a program automatikusan létrehozza, és a függvényeit elérhetõvé teszi a négyzet osztályban, feltéve ha azok nem private függvények! Akkor nézzük a négyzet osztály deklarációját:

class cNegyzet : public cPrimitiv {
public:
cNegyzet(double,int,int);
};

Itt a kettõspont után álló kifejezés azt jelenti, hogy a primitív osztályunktól örököl függvényeket, és a public kulcsszó miatt a protected és a public függvényeit. Elnevezés szerint így ez az osztály az öröklõ osztály, a primitiv osztály pedig az alaposztály. Használva ezeket, tudni kell, hogy

  • az öröklött osztály létrehozása után meghívódik az alaposztályok konstruktora, mejd végül az öröklött osztály konstruktora.
  • a destruktorokra ez pont fordítva igaz, ugyanis elõször az öröklött osztályé fut le, majd az alaposztályoké.
Mivel meghívódik a primitiv osztály konstruktora, és ráadásul ennek az osztálynak definiáltunk konstruktort, ezért nincsen megfelelõ konstruktor, ami meghívódjon. Ezért nekünk kell megmondani a fordítónak, melyik konstruktort hívja meg(ezt csak akkor lehet elhagyni, ha a paraméter nélküli alapértelmezett konstruktort akarjuk meghivatni):

cNegyzet::cNegyzet(double a,int x,int y) : cPrimitiv(4)
{
terulet=a*a;
kerulet=4*a;

pontok[0].x=x; pontok[0].y=y;
pontok[1].x=x+a; pontok[1].y=y;
pontok[2].x=x+a; pontok[2].y=y+a;
pontok[3].x=x; pontok[3].y=y+a;
}

Mint látjuk, ez a négyzetünk konstruktora, melynek fejléce után kettõsponttal elválasztva megadjuk az alaposztályunk meghívandó konstruktorát. Emlékezzünk vissza, hogy a primitív osztályunk konstruktora a pontok számát kéri! Ez most éppen négy (négyzet). Így az alaposztály konstruktora meghívódik, majd azután a négyzet konstruktora, amiben mint látható, kiszámoljuk a teruletet és a kerületet, majd a pontok helyzetét is kiszámítjuk. A négyzet kontstruktora a négyzet oldalának hosszát és bal felsõ pontját kéri. Mindez után ha kiadjuk a

cNegyzet negyzet(10,5,5);

parancsot(változó definiálást), akkor lesz egy 10 oldalu négyzetünk, melyre a

printf("%f, %f, %d, %d",negyzet.terulet, negyzet.kerulet, negyzet.pontok[2].x, negyzet.pontok[2].y);

parancs az alábbi output-ot adja:

100.000000, 40.000000, 15, 15

ami rendre a terület, kerület, és a jobb alsó pont x és y koordinátája. Remélem érthetõ, hogy minden egyes négyzet létrehozás után új primitív osztály jön létre. Ezért hibás az, hogy ha az apuka osztályból örököltetünk egy gyereket, mert minden létrehozott gyereknek új apja lesz! Ennek elkerülésére a beágyazást használjuk. Az alul található példában megírtam a kör osztályt is, mely ugyancsak a cPrimitiv osztályra épül.

Most pár szó a beágyazásról: az öröklõdés a programkód egyszerûségét segíti elõ, viszont nincs igazi programozási eredménye, csupán magát a programozási folyamatot egyszerûsíti, nem az eredményt. Viszont a beágyazás semmi extra, hiszen például már használtuk is: méghozzá a pontoknál. Mert ha azt mondanám, hogy:

class cPrimitiv : public pont

azt jelentené, hogy minden primitív egy ponttal rendelkezne. Ezt mi inkább beágyazással oldottuk meg, lásd felül. Szóval akkor használjuk, ha egy bázis osztály PÉLDÁNYÁHOZ több alosztály PÉLDÁNYT szeretnénk kötni. Ezért például apukánál

class cApuka {

array gyerekek;

};

ahol az array egy gyerek-ekbõl álló tömböt akar jelölni szemléletesen. Mert öröklõdéssel minden gyereknek új apja lenne! Remélem érthetõ, csupán át kell gondolni! Sok sikert!


oroklodes.rar


Kapcsolódó linkek:


A cikksorozat további részei:
DirectX programozás 1. - Bevezetés, Windows ablak létrehozása, Idõzítõ
DirectX programozás 2. - DirectDraw inicializálása, használata
DirectX programozás 3. - DirectDraw megjelenítés 1.
DirectX programozás 4. - DirectDraw megjelenítés 2.
DirectX programozás 5. - Izometrikus grafika
DirectX programozás 6. - Matematika összefoglaló 1.
DirectX programozás 7. - Matematika összefoglaló 2.: Lineáris algebra
DirectX programozás 8. - DirectInput
DirectX programozás 9. - DirectSound, Ogg Vorbis lejátszás
DirectX programozás 10. - Izometrikus grafika 2.
DirectX programozás 11. - Objektumorientált programozás

Értékelés: 0

Új hozzászólás
Nincs megjegyzés