OOP #7 (init, done) v1.0 2003.03.07. 20:45:00
Eszterházy Károly Főiskola Információtechnológia tsz.
Hernyák Zoltán adj. e-mail:
[email protected] web: http://aries.ektf.hu/~aroan OOP
OOP_07
-1-
E jegyzet másolata nem használható fel szabadon, az előadás anyagának kivonata. Ezen teljes jegyzetről, vagy annak bármely részéről bármely másolat készítéséhez a szerző előzetes írásbeli hozzájárulására van szükség. A másolatnak tartalmaznia kell a sokszorosításra vonatkozó korlátozó kitételt is. A jegyzet kizárólag főiskolai oktatási vagy tanulmányi célra használható! A szerző hozzájárulását adja ahhoz, hogy az EKF számítástechnika tanári, és programozó matematikus szakján, a tárgyat az EKF TO által elfogadott módon felvett hallgatók bármelyike, kizárólag saját maga részére, tanulmányaihoz egyetlen egy példány másolatot készítsen a jegyzetből. A jegyzet e változata még tartalmazhat mind gépelési, mind helyességi hibákat. Az állítások nem mindegyike lett tesztelve teljes körűen. Minden észrevételt, amely valamilyen hibára vonatkozik, örömmel fogadok. Hernyák Zoltán
[email protected] OOP
OOP_07
-2-
Constructor Az osztály inicializálása: • az osztály mezőinek alaphelyzetbe állítása • általánosan az osztály alaphelyzetbe állítása • megvalósítható egy szimpla metódussal is, de … o … ez utóbbit a „felhasználó” elfelejtheti meghívni
Constructor
OOP_07
-3-
A konstruktor egy speciális metódus, melynek … • … kötött a neve – ugyanaz mint az osztály neve (C++, Java, C#) • … nincs visszatérési típusa • … fő feladata az objektum mezőinek inicializálása • … végrehajtódik, mielőtt bármely metódus meghívódhatna class TVerem { private int vm; private double[] tomb; ... public TVerem() // konstruktor { vm = 0; } } ... TVerem V = new TVerem(); // konstruktor hívása kötelező V.Push(12.3); // most már hívhatóak a metódusok Constructor
OOP_07
-4-
Paraméteres konstruktor Az overloading-nak köszönhetően egy osztálynak több konstruktora is lehet – különböző paraméterezéssel : class TKor { public int x; public int y; ... public TKor() { x = 0; y = 0; } public TKor(int ax, int ay) { x = ax; y = ay; } } Paraméteres konstruktor
... TKor K1 = new TKor(); TKor K2 = new TKor(8,7); ...
OOP_07
-5-
Saját konstruktorok Ha nem definiálunk konstruktort az osztályhoz, akkor egy üres paraméterezésű és törzsű konstruktor „generálódik” az osztályhoz. Ha készítünk saját konstruktortokat, de mindegyikük paramétert vár, akkor azok közül kell az egyiket alkalmazni. Ha készítünk saját konstruktorokat, üres paraméterezésűt is, és paramétereseket is, akkor választhatunk, melyiket használjuk.
Saját konstruktorok
OOP_07
-6-
Metódus hívás konstruktorból A konstruktor sok egyéb szempontból közönséges metódusnak minősül – pl. lehet belőle egyéb metódusokat is meghívni. class TKor { public TKor(int ax, int ay) // konstruktor ! { x = ax; y = ay; Kirajzol(); // közönséges metódus hívása } protected void Kirajzol() { ... } } Metódus hívás konstruktorból
OOP_07
-7-
Konstruktor hívása konstruktorból • Az osztály konstruktorából lehetőség van az ős osztály valamely konstruktorát meghívni (hogy az beállíthassa az örökölt mezők kezdőértékét), így ezen osztály ezen konstruktorának már csak az új mezők kezdőértékét kell beállítani • Ha egy osztálynak több konstruktora is van, akkor lehetőség van az egyik konstruktorból (ugyanazon osztály) másik konstruktorát is meghívni.
Konstruktor hívása konstruktorból
OOP_07
-8-
Ős osztály konstruktorának hívása: class TPont { int x,y; public TPont(int ax, int ay) { x = ax; y = ay; } } class TKor:TPont { int sugar; public TKor(int ax,int ay, int r) :base(ax,ay) { sugar = r; } } TPont origo TKor buborek
= new TPont(0,0); = new TKor(100,100,10);
Konstruktor hívása konstruktorból
OOP_07
-9-
Saját osztály másik konstruktorának hívása: class TVerem { int vm; int[] tomb; void Kiir() { Console.WriteLine("HELLO"); } public TVerem(int maxDb) { vm = 0; tomb = new int[maxDb]; // ennyi elemű int tömb létrehozása } public TVerem() :this(100) { // egyéb tennivalónk már nem is maradt } } TVerem egyikverem = new TVerem(); TVerem masikverem = new TVerem(300);
Konstruktor hívása konstruktorból
// = new TVerem(100) ! // konstruktor hívása
OOP_07
- 10 -
Örökölt constructor meghívása Kérdés:
ha
az
ős
osztálynak
van
konstruktora,
és
a
gyermekosztálynak is van, és a gyermekből példányosítunk, akkor meg fog-e hívódni az ős osztály konstruktora?
Konstruktor hívása konstruktorból
OOP_07
- 11 -
Ha mi nem hívjuk meg, akkor nem! (Pascal, Delphi) // „a” variáció type TElso = class public x:integer; constructor Create; procedure Kiir;virtual; end; type TMasodik = class(TElso) public constructor Create; procedure Kiir;override; end; constructor TMasodik.Create; begin inherited Create; { explicit hívása az ős konstruktorának } { ha nem írjuk, nem hívódik meg! } x:= 2; end; Konstruktor hívása konstruktorból
OOP_07
- 12 -
Elkerülhetetlenül! (C++, C#, Java) // „b” variáció #include <stdio.h> class TElso { public: int x; TElso() { x=0; } };
// nincs paramétere -> default constructor
class TMasodik:public TElso { public: int y=0; TMasodik() { y=0;} // mire elindul, a TElso() már lefutott }; int main() { TMasodik m = TMasodik(); } Konstruktor hívása konstruktorból
OOP_07
- 13 -
Elkerülhetetlenül, de melyik?! (C++, C#, Java) // „b” variáció #include <stdio.h> class TElso { int x; public TElso(int ax) { x=ax; } } class TMasodik:TElso { int y; public TMasodik() { y=0;} // hibás, nincs „default constructor” } class TMasodik:TElso { int y; TMasodik():base(0) { y=0;} // jó, megjelölt konstruktor } Konstruktor hívása konstruktorból
OOP_07
- 14 -
VMT beállítása mikor? Amennyiben meghívunk egy „hagyományos” metódust (korai kötés) a konstruktorból, az mindig ugyanazon metódus lesz. A kérdés: ha egy virtuális metódust hívunk meg, az melyik lesz? A válasz azon múlik, hogy a VMT a konstruktor kódjának elején vagy a végén állítódik be!?
Konstruktor hívása konstruktorból
OOP_07
- 15 -
A végén! Ekkor a konstruktor belsejében még a korai kötés működik a virtuális metódusokra is! (C++) class TElso { public: int x; TElso() { x=1; kiir(); } // kiir meghívása virtual void kiir() { printf("elso.kiir\n"); } }; class TMasodik:public TElso { public: int y; TMasodik() { y=2;} // az ős konstruktora automatikusan elindul void kiir() { printf("masodik.kiir\n",x); } // autom. override! }; int main() { TMasodik m=TMasodik();// „elso.kiir” íródik ki! } Konstruktor hívása konstruktorból
OOP_07
- 16 -
Az elején! Ekkor a konstruktorok belsejében már működik a késői kötés! (C#, Delphi, Java) class TElso { public int x; public TElso() { x=1; kiir(); } public virtual void kiir() { Console.WriteLine( ”elso.kiir”); } } class TMasodik:TElso { public int y; public TMasodik() { y=2;} // ős konstruktora automatikusan elindul! public override void kiir(){Console.WriteLine("masodik.kiir”);} } static void Main() { TElso m = new TElso(); TMasodik m = new TMasodik(); } Konstruktor hívása konstruktorból
// ”elso.kiir” íródik ki // ”masodik.kiir” íródik ki OOP_07
- 17 -
Statikus konstruktor A statikus konstruktornak … (csak egy lehet belőle!) • „static” módosítóval kell rendelkezni • nem lehet hozzáférési módosítója (public, private, …) • nem lehet paramétere • nem lehet meghívni explicit módon • automatikusan kerül meghívásra „mire szükség van rá”! • csak a statikus mezőknek adhat kezdőértéket class TVeletlenSzamok { static int RandSeed; static TVeletlenSzamok() { RandSeed = Timer_now(); } } Konstruktor hívása konstruktorból
OOP_07
- 18 -
Destruktor Ezen
metódusok
gondoskodnak
arról,
hogy
az
objektum
használatának befejeztekor az objektum által lefoglalt erőforrások (memória,
file-ok,
háttértároló,
csatolt
eszközök,
stb.)
felszabadításra kerüljenek. A destruktorok meghívásának három módja lehet: a. explicit módon (programozó által // desktruktor jelleg) b. implicit módon (objektum megszünésekor (ref.számláló)) c. automatikusan (objektum nincs már használatban (garbage collector = gc)) Destruktor
OOP_07
- 19 -
Explicit destruktor hívás (pl. Delphi) type TAkarmi = class … constructor Create; destructor Done; end; procedure Valami; var A:TAkarmi; begin A := TAkarmi.Create; … A.Done; // a programozó meghívja a destruktort end;
Explicit destruktor hívás (pl. Delphi)
OOP_07
- 20 -
Implicit destruktor hívás (1) Az objektum-példányokhoz a memóriában plusz-ban egy „referencia-számláló” készül, mely mutatja, hogy hány változón keresztül lehet ezt a példányt elérni közvetlenül. A példány megszűnik, ha a referencia-számláló 0-ra csökken. type TAkarmi = class … constructor Create; destructor Done; end; procedure Valami; var A:TAkarmi; begin A := TAkarmi.Create; // példány létrejön, ref.szamlalo=1 … end; // az „A” változó megszűnik (lokális változó) // a példány referencia-számlálója e miatt 0-ra csökken // meghívódik a destruktora automatikusan // a példány „kitakarítódik” a memóriából Implicit destruktor hívás (1)
OOP_07
- 21 -
Implicit destruktor hívás (2) type TAkarmi = class … constructor Create; destructor Done; end; var G:TAkarmi;
// globális változó
procedure Valami; var A:TAkarmi; begin A := TAkarmi.Create; // példány létrejön, ref.szamlalo=1 … G := A; // a ref.szamlalo 2 lesz ! end; // az „A” változó megszűnik (lokális változó) // a példány referencia-számlálója e miatt 1-re csökken, de még nem 0!
Implicit destruktor hívás (2)
OOP_07
- 22 -
Implicit destruktor hívás (3) Az ilyen nyelveknél megszüntették a „memória-felszabadítás”-szerű utasításokat, a memória automatikusan felszabadítódik a ref.számláló kezelés mechanizmus révén. type TLancoltListaElem = class public: kov: TLancoltListaElem; end; var Fej:TLancoltListaElem; BEGIN ... Fej := nil; END. • • • •
// a láncolt lista feltöltése elemekkel // Fej elem „végérték”-re állítása
Ekkor az 1. listaelem ref.számlálója 0-ra csökken -> megszűnik Ekkor a 2. listaelem ref.számlálója csökken 0-ra -> megszűnik … Az összes listaelem „kitakarítódik” a memóriából
Implicit destruktor hívás (3)
OOP_07
- 23 -
Implicit destruktor hívás (3) A kétirányú láncolt listák esetén a mechanizmus nem működik! type TKetiranyuLancoltListaElem = class public: kov : TKetiranyuLancoltListaElem; elozo : TKetiranyuLancoltListaElem; end; var Fej: TKetiranyuLancoltListaElem; BEGIN ... Fej := nil; END.
// a láncolt lista feltöltése elemekkel // Fej elem „végérték”-re állítása
• A kétirányú láncolt listában az elemek ref.számlálója 2, mert mindkét szomszédos elemről van rá hivatkozás (kivéve az utolsó elem esetén) • Fej:=nil -> ekkor az 1. listaelem ref.számlálója 1-ra csökken -> megszűnik • A többi listaelem ref.számlálója nem változik • Az összes listaelem bent marad a memóriában!! • Pedig a listaelem többé már nem elérhetőek a programból! Implicit destruktor hívás (3)
OOP_07
- 24 -
Garbage collector használata esetén A „garbage collector” egy eljárás (programocska)!
A GC algoritmus folyamatosan keresi a memóriában azokat az objektum-példányokat,
akiknek
a
referencia-számlálója
0-ra
csökkent, vagy elérhetetlenné vált a programból (nincs olyan programváltozó, amelyből kiindulva a példányhoz el lehetne jutni). A GC amint ilyet felfedez, megszünteti azt. Mikor teszi ezt a GC? A program futása közben folyamatosan… Garbage collector használata esetén
OOP_07
- 25 -
Nem explicit módon hívott destruktorok Ha a destruktort a rendszer automatikusan hívja majd meg („b” és „c” módszer), akkor a destruktoroknak … • ... nem lehet paraméterük (a rendszer nem fog kitalálni értékeket!) • … csak egy lehet belőlük (a rendszer nem fog válogatni) • … ennek „public” védelmi szintűeknek kell lennie (a rendszer „kívülről” hívja)
Garbage collector használata esetén
OOP_07
- 26 -
Destruktor készítése (C#) • Kötött a neve (ugyanaz, mint az osztály, de ~ van előtte) • Nincs paramétere • Nincs hozzáférés-korlátozása (private, public, protected) • C#-ban nem tudhatjuk, mikor hívódik meg a destruktor (az automatikus garbage collector miatt) • Az overloading mellett sem lehet több destruktor, hiszen kötött a név, és a paraméterezés is
Destruktor készítése (C#)
OOP_07
- 27 -
Destruktor készítése (C#) • Kötött a neve (ugyanaz, mint az osztály, de ~ van előtte) • A garbage collector fogja meghívni! • Paramétere nem lehet! • Overloading mellett sem készíthető több destruktor! • Nem lehet védelmi szint módosítója (automatikusan public!)
Destruktor készítése (C#)
OOP_07
- 28 -
Destruktor példa class TChat { Socket kapcsolat; public TChat(string ChatServerIP, int ChatPortNum) { IPAddress ipcim= Dns.Resolve(ChatServerIP).AddressList[0]; IPEndPoint szgep= new IPEndPoint(ipcim,ChatPortNum); kapcsolat = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp ); kapcsolat.Connect(szgep); } ~TChat() // a deskturktor lezárja a hálózati kapcsolatot megszűnés előtt! { kapcsolat.Shutdown(SocketShutdown.Both); kapcsolat.Close(); } public void KuldUzenet(string Uzenet) { ...; } public string UzenetFogad() { ...; } ... } Destruktor példa
OOP_07
- 29 -