String osztály létrehozása
Sztring osztály létrehozása Feladat: Készítsünk olyan programot, amely sztringeket képes létrehozni, valamint azokkal műveleteket végezni. Az igényelt műveletek: sztring egyes karaktereinek írása illetve olvasása, sztringek másolása, összefűzése, összehasonlítása és nyomtatása.˙
Objektumokat leíró táblázat:
Műveletek: tipikusan operátorokkal célszerű reprezentálni őket. Attribútumok: a specifikáció nem ad támpontot. Régebbi tapasztalatainkra támaszkodjunk! Figyelembe veendő szempontok: tárolt karakterek száma előre nem ismert és az objektum élete során is változó. Kézenfekvő a karakterek helyét a dinamikus memóriából lefoglalni, az attribútumok között csak az adminisztrációhoz szükséges változókat tartjuk nyilván. Ilyen változó: karaktersorozat kezdőcíme a heap-en (pstring) a sztring aktuális hossza (size).
size: redundáns adat, hiszen számolható size: redundáns adat, hiszen számolható Fennáll az inkonzisztencia veszélye. Hatékonyság miatt azonban célszerű!
Sztring osztály definíciója class String { char *pstring; int size; public: String (char* s = ““) { //String default konstruktor pstring = new char [ size = strlen (s) + 1 ]; strcpy(pstring,s); } ~String ( ) { delete [ ] pstring ; } char& operator [ ] (int i ) { return pstring [ i ]; } String operator+( String& s ) { char *psum = new char [size + s.size - 1 ]; strcpy (psum, pstring ); strcat (psum, s.pstring) ; String sum (psum ); delete psum; return sum; } };
A konstruktor feladata - az átadott sztring alapján a szükséges terület lefoglalása a heap-en - a sztring heap-re másolása - az adminisztrációs adattagok kitöltése A konstruktordefiníció tartalmazza az alapértelmezésű (paramétert nem igénylő konstruktort is ( alapértelmezésű argumentuma miatt)
Destruktor feladata A heap-en lefoglalt terület felszabadítása.
Az index operátor Feladata: használatával egy karaktert lekérdezhetünk, illetve megváltoztathatunk. Fontos! Referencia típus· a visszatérési értéke.
main() {. char c;. String s (“én string vagyok” );. c = s [ 3 ]; main() { char c; String s (“én string vagyok” ); c = s [ 3 ]; // c=s.operator[] (3); c=pstring[3]; s[ 2 ] = ‘a’; // s.operator[] (2)=‘a’; pstring[2]=‘a’; } //delete [] pstring
Az index operátor c= s[ 3 ]; az operátor átdefiniálás szerint ekvivalens a c=s.operator[](3) utasítással, ami viszont a String::operator[] törzse szerint a c-hez a string[3]-t rendeli hozzá. s[2] = ‘a’ viszont a s.operator [](2) = ‘a’-nak felel meg. Ez az értékadás bal oldalán függvényhívást jelent. C-ben nem megengedett. Nem referencia típusú visszatérési érték esetén visszaadná a sztring 2-ik karakterének értékét. Referencia típus miatt az s.operator [](2) a pstring[2] helyettesítő neve, így értéke meg is változtatható
Referencia típus Amennyiben olyan függvényt írunk, amelyet balértékként (az értékadás bal oldalán) akarunk használni, akkor annak referenciát kell visszaadnia. Az index operátor szinte mindig referencia típusú!
Összeadás operátor - Ideiglenesen foglalunk egy karaktertömböt - előállítjuk az összefűzött sztringet (az -ideiglenesen foglalt tömbben) - megkonstruáljuk a visszatérési érték String típusú objektumát. - az ideiglenes tömböt felszabadítjuk - visszatérünk és visszatérési értékünk a létrehozott objektum.
Problémák : értékadásnál Tekintsük a következő programrészletet: { String s1 (“baj”), s2 ("van!"); s2 = s1; } Az értékadás operátor (=) az adattagokat egy az egyben másolja (bitmásolás)
Bitmásolás most nem jó! s2.size is 4 értékű lesz “baj”-ra két mutató is mutat, míg “van”-ra egy sem. ha s1 változik, akkor s2 is változik a “baj” kezdőcímére két delete-t hajt végre a program a destruktor meghívásakor, míg a “van”-t senki sem szabadítja fel.˙
Értékadó operátor átdefiniálása class String { ..... void operator=(String& ); }; void String :: operator=(String& s) { delete pstring; pstring = new char [size = s.size]; strcpy (pstring, s.pstring); }
További problémák //nem lehet s1=s2=s3; s1=s2=s3 nem megengedett Ez ekvivalens lenne a következővel: s1.operator=(s2.operator=(s3)); s1.operator= bemenő argumentuma az s2.operator=(s3) visszatérési értéke, ami viszont void, holott String& kellene, hogy legyen. Megoldás: visszatérési érték megváltoztatása
Még további probléma s=s; értékadás hibásan működik. Ekkor ugyanis az s.operator= referenciaként megkapja s-t, azaz önmagát, amelyhez tartozó heap területet felszabadítja, majd innen másol. Megoldás lehet, hogy megvizsgáljuk, hogy a referenciaként átadott objektum címe megegyezik-e a célobjektum címével.
Javított változat class String { ..... String& operator=(String& ); //s1=s2=s3; lehet }; String& String :: operator=(String& s) { if (this != &s ) { //s = s ; miatt delete pstring; pstring = new char [size = s.size]; strcpy (pstring, spstring); } return *this; }
Problémák 2. Értékadással kapcsolatos problémák megoldódtak. Kivéve: ha =-et használunk valamely objektumpéldány kezdőértékkel való ellátására.: { String s1(“baj”); ......... String s2 = s1; }
Az itt szereplő = jel nem az értékadás, hanem az inicializálás műveletét jelöli. Inicializálás alapértelmezése: bitenkénti másolás. (Ha nem lenne különbég a kettő között és az operator= mindkét esetben felülbírálja a műveletet, akkor a String s2 = s1 utasításból kialakuló s2.operator=(s1) függvényhívás azzal indulna, hogy a delete s2.pstring utasítást hajtaná végre (Pedig a pstring még nincs is inicializálva! Az operator= fv-ben nem tudjuk eldönteni, hogy volt-e már inicializálva egy objektum, vagy sem).
Problémák 2. Beláttuk, - hogy jó, ha nem hívja meg az operator= függvényt - rossz, hogy az inicializálás alapértelmezése szerinti műveletet hajtja végre (bitmásolás) Megoldás “inicializáló operátort” az ún. másoló konstruktort kell átdefiniálni.
Másoló konstruktor Másoló konstruktor - az osztály nevét kapja - a másoló jellegét az adja, hogy ugyanolyan típusú objektummal kívánjuk inicializálni, ezért az argumentuma is ezen osztály referenciája - egy x osztályra a másoló konstruktor x ( &x).
Másoló konstruktor A String osztály kiterjesztése másoló konstruktorral: class String { .......... String ( String& s ); }; String::String ( String& s ) { pstring = new char [size = s.size ]; strcpy ( pstring, s.pstring); }
Másoló konstruktor Definícióban történő inicializálás (az előbb láttuk a példát) Függvény argumentum és visszatérési érték Érték szerinti paraméterátadás esetén az argumentumok a verem memóriába kerülnek. A verem memórián tartózkodó ideiglenes változóba másolás megfelel az inicializálatlan objektum kitöltésének. A verembe másolást a másoló konstruktor végzi el.
s0 vermen lévő másolatát s-et a másoló konstruktor inicializálja. Hívás Hívott String s0, s1; s1 = f (s0); String f ( String s ) { String r; //(stack) temp temp ..... return r; } // r, s
Visszatérési érték kezelése - skalár érték esetén regisztert használnak - objektumok azonban nagyobb méretűek, így gyakran a globális memóriát, a heap-et vagy a veremmemóriában lefoglalt területet kell igénybe venni. A visszatérési értéket átadó memóriaterület azonban inicializálatlan, tehát a return utasítás is csak a másoló konstruktor hívásával másolhatja be a visszatérési értéket.
Tanulságok A létrehozott String osztály tulajdonságai: - dinamikusan nyújtózkodó szerkezet, amely minden pillanatban csak annyit foglal el a memóriából, amennyit feltétlenül szükséges. - String típusú objektumok tetszőleges számban előállíthatók. Olyan egyszerűen használhatók, mint a beépített típusok.
Öröklés Az örököltetés szintaktikája: class származtatott osztály_név: elérési mód bázis-osztály_név { // … }
Elérési módok a származtatásnál public private protected
A származtatás: public //Alaposztály class base { int x; public: void setx (int n ) { x=n; } void showx() { cout <<x << ‘\n’; } };
Származtatott osztály class derived : public base { int y; public: void sety (int n ) { y=n; } void showy() { cout <<y << ‘\n’; } };
Publikus tagok elérése main() { derived ob; ob.setx (10); //eléri az alaposztály tagjait ob.sety (20); //eléri a származtatott osztály tagjait ob.showx (); //eléri az alaposztály tagjait ob.showy (); //eléri a származtatott osztály tagjait return 0; }
A private tagok nem elérhetők //Alaposztály class base { int x; public: void setx (int n ) { x=n; } void showx() { cout <<x << ‘\n’; } };
private tagra nem hivatkozhatunk! class derived : public base { int y; public: void sety (int n ) { y=n; } void show_sum() { cout <<x+y << ‘\n’; } //hiba! //x private tag void showy() { cout <<y << ‘\n’; } };
A származtatás: private //Alaposztály class base { int x; public: void setx (int n ) { x=n; } void showx() { cout <<x << ‘\n’; } };
Származtatott osztály class derived : private base { int y; public: void sety (int n ) { y=n; } void showy() { cout <<y << ‘\n’; } };
Publikus tagok elérése main() { derived ob; ob.setx (10); //hiba, ez privát a származtatott osztályban ob.sety (20); //eléri a származtatott osztály tagjait ob.showx (); //hiba, ez privát a származtatott osztályban ob.showy (); //eléri a származtatott osztály tagjait return 0; }
Megoldás az előző példára A származtatás: private //Alaposztály class base { int x; public: void setx (int n ) { x=n; } void showx() { cout <<x << ‘\n’; } };
Származtatott osztály class derived : private base { int y; public: void setxy (int n, int m ) {setx(n); y=m; } void showxy() {showx(); cout <<y << ‘\n’; } };
Publikus tagok elérése main() { derived ob; ob.setxy(10,20); //eléri a származtatott osztály tagjait ob.showxy (); //eléri a származtatott osztály tagjait return 0; }
protected tagok elérése //Alaposztály class samp { int a; protected: int b; public: int c; samp(int n, int m) { a=n; b=m;} int geta ( ) { return a; } int getb ( ) {return b;} };
Hozzáférés protected tagokhoz main() { //ob.b=99; //hiba!!! ob.c=30; //OK. cout<< ob.geta() << ‘ ‘; cout<< ob.getb() << ‘ ‘<<ob.c<<‘\n’; return 0; }
protected tagok //Alaposztály class base { protected: int a,b; public: void setab(int n, int m) { a=n; b=m;} };
Származtatott osztály class derived : public base { public: void setc (int n) { c=n;} void showabc() { cout<<a<<‘ ‘ <<b<< ‘ ‘ <<c << ‘\n’; } };
Publikus tagok elérése main() { derived ob; ob.setab(1,2); ob.setc (); Ob.showabc(); return 0; }
Konstruktorok, destruktorok és a származtatás A bázis osztály és a származtatott osztályok is rendelkezhetnek konstruktorokkal és destruktorokkal. A konstuktorok végrahajtásának sorrendje a származtatásnak megfelelő sorrendben történik. A destruktorok végrehajtása a származtatás sorrendjével ellenkező sorrendben történik.
Konstruktorok paraméterei Különböző esetek: Alaposztálynak és származtatott osztálynak nincs paramétere Alaposztálynak nincs paramétere Származtatott osztálynak nincs, de a bázis osztálynak van paramétere Bázis osztálynak is és a származtatott osztálynak is van paramétere.
Bázis és származtatott osztály konstruktorainak végrehajtási sorrendje #include <stdio.h> class base { public: base { cout << „Bázis osztály konstruktora\n”;} ~base { cout << „Bázis osztály destruktora\n”;} }; Class derived: public base { public; derived { cout << „Származtatott osztály konstruktora\n”;n ~derived { cout << „Származtatott osztály destruktora\n”;}
A konstruktorok és destruktorok lefutása main () { derived o; return 0; } Output: Bázis osztály konstruktora Származtatott osztály konstruktora Származtatott osztály destruktora Bázis osztály destruktora
Paraméterek átadása #include <stdio.h> class base { int i; public: base ( int n) { cout << „Bázis osztály konstruktora\n”; i=n;} ~base { cout << „Bázis osztály destruktora\n”;} void show() { cout <<i << ‘\n’; } };
class derived: public base { int j; public; derived ( int n, int m) : base(m) { cout << „Származtatott osztály konstruktora\n”; j=n; } ~derived { cout << „Származtatott osztály destruktora\n”;} void showj () { cout<< j << ‘\n’;} };
main() { derived o(10,20); o.showi(); o.showj(); return 0; }