C++ programozási nyelv Struktúrák a C++ nyelvben Nyugat-Magyarországi Egyetem Faipari Mérnöki Kar Informatikai Intézet Soós Sándor 2004. szeptember A C++ programozási nyelv Soós Sándor 1/37
Bevezetés A struktúra, record fogalma Különbségek a C és a C++ között szintaktika tagfüggvények automatikusan lefutó függvények konstruktorok destruktorok hozzáférési hatáskörök szabályozása öröklés A C++ programozási nyelv Soós Sándor 2/37
Szintaktikai különbségek A struktúra nevének használata I. Adott a következő struktúra: struct Valami int x; Hogyan definiálunk egy ilyen típusú változót C-ben? 1. A struct Valami típusazonosítóval: void main() struct Valami v; 2.Typedef-fel elnevezzük a típust: typedef struct Valami Akarmi; void main() Akarmi v; A C++ programozási nyelv Soós Sándor 3/37
Szintaktikai különbségek (folyt.) A struktúra nevének használata I. (folyt.) Hogyan definiálunk egy ilyen típusú változót C-ben? 3. Az 1. és 2. megoldás összevonva: typedef struct Valami int x; Akarmi; void main() Akarmi v; A C++ programozási nyelv Soós Sándor 4/37
Szintaktikai különbségek (folyt.) A struktúra nevének használata I. (folyt.) Hogyan definiálunk egy ilyen típusú változót C++ -ban? Nincs szükség typedef-re, az új név struct nélkül is használható. struct Valami int x; ; void main() Valami v; // ez C-ben hibás lenne A C++ programozási nyelv Soós Sándor 5/37
Szintaktikai különbségek (folyt.) A struktúra nevének használata II.: Önhivatkozó struktúrák C-ben az önhivatkozó struktúrában mindenképpen ki kell tennünk a struct kulcsszót a mutató deklarációjánál. struct Valami int x; struct Valami *Kovetkezo; Ez akkor is így van, ha typedef-et alkalmazunk: typedef struct Valami int x; struct Valami *Kovetkezo; Akarmi; C++ -ban nincsen szükség a struct kulcsszó kiírására: struct Valami int x; Valami *Kovetkezo; // C++ -ban ez helyes ; A C++ programozási nyelv Soós Sándor 6/37
Struktúrák és osztályok Az objektum orientált nyelvek új lehetőségei C++ struktúrák (struct) osztályok (class) Csak apró eltérések vannak a kettő között A C-vel való kompatibilitás megtartása érdekében maradt meg a struct. A C++ programozási nyelv Soós Sándor 7/37
Új fogalom: tagfüggvény (metódus, method) Miről van szó? Adott a korábbi Valami struktúra: struct Valami int x; Írjunk egy függvényt, ami megduplázza az x mező értékét! Hogyan oldanánk meg ezt a feladatot C-ben, illetve C++ -ban? A C++ programozási nyelv Soós Sándor 8/37
Tagfüggvény (folyt.) A feladat megoldása C-ben: void ValamiDuplaz(struct Valami* VP) VP->x *= 2; void main() struct Valami v1,v2; v1.x = 3; ValamiDuplaz(&v1); // v1 x-ét duplázzuk printf("v1.x: %d\n",v1.x); // Kimenet: v1.x: 6 v2.x = 4; ValamiDuplaz(&v2); // v2 x-ét duplázzuk printf("v2.x: %d\n",v2.x); // Kimenet: v2.x: 8 A C++ programozási nyelv Soós Sándor 9/37
Tagfüggvény (folyt.) C++ -ban van egy másik megoldás is, definiáljunk egy tagfüggvényt: A deklaráció: struct Valami int x; void Duplaz(); // tagfüggvény ; A függvény definíciója: void Valami::Duplaz() x *= 2; A C++ programozási nyelv Soós Sándor 10/37
Tagfüggvény (folyt.) Hogyan használjuk a tagfüggvényt? void main() Valami v1,v2; v1.x = 3; v1.duplaz(); // tagfüggvény hívása printf("v1.x: %d\n",v1.x); // Kimenet: v1.x: 6 v2.x = 4; v2.duplaz(); // tagfüggvény hívása printf("v2.x: %d\n",v2.x); // Kimenet: v2.x: 8 Hasonlítsuk össze a két megoldást: Hagyományos függvény: ValamiDuplaz(&v1); Tagfüggvény: v1.duplaz(); A C++ programozási nyelv Soós Sándor 11/37
Tagfüggvény (folyt.) Hasonlítsuk össze a két megoldást: Hagyományos függvény: ValamiDuplaz(&v1); Tagfüggvény: v1.duplaz(); Tagfüggvény hívása megegyezik a mezőhivatkozással pointer esetén is: Valami v1; Valami *vp; vp = &v1; vp->x = 3; vp->duplaz(); A C++ programozási nyelv Soós Sándor 12/37
Tagfüggvény (folyt.) Mikor használjunk tagfüggvényt és mikor hagyományos "szabad" függvényt? Erről szól az objektum-orientált programtervezés Erről fog szólni ez a tantárgy Végső megoldás: Java - Csak tagfüggvény létezik! A C++ programozási nyelv Soós Sándor 13/37
Konstruktorok Hogyan inicializálunk egy struktúrát C-ben? void ValamiKonstruktor(struct Valami* VP) VP->x = 0; Az inicializáló fv. meghívása: void main() struct Valami v1; ValamiKonstruktor(&v1); // inicializálás A C++ programozási nyelv Soós Sándor 14/37
Konstruktorok (folyt.) Mi a helyzet dinamikusan allokált struktúra esetén? struct Valami *vp; vp = malloc(sizeof(struct Valami)); ValamiKonstruktor(vp); Miért nem biztonságos ez a megoldás Mi történik, ha elfelejtkezünk az inicializálásról? Hogyan lehetne elkerülni ezt a veszélyt? A megoldás C++ -ban: "szabad" függvény helyett legyen tagfüggvény az inicializáló fv. A szükséges időben hívja meg a rendszer automatikusan. Ez a konstruktor A C++ programozási nyelv Soós Sándor 15/37
A konstruktor fogalma C++ -ban Hogyan ismeri meg a rendszer a konstruktort? neve megegyezik a struktúra nevével nincs visszatérő értéke (nem void!!!) Példánkban: struct Valami int x; Valami(); // konstruktor deklarációja ; Valami::Valami() // konstruktor definíciója x = 0; printf("konstruktor\n"); // demonstráció A C++ programozási nyelv Soós Sándor 16/37
Hogyan működik most a programunk? Ha deklarálunk egy Valami típusú változót, akkor automatikusan lefut a konstruktor: void main() Valami v; // itt lefut a konstruktor! A fordítóprogram helyettünk dolgozik. Amikor létrejön egy új struktúra, akkor megnézi, hogy létezik-e konstruktora. Ha igen, akkor annak meghívását automatikusan beleteszi a kódba. A C++ programozási nyelv Soós Sándor 17/37
A new operátor Mi a helyzet a dinamikusan allokált struktúrák esetén? malloc függvény helyett new operátor A new operátor: lefoglalja a memóriát lefuttatja a konstruktort visszaadott érték Például: NULL, ha nem sikerült a memóriafoglalás egyébként egy pointer, ami az új struktúrára mutat void main() Valami *vp; vp = new Valami; A C++ programozási nyelv Soós Sándor 18/37
Destruktorok A konstruktor párja A struktúra megszűnésekor elvégzendő tevékenységek file-ok lezárása memória felszabadítás hálózati kapcsolatok lezárása C-ben: írhatunk egy függvényt a struktúra felszámolása előtt meghívjuk ezt a függvényt ugyanazok a veszélyek fennállnak, mint az inicializáló függvény esetében C++ destruktor A C++ programozási nyelv Soós Sándor 19/37
A destruktor fogalma C++ -ban Hogyan ismeri meg a rendszer a destruktort? neve: ~ jel + a struktúra neve (~: jobb oldali Alt + 1) nincs visszatérő értéke (nem void!!!) A C++ programozási nyelv Soós Sándor 20/37
A destruktor fogalma C++ -ban (folyt.) Példa: struct Valami int x; Valami(); ~Valami(); ; // konstruktor deklarációja // destruktor deklarációja Valami::Valami() // konstruktor definíciója x = 0; printf("konstruktor\n"); // demonstráció Valami::~Valami() // destruktor definíciója printf("destruktor\n"); // demonstráció A C++ programozási nyelv Soós Sándor 21/37
A destruktor fogalma C++ -ban (folyt.) A főprogram: void main() Valami v; A program kimenete a következő lesz: Konstruktor Destruktor A C++ programozási nyelv Soós Sándor 22/37
A delete operátor A new operátor párja C-ben a free függvénnyel szabadítjuk fel a dinamikusan foglalt tárterületet C++ -ban a delete operátorral szabadítjuk fel a new operátorral létrehozott objektumokat delete operátor meghívja a destruktort felszabadítja az objektum által foglalt tárterületet A C++ programozási nyelv Soós Sándor 23/37
new és delete operátor példaprogram void main() Valami *vp; printf("new előtt\n"); vp = new Valami; // meghívja a konstruktort... delete vp; // meghívja a destruktort printf("delete után\n"); Az eredmény: new előtt Konstruktor Destruktor delete után A C++ programozási nyelv Soós Sándor 24/37
Hozzáférési hatáskörök szabályozása Adatelrejtés: minden adattagról és tagfüggvényről eldönthetjük, hogy ki férhet hozzá. private csak az osztály tagfüggvényei férhetnek hozzá class esetében ez az alapértelmezés public minden külső függvény hozzáférhet az adott taghoz struct esetében ez az alapértelmezés protected később, az öröklődéskor fogunk róla beszélni mint a private, de a leszármazottak tagfüggvényei hozzáférhetnek A C++ programozási nyelv Soós Sándor 25/37
A private hozzáférés Vegyük a korábbi példánkat: struct Valami private: int x; void f1(); ; void main() Valami v; v.x = 7; // ez hiba! De: void Valami::f1() x = 7; // ez rendben van, mert f1 tagfüggvény! A C++ programozási nyelv Soós Sándor 26/37
A private hozzáférés (folyt.) Nem csak adattag lehet private, hanem tagfüggvény is: struct Valami public: void PublikusFv(); // public függvény private: void PrivatFv(); // private függvény ; void Valami::PrivatFv() printf("privatfv"); void Valami::PublikusFv() PrivatFv(); // Ez szabályos Ekkor: void main() Valami v; v.publikusfv(); // ez rendben van! v.privatfv(); // ez hiba, private fv. itt nem látszik A C++ programozási nyelv Soós Sándor 27/37
A private hozzáférés (folyt.) Hogyan férhetünk hozzá a private adattagokhoz? Az osztály tagfüggvényeiből közvetlenül A külvilág számára írhatunk olyan public tagfüggvényeket, amelyek szabályozott módon engednek hozzáférést a private adattagokhoz. Példa a következő dián A C++ programozási nyelv Soós Sándor 28/37
A private hozzáférés (folyt.) struct Valami int GetX(); // az x-et kiolvasó függvény void SetX(int ax); // az x-et beállító függvény private: int x; ; int Valami::GetX() return x; void Valami::SetX(int ax) x = ax; // ez engedélyezett void main() Valami v; int a; v.setx( 10 ); a = v.getx(); A C++ programozási nyelv Soós Sándor 29/37
Mire jó ez az egész? Miért korlátozza magát a programozó a private tagokkal? 1. példa: Készítsünk egy listát egész számok tárolására! Ehhez definiáljuk a következő struktúrát: struct Lista int Tomb[100]; int N_Elemek; Miért veszélyes ez? A struktúrát használó programozónak tisztában kell lennie a használat módjával: nem tehet 100-nál több elemet a listába megfelelően léptetni kell az N_Elemek mezőt, stb. Ha úgy döntünk, hogy megváltoztatjuk a használt adatszerkezetet, akkor a teljes programot módosítani kell. A megoldás: Rejtsük el a külvilág elől az adatstruktúra belsejét (private). A C++ programozási nyelv Soós Sándor 30/37
Mire jó ez az egész? Miért korlátozza magát a programozó a private tagokkal? 2. példa: Nem a teljes adatszerkezetet változtatjuk, csak az adattípust. Egy üzlet adatait kezeljük egy programmal. Tegyük fel, hogy először forintban tartjuk nyilván az árakat. Nem kezelünk filléreket, ezért az áru árát egész típusú változóban tároljuk. Később áttérünk eurora, ezért szükség van a centek tárolására is. Ehhez módosítanunk kell a belső adattípust, de például a havi és éves forgalmi adatokat visszaadó függvényeket nem kell módosítani, azok maradhatnak egész típusúak. A C++ programozási nyelv Soós Sándor 31/37
Mire jó ez az egész? Miért korlátozza magát a programozó a private tagokkal? 3. példa: Bizonyos esetekben csak együtt változhat meg két vagy több adatmező, vagy csak több mező felhasználásával lehet visszaadni egy értéket. Az előző üzleti példában egy áru eladását be kell jegyezni az eladások listájába, ugyanakkor csökkenteni kell az árukészletet is. Ha ezeket külön-külön kell elvégeznie a programozónak, akkor fennáll a veszélye, hogy nem lesz szinkronban a két adatbázis. Ennek elkerülésére írhatunk egy Eladás tagfüggvényt, ami elvégzi mindkét műveletet. A C++ programozási nyelv Soós Sándor 32/37
Mire jó ez az egész? Miért korlátozza magát a programozó a private tagokkal? 4. példa: Vannak esetek, amikor csak bizonyos szabályok betartásával szabad beállítani egy mező értékét. Vannak olyan azonosító kódok, amelyek nem lehetnek tetszőlegesek. Ilyenek például a TAJ szám, vagy a hajdan volt személyi szám. Ezek egy ellenőrző algoritmust tartalmaznak, amivel egy adott számról eldönthető, hogy helyes-e. Ha ezt a mezőt private-tá tesszük és csak egy tagfüggvénnyel engedünk hozzáférést, akkor biztosíthatjuk, hogy mindig csak hibátlan adatok kerülhessenek az adatbázisba. A C++ programozási nyelv Soós Sándor 33/37
Az információrejtés elve adatbezárás, encapsulation A tényleges, fizikai tárolást végző adatszerkezetet elrejtjük a struktúra belsejébe (private adattagok) Publikus tagfüggvényként azokat a műveleteket deklaráljuk, amelyek megvalósítják a struktúra szolgáltatásait. Például új elem hozzáadása, törlés stb. Ily módon a struktúra egy szolgáltatóvá válik, amitől szolgáltatásokat lehet kérni, de hogy azt hogyan hajtja végre, azt nem kell tudnia a felhasználónak. Természetesen mindez alapos tervezést, kísérletezést igényel! Az objektum-orientált tervezést is meg kell tanulni! Ha azonban egyszer megtanultuk ezt a gondolkodás módot, akkor el sem tudjuk képzelni, hogy eddig hogyan gondolkoztunk és programoztunk másképp. A C++ programozási nyelv Soós Sándor 34/37
Öröklődés, örököltetés Ezzel a témakörrel külön előadásokon fogunk foglalkozni. Egyelőre annyit jegyezzünk meg róla, hogy ez az objektumorientált nyelvek talán legfontosabb újítása a "hagyományos" nyelvekhez képest. Röviden arról van szó, hogy egy struktúra, illetve osztály definiálásakor felhasználhatjuk egy már meglévő osztály tulajdonságait. Az új, ún. leszármazott osztály örökli az ősosztály tulajdonságait (adatmezőit és tagfüggvényeit). Ráadásul ezt anélkül tudjuk megtenni, hogy hozzáférnénk az eredeti osztály forráskódjához. A leszármazott osztály az örökölt tulajdonságokat módosíthatja és újakat is hozzájuk tehet. A C++ programozási nyelv Soós Sándor 35/37
Hogyan tovább? Mire van szükség az objektum-orientált programozás elsajátításához? a nyelvi eszközök megtanulására az eszközök célszerű használatának megtanulására A nyelvvel kapcsolatban megtanulandó témakörök: A C++, illetve az objektum-orientált írásmód megszokása A program működésének megértése összetett struktúrák esetében milyen sorrendben futnak le a konstruktorok, destruktorok mikor generál a fordítóprogram önállóan egy függvényt (konstruktort, másoló operátort) mi történik egy kivétel kiváltásakor, stb. A hozzáférési jogosultságok alapos megértése, különösen az örökléssel kapcsolatban A C++ esetében különösen fontos feladat a dinamikus memóriakezelés megértése! A C++ programozási nyelv Soós Sándor 36/37
Mi a megoldás! Folytonosan legyünk tisztában azzal, hogy kétféle területen kell előrehaladnunk: a nyelv megismerésében, illetve a programtervezés módszereinek elsajátításában. Egy adott feladatra szinte mindig érdemes többféle megoldást kipróbálni, pl. először szabad függvénnyel, aztán tagfüggvénnyel valósítjuk meg az adott funkciót; egyszer örököltetést, egyszer kompozíciót (struktúrák egymásba ágyazását) használunk, stb. Legjobban összehasonlításokon keresztül ismerhetők meg az eszközök! A hozzáférési jogosultságokat érdemes először lazán (public) hagyni, majd amikor egy-egy programrész már kialakult, lépésenként elrejteni a tagokat. Ha már a kezdet kezdetén sok private tagot definiálunk, nagyon nehézzé, áttekinthetetlenné válnak az első kísérletező lépések. Induljunk ki egy olyan (pl. kevésbé objektum-orientált) megoldásból, amelyet eddigi tapasztalataink alapján meg tudunk írni és próbáljuk meg lépésenként átalakítani! Ne keseredjünk el (nagyon), ha nehéznek érezzük ezt a témát. Tényleg az. A C++ programozási nyelv Soós Sándor 37/37