Programozási nyelvek II. C # 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 tel jes körűen. Minden észrevételt,amely valamilyen hibára vonatkozik,örömmel fogadok. H
C++
C#
R
JScript
N
Y
Á
Z
K
J#
Common Language Specification ASP .NET Windows Web Forms Web Services Forms Mobile Internet Toolkit
ADO.NET and XML Base Class Library Common Language Runtime Operating System
O
Visual Studio.NET
VB
E
Alapfogalmak CLR: Common Language Runtime: egy rendszer, amely egy „virtuális gépi kódú nyelv”-re fordított programokat (.exe) képes futtatni. BCL: Base Class Library: egy osztálygyujtemény, amely „kész”, általános célú objektumokat tartalmaz. Ezeket az objektumokat a .NET minden támogatott nyelvében el lehet érni. CLS: Common Runtime Specification: A „virtuális gépi kódú” nyelv, melyre le kell fordítani a .NET alatt futtatandó programokat. A .NET programok (bár .exe-k), nem közvetlenül futtatható programok, hanem egy köztes kódra fordított programok, amelyeknek az „eleje” normális gépi kód, de csak annyit „csinál”, hogy értesíti az operációs rendszert, hogy töltse be rá a CLR rendszert, amely átveszi a program futtatásának feladatát. A CLR, a BCL szabadon letöltheto (free software) a Microsoft honlapról .NET Framework” címszó alatt. Ezt (többek között) a “C:\WINDOWS\Microsoft.NET\Framework\v1.0.3705\” alkönyvtárba telepszik fel. Ebben található egy „CSC.EXE”, amely a C# (C-Sharp, ejtsd szí-sarp) programnyelv parancssori fordítója.
L
T
Á
N
[email protected]
elindított programok ne legyenek képesek „illegális” tevékenységeket folytatni (vírusok), ne olvashassák a file-okat, ne küldhessenek adatokat ki valamely hálózati kapcsolaton keresztül, … A CLR interpreteres üzemmódban futtatja a .NET programokat (ez lassú).
- ugyanakkor nem érdekes, hogy melyik programnyelven írtuk az eredeti kódot, a számítógép a generált .exe-t fogja futtatni - ezért tetszoleges programnyelven meg lehet (majd) írni a programokat, csak a lényeges, hogy a fordító és szerkeszto programok ne „natív” windows-os .exe-t generáljanak, hanem .NET-es .exe-t. - a virtuális gépi kód OOP-t támogató kód! - ezért az általunk használt magasszintu programnyelveknek is OOP nyelveknek kell lenniük! - jelenleg is már 10 fölötti a .NET-et támogató nyelvek listája - a Microsoft a Visual Basic, C++, C#, Java Script, J# (Java-alapú) nyelvekhez elkészítette a fordítókat - egyéb nyelvekhez külso cégek készítik el a fordítókat (pl. Inprise-Delphi) A CLR interpreteres üzemmódban futtatja a .NET programokat (ez lassú).
- az interpretert egy JIT (Just-In-Time) compiler támogatja, amely „dönthet” úgy, hogy a virtuális gépi kód bizonyos (surun hívott) részeit futtatás közben a memóriában natív kódra fordítja (sebességnövekedés) - ezzel pl. megoldható az, hogy a jövo processzorai új regisztereket is tartalmaznak majd, akkor ugyanaz az .exe kód ezeken ezt máris ki fogják használni - a processzor-specifikus gépi kódú utasításokat használja (AMD, Intel Pentium,…) - az adott operációs rendszer lehetoségeit messzemenokig kihasználja a program A CLR interpreteres üzemmódban futtatja a .NET programokat (ez lassú).
Ugyanakkor az NGEN.EXE-vel a lefordított .NET programot natív kódra lehet fordítani. Ezzel a kód az adott processzorhoz és operációs rendszerhez lesz optimalizálva, futása ebben a környezetben 2x 3x gyorsabb lesz (lehet)! A CLR interpreteres üzemmódban futtatja a .NET programokat (ez lassú).
- ezzel megszunnek az eredeti magasszintu nyelvek adatábrázolási különbségei: - sztringek tárolása (C és PASCAL stílus, ASCII és UNICODE, stb…) - számtípusú adatok tárolási különbsége (méret, pontosság, stb…) - tömbök indexelése (egységesen 0..n-1 indexelésú (Basic-esek sírnak!!!)) - stb… - a CLS-nek megfelelo nyelvek képesek együtt muködni: - a kész projekt egyik felét pl. C++-ban is meg lehet írni - a másik felét Visual Basic-ben - mindkét felét eloször CLS-nek megfelelo „virtuális gépi kódra” fordítják le - a kettobol a szerkeszto összeállítja a muködo kész programot. A CLR interpreteres üzemmódban futtatja a .NET programokat (ez lassú).
A másik mód, hogy a „Microsoft Visual Studio v7.0” másik nevén „Microsoft Visual Studio .NET” fejlesztorendszert tesszük fel (5 cd). Ezt tartalmazza a Framework-t is. A Framework-nek készül a LINUX-os változata!!!! (http://www.go-mono.org) A C# fordító is készült LINUX-ra. A CLR interpreteres üzemmódban futtatja a .NET programokat (ez lassú).
Ugyanakkor ezzel megvalósítható a kód futtatásának felügyeletét: - eroforrás-felügyelet: szabályozható, hogy a program milyen eroforrá sokhoz férhet hozzá (file-ok, internet, hálózati kapcsolatok, stb…) - a programok ezt az ellenorzést nem kerülhetik meg - minden eroforrásigénylésüket a CLR-en keresztül kell megvalósítaniuk - minden programhoz beállítható, hogy miket engedélyezünk neki: START menü / Beállítások / Vezérlopult / Felügyeleti eszközök / Microsoft .NET Framework Configuration
- Ezzel megvédhetjük számítógépeinket, hogy pl. az Internet-rol letöltött, és
- de a program futtatásához szükséges DLL (Dynamic Link Library = közös (shared) eljárás és fvgyujtemény) verzióit minden program magának specifikálhatja, azaz nem okoz problémát, hogy ha valamelyik program ugyanazon néven feltesz egy közös DLL-t, azt erre hivatkozó programok vagy „megbíznak” ebben a DLL-ben, és használják, vagy a saját régebbi verziót használják tovább - ennek kezelését a CLR végzi - A DLL-ek nem csak függvényeket exportálhatnak, hanem típusinformációval ellátott objektumosztályokat!!! - a programok védettek a buffer overflow vírustechnikával szemben. A CLR interpreteres üzemmódban futtatja a .NET programokat (ez lassú).
- a CLR nyilvántartja a program eroforrás-felhasználását, és hiba esetén ter minálja a programot, és korrekt módon felszabadítja az eroforrásokat - ez kiterjed az objektumok destruktorainak automatikus meghívására is (garbage collector funkció) „normális” programmfuttatás közben is.
1
object string sbyte short int long
The ultimate base type of all other types String type; a string is a sequence of Unicode characters 8- bit signed integral type 16- bit signed integral type 32- bit signed integral type 64- bit signed integral type
byte ushort uint
8- bit unsigned integral type 16- bit unsigned integral type 32- bit unsigned integral type
ulong
64- bit unsigned integral type
float double
Single- precision floating point type Double- precision floating point type
bool
Boolean type; a bool value is either true or false
char Character type; a char value is a Unicode character decimal Precise decimal type with 28 significant digits
object o = null; string s = "hello"; sbyte val = 12; short val = 12; int val = 12; long val1 = 12; long val2 = 34L; byte val1 = 12; ushort val1 = 12; uint val1 = 12; uint val2 = 34U; ulong val1 = 12; ulong val2 = 34U; ulong val3 = 56L; ulong val4 = 78UL; float val = 1.23F; double val1 = 1.23; double val2 = 4.56D; bool val1 = true; bool val2 = false; char val = 'h'; decimal val = 1.23M;
Felsorolás típus enum Color { Red, Blue= 10, Green } Color hatterszin = Color.Red; Változók és adatmezõk • Ha egy fv belsejében van deklarálva -> változó int int int int
a; b =1; c, d = 1; e = c + d; // hiba, ”c” még nem kapott értéket
• Ha osztály szinten -> adatmező o „static” módosítóval osztálymez ő o „static” nélkül példánymező.
C# „goto” ... goto done; ... done: ... C# egyszerû elágazás • if ( logikai kif ) utasítás; • if ( logikai kif ) { utasítás1; ...; utasításN; } • if ( logikai kif ) utasítás; else utasítás; • if (logikai kif ) {utasítás1; ...; utasításN;} else utasítás; C# többirányú elágazás switch ( egész vagy string típusú kifejezés) { case konstans1 : ...; break; case konstans2 : ...; break; default : ...; break; }
C# „while” ciklus while (logikai feltétel) utasítás; while (logikai feltétel) {utasítás1; ...; utasításN;} C# „do- while” ciklus
Tömbök int[] a1 = {1, 2, 3}; int[] x = new int[ 3] {0, 1, 2}; int[] arr = new int[ 5]; //5 elemű vektor int[,] arr2 = new int[ 5,4]; // 5x4 elemű mátrix int [][] arr3 = new int[ 2]; // ”jagged” tömb arr3[0] = new int[ 5]; // arr3[0] legyen 5 elemű arr3[1] = new int[3]; // arr3[1] legyen 3 elemű
do utasítás; while (logikai feltétel); do {utasítás1; ...;utasításN;} while (logikai feltétel); C# „for” ciklus for (utasítás1; feltétel; utasítás3){ …; }
for (int i =0;i <arr.Length;i++) arr[i]=i; foreach(int x in arr ) Console.WriteLine(x ); for (int i=0;i<arr3.Length;i++) for (int j=0;j<arr3[i].Length;j++) arr3[i][j]=0;
Konstansok deklarálása const double Pi = 3.14;
(a konstansok mellé nem lehet “static” jelzőt írni, eleve úgy viselkedned) „Property”(csak adatmezõ)
• Ha egy vector minden elemén kívánunk m ű veletet végezni • deklarálunk egy olyan változót, mint a vector alaptípusa • A ciklusmagban ezen változóra hivatkozunk – ez a vector aktuális eleme // a ciklusmagban x csak olvasható !!! C# „break” • switch utasítás természetes része -> ugrás a switch utáni utasításra • ciklusok magjában használva a ciklusból kiugrásra alkalmas.
Paraméterek módosítói //nincs módosító // „ref” módosító //„out” módosító //„params” módosító
void F(int p) { p++; } void F( ref int p) { p++; } void F(out int p) { p=12; } static void F(params int[] args) { … }
Overloading támogatás • A metódusoknak lehet ugyanaz a nevük, ha más a „szignatúrájuk”. • A formális paraméterezése más o Paraméterek számában o Paraméterek típusa o A paraméterek módosítói static void F() { … } static void F( object o) { …} static void F( int value) { …} static void F(ref int value) { … } static void F(int a, int b) { … } static void F(int[] values) { … }
Pl: for (int i= 0; i< 10; i++) a[ i]= i; C# „foreach” ciklus
int[] tomb = new int[ 5]; foreach (int x in tomb) Console. WriteLine( x);
string IPCim { get { ...; return ”127.0.0.1”;} set { ...; elmenteni(value); } }
• A paraméterek is változóknak minősülnek o Érték szerinti paraméter (bejövőadat) o Cím szerinti paraméter (átmen ő adat) o Kimen ő paraméter (kimen ő adat) o Paraméter tömb (vált.param.szám)
utasítás1; while (feltétel) { ...; utasítás3; }
C# „continue” • Ciklus magjában használva “átugorja” a ciklusmag még maradék utasításait o „while„ ciklusban ugrás a ciklus tesztfeltétel kiértékelésére o „do- while” ciklusban szintén o „for” ciklusban ugrás a növel ő utasításra (utasítás3) o „foreach” ciklusban ugrás a következ ő elem kiértékelésére C# „return” • A fv visszatérési értékét határozza meg • „return kifejezés” – a kifejezés eredménye lesz a fv visszatérési értéke • a „return” azonnal visszatér, a fv maradék utasításait átugorja • ha a fv void típusú, a return után nem lehet írni kifejezést! C# program kezdõ pontja public static { … a program for (int i= Console. … }
int Main( string[] args) itt kezd ő dik … 0; i< args. Length; i++) WriteLine( args[i] );
C# hatáskörök Az elérés korlátozott az adott osztályra Az elérés korlátozott az adott osztályra, és a leszármazott osztályokra public Az elérés nincs korlátozva internal Az elérés korlátozott az adott programra protected internal A “protected” és az “internal” együtt private protected
2
#1 (Bevezetés) OOP története Struktúrált programozás: • Program = adat + algoritmus • 60- as években Böhm és Jacopini sejtése : bármely algoritmus leírható az alábbi 3 vezérlési szerkezet véges sokszori alkalmazásával: - szekvencia - szelekció - iteráció • A fenti sejtést Mills bizonyította Objetum orientált programozás: • Alan Kay diplomamunkája 1969 • Tervek és elképzelések • SmallTalk programozási nyelv megtervezése • Meglév ö eljárásorientált nyelveket b ö vítik OOP lehet ö ségekkel (Pascal,C) • Új, tisztán OO nyelveket terveznek (Java,C#)
Nyelvek osztályozása: • Hagyományos nyelvek (C) • OOP támogató nyelvek (Pascal, Delphi, C++) • Tisztán OOP nyelvek (C#, Java). OOP alapelvei 1. Egységbezárás (encapsulation): az adatok és a hozzájuk tartozó eljárásokat egyetlen egységben kezeljük (objektum-osztály) • Az osztály mezöi tárolják az információkat • A metódusok kommunikálnak a külvilággal • Az osztály változóit csak a metódusokon keresztül változtathatjuk meg • A valós világ szemléletesebb leírása. 2. Öröklés (inheritance) : az objektum-osztályok továbbfejlesztésének lehet ö sége.Ennek során a származtatott osztály örökli ösétöl azok attribútumait, és metódusait, de ezeket bizonyos szabályok mellett újakkal egészítheti ki, és meg is változtathatja • Az eredeti osztály ö sosztálynak nevezzük (szülö) • Az új, továbbfejlesztett osztályt származtatott osztálynak (gyerek) • Egy ösböl több leszármaztatott osztályt is készíthetünk • Egy származtatott osztálynak - Legfeljebb egy szülöje lehet (pl.: Pascal, Java, C#)–öröklödési fa - Több szülöje is lehet (pl.:C++) –öröklödési gráf • Metódusok törzsét megváltoztathatjuk • Mezök neveit, típusait általában nem változ tathatjuk meg • Új mezökkel, és metódusokkal egészíthetjük ki az osztályt. Sokalakúság (polymorphism): a származtatás során az ö s osztályok metódusai képesek legyenek az új átdefiniált metódusok használatára újraírás nélkül is • Ezt virtuális metódusokon keresztül érhetjük el - Egyes nyelvekben minden metódus virtuális (pl.: Java) - A metódusoknál külön jelezni kell, melyik a virtuális (pl.:Delphi, C#, C++) 1. Példa (C és C++) (csak egy verem) #include <stdio. h> struct TVerem { int vm; float tomb[ 100]; }; struct TVerem V; void Init() { V. vm = 0; } void Push( float x) { V. tomb[ V. vm]= x; V. vm++; } float Pop() { V. vm--; Return V. tomb[ V. vm]; } int main() { Init(); Push( 12.5); Push( 16.3); Printf(”% f\ n”, Pop()); // 16.3 Printf(”% f\ n”, Pop()); // 12.5 }
1. Példa (Pascal) (csak egy verem) type TVerem = record vm: integer; tomb: array [1.. 100] of real; end; var V: TVerem; procedure Init; begin V. vm := 0; End; procedure Push( x: real); begin V. tomb[ V. vm]:= x; Inc( V. vm); End; function Pop: real; begin dec( V. vm); Pop := V. tomb[ V. vm]; End; BEGIN Init; Push( 12.5); Push( 16.3); Writeln( Pop); // 16.3 Writeln( Pop); // 12.5 END.
2. Példa (C és C++) (több verem) #include <stdio. h> struct TVerem { int vm; float tomb[ 100]; }; void Init( TVerem *V) { V-> vm = 0; } void Push( TVerem *V, float x) { V-> tomb[ V-> vm]= x; V-> vm++; } float Pop( TVerem *V) { V-> vm--; Return V-> tomb[ V. vm]; } struct TVerem V1; struct TVerem V2; int main() { Init(& V1); Init(& V2); Push(& V1,12.5); Push(& V1,16.3); Push(& V2,11.3); Printf(”% f\ n”, Pop(& V1)); Printf(”% f\ n”, Pop(& V2)); Printf(”% f\ n”, Pop(& V1)); }
2. Példa (Pascal) (több verem) type TVerem = record vm: integer; tomb: array [1.. 100] of real; end; procedure Init( var V: TVerem) begin V. vm = 0; End; procedure Push( var V: TVerem; x: real) begin V. tomb[ V. vm]= x; Inc( V. vm); End; Function Pop( var V: TVerem): real; begin dec( V. vm); Pop := V. tomb[ V. vm]; End; var V1, V2: TVerem; BEGIN Init( V1); Init( V2); Push( V1,12.5); Push( V1,16.3); Push( V2,11.3); Writeln( Pop( V1)); Writeln( Pop( V2)); Writeln( Pop( V1)); END.
3
3. Példa (C++) (verem osztály) #include <stdio. h> class TVerem { public: int vm; float tomb[ 100]; void Init(); void Push( float x); float Pop(); }; void TVerem:: Init() { vm = 0; } void TVerem:: Push( float x) { tomb[ vm]= x; vm++; } float TVerem:: Pop() { vm--; return tomb[ vm]; } int main() { TVerem v1; TVerem v2; v1. Init(); v2. Init(); v1. Push( 12.5); v2. Push( 13.4); v1. Push( 16.3); printf("% f\ n", v1. Pop()); printf("% f\ n", v1. Pop()); printf("% f\ n", v2. Pop()); return 0; }
3. Példa (Pascal) (verem osztály) type TVerem= object vm: integer; tomb: array [1.. 100] of real; procedure Init; procedure Push( x: real); function Pop: real; end; procedure TVerem. Init; begin vm := 0; end; procedure TVerem. Push( x: real); begin tomb[ vm]:= x; inc( vm); end; function TVerem. Pop: real; begin inc( vm); Pop := tomb[ vm]; End; var V1, V2: TVerem; BEGIN v1. Init; v2. Init; v1. Push( 12.5); v2. Push( 13.4); v1. Push( 16.3); Writeln( v1. Pop); Writeln( v2. Pop); Writeln( v1. Pop); END.
• De csak ellenörzött módon! Az objektum belvilága – a saját metódusai. Külvilág (minden más) • Közeli külvilág: az osztály leszármazottjai,s azok metódusai • Távoli külvilág: a példányokat készítö program modulok (pl.f ö program). Attribútumokkal kapcsolatos - PRIVATE: a külvilág nem férhet ezen attribútumukhoz hozzá (osztály „magánügye”). Ezek általában segédváltozók,segédmezök. - PROTECTED: olyan attribútumok, melyekhez a távoli külvilág nem férhet hozzá (számára private), de a közeli külvilág, leszármazott osztályok metódusai hozzáférhetnek (számára public).ö bennük „megbízunk”! - PUBLIC: olyan attribútumok, melyek jellegüknél fogva nem igényel nek speciális szabályozást, azok tetsz ö leges id ö ben és értékre történö megváltoztatása nem okoz, nem okozhat problémát az objektum mûködésében. Metódusokkal kapcsolatos A metódusokhoz való hozzáférést is a fentiek szerint osztályozzuk, de itt az adott metódus meghívható-ságát jelöli: - PRIVATE : ezen metódusokat a külvilág nem hívhatja meg, csak az adott osztály metódusai hívhatják meg - PROTECTED : olyan metódusok, melyeket a távoli külvilág nem hívhat meg (számára private), de a közeli külvilág, a leszármazott osztályok metódusaiból meghívhatóak (számukra public) - PUBLIC : ezen metódusokat a távoli külvilág meghívhatja.
Pl: C# class TVerem { private int vm; private double[] tomb = new double[ 100];
}
public void Push( double x){ ... } public double Pop() { ... } public void Init() { ... }
––––––––––––––––––––––––––––––––––––––––– class TVerem { int vm; // alapból private double[] tomb = new double[ 100]; // alapból private
}
public void Push( double x) { ... } public double Pop() { ... } public void Init() { ... }
Attribútumok nem elrejtése class TVerem { public int vm; ... } ... TVerem V = new TVerem(); V. vm = 12;
Alapfogalmak Osztály: egy felhasználó által készített típus, mely összetett adatszerkezet – elvileg tartalmazza az adott objektum adatait, és az azokat kezel ö eljárásokat. Objektum: egy változó, melynek típusa valamely objektumosztály, vagyis az osztály egy példánya. Attribútum (adatmezö): az osztály egy mezöje, konkrét adattárolási képes-ségû adattagja..Metódus: olyan eljárás, mely része valamely objektumosztálynak, így az adott osztály attribútumaival végez valamilyen mûveletet. Példányosítás: egy objektumosztályból konkrét objektum készítése (objektum változó deklarálása) Inicializálás: az objektum attribútumainak kezdö értékbeállítása, általánosabban az objektumpéldány alaphelyzetbe állítása
#2 (Adatrejtés) Adatrejtés (data hiding) Az adatrejtés az egységbezárás alproblémája.Cél: • Az objektum képes legyen adatokat tárolni • Azokat a külvilág képes legyen lekérdezni és megváltoztatni
Mivel a „vm” nem rejtett mezö, ezért a külvilág meg-változtathatja azt, és ezzel a fenti példában a LIFO adatelérési elvet megsértheti – és hibás értékre állítva futási hibát generálhat. Attribútumok elrejtése class TVerem { private int vm; ... } ... TVerem V = new TVerem(); while (V.vm>0) //hibás,nincs „vm” mezö { Console.WriteLine(”Adat={0}”,V.Pop()); }
Mivel a „vm” mezöt elrejtettük,annak tartalmát kiolvasni sem lehetséges a továbbiakban – pedig az néha szükséges. Csak olvasható attribútumok készítése class TVerem { private int vm; public int getVM() { return vm;} ... } ... TVerem V = new TVerem(); // V. vm = 12; // nem sikerülne ! while (V.getVM()>0) // ez most működik ! { Console.WriteLine(”Adat={0}”,V.Pop()); }
4
• Tegyük a mezöt rejtetté (private,protected) • Készítsünk egy „get” jelleg û fv-t (C++-ban leggtöbször inline fv). Ellenörzött elérésû attribútumok készítése class TVerem { private int vm; public int getVM() { return vm; } public void setVM(int uj) { if (( 0<=uj)&&(uj<=vm))vm=uj; } ... } ... TVerem V = new TVerem(); ... V.setVM(1); while (V.getVM()>0) Console.WriteLine(”Adat={0}”,V.Pop());
• Lassú programfutás • Hosszabb kód • „Csúnya” programkód (csúnya szintaxis). Property készítése A property (jellemzö) egy virtuális attribútum • Definiálása során nem keletkezik külön valósá gos változó, amely ben a tényleges adatokat tárolni lehet. • Meg kell adni, hogy kiolvasáskor (read) mi ezen mezö értéke • Meg kell adni,hogy íráskor (write) mi történjen a tárolandó értékkel (ezt „value” néven lehet elérni) class TVerem { private int fvm; // „fvm” tényleges mezö public int vm // „vm” virtuális mezö { get { return fvm; } set { if (( 0 <= value) &&(value <= fvm)) fvm= value; } } }
Használata:
... TVerem V = new TVerem(); V.vm =12; // -> V. vm. set( 12 ),így ”value”= 12 while (V. vm> 0) // -> V.vm.get() Console.WriteLine(”Adat={0}”,V.Pop());
• • • •
Lassú programfutás Hosszabb kód „Szép” programkód A külvilág azt hiszi, hogy a „vm” mezö fizikailag létezö, közönséges „int” típusú mezö. Példa: // a téglalap tartalmaz bal_x,felso_y mezö ket a bal felsö sarok // koordinátáihoz valamint „széles” és „magas” adatokat, jelezvén hogy // milyen széles és magas a téglalap jobb széls ö koordinátájának // számítása (jobb_x) kiszámítható! ha valaki beállítja a jobb_x értékét, // akkor a szélesség változik! class Teglalap { public int bal_x,felso_y; public int szeles,magas; public int jobb_x { get { return bal_x +szeles; } set {szeles = value - bal_x; } } }
Megjegyzések • A property belsejében természetesen lehet ivatkozni a „private” mezökre, hisz a „private” a külvilág felé takar, de a „set” és „get” belseje egyben az osztály belseje is, ezért ott minden – az adott osztályban – definiált private mezö teljesen hozzáférhetö • Ha csak a „get” részt adjuk meg a property-ben, ak-kor egy csak olvasható mezöt kapunk • Ha csak a „set” részt adjuk meg, akkor egy csak írható mezöt kapunk. Turbo Pascal 6.0: • A TP 6.0-ban csak két szint létezik:private és public • De csak a private kulcsszó létezik • Ezért az osztály elején kell felsorolni a „public”-okat, majd a „private” kulcsszó után kell felsorolni a privát részeket: • Nincs lehetöség property kezelésére Pl: type TVerem = object procedure Push( X: integer); function Pop: integer; private: vm: integer; tomb: array [1..100] of integer; end;
Turbo Pascal 7.0: • A TP 7.0- ban is csak két szint létezik: private és public • De bevezették a „public” kulcsszót, ezért az osztály tagjait tetszöleges sor rendben lehet megadni • Nincs lehetöség property kezelésére Pl: type TVerem = object private: vm: integer; tomb: array [1..100] of integer; public: procedure Push(X: integer); function Pop: integer; end;
Delphi: • Van „protected” is, a „protected” kulcsszóval kell jelölni • Van „property” - A „get” helyett a „read”, a „set” helyett a „write” kulcsszóval - A „read” után egy fizikai mezö nevét is lehet írni, vagy egy ugyanolyan típusú, paraméter nélküli fv nevét - A „write” után egy fizikai mezö nevét, vagy egy egyparaméteres eljárás nevét lehet írni (az egy paraméter típusa meg kell egyezzen a property alaptí pusával) - A property- t lehet indexelni is (tömbként kezelni) • Osztályt az „object” és „class” kulcsszóval is lehet jelölni • Az elözö TP kompatibilis objektum-osztály, az utóbbit a Delphireferencia alapon kezeli. Delphi Pl.: type TVerem = class private: procedure SetVM( ujVM: integer); protected: fvm: integer; tomb: array [1.. 100] of integer; public: procedure Push( X: integer); function Pop: integer; property vm: integer read fvm write SetVM; end;
C++: • A C++-ban létezik a „private” , „protected”, és „public” • Nincs lehetöség property kezelésére, de az „inline” fv-ekkel propertykhez hasonló hatást lehet elérni • Az osztályokat a „class” és „struct” kulcsszavakkal is létrehozhatjuk. Elöbbi esetben az alapértelmezett hozzáférés a „private” hacsak másképp nem jelöljük.A „struct” esetében az alapértelmezett hozzáférés a „public”. class TVerem { private: int vm; int[ 100] tomb; public: void Push(int X); int Pop(); }
C#: • A C# -ban a private, protected, public- on kívül hozzáférési szint is be van vezetve: internal, és internal protected • Van property, bár nem kell használni ezt a kulcsszót
még két
class TVerem { protected int fvm; protected int[] tomb = new int[ 100]; public void Push( int X) { ...; } public int Pop() { ...; } public int vm { get { ...; } set { ...; } } }
JAVA: • A JAVA- ban háromszintű hozzáférés szabályozás van, bár csak két kulcsszót ismer „private” és „public”. A jelöletlen tagokat „félnyilvános” tagoknak nevezi, és elérése hasonló korlátozású, mintha protected lenne. • Nincs benne property kezelés. class TVerem { int vm; int[ 100] tomb; public void Push( int X) { ...; } public int Pop() { ...; } }
5
#3 (Adattagok) Adattagok Egy objektumosztály kétfajta tárolási technikát támogat: · Osztály szintu adatok – osztálymezok („static” módosító) · Példány szintu adatok – példánymezok (nincs módosító) Osztálymezok: ezen „változók” egyetlen példányban lé-teznek a memóriában, akárhány objektumot is hozunk létre az adott osztályból. Példánymezok: ha újabb objektumot hozunk létre, akkor ezen „változóknak” külön memória foglalódik le. BA : BenzinAlapAra DF : DollarForint ny : nyeresegkulcs class TBenzinKut { static float BenyinAlapAra;// osztálymezo static int DollarForint;// osztálymezo float nyeresegkulcs; // példánymezo int literje { get { return BenyinAlapAra*DollarForint*nyeresegkulcs; } } void Dragulas(float UjAlapAr) { BenyinAlapAra = UjAlapAr;} } TBenzinKut.BenyinAlapAra = 1.4; // 1 liter benzin 1.4$ TBenzinKut.DollarForint = 276; // 1 $ = 276 Ft TBenzinKut EgriMOLKut = new TBenzinKut(); TBenzinKut Q8 = new TBenzinKut();
Osztálymezok Az osztálymezok kezelése: · Elérése (címzése) az osztály nevén keresztül történik · Nem címezheto meg egy példányon keresztül TBenzinKut.BenzinAlapAra = 1.4; // rendben EgriMOLKut. BenzinAlapAra = 1.4; // nem jó
· Az osztály minden metódusán belül lekérdezheto, és beállítható közvetlenül int literje { get { return BenyinAlapAra*DollarForint*nyeresegkulcs; } } void Dragulas(float noveles) {BenyinAlapAra += noveles;}
Ha megváltoztatunk egy osztálymezot, akkor azt minden példány észleli (mert közös változó): TBenzinKut.BenyinAlapAra = 1.6; // rendben Után az EgriMOL és Q8 benzinára is megváltozik azonnal! Ha ezen két példányunk van a TBenzinKut osztályból, összes memóriafelhasználásunk: 1 float (BenzinAlapAra) 1 int (DollarForint) 2 db float (példányonként egy nyereségkulcs) minden új példány létrehozásakor +1 float. Konstansok A konstans : · Értéke már fordítási idoben ismert · Nem változhat meg a program futása során Ilyen értelemben egy osztály konstansai hasonló viselkedést mutatnak, mint az osztálymezok: · Értelmetlen lenne minden példány számára külön konstans példányt létrehozni · A konstans ugyanúgy közös a példányok között. Pl: class Szogek { const double Pi = 3.14159265358979323846; double szog; double Sin() { return ...; } double Cos() { return ...; } } Szogek alfa = new Szogek(); alfa.szog = 48; Console.WriteLine(alfa.Sin() );
A „const” esetén nem kell (nem szabad) kiírni a „static” módosítót! Példánymezok A példánymezok a példányokhoz kötodnek, új példány létrehozásakor újabb memóriát igényelnek. Egy példánymezo értékének megváltoztatása csak az adott példányra fejti ki a hatását. Ilyen mezo a fenti példában „nyeresegkulcs” mezo, mely példányonként más-más lehet. Property A property egy virtuális adatmezo. Egyaránt lehet osztály-szintu, és példányszintu. Osztályszintu property „get” és „set” része osztályszintümetódusnak tekintendo, ezért a benne szereplo kifejezésekben csak · osztálymezo, · konstans szerepelhet. Metódusok Metódusok több fajtáját ismerjük, jelen esetben a fenti szemlélet szerint két típust emelünk ki: · Osztályszintu metódusok – („static” módosító) · Példány szintu metódusok – (nincs „static” módosító). Az osztályszintu metódusok csak az osztálymezokkel és az osztály konstansaival végezhetnek muveleteket (hivatkozhatnak rájuk), a példánymezokhöz nem férhetnek hozzá! Egy példányszintu metódus hivatkozhat, végezhet muve-letet a konstansokkal,
az osztálymezokkel, és a példány-mezokkel is! Az osztálymetódus meghívása az osztály nevén keresztül történik. A példánymetódus meghívása a példány nevén keresztül történik. Az osztálymetódus meghívásához nincs szükség példányra! Pl: class Szogek { const double Pi = 3.14159265358979323846; double DegToRad( double szog_fokban) { return szog_fokban/180*Pi; } double RadToDeg( double szog_radianban) { return szog_radianban/Pi*180; } } Console.WriteLine(„145 fok = {0} radián”, Szogek.DegToRad(145));
Osztálymetódusok az alábbi feladatot szokták ellátni (pl): · Olyan stílusú számítások, melyekhez minden változó információ a metódus paraméterezésében van Pl: double A1 = System.Convert.ToDouble(”143.23”);
· Olyan „osztályok” metódusai, melyekbol egy alkalmazásban úgyis csak egy példány lesz (felesleges példányosítani) Pl: System.Console.WriteLine(”Hello World!”);
· Az osztály tartalmaz osztályváltozókat. Ezekkel való muveletvégzéshez ne kelljen példányokat létrehozni! static void Dragulas(float noveles) { BA += noveles; }
Adatrejtés A ... · példánymezokre és osztálymezokre, · a példánymetódusokra és osztálymetódusokra, · a példánypropertykre és osztályproperty-kre, · a konstansokra ... egyaránt alkalmazhatók a hozzáférési szint korlátozó módosítók: public private protected
#4 (Öröklõdés) Származtatás Egy objektumosztály fejlesztése két módon kezdodhet el: · Abszolút nulláról · Egy már meglévo osztály továbbfejlesztése révén. Abszolút nulláról · A valós életben ritkán kerül rá sor, mert a fejleszto rendszerekben sok elore definiált osztály van, valószínu, hogy találunk egy jó kiindulási pontot · A legtöbb fejlesztoeszközben (pl. C#, Delphi) ekkor is van os, mely így minden, az adott fejlesztorendszerben készített objektumosztály közös ose lehet (típuskompatibilitás) · Ettol eltekintve az osztály „üres”, vagyis nincs sem mezoje, sem metódusa. Mindent a programozónak kell definiálni. Továbbfejlesztés (1) Egy már meglévo (akár más programozó által elkészített) osztály továbbfejlesztése révén úgy kell tekinteni, hogy az általunk fejleszett osztály induláskor nem „üres”, hanem eleve tartalmazza az „os” osztály minden mezojét és metódusát. class TPont {
// nincs os, abszolút // nulláról indul
public int x,y; public TPont() { ... } public void Kirajzol() { ... } ...
} class TKor:TPont // származtatás a TPont-ból { ... }. · Tartalmazza (örökli) a public mezoket és property-ket, azokat felis
használhatja a saját metódusokban · Tartalmazza (örökli) a protected mezoket és property-ket, és azokat fel is használhatja a saját metódusokban · Tartalmazza (örökli) a private mezoket és property-ket is, de azokat nem tudja felhasználni, ha hivatkozna a metódusok törzsében ezekre a mezokre, a „azonosító nem létezik” fordítási üzenetet kapnjuk. Ezen private mezok tartalmazása azt jelenti, hogy a memóriaigénybe beleszámít, de az új osztály belsejében nem lehet rá hivatkozni. Ugyanakkor az új osztály metódusai meghívhatják az örökölt metódusokat, és azok természetesen felhasználhatják ezeket a private mezoket, hiszen ok még az os osztály részei, és számukra ezen private mezok hozzáférhetok (voltak). class TPont { private int x,y; public void Balra_tol () { if (x>0) x--; } ... } class TKor:TPont { // származtatás a TPont-ból public void KorBalratol() { Balra_tol(); // muködik x--; // nem muködik, nincs x! } }
6
A származtatott osztályban (gyermekosztály) Lehetoség van: · újabb mezok definiálására (tetszolegesen lehet private, protected, public) (Általában) nincs lehetoség (C#-ban van!): · mivel a származtatott osztályban eleve létezonek tekinthetoek a protected vagy public mezok, ugyanilyen nevu mezok definiálására nem lehetséges Nincs lehetoség: · nincs lehetoség az örökölt mezok típusának (vagy nevének) megváltoz tatására. A private mezok a gyermekosztályban ebbol a szempontból nem létezonek tekinthetok, ezért ugyanolyan nevu mezok definiálása lehetséges a gyermekosztályban is. class elso { private int x; protected int y; public int z; } class { int int int }
masodik:elso x; // muködik,mert X itt private (volt) y; // nem muködik, mer elrejtené az örökölt y-t z; // nem muködik, mert elrejtené az örökölt z-t
C#-ban lehetoség van ... ... a származtatott osztályban eleve létezonek tekinthetoek a protected vagy public mezok átdefiniálására. Ugyanilyen nevu mezok definiálására lehetséges – a „new” kulcsszóval kell jeleznünk a fordítóprogramnak, hogy szándékosan csináljuk! A new kulcsszó hiányában a C# fordítási hibaüzenettel jelez, ezzel figyelmeztet, hogy figyeljünk oda! class { int new new }
masodik:elso x; int y; int z;
Az elso eset azt jelenti, hogy a gyermekosztályban lehetoség van egy ugyanolyan nevu metódus definiálására, amelynek a törzse (utasítás-része) természetesen más is lehet. Ezen metódus ekkor „elfedi” az örökölt metódust. A második eset azt jelenti, hogy a gyermekosztályban le-hetoség van egy ugyanolyan nevu, de más paraméterezésu metódus definiálására. Ez még az overloading-ot nem támogató nyelvekben is muködik. A különbség a következo: · Overloading-ot alapból nem támogató nyelvekben (pl. Delphi) innentol „nem elérheto” a régi metódus (csak minosített névvel), még akkor sem, ha a paraméterezés megváltozott · Overloading-ot támogató nyelveknél (C++, C#) a régi metódus meghívható a minosített név használata nélkül is, ha más a paraméterezése. -- A nagy kérdés: az os osztály azon metódusai, --- amelyek meghívják a szóban forgó metódust, --- vajon még a régi változatot, vagy az új --- változatot fogják meghívni? -(csak akkor kérdéses, ha egyforma a paraméterezés). class elso { public int visszaad() { return 1; } public void kiir() { System.Console.WriteLine("Az érték={0}",visszaad()); } } class masodik:elso { new public int visszaad() { return 2; } } class Test { static void Main() { masodik a= new masodik(); a.kiir(); } }
C#-ban sincs lehetoség ... · ... az örökölt mezok típusának megváltoztatására! · De van lehetoség, hogy a „new” kulcsszó révén újra bevezett mezok típusa ne ugyanaz legyen, mint az örökölt mezo típusa
-----------------------------------------------------------------------
class { int new new }
class elso { public int visszaad() { return 1; } public void kiir() { Console.WriteLine("Az érték={0}" ,visszaad()); } }
masodik:elso x; float y; float z;
Ezen mezok használata esetén tudni kell, hogy az „elso” osztály metódusai a régi y és z mezoket használják, a „masodik”, és a belole származtatott osztályok metódusai már csak az újonnan definiált mezoket látják. Viszont a memóriába mindketto bekerül. Ezen átdefiniálás révén a mezok hatáskörét változtatjuk meg. Az örökölt mezok hatásköre az os osztály metódusaira korlátozódik, a gyermekosztályban újradefiniált mezok hatásköre a gyermekosztályra, és a belole származtatott további gyermekosztályokra terjed ki. Lehetoség van a régi mezo elérésére – az osztály nevének – mint minosített név - használata esetén: class masodik:elso { ... new float z; public akarmi() { z=1.1; // a float „z” elso.z = 1; // az int (egyébként elfedett) „z” } }
Továbbfejlesztés (2) · A származtatott osztály tartalmazza (örökli) a protected és public metódusokat, azokat meg is lehet hívni az új gyermekosztály metódusaiból · Az os osztály private metódusait természetesen nem lehet meghívni a gyermekosztály metódusaiból, de meg lehet hívni egy olyan protected vagy public örökölt metódust, amely meghívja ezeket az „os” private metódusokat. Lehetoség van (újradefiniálás révén): · Megváltoztatni egy örökölt metódus törzsét · Megváltoztatni egy örökölt metódus paraméterezését Nincs lehetoség: · Megváltoztatni egy örökölt metódus hozzáférési szintjét (private, protected, public).
-- Kérdés: „1”-t vagy „2”-t fog kiírni? --
class masodik:elso { public int visszaad(int a) { return a*2; } } class Test { static void Main() { masodik a= new masodik(); a.kiir(); } } -- Itt nem kérdéses, mivel más a paraméterezése! --
#5 (örökölt metódusok) Örökölt metódusok C# · Egy objektumosztály az öröklés során metódusokat is örököl · Az örökölt metódusok közül most csak a protected vagy public metódu sok az érdekesek · A gyermekosztályban lehet ugyanolyan nevu metódust létrehozni · Ha a paraméterezése is ugyanaz, használni kell a „new” kulcsszót · Ha más a paraméterezése, akkor nem kell a „new” használata (az overloading miatt) · Az új metódust ekkor az os osztály metódusai „nem látják”! class elso{ public int visszaad() { return 1; } public void kiir() { Console.WriteLine("Az érték={0}",visszaad()); } } class masodik:elso{ new public int visszaad() { return 2; } } class Test{ static void Main() { masodik a= new masodik(); a.kiir(); } }
7
-- Ebben a példában a kiírás „1” lesz! --
Korai kötés
class elso { public int visszaad() { return 1; } public void kiir() { Console.WriteLine("Az érték={0}",visszaad()); } }
class Elso { void metodus_a() { ... } void metodus_c() { metodus_a(); // (a) } } class Masodik:Elso { new void metodus_a() { ... } ... }
class masodik:elso { public int visszaad(int a) { return a*2; } }
class Test { static void Main() { masodik b= new masodik(); b.metodus_c(); } }
class Test { static void Main() { masodik a= new masodik(); a.kiir(); } } -- Itt eleve nem is kérdés, hiszen más a paraméterezése ! -class elso { public int visszaad() { return 1; } public void kiir() { Console.WriteLine("Az érték={0}",visszaad()); } }
Ekkor az eljáráshívás sorát összeköti a konkrét eljárással: metodus_a() <=> Elso.metodus_a { ... } hívása
class masodik:elso { public int visszaad(int a) { return a*2; } new public void kiir() { Console.WriteLine("Az érték={0}",visszaad()); } }
Ezt a kötést (döntést) korán hozza meg a rendszer, ezért ezt korai kötésnek (early binding) nevezzük. Ezt a döntést a fordító még akkor meghozza, amikor a „class Elso” osztályt fordítja le. Ezt a döntést akkor meg kell hoznia! Ezen döntés meghozatalát késobb már nem másítja meg, hiába definiáljuk felül a „metodus_a”-t!
class Test { static void Main() { masodik a= new masodik(); a.kiir(); } }
A „b.metodus_c()” hívása során ezen programkód kerül végrehajtásra, amely ezen döntés értelmében egyértelmuen az „elso.metodus_a()” hívását fogja jelenteni!
Itt a kiírás „2” lesz, de nem azért, mert az új „visszaad”-t használja a régi „kiir”, hanem mer az új „kiir”-t hívtuk meg, amely az új „visszaad”-t használja !
(nyilván nem megoldás a metódus lemásolása!) class elso { public virtual int visszaad() { return 1; } public void kiir() { Console.WriteLine("Az érték={0}",visszaad()); } }
Késöi kötés class Elso { virtual void metodus_a() { ... } void metodus_b() { ... } void metodus_c() { metodus_a(); // (a) } } class Masodik:Elso { override void metodus_a() { ... } ... }
class masodik:elso { public override int visszaad() { return 2; } }
class Test { static void Main() { elso a= new elso(); a.metodus_c(); masodik b= new masodik();b.metodus_c(); } }
class Test { static void Main() { masodik a= new masodik(); a.kiir(); } }
Ez esetben a „virtual”-„override” kulcsszavakat használtuk, amely jelzés a fordítónak, hogy a „metodus_a” nem közönséges metódus, újradefiniálása valószínu, ezért az (a) sorban nem eldöntheto fordításkor, hogy melyik osztály metódusát is kell majd meghívni.
Itt a kiírás „2” lesz, mert a régi „kiir” az új „visszaad”-t használja !!! class elso { public virtual int visszaad() { return 1; } ... } class masodik:elso { public int visszaad() { return 2; } new public int visszaad() { return 2; } public override int visszaad() { return 2; } }
Az (a) sorban a fordító program az „Elso” osztály „metodus_a()” metódusának meghívása mellett „dönt”, mert a metodus_a() definiálásakor nem jelöltük a „virtual” kulcsszó segítségével, hogy ezen metódus (metodus_a) a késobbiek során valószínuleg felüldefiniálásra fog kerülni.
// (a) // (b) // (c)
· (a) nem helyes szintaktikailag · (b) muködik, de a kiírás „1” lesz, nem használja az új „visszaad”-t · (c) muködik, a kiírás „2” lesz. Virtuális metódusok · A „virtual” kulcsszóval jelezzük a fordítónak, hogy az adott metódust valószínuleg felül fogják majd definiálni a fejlesztés következo szintjein. · Ez csak akkor érdekes, ha a szóban forgó metódust más metódusból meghívjuk – és azt akarjuk, hogy ezen metódusok képesek legyenek a továbbfejlesztett változatokat használni. · Ezt az igényt a metódus elso bevezetésekor a „virtual” kulcsszóval kell jelölni – a felüldefiniálás során pedig az „override” kulcsszóval. · Ha az „override”-t nem tesszük ki, a fordító hibát fog jelezni. · Ha az „override”-t nem akarjuk kitenni, a „new” kulcsszóval kell jelezni azt, hogy „szándékosan” tesszük ezt. · Ekkor a metódus megszunik virtuális lenni!
Az elso esetben, az a.metodus_c() esetén elindul a „metodus_c()”, és meg kell hívni egy „metodus_a()”-t. Ez esetben ez csakis az „elso.metodus_a” lehet, mert az „a” példány az „elso” osztály egy példánya, és számára ezen metódus legfrisebb verziója ez. A második esetben a b.metodus_c() esetén is ugyanezen el-járás indul el, de a „metodus_a()” meghívásakor megkeresi a legfrisebb verziót ebbol a metódusból. Ez a „masodik. metodus_a()” lesz, hiszen a „b” példány a „masodik” osztály példánya, számára ez a legfrisebb verzió. Az OOP ezen viselkedését, hogy egy metódus (metodus_c) belsejében szereplo másik metódushívás más-más viselkedést mutat (más-más tényleges eljáráshívás történik a futtatás során) – sokalakúságnak (polymorphism) nevezzük. Az, hogy konkrétan melyik metódus kerül „majd” meghívásra – ez csak futás közben derül ki. Ezért ezt a kötést késoi kötésnek (late binding) nevezzük. Késoi kötésre csak a virtuális metódusok meghívásakor kerülhet sor. Ilyen metódushívás esetén a fordító nem egy konkrét eljáráshívást fordít le (korai kötés) - hanem egy utasítássorozatot, amely futás közben egy „keresést” hajt végre, hogy meghatározza, melyik metódusverziót kell konkrétan meghívni.
8
#6 (VMT és DMT) Virtuális Metódus Tábla • A késöi kötés egy „keresés”-t jelent, meg kell határozni a ténylegesen meghívandó metódust • A cél az, hogy ez a keresés a lehet ö legegyszerűbb kóddal, a lehetö leg gyorsabban megvalósuljon • Ehhez egy plusz táblázat felépítése és megtartása szükséges: a Virtuális Metódus Táblázat (VMT). • Ezen táblázat mindig egy osztályhoz tartozik • A VMT tábla induláskor megegyezik az ösének a VMT-jével (ha nincs ös, akkor induláskor üres) • Ha az osztályban bevezetünk egy új virtuális metódust a „virtual” kulcsszóval, akkor ezen metódus bekerül a táblázatba (a végére) • Ha az osztályban felüldefiniáltunk egy már létezö virtuális metódust az „override” kulcsszóval, akkor a táblázatba ezen bejegyzés kicserélödik az új metódusra • A táblázatban a metódusok indításához szükséges információk vannak eltárolva (pl. a metódusok memóriacímei, amely alapján azokat el lehet inditani). class elso { public virtual int metodus_ a() { ... } // a : új sor public virtual int metodus_ d() { ... } // d : új sor public void metodus_ c() { metodus_ a(); } // nem virtuális ! } class masodik: elso { public override int metodus_ a() { ... } // a : módosító sor public virtual int metodus_ b() { ... } // b : új sor }
class elso VMT Int metodus_ a() Int metodus_ d()
elso. metodus_ a elso. metodus_ d
class masodik VMT Int metodus_ a() Int metodus_ d() Int metodus_ b()
masodik. metodus_ a elso. metodus_ d masodik. metodus_ b.
A késöi kötés fordítása során olyan programkód kerül fordításra, amely a VMT táblázat alapján hozza meg a döntést, hogy melyik konkrét metódust kell meghívni. Az „e.metodus_c()” esetén az „e” példányhoz az „class elso” VMT tábla tartozik, hiszen az „e” példány az „elso” osztály egy példánya! Ezért a késöi kötés a „class elso” VMT tábla szerint a „metodus_a()” hívás esetén az „elso.metodus_ a()” metódust kell meghívni. A „m.metodus_c()” esetén az „m” példányhoz az „class masodik” VMT tábla tartozik,hiszen az „m” példány az „masodik” osztály egy példánya! Ezért a késöi kötés a „class masodik” VMT tábla szerint a „metodus_ a()” hívás esetén az „masodik.metodus_ a()” metódust kell meghívni. Elönyök: • A késöi kötést feloldó programkód rövid, egyszerű ,gyors. Hátrányok: • A VMT táblát el kell készíteni (fordítási idö) • A példányokhoz a VMT táblát hozzá kell rendelni (futási idö) • A VMT tábla túl sok memóriát köt le o Mindig legalább annyi sora van, mint az ös VMT táblának o Újabb sorokkal bövül, ha új virtuális metódust vezetünk be o Akkor is öriz egy bejegyzést, ha annak átdefiniálása nem történt meg (pl.„class masodik VMT – metodus_d()” sor!) A VMT táblát a fordító program el tudja készíteni, és a VMT tábla eltárolásra kerül a futtatandó állományban. A példányhoz rendelést a konstruktor teszi meg, automatikusan (generált programkód). Dinamikus Metódus Tábla • A szerepe megfelel a VMT-nek (késöi kötés feloldását támogatni) • Kevesebb memóriaigénye van, mint a VMT-nek • Lassúbb a kezelése Felépítése: • Hasonló, mint a VMT • Osztályhoz van hozzárendelve • A DMT induláskor üres • Ha az osztályban bevezetünk egy új virtuális metódust a „virtual”
kulcsszóval, akkor ezen metódus bekerül a táblázatba (a végére) • Ha az osztályban felüldefiniáltunk egy már létezö virtuális metódust az „override” kulcsszóval, akkor ez is bekerül a táblázatba. • A DMT mindig tartalmaz egy bejegyzést, hogy hol van az ös osztály DMT-je. Ha ilyen nincs, akkor azt a NIL jelzi. • A táblázatban a metódusok indításához szükséges információk vannak eltárolva (pl. a metódusok memóriacímei, amely alapján azokat el lehet indítani) Lényeges különbség: a DMT induláskor nem tartalmazza az ösosztály DMT táblájának sorait. class elso DMT int metodus_a() int metodus_d()
öS DMT = NIL elso.metodus_a elso.metodus_d
class masodik DMT int metodus_a() int metodus_b()
öS DMT =elso DMT masodik.metodus_a masodik.metodus_b
Nem szerepel a „class masodik DMT”-ben a „int metodus_ d()” sora, mert azt nem definiáltuk felül a „masodik” osztályban. A DMT alapján a kés ö i kötés feloldás kódvázlata: P := példány. DMT kezdöcíme Ciklus amíg P<>NIL IF P-ben szerepel a keresett metódus Metódus meghívása Kilépés a ciklusból ENDIF P :=P.ö S_DMT tábla kezdöcíme CVÉGE
Vagyis a DMT táblákban keresés során ha a jelenlegi DMT táblában nincs benne a keresett metódusról az információ, akkor visszalépünk az ö s metódus DMT-jébe (masodik -> elso), és ott folytatjuk a keresést. Elönyök: • A DMT táblák kevesebb memóriát kötnek le o Csak azon bejegyzések szerepelnek benne, amelyek ténylegesen változást szenvedtek el az ös DMT-hez képest Hátrányok: • A késöi kötést feloldó generált programkód bonyolultabb és lassúb • A DMT táblát el kell készíteni (fordítási idö) • A példányokhoz a DMT táblát hozzá kell rendelni (futási idö) Amely nyelvekben mindkettöt lehet használni, ott az a javaslat, hogy azon metódusokat kezeljük VMT technikával, amelyek hívása gyors kell legyen (sokszor hívjuk meg, pl. egy ciklus belsejében). Azokat tegyük DMT táblába, amelyeket az öröklés-továbbfejlesztés során felüldefiniálhatnak mások (bár erre elöre láthatólag ritkán kerül majd sor), vagy a metódust ritkán hívják meg – ezért nem számottevö a lassúbb kezelésböl adódó lassúbb programfutás. Amely metódusokat nem szükséges, ne tegyük egyik táblába sem, mert feleslegesen kötünk le memóriát,és feleslegesen lassítjuk a program futását.
#7 (init, done) 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. 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.
9
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; TKor K1 = new TKor(); public int y; TKor K2 = new TKor(8,7); ... ... public TKor() { x = 0; y = 0; } public TKor( int ax, int ay) { x = ax; y = ay; } }
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. 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() { ... } }
Konstruktor hívása konstruktorból • Az osztály konstruktorából lehetöség van az ös osztály valamely kon struktorá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. ö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 = new TPont (0,0); TKor buborek = new TKor(100,100,10);
Saját osztály másik konstruktorának hívása: class TVerem { int vm; int[] tomb; void Kiir() { Console. WriteLine("Hi"); } 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 } }
(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;
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(); }
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 }
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!? 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! }
TVerem egyikverem = new TVerem(); // = new TVerem( 100)! TVerem masikverem = new TVerem(300); //konstruktor hívása.
Örökölt constructor meghívása Kérdés: ha az ös osztálynak van konstruktora, és a gyer-mekosztálynak is van, és a gyermekböl példányosítunk, akkor meg fog-e hívódni az ös osztály konstruktora? Ha mi nem hívjuk meg, akkor nem!
10
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(); // ”elso. kiir”íródik ki TMasodik m = new TMasodik();// ”masodik.kiir” íródik ki }
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(); } }
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.
Implicit destruktor hívás (3 ) Az ilyen nyelveknél megszüntették a „memória-felszaba-dí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 ... // a láncolt lista feltöltése elemekkel Fej := nil; // Fej elem „végérték”- re állítása END.
• • • •
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
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ö a programból! Garbage collector használata esetén A „garbage collector” egy eljárás (programocska)!
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)) 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;
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ál-tozó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
A GC algoritmus folyamatosan keresi a memóriában azokat az objektumpé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él-dányhoz el lehetne jutni). A GC amint ilyet felfedez, megszünteti azt. Mikor teszi ezt a GC? A program futása közben folyamatosan... 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) .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 • 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!)
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!
11
Destruktor példa Típuskompatibilitás Szabály: az objektum (példány) típusa felülröl kompatibilis minden ös típusával. • Ez logikus: egy „továbbfejleszett” gyermek osztály mindent tud, amit az ösei tudnak, mivel mindent örökölt tölük. • Egy gyermekböl származtatott példány minden olyan kifejezésben használható, ahol az öséböl származtatott példány használható lenne. • Eljárások és fv-ek paramétereiben is használhatóak ott, ahol valamely ös típus van jelölve a paraméter típusának.
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() { ...; } ... }
#8 („this” paraméter)
Pl (1/ a):
This • Az osztály valamely metódusában szükség lehet saját magára hivatkozni • Leggyakrabban akkor, ha egy „külsö” osztály metó dusát hívjuk, és át kell adni „saját magát” • A C alapú nyelvekben a „this” változó a saját példányra mutat • A Pascal alapú nyelvekben ezt „self”-nek nevezik, funkciója ugyanaz • A „this” foglalt szó (nem használható fel saját célokra).
class TElso{ } class TMasodik{ }
”this” mint automatikus paraméter
}
class Elso { public int x; public void beallit(int ujX) // +1 „láthatatlan” paraméter: this { x = ujX; } ... } Elso e1 = new Elso(); e1.Beallit(10); Elso e2 = new Elso(); e2.Beallit(20);
public void beallit( int ujX, Elso this ) { this.x = ujX; } e1.Beallit( 10); -> Elso.Beallit(10,e1); e2.Beallit(20); -> Elso.Beallit(20,e2);
class MainClass { static void Kiir( TElso x){ Console. WriteLine( x); } static void Main() { TElso e = new TElso(); TMasodik m = new TMasodik(); Kiir( e); Kiir( m); // nem jó, „m” nem kompatibilis TElso- vel }
Pl (1/ b): class TElso { public string nev; } class TMasodik { } class MainClass { static void Kiir( TElso x){ Console. WriteLine( x. nev); } static void Main() { TElso e = new TElso(); TMasodik m = new TMasodik(); Kiir( e); Kiir( m); // nem lehet jó, „m. nev” nem létezik! } }
Pl (2): class TKiirjaMagat { public string uzenet = override public string { return public void Kiir() { Console. WriteLine( } }
” ”; ToString() ”Hello ”+ uzenet; } this );
//„saját maga”
class MainClass { static void Main() { TKiirjaMagat x = new TKiirjaMagat(); TKiirjaMagat y = new TKiirjaMagat(); x. uzenet = ”world!”; x.Kiir();// -> Console.WriteLine( x ); y.uzenet = ”világ!”;y.Kiir();// -> Console. WriteLine(y ); } }
class TMasodik: TElso }
{
// származtatás !!
class MainClass { static void Kiir( TElso x){ Console.WriteLine( x. szam ); } static void Main() { TElso e = new TElso(); Kiir( e); TMasodik m = new TMasodik(); Kiir( m); // jó, „m. szam” létezik,„m” kompatibilis TElso- vel } }
Két lépésben történ ö példánylétrehozás:
class TPont { int x; public void setX_ 1( int x){ x = x; // nem jó, ”x” a paraméter mindkétszer! }
}
class TElso { public int szam = 10; }
public void setX_ 2( int x){ this. x = x; }
// jó!
public void setX_ 3( int aX){ x = aX; }
// javasolt
”this”és az osztálymetódusok • A „this” a példányt azonosítja • Az osztálymetódus nem példányon keresztül kerül meghívásra • Az osztálymetódus belsejében nincs „this”! • A „this”- t láthatatlan paraméternek is nevezik, amelyet a metódus hívásakor a rendszer automatikusan ad át (aktuális paraméterlista), és vesz át (formális paraméterlista). • Ezért „this” nevű paramétert mi már nem írhatunk! (Egyébként sem, mert foglalt szó !)
TElso e; e = new TElso();
Típuskompatibilitás miatti értékadás: TElso e; //nem jön létre még a változó TMasodik temp = new TMasodik(); e = temp;
Ha a fenti helyes, akkor az alábbi is, mert ez ugyanaz, csak egy lépésben ... TElso e = new TMasodik();
//!!!!!jó
Nem megfelel ö típuskompatibilitás miatti hibás kód: TMasodik e = new TElso (); // !!!!! nem jó
12
Pl (3):
Pl (8):
class TElso { public int szam() { return 10; } }
class TElso { public int szam; public TElso() { szam= 10; } }
class TMasodik: TElso // származtatás !! { }
class TMasodik: TElso { new public int szam; public TMasodik() { szam= 20; } }
class MainClass { static void Kiir( TElso x) { Console. WriteLine( x. szam() ); }
}
static void Main() { TElso e = new TElso(); TMasodik m = new TMasodik(); Kiir( e); Kiir( m); // jó, „m” kompatibilis TElso-vel }
Pl (4): class TElso{ public int szam() { return 10; }
}
class TMasodik: TElso { // származtatás !! new public int szam() { return 20; }
}
class MainClass { static void Kiir( TElso x){ Console. WriteLine( x. szam() ); }
}
static void Main() { TElso e = new TElso(); TMasodik m = new TMasodik(); Kiir( e); // 10 Kiir( m); // ez is 10 !! (miért?!) }
Pl (5): class TElso{ public virtual int szam() { return 10; }
}
class TMasodik: TElso { public override int szam() { return 20; } } class MainClass { static void Kiir( TElso x) { Console. WriteLine( x. szam() ); } static void Main() { TElso e = new TElso(); TMasodik m = new TMasodik(); Kiir( e); // 10 Kiir( m); // 20 !! (miért?!) } }
Pl (6): class TElso { public virtual int szam() { return 10; }
}
class TMasodik: TElso { public override int szam() { return 20; } } class MainClass { static void Kiir(TElso x) {Console. WriteLine( x. szam());} static void Main( string[] args) { TMasodik m = new TMasodik(); Kiir( m); // 20 (persze) TElso e = new TMasodik() ; Kiir( e); // 20 (miért?!) (mi lesz a VMT- je ?!) } }
Pl (7): class TElso { public int szam; public TElso() { szam= 10; } } class TMasodik: TElso { public TMasodik() { szam= 20; } } class MainClass{ static void Kiir(TElso x){ Console.WriteLine(x.szam); } static void Main() { TElso e = new TElso (); TMasodik m = new TMasodik(); TElso h = new TMasodik(); // ez pedig így jó! :) Kiir(e); // 10 Kiir(m); // 20 Kiir( h); //20 (miért?!) (melyik konstruktor fut le utoljára?) } }
class MainClass { static void Kiir( TElso x) { Console. WriteLine( x. szam); } static void Main() { TElso e = new TElso(); TMasodik m = new TMasodik(); Kiir( e); // 10 (miért?!) // TElso. szam Kiir( m); // 10 (miért?!) // TElso. szam ! } }
#9 (típuskompatibilitás) Típuskompatibilitás • C#- ban ha nem jelölünk egy osztálynálöst,akkor automatikusan egy elöre definiált „Object” osztály lesz az ö se. • Ha egy osztály öröklödési láncán elindulunk felfele, elöbb-utóbb el kell jussunk a kiindulási ponthoz,amelynek a közvetlen ösemár az „Object” Ezért minden osztály mindig kompatibilis az Object típussal! ----------------------------------------------------------------------Univerzális paramétertípus class ArrayList // System.Collections. ArrayList { ... public void Add( Object o) { ... } } class Stack // System. Collections. Stack { ... public void Push( Object o) { ... } public Object Pop() { ... } } class Console // System. Console { ... public void WriteLine( Object o) { ... } }
Futás közbeni típusellenörzés • Mivel egy példány típusa nem egyértelm ű (lásd elözö példák), ezért szükséges lehet a tényleges típus ellenörzésére • Ezen típusellen ö rzés biztonsági okok miatt mindenképpen szükséges explicit típuskonverzió elött! • A legtöbb nyelv támogatja ezt valamilyen módon. A C#-ban az „is” operátorral lehet elvégezni. • Az „is”igazi jelentése: „kompatibilis-e ?” • „is”általános alakja: példánynév is osztálynév • az „is” operátor logikai értéket ad vissza. class TElso { public int szam; } class TMasodik:TElso { } class MainClass { static void Beallit(TElso x) { //mindenre igaz! if (x is Object) x.szam=10; if (x is TElso) x.szam=20; //„e”és „m”-re is igaz if (x is TMasodik) x.szam=30; //csak „m”-re igaz } static void Main() { TElso e = new TElso(); Beallit(e); TMasodik m = new TMasodik(); Beallit (m); } }
Futás közbeni típuskonverzió • Miután kiderült, hogy egy példány valójában más típussal is kompatibilis, elöfordulhat, hogy ki akarjuk használni ezen másik típus speciális lehetöségeit. • De ehhez a példány- ra típuskonverziót (típuskényszerítést) kell alkalmazni • A típuskényszerítés történhet „C”stílusban ((TMasodik)x).szam =10; • Az „as” operátorral (x as TMasodik).szam =10; • Módszert ö l függetlenül – ha a példány valójában nem kompatibilis a szóban forgó típussal, akkor futás közbeni hibát fogunk kapni
13
class TMasodik: TElso { public float jelzes; // +1 mezö } static void Beallit(TElso x) { if (x is TElso) x.szam=20; //„e”és „m”-re is igaz if (x is TMasodik) x.jelzes=30.3; //hibás !!! (x as TMasodik).jelzes =30.3; //„e”-re hibás!! if (x is TMasodik)(x as TMasodik).jelzes=30.3; //jó !!! } TElso e = new TElso(); TMasodik m = new TMasodik();
Beallit(e); Beallit(m);
Stack a = new Stack(); TElso e = new TElso(); a. Push ( e ); // odafele működik, TElso Y Object autom. e = a. Pop(); //visszafele nem, Pop() fv típusa Object ! e = a. Pop() as TElso; // így már jó e = (TElso) a. Pop(); // így is jó Object o = a.Pop(); if (o is TElso) e = o as TElso; ArrayList l = new ArrayList(); l.Add ( new TElso() ); TElso e; e =l[0]; // nem megy, Object-t ad vissza e =(TElso) l[ 0]; e =l[0] as TElso;
#10 (absztrakt metódusok) Absztrakt metódusok A fejlesztés során eljuthatunk egy olyan fázisba, hogy deklarálnunk kell egy olyan metódust, amelynek el tudjuk dönteni a paraméterezésének formáját, és a visszatérési típusát, de nem tudjuk megírni magának a metódusnak a kódját. class TAltalanosGrafikusObjektum { public int x, y; public Mozgat( int eltolasX, int eltolasY) { Letorol(); x = x + eltolasX; y = y + eltolasY; Kirajzol(); } public virtual void Letorol() { // ??? ide mit írjunk ??? }
}
public virtual void Kirajzol() { // ??? ide mit írjunk ??? }
Megoldást jelenthet a problémára, hogy elkészítjük a metódusokat üres törzzsel, virtuális metódusként, és rábízzuk a konkrétabb osztály tervez ő jére, hogy írja meg ő ket. Vegyük észre, hogy ezen metódusok mindig virtuálisak lesznek, mert az általunk megírt „Mozgat” metódus csak ekkor fogja tudni meghívni a konkrétabb, kifejlesztett metódust. A fenti megoldással két baj van • nem kényszeríti a továbbfejlesztő t a metódus felülírására (override), mert a virtuális metódusok nem kötelezőenfelüldefiniálandók. • Meg kell írnom egy fv-t, amelyet igazából nem tudok megírni. A megoldás, hogy az ilyen metódusokat „abstract” kulcsszóval megjelölöm: public abstract void Letorol(); public abstract void Kirajzol();
• A megjelölés egy jelzés a C# fordító felé, jelezve, hogy engedje meg, hogy ezen metódus törzsét ne írjam le. • Az abstract metódusok egyben virtuálisak is (virtual), ezért ezen kulcsszót nem lehet együtt használni az abstract kulcsszóval. • Csak példányszint ű metódus lehet abstract, ezért nem szerepelhet együtt a static kulcsszóval sem. • Lehet property is abstract. abstract public int x { get ; set ; }
Ha egy osztály tartalmaz abstract metódusokat, akkor magát az osztályt is meg kell jelölni az abstract kulcsszóval!
abstract class TAltalanosGrafikusObjektum { public int x, y; public Mozgat( int eltolasX, int eltolasY) { Letorol(); x = x + eltolasX; y = y + eltolasY; Kirajzol(); } public abstract void Letorol(); public abstract void Kirajzol(); }
• Az abstract osztályból származtatás esetén a gyermekosztályban lehetőség van (természetesen) az örökölt abstract metódusok felüldefiniálására (konkretizálására) az override kulcsszó segítségével. • De nem kötelez ő az összes abstract metódust kifejteni. • Ha egy ilyen gyermekosztályban még mindig van kifejtetlen abstract metódus (nem mindet írtuk felül konkrét metódussal), akkor ezen osztályt is meg kell jelölni az abstract kulcsszóval. • „abstract” jelzést olyan osztályra is rátehetek, amely nem tartalmaz abstract metódust. • Ezen osztály gyermekosztályáról már le lehet venni az abstract kulcsszót (akkor is, ha a gyermekosztály semmi mást nem tartalmaz pluszban). • „abstract” jelzésű osztályból példányosítani nem lehet, csak osztályszintű mez ő ket és metódusokat használhatok bel ő le. abstract class Elso //ebből nem lehet példány { static public int x; static public void kiir() {} } class Masodik:Elso { }
// ebből lehet példány
„sealed” osztályok • Az osztályt megjelölhetem „sealed” kulcsszóval, ez megakadályozza, hogy ezen osztályból származtatás útján gyermekosztályt hozhassak létre (nem továbbfejleszthet ő osztály, nem választható „ ős”-nek). • A „sealed” és az „abstract” nem szerepelhet együtt (értelemszerűen az abstract osztályokat kötelező továbbfejleszteni, mert nincsenek készen, a sealed-et nem lehettovábbfejleszteni =ellentmondás) „sealed =(levelet) lepecsétel, leplombál”.„sealed” „Viktor probléma”: hogy lehet olyan osztályt készíteni (amelynek csak osztálymetódusai vannak) amelyből nem lehet példányosítani? (a) Nem lehet ilyen osztályt létrehozni,a közös ős (Object) tartalmaz példányszint ű metódusokat, amelyeket mindenképpen örököl az adott „csak osztályszint ű ”objektumosztály (b) Ha megjelöljük „abstract” kulcsszóval, akkor nem lehet belőle példányosítani, de ez nem teljeskör ű védelem, mert készíthetünk belőle szár maztatott osztályt, amelyr ő l eltávolítjuk az „abstract” jelzést, és máris lehet példányosítani. (c) Ha megjelöljük „sealed”- el, nem lehet bel ő le származtatni, de példányosítani lehet. (d) Az „abstract” és a „sealed” nem szerepelhet együtt. Megoldás: constructorok elrejtése! class NemPeldanyosithato { private NemPeldanyosithato() {} // fontos,hogy PRIVATE ! } NemPeldanyosithato nmp = new ???; // nem megy! class Proba: NemPeldanyosithato // nem megy! { }
A fenti példában az osztály egyetlen konstruktora sem elérhető. Nem tudunk bel ő le példányt csinálni, mert a „new” után le kellene írni a konstruktor nevét! De a továbbfejlesztett osztályokból is meg kell hívni az ős konstruktorát (base!).
#11 (indexelõk) Indexelõ Akkor használjuk, ha az osztályt mint egy tömböt akarjuk kezelni: • Tömbszer ű szintaxissal • Ekkor valójában egy property-t írunk magához az osztályhoz • Ezen property neve kötelez ő en „this”
14
Pl. (vektor- szerű indexelő): class Verem { object[]tomb =new object[100]; public object this[int index] { get { if (0<=index &&index
Pl. (vektor- szerű indexelő ): class LancoltLista { public TListElem this[int index] { get { ... a láncolt listában az „index”-edik elem megkeresése return megtalalt_elem; } set { ... a láncolt lista „index”- edik elemének javítása, vagy új elem felvétele a láncolt listába ... (value) } } } class String { public char this[int index] { get { ... } set { ... }
jönezekre a példányokra is, újabb if- eket kell írni! Ez nem jó!! Mi a teendő , ha két teljesen különböző objektumosztályom van, amelyek nem kompatibilisek egymással, de vannak közös vonásaik? Pl. mindegyiknek van „kiir” metódusa! Hogyan tudok olyan eljárást írni, amelyik minden olyan példányra működik, akinek van kiir metódusa? A probléma bővebb: olyan kiir metódus kell, amelyik string- et ad vissza, és nincs paramétere! Ötlet: csináljunk erre egy segéd- osztályt! class TKiir { public virtual abstract string kiir(); } class TElso: TKiir // ( de mi legyen az eredeti TValamilyenOs- el? ) { public override string kiir() { ...; } } class TMasodik: TKiir { public override string kiir() { ...; } } void MeghivKiir( Tkiir x) { x. kiir(); }
Ötlet nem m ű ködik a gyakorlatban! A TElso- nek már van őse! Nem cserélhetem le ezt a fontos ő st a „TKiir”- ra! Egy osztálynak márpedig csak egy őse lehet! Interface = segéd osztály
Pl. (mátrix-szerű indexelő): class THaromszogMatrix { public object this[int sor,int oszlop] { get { ... } set { ... } } ... }
#12 (interface) Típuskompatibilitás!? class TElso: TValamilyenOs { public string kiir() { return ”Hello, én TElso vagyok!”; } } class TMasodik { public string kiir() { return ”Hello, én TMasodik vagyok!”; } } void MeghivKiir( ??? x ) { string s = x.kiir(); }
Mit válasszunk „x” típusának, hogy mindkét osztályból származó példányra m û ködjön a fenti példa? Ez csakis az Object lehet, azzal mindenki kompatibilis! (a) void MeghivKiir( Object x) { x.kiir(); // nem m ű ködik, az Object- nek nincs ilyen metódusa! } (b) void MeghivKiir(Object x ) { string s; if (x is TElso) s=( x as TElso).kiir(); //rendben if (x is TMasodik) s=( x as TMasodik). kiir(); //rendben } De hogyan tovább!? Mi van, ha lesznek újabb osztályok is, akiknek szintén van „kiir” metódusuk? Hogy a „MeghivKiir” jól mûköd-
interface IKiir { string kiir(); } class TElso: TValamilyenOs, IKiir { public string kiir() {return "Hello, én TElso vagyok"; } } class TMasodik: IKiir { public string kiir() {return "Hello,én TMasodik vagyok";} } void MeghivKiir( Ikiir x) { x.kiir(); }
• Az interface belsejében csak fv-eket, és propertyket, és indexelőket lehet definiálni. interface IGrafikusObjektum { void kirajzol(); // fv int x // property { get; set; } }
• Az interface belsejében definiált „dolgoknak” nem lehet elérhetőségi módosítójuk (private,stb.) • Nem lehet sem „static”, sem „virtual” módosítóik • Automatikusan „abstract” módosítójuk van, tehát sosincsenek kidolgozva! Egy osztály ha implementál egy interface- t, akkor az interface-ben feltüntetett minden fv- t, property- t, indexel ő t implementálnia kell! Ha bármit kihagyunk, hibajelzést kapunk! • Az interface- t implementáló osztályban az a kidolgozott „dolgok” kötelez ő en „public” módosítóval kell rendelkezniük! • Az interface olyan, mintha ő s lenne. Az ő t implementáló osztály innent ő l kezdve típuskompatibilis az interface- el! Interface-t támogató nyelvek • Az egyébként egymással nem kompatibilis osztályok közötti „rokonsági” kapcsolatot írják le az interface-k (vannak bennük közös vonások, egyfor ma nevű és paraméterezésű metódusok). • Egy osztálynak csak egy őse lehet,de tetszőleges számú interface-t implementálhat. • Ilyen nyelvek: o Delphi o Java o C#
15
Interface-t nem támogató nyelvek • Ezen nyelveken egy objektumosztálynak több ő se is lehet • Ekkor az adott osztály mindkett ő t ő l egyszerre örököl, és mindkettővel kompatibilis • Ezen nyelveken az interface helyett ténylegesen osztályokat hozunk létre A több ős problémákat is felvet, mert a több ősnél lehetnek egyforma nevű mezők, egyforma nevű és paraméterezésű metódusok. A gyermekosztályban, és a belőle származó osztályokban e pillanattól kezdve minősített nevet, vagy típuskényszerítést kell használni a konfliktusok kezelésére. # include <stdio. h> class elso { public: int a, b; elso( void) {a= 1; b= 2;} int getb( void) {return b;} }; class masodik { public: int c; float b; masodik( void) {b= 1.0; c= 3;} float getb( void) {return b;} }; class harmadik: public elso, public masodik { public: int d; harmadik( void) {d= 4;} }; int main() { elso a1; masodik a2; harmadik a3; printf(" printf(" printf(" printf(" printf("
}
a1. a2. a3. a3. a3.
a=% b=% a=% b=% b=%
A névtér nem csak egy darabból állhat: namespace HalozatokBT.Internet.HTTP { class SuperString { ... } }
Ekkor az osztály pontos neve: namespace SajatProgram { public static void Main() { TomoritoKFT.Internet.HTTP.SuperString s1; } }
A névtéren belül lehet beágyazott névtér: namespace HalozatokBT { namespace Internet { namespace HTTP { class SuperString { ... } } } }
Ezen osztály teljes neve úgyanúgy TomoritoKFT.Internet.HTTP.SuperString s1;
A gépelést rövidítendő : using d f d d d
a1. a2. a3. a3. a3.
b=% c=% c=% b=% b=%
d\ d\ d\ f\ f\
n n n n n
", a1. a, a1. b); ", a2. b, a2. c); ", a3. a, a3. c); ", a3. elso:: b, a3.masodik::b); ",( elso) a3.b, (masodik) a3.b);
printf(" a3. get=% d\ n ", a3. elso:: getb()); return 0;
#13 (névterek) Névtér Nagyobb méret ű program esetén az azonosítók választása problémás lehet már – elfogynak a jó nevek. Külső (third party) objektumok alkalmazása esetén névütközések szinte elkerülhetlenek. Pl: két különböz ő cég által megírt külső DLL- ben is van SuperString nevű objektumosztály, amely valamilyen formában különleges stringeket kezel (pl. tömörítve tárolt extra hosszú string-eket). A programunkban mindkett ő t használni akarjuk. A probléma az, hogy a név mindkét esetben ugyanaz, de maga az osztály természetesen nem. Ekkor a két azonosító elfedi egymást, és nem egyértelmű , mikor melyiket használjuk. A probléma rokon azzal, ha van egy küls ő („ globális”) változónk, és egy lokális (pl.paraméter) változó is, amelyeknek a neve elfedi egymást. Az egyforma nev ű osztályok esetén a megoldást a névtér biztosítja. (A .NET- en belül pl. három timer osztály van: System.Timers.Timer, System.Threading.Timer, System.Windows.Forms.Timer). namespace TomoritoKFT { class SuperString { ... } } namespace HalozatokBT { class SuperString { ... } } namespace SajatProgram { public static void Main() { TomoritoKFT.SuperString s1 =new TomoritoKFT.SuperString(); } }
using TomoritoKFT.Internet.HTTP; namespace SajatProgram { public static void Main() { SuperString s1; //itt már nem kell kiírni a teljes nevet } }
Alias létrehozása: using alias1 =TomoritoKFT.Internet.HTTP; { public static void Main() { alias1.SuperString s1; //itt az alias név is elég } }
A névterek bővíthetők,vagyis egy már létez ő névteret újból használatba véve az újonnan definiált osztályok pluszban b ő vítik a névtér tartalmát. Ha egy objektumosztályt nem teszünk névtérbe (nem kötelez ő ), akkor az a globális (névtelen) névtérbe kerül. using System; class MyClass { ... }
A globális névtér mindig nyitott, könny ű használni, de nem tegyünk bele túl sok mindent, mert könnyen ütközések lehetnek. Ha egy adott névtér belsejében a névtér „nyitott” mintha eleve kiiírtuk volna a „using nevter” sort. namespace Sajat { class Elso { ... } } namespace Sajat { class Masik: Sajat.Elso { ... } // nincs rá szükség class Masodik:Elso { ... } // ennyi is elég }
Ez akkor is működik, ha a fenti sorok egyetlen modul belül vannak (.cs file), illetve ha külön modulban vannak (kett ő db .cs file). using Sajat; // erre itt nincs szükség igazából namespace Sajat { class Masodik:Elso { ... } }
16
#14 (referencia-elv) Referencia-elv • Az objektum-példány definiálása önmagában nem jelent memóriafoglalást. • A példányhoz tartozó memóriafoglalás a „new” operátor hívásakor valósul meg. • A „new” nem a példány alaptípusának foglal helyet, hanem a konstruktor típusa dönti el a memóriafoglalást! • A „new”-al kötelez ő megadni egy konstruktor-t is. • A példány e pillanattól kezdve ezen memóriaterületre vonatkozó referenciát (hivatkozást) tartalmaz. • Ez nagyon hasonlít egy pointerre. • A különbség az, hogy az objektumhoz tartozó memóriaterületet az operá ciós rendszer „elmozgathatja”, a referencia azonban továbbra is azonosítja a területet. • A pointer a memória kezd ő címét tartalmazza, ha az operációs rendszer „elmozgatja” a foglalt területet, a pointer már rossz helyre mutatna. • Ha két példány között értékadás m ű veletet hajtunk végre, akkor a referencia másolódik csak át: TMasodik m1 = new TElso(); TMasodik m2 = m1;
• Ugyanez történik objektumokkal kapcsolatos paraméter-átadásakor is! void Berak(TMasodik m) { m. szam = 20;} TMasodik x =new TMasodik(); x.szam =10; Berak(x ); Console.WriteLine(x.szam );
//20 !!!!
• Az objektum típusú paraméterek ezért akkor is „ref” típusú paraméterek, ha azt nem jelöljük a paraméter-módosítóval.
Ha C#- ban a struct-al definiálunk egy példányt • Nem kell a new kulcsszót használni a példányosításhoz • Ha nem használjuk a new kulcsszót, akkor a struktúra mezőinek nem lesz kezdőértéke (memóriaszemét) • Ilyen osztály mezőire nem használhatunk kezdőértékadást. • Ezért a struct- nak általában van konstruktora • A paraméter nélküli (default) konstruktort a nyelv automatikusan készíti. Mi csak paraméteres konstruktort készíthetünk! • A struct típus korlátozott OOP tulajdonságokkal rendelkezik, pl. nem működik rá az öröklődés semmilyen formában (egy struct nem lehet ő s, ő nem lehet gyermek) • Ugyanakkor a struct őse is az Object • Viszont implementálhat interface-t • A példányosítás kevesebb memóriát igényel, mert a példány nem kerül +4 byte-ba a referencia tárolása miatt. A nyelv alaptípusai (int, double, boolean, char, ...) mind struct típusúak, hogy a kifejezésekben a szokott módon használható legyenek. • Amíg a struktúra mez ő i nincsenek feltöltve, addig nem használható • Ezért az „ int a; ” deklaráció után az „a” változó még nem használható fel. Az „ int a= 0; ” után már igen! • Ez akár az alábbi formában is írható: „ int a = new int();” Referencia-elv el ő nyei: • garbage collector m ű ködését támogatja • nem pointer, ezért a memóriaterület áthelyezhet ő (windows memóri akezelés mellett ez sűrűn elő fordul :) ) Hátrányai: • Nem minden esetben egyértelm ű a viselkedés. Pl:
Null-referencia • A példányok valójában referencia-típusúak (reference type) TMasodik m;
Ez egy „m” nev ű referenciát definiál, mely 4 byte- ot foglal el, és induláskor még nem tartalmaz referenciát egyetlen memóriaterületre sem: • A nem létez ő referencia a „null” konstans. Pl: if (e== null) Console.WriteLine(”A példány még nem létezik”);
Referencia és típuskompatibilitás TMasodik m = new TMasodik (); TElso e = m; // rendben, típuskompatibilis e. szam = 10; Console. WriteLine( m.szam ); // 10-et ír ki
• „e” használható, de mivel az ő alaptípusa TElso, a fordító úgy tekint rá, mintha ő valódi TElso lenne, ezért „e”- nek csak olyan mezői és metódusai vannak (szerinte), amelyek a TElso- ben vannak definiálva. • „e” ugyanaz mint az „m”, de visszabutítva TElso szintre, vagyis „e” mezőinek az értéke ugyanaz, mint „m”- nek, de a korai kötések a TElso szerint működnek. • Az „e” VMT- je a TMasodik- é, ezért a kés ő i kötések a TMasodik szerint m ű ködnek! TElso e; //nem jön létre még a változó TMasodik temp = new TMasodik(); e = temp;
Összevonva egy sorba: TElso e = new TMasodik();
//!!!!! jó.
Minden olyan „változó” referencia elven van kezelve, amelynek típusa class”. Ez nem minden esetben jó! int a = 10; int b = a; a = 20; // b= 20 szintén !?! nem !!!
A nyelv építőkő jellegű alaptípusai nem lehetnek referencia-elvűek. Ezen típusok érték típusú (value type) adatok. Értékadáskor csak a bennük tárolt érték másolódik át. Az OOP nyelveken nem lehet olyan típus, amelyik nem OOP típus! Ezért a nyelvek kétfajta objektum- típust támogatnak (kétféle módon lehet osztályt definiálni): • A „class” kulcsszóval -> referencia elv ű példány • A „struct”kulcsszóval -> érték típusú példány • Az enumeration típusú változók is érték típusúak.
class THallgato { public string nev; public int[] jegyek= new int[ 20]; } class IskolaiOsztály { private System.Collections.ArrayList tanulok; public THallgato this[ int index] { get { return (THallgato) tanulok[index]; } } }
Ebben az esetben azt hihetnénk, hogy a csak olvasható propertyben visszaadott THallgato is csak olvasható lesz. De nem! int[] vektor = new int[10]; foreach( int a in vektor) { a = 10; // nem megy, a ciklusváltozó csak olvasható! } -----------------------------------------------------------THallgato[] vektor = new THallgato[10]; foreach(THallgato a in vektor) { a.nev = ”ismeretlen”; // m ű ködik, mert a ciklusváltozó csak egy ref. } -----------------------------------------------------------void akarmi( int a) // érték szerinti paraméterátadás { a = 10; } int x = 20; akarmi( x); Console. WriteLine( x); // még mindig 20 -----------------------------------------------------------void akarmi( THallgato a) // érték szerinti paraméterátadás, de ref ! { a. nev = ”Jancsi”; } THallgato x = new THallgato(); x. nev = ”Juliska”; akarmi( x); Console. WriteLine( x. nev); // ez már Jancsi
A string típus azonban referencia típusú, de az értékadás mindig új string létrehozását jelenti! -----------------------------------------------------------void akarmi(string a) // érték szerinti paraméterátadás, de ref ! { a =”Jancsi”; // új string létrehozása !!! } string x =”Juliska”; akarmi(x); Console.WriteLine(x.nev); //ez még mindig Juliska -----------------------------------------------------------string a = ”Jancsi”; a =a. ToUpper();//ennek során valójában új string jön létre, //és a régi megszűnik (garbage collector !)
17
-----------------------------------------------------------a=a+”és Juliska”; //ennek során új string jön létre, amely értéke //a ”Jancsi és Juliska”,és a régi string megszűnik
-----------------------------------------------------------string a =”Jancsi”; Console.WriteLine(a+”és Juliska”);
A fv hívásakor (röptében) létrejön egy string, amely tartalmazza az összefűzött szöveget, az „a” továbbra is csak a „Jancsi” szöveget tartalmazza a fv futása után is (nem változott a referencia).
#15 (garbage collector) Memóriakezelés Egy számítógépes program alábbi változótípusokkal dolgozik: • statikus: egy „globális” memóriaterületen tárolódik az értéke, mely terület a program indulásának pillanatában foglalódik le (allokálódik), és a program futásának befejésekor szabadítódik fel. o Ezen területen tárolt változók értékei a program futásának végéig megőrződnek, ”biztonságban” vannak. o Ezen terület induló méretét a compiler számolja ki a programban definiált statikus változók összmérete alapján. • automatikusan dinamikus: a lokális változók és paraméterek. Ezen változók egy erre a célra fenntartott,szintén a program indulásának pillanatában lefoglalt memóriaterületen a „stack” területen kerülnek elhelyezésre. o Ezen terület összmérete induláskor eldől,később nem növelhető. o Ha a terület elfogy, akkor „stack overflow error” futási hibával a program kiakad (ez a rekurzív programoknál fordul elő gyakran) Az ilyen változók területfoglalását, és felszabadítását az alprogramba lépéskor és kilépéskor a programnyelv automatizmusa végzi el. • manuálisan dinamikus:A programozó pointereket vagy referencia típusú változókat definiál, és a másodlagos adatterület lefoglalását és felszabadítását maga kezdeményezi megfelelő függvények hívásával (pl:malloc() és free() ) o ennek hátránya, hogy - a programozó elfelejtheti lefoglalni a memóriát (ritkábban) - elfelejti felszabadítani (sűrűbben) • félmanuálisan dinamikus: A programozó referencia típusú változókat definiál, és a másodlagos adatterület lefoglalását maga kezdeményezi megfelelő függvények hívásával (pl: new), de a felszabadítását ő már nem kezdeményezheti. Erről a nyelv egy automatizmusa gondoskodik, a garbage collector. Garbage collector A garbage collector (szemétgyűjtő) egy algoritmus, amely megkeresi a memóriában azokat az objektum-példányokat, amelyek a program egyetlen változójából kiindulva sem elérhetőek. Ezen memóriaterületek felkutatása nem minden esetben triviális feladat. Pl: static void Kiiras() { Jatekos j = new Jatekos(); ... }
A fenti sor hatására létrejön egy példány a memóriában, melynek a referenciaszámlálója 1 lesz a fv futási ideje alatt. A fv- ből kilépéskora „j” változó megszűnik létezni, az általa hivatkozott példány referenciaszámlálója lecsökken 0ra. Ezen esetekben a gc ezt a példány fel tudja szabadítani. static void Kiiras(string s) { ... } string nev = ”Jancsi”; kiiras(nev);
A fenti példában a fv futási idejére létrejön egy string változó (paraméter), mely szintén a „nev” változó területére vonatkozó referenciát tárol. A fv lefutása után ezen „s” megszűnik, de a példány nem felszabadítható, mert a példányra van még referencia (a nev változó). class Jatekosok { TJatekos[] vektor = new TJatekos[10]; void JatekosHozzaad() { TJatekos j = new TJatekos(); j.nev = ”Jancsi”; vektor[0] =j; } }
Bár a fv lefutása végén a „j” megsz ű nik, de a példány nem felszabadítható, mert még mindig van rá referencia, a vektor 0. eleme.
void akarmi() { Jatekosok csapat = new Jatekosok(); ... }
Ezen fv lefutása után megsz ű nik a csapat, ezzel együtt megsz ű nik a vektor, amely több játékost tartalmaz. E miatt hirtelen n db Jatekos objektum példányra sz ű nik meg a referencia, ezeket mind fel lehet szabadítani. static void Verem () { Object tomb = new Object[100] int vm = 0; void Push( object o) { tomb[ vm]= o; vm++; } object Pop() { vm--; return tomb[ vm]; } } Verem v = new Verem(); v. Push( new Jatekos() ); // tomb[ 0]- ba, ezért „megmarad” v.Pop(); // a tomb[ 0] ettől még őrzi !!! v.Push(new Jatekos()); // felülírja a tomb[ 0]- t, // a régi példány felszabadítható ! object Pop() // javított POP() { vm--; object o = tomb[vm]; tomb[vm] = null; return o; }
--------------------------------------------------------------------------------------class TListaElem { TListaElem kov; string adat; }
A fenti elemekből felépített láncolt lista elemeit azért nem szünteti meg a gc, mert az előző listaelem „köv” mezője hivatkozik rájuk. Egy láncolt lista törlésekor megtehetjük azt, hogy a listaelemek „köv” mezőjét „null”-ra állítjuk, mert ekkor megszüntetjük a következ ő listaelemre vonatkozó referenciát, s ekkor a gc ezt észreveszi, és törli ezen elemeket. A másik, egyszerűbb módszer, hogy a listafejnél töröljük az első elemre mutató referenciát „null” értékre állítással. Ekkor a lista első elemére már nem lesz rámutató referencia, ezért ő t megszünteti a gc, de ekkor észereveszi, hogy a lista második elemre mutató referencia is megsz ű nik, ezért ő t is törli, ... Ennek megfelelően a gc lassanként „megeszi” a teljes listát. Ezen módszer nem kevésbé lesz hatékony, mint a „gondos programozó” módszere. class TKetiranyuListaElem { TListaElem kov; TListaElem elozo; string adat; }
A kétirányú láncolt lista elemei nemcsak előre, de vissza mutató referenciát is tartalmaznak. Ekkor az utolsó elem kivételével minden listaelem hivatkozási számlálója 2, mert az i. elemre az i- 1 elem köv mezője, és az i+ 1 elem elozo mezője is hivatkozik. A lista fej elemének „null”- ra állítása mellett az elemek nem megszütethetőek lesznek, mert az első listaelem referenciaszámlálója ekkor még csak 1- re csökken le, hiszen a 2. listaelemnek van egy elozo mutatója, amely még hivatkozik rá. A jó minőségű gc algoritmus képes az ilyen objektum-csoportok felkutatására, amely egy önnmagában zárt lánccsoportot alkot, de a program jelenleg élő változói közül egyik sem mutat a lánccsoport belsejébe. Ennek felfedezése és a teljes csoport feltérképezése után a teljes csoport törölhető. A gc algoritmus bonyolultabb, sok változót tartalmazó programok esetén komoly keresztreferencia táblázatokat és összefüggőségi gráfokat épít fel futás közben, és kezel le. Mikor mûködik a gc? Lehetőségek: • A gc akkor fut, amikor van rá egy kis id ő . Pl. a program jelenleg nem dol gozik, mert eseményre vár (pl. adatbevitelre) (idle time). • A gc minden memóriafoglaláskor fut, a memória kiosztás előtt „szétnéz”, hogy van-e felszabadítható hely. • A gc mindig fut, mert külön szálon indult el a program indulásának pil lanatában, és folyamatosan gyüjtögeti az információkat. • A gc akkor fut, amikor a programozó elindítja // System.GC.Collect() metódus //.
Mit nem tud kezelni a gc? • Az érték típusú változókat (struct,...) • A pointereket A pointerek használatára van lehet ő ség C#- ban, de ellentétes az gc elveivel. Ha a programozó C#- ban ragaszkodik a pointerek használatához, úgy magának kell gondoskodni a memória felszabadításáról.
18
Életciklus 1. „live” státusz az objektumra a new operátor és a konstruktor hívása után 2. „no longer in use” státusz, ha az objektum már soha többé nem lesz elérhet ő a program futtatása során 3. valamivel kés ő bb lefut a destruktor 4. „inaccessible” státusz a destruktor lefutása után 5. a gc felszabadítja a memóriát using System; class A { ~A() { Console. WriteLine(" Destruct instance of A"); } } class B { object Ref; public B( object o) { Ref = o; } ~B() { Console. WriteLine(" Destruct instance of B"); } } class Test { static void Main() { B b = new B( new A()); b = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }
A b=null hatására mind az „A” beli, mind a „B” beli példány „no longer in use” lesz. Hogy milyen sorrendben hívódik meg a destruktoruk, az viszont nem egyértelmű (nem specifikált a nyelv leírásában). using System; class A { ~A() { Console. WriteLine(" Destruct instance of A"); } public void F() { Console. WriteLine(" A. F"); Test. RefA = this; } } class B { public A Ref; ~B() { Console. WriteLine(" Destruct instance of B"); Ref. F(); } } class Test { public static A RefA; public static B RefB; static void Main() { RefB = new B(); // B-re egy hivatkozás RefA = new A(); // A-ra egy hivatkozás RefB.Ref = RefA; // A-ra még egy hivatkozás RefB = null; // B-re nincs már hiv. RefA = null; // A-ra sincs már külső hiv! //A and B now eligible for destruction GC.Collect(); GC.WaitForPendingFinalizers(); // B now eligible for collection,but A is not if (RefA !=null) Console.WriteLine("RefA is not null"); } }
Az „A” és „B” destruktorok hívása csak „B” és „A” sorrendben jó, mert az „A”- ra van még m ű köd ő hivatkozás „B”- ből,melyet a „B” destruktorában még ki is használunk. Ha el ő ször az „A”- t szünteti meg a „gc”, akkor a „B” destruktorának közepén a Ref. F() fvhívás már futási hibát fog generálni ! Amennyiben az „A”- ban is van kereszthivatkozás „B”- re (alkalmasint az „A” destruktorában), akkor ezek megszüntetésének nincs jó sorrendje. A programozási nyelv számára csak az van definiálva, hogy a memóriafelszabadítást egyfajta garbage collector fogja végezni. A gc konkrét mûködésérõl a nyelv maga nem rendelkezik!
#16 (boxing és unboxing) Boxing és unboxing Ha van egy „value type” változónk (struct), és nekünk át kell alakítani referencia- típusúvá (mert olyan helyen akarjuk használni), akkor BOXING-t végzünk. boxing = value -> reference Pl: ArrayList r = new ArrayList(); int i= 10; r. Add( i);
A fenti sor nem m ű ködik, mert az .Add metódus m ű ködését tekintve valójában az átadott példány referenciáját menti el ... de az i változónak nincs referenciája, mert ő nem referencia elv ű . A boxing során létrejön egy object osztályból származó példány, amely magába foglalja (körülöleli) a struct-ban tárolt információt. int i=10; object o = i; r.Add(o );
// boxing
A létrejött példány lemásolja az i változó memóriaterületét, ezért az i értéke később megváltoztatható, ez nem változtatja meg a létrejött object által tárolt értéket: ArrayList r = new ArrayList(); int i=10; r.Add(i); i =20; ... object o =r[0]; i =(int)o; //i =10 újra
//unboxing
rövidebben írva: i = (int) r[0]; //i =10 újra
//unboxing
UNBOXING a visszaalakítás, amikor az object-b ő l visszanyerjük a benne tárolt értéket. Természetesen ügyeljünk rá, hogy ugyanazon típusba konvertáljuk vissza a benne tárolt értéket. object o =r[0]; i =(int)o; //i =10 újra i =(int)r[0]; //i =10 újra
//unboxing //unboxing
Megj: A string típus nem value type! object = System.Object int = System.Int32 string = System.String ...
#17 (enumeration) Enumeration Az „enum” egyszerre állít el ő egy felhasználói típust, és konstansok sorozatát: enum Color { Red, Green, Blue }
Ezen típus felfogható úgy is, mint egy objektumosztály, és a benne lévő azonosítók az osztályon belül definiált konstansok lennének. Ennek megfelelöen változót készíteni: Color szin = Color.Red;
//mintha static mez ő lenne !
void Atszinez(Color ujszin) { ... } ... Atszinez(Color.Green ); ...
Az enum típusú változó helyigénye automatikusan int. Ha ettől el akarunk térni, akkor meg kell adni az alaptípust: enum Color:uint { Red ,Green , Blue }
Az azonosítók értékkel történ ő összerendelése automatikus: Red= 0, Green= 1, Blue= 2. Ez akkor derül ki, ha típuskényszerítjük: int kod = (int) szin;
Az automatikus kiosztást meg lehet változtatni: enum Color { Red, Green = 10, Blue }
enum Color { Red, Green = 10, red = Red }
19
#18 (kivételek) Kivételek A program bizonyos pontjain olyan szituációkkal találkozhatunk, amelyet akkor és ott nem tudunk lekezelni, de ugyanakkor a futást semtudjuk folytatni. Pl: class Stack { ... object Pop() { if (vm> 0) { vm--; return tomb[ vm]; } else ??????; } }
Ebben a kódrészletben megoldható lenne, hogy hangüzenetet adunk (beep), vagy kiírhatunk a konzolra egy hibaüzenetet (de mi van,ha windows- os a fő program?), stb...
A System. Exception az általános hibajelenségek leírásakor használjuk. A hiba okait típusuk szerint csoportosíthatjuk (pl.): System. System. System. System. System. System. System. System. System.
ArithmeticException ArrayTypeMismatchException DivideByZeroException IndexOutOfRangeException InvalidCastException NullReferenceException OutOfMemoryException StackOverflowException TypeInitializationException.
Mindegyik speciálizált Exception osztálynak közösőse a System.Exception! De további ős-gyermek viszony is megfigyelhet ő a kivétel-osztályok között (kivétel-hierarchia)!
Az általánosan megírt objektum mit sem tud arról a környezetr ő l (a f ő programról), amibe beágyazva futtatják.Ezért mit sem tud a hibajelzés módjáról.
System.Object System.Exception System.SystemException System.IO.IOException System.IO.DirectoryNotFoundException.
Ráadásul nagyon fontos, hogy az ő t futtató f ő program értesüljön a hibáról, mert lehet, hogy az számít a problémára, és fog is tudni vele mit kezdeni.
Ha jobban akarjuk specifikálni a hibát, akkor specifikáltabb hibát dobhatunk!
Ezen szituációk egy része a főprogramban megelőzhető lenne:
throw new FileNotFoundException(” A program INI file- ja sehol!”); ... catch (FileNotFoundException e) { ... }
Stack v = new Stack(); if (!v.Tele()) v.Pop(); //csak akkor Pop(),ha van még elem!
De némelyik nem (text file megnyitása): using System.IO; StreamReader INI = new StreamReader( FName);
A fenti példa megpróbál létrehozni egy StreamReader objektumot, amellyel jelen példában egy file-ból lehetne olvasni. De ha a file nem létezik, vagy létezik, de nem megnyitható (mert pl. nincs rá olvasási jogunk, vagy valaki más már megnyitotta), akkor a nyitás nem sikerülhet. Hogyan jelezzen hibát a StreamReader konstruktora?
Megoldható lenne, hogy ekkor nem engedi létrehozni a példányt, és a futás után az „ INI ” változó „ null ” lesz. De ez csak azt jelezné: „valami hiba történt”, és nem adna információt arról, hogy mi volt a hiba oka.
Az „e” változó felveszi a létrehozott FileNotFoundException- ból létrehozott példány referenciáját, így az e. Message a szóban forgó üzenet lesz. A metódusok leírásánal olvasható a HELP- ben, hogy hiba esetén azt hogyan jelzik: public StreamReader( string path ); Exceptions Exception Type ArgumentException ArgumentNullException FileNotFoundException DirectoryNotFoundException IOException
Condition path is an empty string (""). path is a null reference (Nothing in Visual Basic). The file cannot be found. The directory cannot be found. path includes an incorrect or invalid syntax for file name, directory name, or volume label.
A legtöbb „régi” megoldás szerint majdnem mindent függvények formájában írtak meg, és a függvény minden esetben egy egész számmal (egy hibakóddal) tért vissza.
Ha egy metódus kivételt dob, akkor elkaphatjuk csak a bennünket érdeklő típusú kivételeket, a többit továbbengedhetjük:
Ez egy működő megoldás volt, de sok programozási pluszmunkát jelentett a visszaadott hibakódot „visszapasszolgatni” az egymást hívó függvények mélységéből a problémát kezelő „felsõbb” szintû függvényig.
try { StreamReader r = new StreamReader(” program. ini”); } catch (FileNotFoundException e) { ... } catch (DirectoryNotFoundException e) { ...}
A megoldást a kivételek jelentik: • A kivétel egy jelzés, amelyet a programozó maga indít útnak. • A jelzés indítása után a függvények elkezdenek „visszatérni” (terminálni) a hívó pontra (átugrován a még végrehajtatlan sorokat). • Ez a visszatérés mindaddig folytatódik, amíg valaki a jelzést el nem „fogja”. • Ha senki semfogja meg, akkor a program befejezheti a futását (Console Application), vagy hibaüzenetet írhat ki, és folytathatja a futását (Windows Application). throw - try - catch double hanyados( int x, int y) { if (y!= 0) return (double) x/ y; else throw new Exception(” Nullával nem tudok osztani!”); } double sokhanyados() { double a = hanyados( 1,3); double b = hanyados( 2,0); double c = hanyados( 3,4); // ez már nem számolódik ki return (a+ b+ c)/ 3; // ez sem hajtódik végre } void teszteles() { try { Console. WriteLine(” Átlaguk:{ 0}”, sokhanyados()); // ... ez itt már nem kerül végrehajtásra ... } catch (Exception e) { Console. WriteLine(” Hiba:{ 0}”, e. Message; } }
Ekkor már működik a típuskompatibilitás: System.Object System.Exception System.SystemException System.IO.IOException System.IO.DirectoryNotFoundException System.IO.EndOfStreamException System.IO.FileLoadException System.IO.FileNotFoundException System.IO.PathTooLongException
A catch (System.IO.Exception e) el fog kapni bármilyen olyan kivételt, amely kivételosztály bel ő le lett származtatva: Ha elkaptunk egy kivételt, de később kiderül, hogy mégsem kellett volna elfogni, akkor újra feldobhatjuk: catch (FileNotFoundException e) { ... if ( ... ez nem ránk tartozik ... ) throw (e); }
Ha nem akarjuk felhasználni a kivétel példányt, akkor nem muszáj változót deklarálnunk: catch (FileNotFoundException e) { Console. WriteLine(” ajjaj!”); //itt szólni fog a C# fordító, } // hogy az „e” változót nem használtuk fel catch (FileNotFoundException) { Console. WriteLine(” ajjaj!”); // itt már nem }
20
Befejezõ utasítások:
Ha a metódus már belekezdett valamibe, és a metódus közepén keletkezett egy kivétel, akkor elképzelhető , hogy a metódus nem engedheti el szó nélkül ezt a kivételt: void FTP_DOWNLOAD( string file_url) { ... hálózati kapcsolat kiépítése ... try { ... ciklus amíg file nem nem tölt ő dött ... ... file rész fogadása ... } catch (Exception e) { ... hálózati kapcsolat zárása ... //ha hiba volt, akkor is! throw (e); // a kivétel továbbdobása, a fv terminál } ... hálózati kapcsolat zárása ... }
try - finally void FTP_DOWNLOAD(string file_url) { ... hálózati kapcsolat kiépítése ... try { ... ciklus amíg file nem nem tölt ő dött ... ... file rész fogadása ... } finally { ... hálózati kapcsolat zárása ... //ha hiba volt, ha nem ! } // a hiba ezek után magától továbbdobódik ! }
#20 (operátorok) Operátorok Az objektum- osztályokhoz operátorokat is definiálhatunk. Ezzel a kidolgozással közelíthetjük az objektum- osztályainkat a beépített típusok használhatóságához. Három fajta operátort adhatunk az objektum- osztályhoz: - Unáris (egyoperandusú) - Bináris (kétoperandusú) - Konverziós (egyoperandusú) public static MyClass operator+( MyClass a, MyClass b) { return new MyClass( a. MyField + b. MyField); }
Unáris operátorok public static result-type operator unary-operator (op-type operand)
„result-type” „unary-operátor” „op-type” „operand”
kötelezően ugyanaz,mint az osztály típusa a következők egyike:+ - ! ~ ++ -kötelezően ugyanaz, mint az osztály típusa a paraméter neve (szabadon választott)
class Datum { private int ev, honap, nap; // (A) megoldás public static Datum operator ++(Datum x) { return new Datum(x.ev,x.honap,x.nap+1); } //(B)megoldás (még op.overloading mellett sem lehet mindkettõ public static Datum operator ++(Datum x) { x.nap++; return x; } } Datum d = new Datum(); d++; uaz mint d = Datum.++(d);
class Datum { ... public static Datum operator - (Datum d, int n) { return ...; }
}
public static int operator - (Datum a,Datum b) { return ...; }
A fenti példa az operátor overloading miatt egy időben is létezhet egy osztályon belül. Konverziós operátorok Az implicit konverziós operátort a C# fogja meghívni automatikusan ha típusillesztésre (típuskonverzióra) van szükség - függvény híváskor - értékadáskor - kifejezésekben - ... Az explicit konverziós operátort a programozó által leírt típuskonverziók esetén használt. public static implicit operator conv- type- out ( conv- type- in operand ) public static explicit operator conv -type -out ( conv -type -in operand )
A konverzió a „conv- type- in” típusról „conv- type- out” típusra történik. Értelemszerűen a két típus közül az egyiknek az osztály típusának kell lennie. class Datum { ... public static implicit operator string(Datum d)
//Datum ->string
{
}
return d.ToString(); } public override string ToString()//egyébként is hasznos !!!! { return ev.ToString()+"."+honap.ToString()+"."+nap.ToString(); }
Datum d = new Datum(); // implicit típuskonverzió string s = d;
Ha „explicit” operátort írtunk volna, akkor a fenti sor: // explicit típuskonverzió string s = (string) d; // t. ToString() hívása Console. WriteLine( d); // explicit típuskonverzió hívása, // majd a kapott típusra a ToString() hívása Console. WriteLine(( string) d);
#21 (DLL írása) DLL A DLL a Dynamic-Link Library a Windows alatt egy eljárás és függvénygyűjteményt jelentett. Egy ilyen DLL több alkalmazáshoz is hozzátartozott. Ez helytakarékossági megfontolások, és update megfontolások miatt volt (a DLL-ben lévő hiba javítását minden őt használó alkalmazás megérezte). .NET alatt ennél több: objektumok gyűjteménye.
Bináris operátorok DLL készítése: File / New Project / Class Library. public static result-type operator binary-operator (op-type operand,op-type2 operand2); „result type” „binary operator” „op-type” „op-type2” „operand” „operand2”
Egy DLL- ben nem lehet Main függvény.
a művelet eredménye (’bármilyen’típus lehet) + - * / % & | ^ << >> == != > < >= <=
A DLL-ben jellemző en osztályokat készítünk. De nem minden osztály lesz majd látható a DLL-en kívül:
a két operandus típusai. Legalább az egyiknek az osztály alaptípusának kell lennie
public class Sakkfigura { ... }
a két paraméter neve (tetszőleges).
A nem public class-ok a DLL-en kívül nem lesznek láthatóak. class Sakkfigura { ... } public class Kiralyno:Sakkfigura { ... } Ellentmondás: a Sakkfigura nem public, de a Királynő örököl(het) tőle publikus
mezőket és metódusokat.Ezért ez nem megengedett.Vagy mindkettő publikus, vagy egyik sem!
21
public class Sakktabla { public Sakkfigura[,] tabla = new Sakkfigura[8,8]; ... }
Szintén ellentmondás: ha a „Sakktabla” publikus, és a „tabla” is az, akkor a „Sakkfigura”- nak is annak kell lennie! Ezen túl a védelmi szinteket ki lehet (kellett) egészíteni újakkal: internal: a projekten (DLL- en) belül úgy viselkedik, mintha public lenne, de a DLL- en kívül nem látszik (mintha private lenne) internal protected: az „internal” és a „protected” együtt, vagyis a projekten belül public, de kívülre csak akkor látszik, ha azt továbbfejlesztik.
Pl: public class Kiralyno { internal int x; internal int y; internal protected void Athelyez( int ujx, int ujy) { ... } }
DLL tesztelése (a) A DLL-t nem lehet elindítani (nincs Main fv- e), de le lehet fordítani (BUILD / BUILD SOLUTION).
Kell készíteni egy kis teszt programot, amely a DLL- ből exportált osztályból készít példányt,és azt működteti. FILE /NEW /PROJECT /CONSOLE APPLICATION
Majd ezen APPLICATION-nak jeleznie kell,hogy az osztályt ebből a DLLből kell kivenni (ezt reference-nek nevezi a .NET): PROJECT / ADD REFERENCE Jobb oldalt fent:BROWSE gomb.
A megtalált DLL-t a .NET IDE bemásolja a Console Application forráskódjának ./bin/Debug alkönyvtárába (ahova az .EXE-t is fordítja). Ezek után már lehet is használni. Nem elfelejtendő, hogy a DLL-ben a public class is egy namespace-ban van!! DLL: namespace MyDLL { public class Sakkfigura { ... } }
APP: BB MyDLL.Sakkfigura bastya = new MyDLL.Sakkfigura();
vagy (nem kötelező – az Add Reference-n múlik!) using MyDLL;
A solution-hoz újabb ilyen kis teszprogramokat is adhatunk még hozzá, amelyek a DLL más-más képességeit tesztelik. Ez esetben be kell állítani, hogy a solution két (vagy több) projektjéből melyiket akarjuk indítani akkor, amikor F5-t nyomunk (Debug/Start). Ezt úgy kell megandi,hogy a solution explorerben a project-re kattintunk, és a PROJECT /SET AS STARTUP PROJECT menüpontra kattintunk. A solution explorer-ben a startup projectneve mindig vastag betűkkel van szedve (kiemelt). Ha egy ilyen összetett solution-t készítünk, és ’másnap’folytatni akarjuk a munkát,a solution-t kell betölteni, nem valamelyik projectet! Az elkészült munka már önállóan is futtatható: Másoljuk ki egy külön alkönyvtárba a DLL- t és a teszt EXE-t, és egyszerűen működni fog. Ugyanis az EXE-ben el van tárolva, hogy neki még kell egy DLL is, és ezt a futtató rendszer (.NET framework) első esetben ugyanabban az alkönyvtárban keresi majd,ahol az EXE is van.
#21 (delegate-callback) DELEGATE Az OOP-ban van mód CALLBACK típusú fv-ek készítésére. A CALLBACK fv azt jelenti, hogy idejekorán szólunk, hogy ha valamielőre látható esemény bekövetkezik,akkor indítsák el egy saját függvényünket. Pl:van egy file-tömörítő objektum, amely hosszan fut egy filetömörítése közben. Azt szeretnénk, hogy miközben fut a tömörítés,egy progress bar (folyatamjelző) jelezzen a képernyőn, hogy hol tarta tömörítés (hány százaléknál). Két megoldás van: virtual-override,és a callback fv. class ZIP { public virtual void ProgressBar( int Percent) {} public void Becsomagol( string inputfile, string zipfile) { ... ciklus amíg nincs kész ProgressBar( szazalek ); ... cvége ... } ... } /// és nem abstract a ProgressBar fv !!
és ha mi azt akarjuk, hogy a százalék látszódjon: class MyZIP: ZIP { public override void ProgressBar( int Percent) { Console.WriteLine(”A csomagolás {0}%- nál jár!”,Percent); } } // és utána a MyZIP- et használni csomagolásra!
Ennek a megoldásnak az a hátránya, hogy folyton származtatni kell, és rengeteg VMT tábla készül, sokat kell programozni. delegate void PercentCallback( int); // a callback fv prototípusa class ZIP { public PercentCallback callbackproc = null; public void Becsomagol( string inputfile, string zipfile) { ... ciklus amíg nincs kész if (callbackproc!= null) callbackproc( szazalek ); ... cvége ... } ... }
DLL tesztelése (b)
majd: (példány szintű callback)
SOLUTION: „megoldás” – projektek összessége (EXE+ DLL-ek)
class AkarmilyenOsztaly:AkarmilyenOs { public void KijelezSzazalek(int szazalek) { Console.WriteLine(”A csomagolás {0}%-nál jár!”,Szazalek); } }
több projektből állhat PROJECT: vagy egy DLL vagy egy EXE több forráskódból állhat Tesztelési javaslat: FILE /NEW /BLANK SOLUTION FILE /NEW /PROJECT /CLASS LIBRARY FILE /NEW /PROJECT /CONSOLE APPLICATION (add to solution)
Ekkor a Cons.App.-hoz szintén hozzá kell adni a DLL-t mint reference: PROJECT /ADD REFERENCE,de ekkor kattintsunk a Projects fülre (3. fül fent),itt a listában kiválaszthatjuk a DLL- t (a solution- n belülről). Ekkor azt érjük el, hogy felváltva írhatjuk a teszt programot, és a DLL kódját (használva jobb oldalt a solution explorer-t), és amikor futtatni akarjuk a teszt programot,akkor előtte a fordító a DLL-t is újrafordítja.
végül pedig: AkarmilyenOsztaly a =new AkarmilyenOsztaly(); ZIP z =new Zip(); z.callbackproc =new PercentCallback(a.KiejelezSzazalek);
Erről a Garbage Collector-nak is tudnia kell,mert ha egy példány kellős közepére van egy callback visszahívás kérés, akkor a példányt nem szabad egszüntetni még akkor sem, ha a példányra egyébként nincs hivatkozás (pl.a „a” változó már megszűnt, de a „z” még él, és a „z”-ből van callback az „a” példány egyik metódusára!!!)
22
Callback fv lehet osztály-szintű metódus is: class AkarmilyenOsztaly:AkarmilyenOs { public static void KijelezSzazalek(int szazalek) { Console.WriteLine(”A csomagolás {0}%-nál jár!”,Szazalek); } } //ilyenkor a GC-nek nem kell figyelni ...
végül pedig: ZIP z = new Zip();
// callback eljárás regisztrálása
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]
z. callbackproc =new PercentCallback( AkarmilyenOsztaly.KijelezSzazalek); z. Becsom(” c:\\ nagyfile. dat”,” c:\\ nagyfile. zip”);
// callback eljárás unregisztrálása z. callbackproc = null; z. Becsom(” c:\\ nagyfile. dat”,” c:\\ nagyfile. zip”); class ZIP { public ArrayList procs = new ArrayList(); public void Becsomagol( string inputfile, string zipfile) { ... ciklus amíg nincs kész foreach (PercentCallback p in procs) procs( szazalek );
} ... }
... cvége ...
ZIP z = new Zip();
//callback eljárások regisztrálása z.procs.Add(new PercentCallback(AkarmilyenOsztaly.KijelezSzazalek)); z.procs.Add(...);
//a felsorolt eljárások mindegyike meg fog hívódni a foreach ciklusban (ezt majd később eseménykezelésnek hívjuk!) // probléma: az ArrayList nem ellenőrzi, hogy valóban csak PercentCallback típusú dolgokat adtunk-e hozzá,ami típus- inkompatibiltási kivételt fog dobni a foreach ciklusban! class ZIP { private ArrayList procs = new ArrayList(); public RegisterCallback(PercentCallback proc) { procs.Add(proc); } } z.RegisterCallback( new PercentCallback(AkarmilyenOsztaly.KijelezSzazalek));
23
#
#