Tartalmi kivonat
BMF-NIK Programozási paradigmák és technikák by UFO (2003) 1. Moduláris programozás (unitok, paraméterek) A Unitok Delphiben minden formhoz szükségszerűen egy unit is tartozik, de egy unithoz nem feltétlenül kell formnak tartoznia. A unitok használata a struktúráhatóság szempontjából fontos, hiszen az egy adott feladatrész megoldását szolgáló programelemek ilyen módon különválaszthatók a főprogramtól. Deklarációja: unit unit név; A unit nevének nem kell kötelezően megegyeznie a fájl nevével, de előnyös az áttekinthetőség szempontjából. Módja: uses unit2 in „C: ufo.pas”; (relatív: \) Részei INTERFACE Itt definiálhatjuk azt, amit más programrészek is láthatnak. Az itt nem definiált részek csak az adott unit számára elérhetők. Itt lehet megadni azt is, hogy melyik más unitok tartalmát akarjuk használni. Ezt kell felsorolni a uses kulcsszó után. Az interface deklarációs részében a mások számára is látható
típus, változó-, konstans- és címkedeklarációkat kell megadnunk, továbbá a globális metódusfejeket kell felsorolnunk. IMPLEMENTATION Itt kell a unitot alkotó eljárásokat, függvényeket leírnunk. Ezek közül kifelé csak azok láthatók, amelyeket az interface után felsoroltunk. (az eljárás- és függvényfejeket) Körkörös hivatkozás esetén ide kell írni a második unit uses részét. Itt kell megvalósítani a globális metódusokat, és a lokálisakat egyaránt. INITIALIZATION Inicializálás: például alaphelyzetbe hozza a változókat, vagy a dinamikusakat létrehozza, stb. A unitok initialization része abban a sorrendben hajtódik végre, ahogyan a uses után felsoroltuk őket. FINALIZATION Véglegesítés: felszabadítjuk a lefoglalt erőforrásokat. A finalization részek végrehajtása fordított. Az inicializációs és a lezáró rész elhagyható. Ha nincs lezáró rész, akkor az initialization helyett begin is használható. Ha mindkét részt
elhagyjuk, akkor az ’end.’ utasítás az implementációs részt zárja A modul inicializációja csak egyszer történik meg, függetlenül attól, hogy hányszor szerepel a modul neve a különböző uses hivatkozásokban. * Unit unitnév; Interface Uses modullista; Const konstansdefiníciók; Type típusdeklarációk; Var változódeklarációk; {Globális eljárások és függvények fejlécei} implementation Uses modullista; Label cimkelista; Const konstansdefiníciók; Type típusdeklarációk; Var változódeklarációk; {Globális és lokális metódusok törzse} initialization {Pascal utasítások} finalization {Pascal utasítások} end. * A Paraméterek típusai ÉRTÉK SZERINTI (változó: integer) Az eljárás létrehoz egy segédváltozót és belemásolja a paraméterként kapott értéket (stack overflow lehetséges) Az eredeti változó értékét csak felhasználni tudja, módosítani nem (a segédváltozót módosíthatja) A másolás miatt nagy
adatmennyiség esetén lassú Az eljárás végeztével felszabadul a lefoglalt erőforrás Hatóköréből a segédváltozó nem jut ki (csak itt látható) CÍM SZERINTI (var változó: integer) Megkapja a változó címét, így közvetlenül annak értékével dolgozhat Hatékonyabb, hiszen nincs adatmásolás Hátránya, hogy az eljáráson belül megváltoztatható a cím szerint kapott paraméter értéke KONSTANS TÍPUSÚ (const változó: integer) Az érték szerintinek megfelelő paraméterátadás, azzal a különbséggel, hogy a segédváltozó értéke az eljáráson belül nem változhat meg. Ha ezen az eljáráson belül egy másikat hívunk meg, annak is konstans típusúnak kell lennie KIMENŐ (out változó: típus) Az eljáráson belül kap értéket a változó Ezért változóval kell hívni, hogy abba másolhassa a belül kapott értéket TÍPUS NÉLKÜLI (const/var/out változó) Csak const/var/out esetén használható,
érték szerintinél nem Ennek segítségével nem kell különböző típusú változókra külön eljárásokat írni (néha átláthatóbb így) Példa {a függvény} function egyenlo(var source, dest; size: integer): boolean; type TBytes=array[0.maxint-1] of byte; var n: integer; begin n:=0; while (n<size) and (TBytes(dest)[n]=TBytes(source)[n]) do inc(n); result:=(n=size); //if n=size then true else false end; {a függvény hívása} procedure TForm1.Button1Click(Sender: TObject); type TVektor=array[1.10] of integer; TPoint=record x,y: integer; end; var Vec1,Vec2: TVektor; n: integer; p: TPoint; begin egyenlo(Vec1,Vec2,sizeof(TVektor)); egyenlo(Vec1,Vec2,sizeof(integer)*n); //a tömb első n elemének kijelölése egyenlo(Vec1[1],Vec2[6],sizeof(integer)*5); //a tömb első 5 elemét hasonlítja össze az utolsó 5-tel egyenlo(Vec1[1],p,4); //összehasonlítja Vec1 első két elemét P két elemével külön-külön //Vec1[1]=p.x; Vec1[2]=py; end; 2. Dinamikus
memóriakezelés A strukturált típusú változók mérete a program fordításkor dől el, így a futás során nem módosítható. A memóriafoglalás és felszabadítás műveleteivel a memória dinamikus felhasználását valósítjuk meg. A mutató típus és a dinamikus változók A pointer (mutató) olyan speciális változó, amely memóriacímet tartalmaz. A mutatók típussal rendelkeznek A típus kijelöli, hogy a dinamikus helyfoglalás során mekkora területet kell a mutatóhoz hozzárendelni. A mutatók deklarálásának általános formája: var változónév: ^típusnév, ahol a típusnév tetszőleges szabványos vagy felhasználói típus lehet. A mutató a deklaráció után meg sehova sem mutat. A programon belüli helyfoglalás elvégzésére a new utasítás használható, amely a lefoglalt terület címét a paraméterben adott mutatóba tölti. Ha nincs elég hely a memóriafoglalás elvégzésére, akkor ezt az EOutOfMemory kivétel jelzi. Az üres mutató
jelölésére a nil konstans való. A mutató által kijelölt területre a nevével és utána egy ^ jellel hivatkozunk: p^:=10; Felszabadítás: dispose(p); A dinamikus helyfoglalás előnye akkor tűnik ki igazán, ha több kilobájtos dinamikus változót (rekord, tömb) használunk. A strukturált típusokhoz csak akkor definiálhatunk mutatót, ha a típushoz egyetlen típusnevet rendelünk, a deklarációs rész type szekciójában. A mutatók közötti értékadás lehetséges new utasítás nélkül is, azonban értéket így nem adhatunk a mutatónak. Például: Q:=P akkor is működik, ha nem volt new(Q). De Q^nev:=P^nev esetén nem tudjuk, hova fog mutatni, hibákhoz vezet. De a terület lefoglalása után ez megengedett Ha egy változót felszabadítunk, utána még ugyanoda fog mutatni, de a terület újra felhasználhatóvá válik. Ezért a dispose utasítás után állítsuk a mutató értékét nil-re. Ha egy nem létező változóra disposet adunk ki, programhibához
vezet Type pelem=^TFa; TFa=record tart: integer; bal: pelem; jobb: pelem; end; var Form1: TForm1; gyoker,p: pel; További memóriakezelő metódusok A pointer típussal általános mutatót is deklarálhatunk, amely nem rendelkezik konkrét típussal, ezért a new eljárást nem használhatjuk. Ebben az esetben az adott bájtméretű memóriaterület lefoglalását a GetMem, felszabadítását pedig a FreeMem eljárások segítségével végezhetjük el. Procedure GetMem (var p: pointer; meret: integer); Procedure FreeMem (var p: pointer [; meret: integer]); Az AllocMem függvénnyel is lefoglalható a terület, amelyet 0 értékű bájtokkal tölt fel. Function AllocMem (meret: cardinal): pointer; A lefoglalt memóriablokk mérete a ReAllocMem eljárással módosítható. Procedure ReAllocMem(var p: pointer; ujmeret: integer); Az alkalmazás által lefoglalt memóriablokkok számát az AllocMemCount, míg a lefoglalt terület összméretét az AllocMemSize egész típusú
globális változók tartalmazzák. A pointer típusú mutatókat dinamikus pufferterületek kijelölésére használjuk, amelyek méretét a program futása során adjuk meg. A mutatók léptetése az Inc és a Dec függvények segítségével lehetséges. A léptetés során a mutató értéke annyi bájttal módosul, amennyi a típusának megfelelő adatméret. Azonos típusú mutatók esetén használhatjuk az = és a <> műveleteket. A nil konstansmutató és a pointer típussal deklarált mutatók bármely más mutatóval összehasonlíthatók. Pl. if (ip=p) and (ip<>nil) then halt; A mutató típusa típuskonverzióval megváltoztatható. Ily módon a pointer típusú mutatókhoz is rendelhetünk konkrét típust. Pl longint(p ^):=123456; Az Addr függvény, illetve az ennek megfelelő @ operátor segítségével bármely objektum címét lekérdezhetjük, és tetszőleges típusú mutatóba tölthetjük. Pl. i:=5; ip:= @i; ip ^:=i+6; ki(i); //i=11 lesz 3.
Láncolt lista (rendezett, nem rendezett, strázsa technika) Az egyirányú láncolt lista Ez egy nagyon egyszerű dinamikus adatszerkezet, amely egy eleje mutatóból, és rekordokból áll. A rekordok azonos típusúak: egy tartalom mezőből és egy következő mutatóból állnak. Attól, hogy a mutatók egyértelműen meghatározzák a végighaladás sorrendjét, az adatok a memóriában össze-vissza helyezkednek el. Az utolsó elem nil-re mutat Ez jelzi a láncolt lista végét. A tömbbel összehasonlítva Új elem felvétele egyszerű, mert csak két mutatót kell beállítani. Törölni is gyorsan lehet: a törlendő előtti elem mutatóját a következőre állítjuk, őt pedig felszabadítjuk. Hátránya a tömbhöz képest: átlagosan n/2 lépésben találunk meg egy elemet, mert csak szekvenciálisan tudunk keresni egy rendezetlen listában. Akkor érdemes láncolt listát használni, ha az elemek mennyisége változik, mert a memória dinamikusan foglalható. Rendezni
nem érdemes, mert az elemek közvetlen elérése nem valósítható meg, és csekély sebességnövekedést eredményez. Műveletek Lista létrehozása type Pelem=^Elem; Elem=record tartalom: integer; kovetkezo: Pelem; end; Itt megengedett, hogy előbb használjuk a típus (Elem), mint deklarálva lenne. Eleje:=nil; //nem hagyható el Lista bejárása P:=eleje; //segédváltozó While p<>nil do begin Kiir(P^.nev); P:=P^.kov; End; Új elem felvétele Lehetséges az elejére beszúrni vagy rendezett lista esetén megfelelő helyre. (előbb egy keresési feladat) Láncol listában tudunk visszalépni, ezért tárolni kell az előző címét. Elejére szúrás New(Q); //elem létrehozása Q^.tart:=x; //tartalommal való feltöltés Q^.kov:=eleje; //beláncolás az eleje mutató elé Eleje:=Q; //Q lesz az eleje a listának egy nem Rendezett beszúrás Elejére: ha az első elemnél kisebb a beszúrandó, vagy üres a lista. Középre: meg kell keresni az elem helyét.
Végére: ha nagyobb, mint az utolsó. Lánc végére szúrás While p^.kov<>nil do P:= P^kov; P^.kov:=R; R^kov:=nil; //a végéig lépkedés //beláncolás, vége nil Mutatott elem elé való beszúrás New(R); R^.tart:=p^tart; //kimentjük a felülírandót P^.tart:=x; //új elem R^.kov:=P^kov; //a mutatókat utánaállítjuk P^.kov:=R; procedure TForm1.Button fel rendClick(Sender: TObject); begin //Rendezett P:=eleje; X:=strtoint(edit1.Text); if (P=nil) then begin new(eleje); //ha üres a lista, eleje mtató létrehozása eleje^.tartalom:=X; //és az első elem feltöltése eleje^.kovetkezo:=nil; //nincs következő elem end else begin new(Q);new(R); P:=eleje; //P mutató az első elemre mutat if (X<P^.tartalom) then begin //P elé fűzűnk R^.tartalom:=P^tartalom; //P mentése R^.kovetkezo:=P^kovetkezo; //R-be P^.tartalom:=X; //P-be az adat P^.kovetkezo:=R; //R beláncolása end else begin //ha <X lenne, nem tudnánk egyelemű lista esetén egy //ugyanekkora X
elemet beszúrni, mert be sem lép //a while ciklusba while (P<>nil) and (P^.tartalom<=X) do begin Q:=P; //Q felülírása P-vel P:=P^.kovetkezo; //P már a következőre mutat end; end; new(R); R^.tartalom:=X; Q^.kovetkezo:=R; R^.kovetkezo:=P; end; end; Keresés procedure TForm1.Button keresClick(Sender: TObject); var n: integer; begin P:=eleje;X:=strtoint(edit2.Text); n:=0; if P^.tartalom=X then listbox1Itemindex:=0; while (p<>nil) and (P^.tartalom<>X) do begin p:=p^.kovetkezo; n:=n+1; if (p=nil) then begin listaz; beep; end else listbox1.Itemindex:=n; end; end; Törlés procedure TForm1.Button torolClick(Sender: TObject); var kijelolt,sorszam: integer; begin P:=eleje; sorszam:=listbox1.itemindex; if sorszam<>-1 then begin kijelolt:=strtoint(listbox1.items[listbox1itemindex]); while ( P^.tartalom<>kijelolt ) and (P<>nil) do begin Q:=P; P:=P^.kovetkezo; end; if (P=eleje) then eleje:=P^.kovetkezo else Q^.kovetkezo:=P^kovetkezo; P:=nil;
listaz; end; end; Strázsa-technika Alapelv: definiálható két olyan elem (max és min), amelyek a lánca berakható elemek intervallumának széleit képezik. Ezek a megállási feltételek. Így a lista soha nem lesz üres, az ebből származó problémák megoldódtak. Nem rendezett keresés While (p<>nil) and (P^.tart<>x) do P:=P^kov; If p=nil then nincs else van Kör adatszerkezet A lista utolsó eleme nem nilre, hanem egy másik lista elejére mutat. A másik lista vége pedig ennek az elejére. Így csak a belépési pontot kell megjegyezni. A kétirányú láncolt lista Visszafelé is van mutató, Rendezettségnél jól használható. Felszabadítás P:=eleje; eleje:nil; While p<>nil do begin Q:=P; P:=P^.kov; Dispose(Q); End; oda-vissza is tudunk lépkedni. 4. Bináris Fa (adatszerkezet, felépítés, bejárások) Adatszerkezet A fa egy összefüggő körmentes gráf, tehát bárhonnan bárhová el lehet jutni rajta. Irányított, mert van
egy gyökérpontja és ágai is A gyökérpontba nem megy él, csak innen indulhat ki. A bináris fa egy dinamikus (és rekurzív) adatszerkezet. Egy-egy levélpontja áll egy tartalom mezőből, egy bal –és egy jobb mutatóból. Felépítése: Az első elemfelvételnél a gyökérponthoz viszonyítva balra tesszük az elemet ha a gyökérnél kisebb, és jobbra, ha nagyobb. A többi elemfelvételnél ugyanígy először a gyökérponthoz viszonyítunk, majd elmegyünk az egyik irányba. Ha itt van már elem, azzal is összehasonlítjuk az új elemet, és elrakjuk annak a megfelelő oldalára. Elfajult eset: ha növekvő sorrendben vesszük fel az elemeket, egyágú bináris fát kapunk, amellyel a bináris fa már értelmét veszti, hiszen egy ilyen fában átlagosan n/2 lépésben találunk meg egy elemet, ellentétben a kiegyensúlyozott fával, amelyben egy keresés átlagosan log 2 N ideig tart. 17 8 5 Például ilyen fa esetén az elemek felvitelének egy lehetséges
sorrendje: 17, 8, 5, 9, 20, 40. De nem tudhatjuk mindig biztosan, hogy az elemeket milyen sorrendben vették fel. 20 9 40 Bejárások InOrder (bal, gyökér, jobb); PreOrder (gyökér, bal, jobb); PostOrder (bal, jobb, gyökér). Az In/Pre/Post a gyökér feldolgozásának idejére utal. InOrder procedure InOrder(p: pelem); begin if P<>nil then begin InOrder(P^.bal); Kiír(P^.tart); InOrder(P^.jobb); end; end; //param.-ként kapott gyökér //megállási feltétel //rekurzív hívások //Tevékenység A rekurzív hívás mindig eltárolja, hogy P hova mutatott. Működése: az összes bal oldali elemet feldolgozza, mielőtt a gyökérre kerülne a sor. Rendezett, növekvő listát állít elő Az előbbi példa esetén: 5, 8, 9, 17, 20, 40. Ha csökkenő, rendezett listát szeretnénk, felcseréljük a bal mutatót a jobbra. PreOrder procedure PreOrder(p: pelem); begin if P<>nil then begin Kiír(P^.tart); PreOrder(P^.bal); PreOrder(P^.jobb); end; end; Az ezzel a
módszerrel kapott lista alapján a fa rekonstruálható. (Ha ilyen sorrendben vesszük fel az elemeket egy új, üres fába) A példa alapján: 17, 8, 5, 9, 20, 40. PostOrder procedure PostOrder(p: pelem); begin if P<>nil then begin PostOrder(P^.bal); PostOrder(P^.jobb); Kiír(P^.tart); end; end; Először a levélelemeket dolgozza fel. A fa felszabadítása így lehetséges. Ha paraméterként nem a gyökérelemet kapja, hanem egy levélpontot, akkor az ez alatti elemeket szabadítja fel, beleértve a levélelemet is. (A részfa-törlő algoritmust lásd az 5 tételben) A példa alapján: 5, 9, 8, 40, 20, 17. A bináris fa alapja Type pelem=^TFa; TFa=record tart: integer; bal: pelem; jobb: pelem; end; var Form1: TForm1; gyoker, p: pelem; 5. Bináris Fa (keresés, törlés) Keresés (a beszúrás algoritmus mutációja) procedure beszuras(x: integer; var p: pelem); begin if p=nil then begin new(P); P^.tart:=x; P^.bal:=nil; P^.jobb:=nil; end else begin if (x<P^.tart) then
beszuras(x,P^bal) else if (x>P^.tart) then beszuras(x,P^jobb) else begin Kiír(Már van ilyen elem); beep; end; end; end; Működése: a rekurzív újrahívások során az algoritmus mindig elmegy balra, vagy jobbra, aszerint, hogy kisebb vagy nagyobb a beszúrandó elem az éppen vizsgáltnál. Az újrahívás után a gyökérelem (p) például a bal oldali mutató lesz. Így lehetséges annak a vizsgálata, hogy ez az új gyökérelem mutat-e valahová. Ha nem mutat sehová, ide be lehet láncolni az új elemet. Az eljárás befejeződik Az algoritmus mindaddig lépeget a fán lefelé, amíg kisebb vagy nagyobb az új elem az éppen vizsgáltnál. Ha egyenlőséget talál, hibát jelez A keresés annyiban módosul, hogy a beláncolás művelete elhagyható, és a hibajelzéshez tesszük azt a tevékenységet, amely a keresett elem megtalálásakor történjen. Például egy függvény visszatérési értéke lehet, hogy egy típusos fájlban hol helyezkedik el a tartalom
mezőhöz tartozó adatrekord. A megállás a tevékenység végrehajtása után következik be, mivel nem történik újabb rekurzív eljáráshívás. Iteratív keresés While (P<>nil) and (P^.tart<>X) do begin If P^.tart<X then P:=P^jobb else P:= P^.bal; end; Egy elem törlése Cél: egy elem törlése minimális munkával. A levélelemeket könnyű törölni, mert nincs alattuk semmi. Dispose(p) után a „szülő” elem mutatóit nil-re állítjuk. Az egy leszármazottal rendelkező elemeket is könnyű törölni: kiláncolás. Nehéz törölni azokat az elemeket, amelyeknek két leszármazottjuk van, mert feltétel a fa tulajdonságainak megtartása. Ebben az esetben a törlendő elem bal oldali részfájának legjobboldalibb elemével kell felülírni a törlendő elemet. Sorrendben ez a kisebb a törlendőnél. Az eljárás hívása: Torol(törlendő, gyökér) procedure torol(x: integer; var p: pelem); var q: pelem; procedure tor(var R: pelem); begin if
R^.jobb<>nil then tor(R^jobb) else begin Q^.tart:=R^tart; Q:=R; R:=R^.bal; end; end; //a részfa legjobboldalibb eleméig megy //törlendő eltárolása //R mutatóinak eltárolása //ráláncoljuk a balra lévő elemeket begin if p=nil then form1.Label1Caption:=Nincs ilyen elem else if (x<P^.tart) then torol(x,P^.bal) else if (x>P^.tart) then torol(x,P^.jobb) else begin //ha nem kisebb és nem nagyobb a törlendő elem Q:=P; //az éppen nézettnél, akkor ki kell láncolni if (Q^.jobb=nil) then P:=P^.bal else if (Q^.bal=nil) then P:=P^.jobb //ha egyik mutatója sem nil, else tor(Q^.bal); //2 elem van alatta, TOR elj dispose(Q); //a korábban eltárolt törlendő elemet felszabadítjuk end; end; Részfa törlése Procedure reszfa(var p: pelem); Begin If P<>nil then begin If P^.bal<>nil then reszfa(P^bal); If P^.jobb<>nil then reszfa(P^jobb); Dispose(P); P:=nil; End; End; Működése: kap egy csomópontot, amely alatt törli a részfát. (PostOrder
típusú bejárás) 6. Bináris Fa és Típusos Fájl Összekapcsolás Cél: nagyobb adatmennyiség gyors elérése háttértárról, a keresés megvalósítása log 2 N lépésben. Módszer: Legyen egy név szerint rendezett típusos fájl. Ebben a logaritmikus keresés használható. Hátránya, hogy csak egy szempont szerint lehet egy időben rendezett a fájl (fizikailag). Új elem felvétele, törlése a rendezettség megtartásának kényszere miatt lassú. A bináris fa tulajdonságai: gyors keresés, törlés, beszúrás; a memóriában van, ezért gyors a többi művelet is; ugyanakkor a memória véges, az összes adatot nem tárolhatjuk itt (könnyen is sérülhet); csak egy szempont építhető fel egyszerre; ha nem kulcsmező alapján keresünk, lassú. Megoldás: indexelés. A bináris fában csak egy kulcsmező szerepel (amely szerint rendezett a fa), továbbá egy index, amely a kulcsmezőhöz tartozó adatrekord helyét mutatja meg a típusos fájlban. A típusos
fájl így lehet rendezetlen. Így megvalósul a két adatszerkezet előnyeinek ötvözete: gyorsaság, nagy tárolókapacitás. A gyakorlatban annyi bináris fát rendelünk a fájlhoz, ahány szempont szerint kell majd keresni. A keresés megindításakor dinamikusan bomlik le a régi, és épül fel az új fa. Inicializálás gyoker:=nil; for i:=1 to filesize(f) do begin read(f,rec); beszuras(rec.marka,gyoker,i-1); end; A fenti példában (i-1) az adatrekordnak a fájlban elfoglalt helyét mutatja meg. Így előnyösebb, hiszen a típusos fájl nullától indexelődik. Az első „beszúrás” eljáráshívás után lehetne írni a többi fa felépítését, ha szükséges. Keresés Megjegyezzük egy változóban, adatrekord a fájlban. hogy hol helyezkedik procedure KeresFaban(var p: pelem; keresett: string); begin if P<>nil then begin KeresFaban(P^.bal,keresett); if P^.tart=keresett then begin number:=P^.sorszam; end; KeresFaban(P^.jobb,keresett); end; end; el az
Új elem felvétele Meg kell nézni, hogy van-e már ilyen elem a fán. Ha van, hibaüzenet, egyébként felfűzzük a fára és a fájlba (a végére) is. A beszúrás eljárásban megvan már az egyenlőségvizsgálat, ezzel külön foglalkozni nem kell. Törlés Az indexeléses törlés annyiban bővül ki a hagyományos bináris fás törléshez képest, hogy a fából való elemtörlés előtt megjegyezzük az adatrekordjának helyét, és azt is kitöröljük a fájlból. Konkrét módszer: a keresés eljárással megnézzük, hogy van-e egyáltalán ilyen elem. Ha van, akkor kitöröljük a „töröl” eljárással a fából majd a fájlban a hozzá tartozó adatrekordhoz lépünk, majd az utolsó adatrekorddal felülírjuk azt. Listázás adatrekordokkal együtt A bejárások segítségével a Tevékenység részben a megfelelő adatrekordhoz lépünk, és kiírjuk a tartalmát. /seek(f, P^.sorszam); read(f,rec); kiír(rec);/ Fontos, hogy ne a rekurzív eljárásban legyen a
fájlnyitás, mert feleslegesen lassítja a végrehajtást. Módosítás Ha nem kulcsmezőt kell módosítani, egyszerűen rákeresünk a fában, megkapjuk a fájlbeli helyét, majd itt módosítjuk. Ha kulcsmezőt kell módosítani, megnézzük, hogy a módosított változat rajta van-e a fán (ilyenkor nem fűzhetjük fel). Ha nincs, kitöröljük a fából, majd újra felfűzzük. Az index változatlan marad, csak a fájlban kell megváltoztatni a kulcsmező értékét. Tippek Ha kénytelenek vagyunk azonos nevű elemeket felvinni a fára (például azonos felhasználónév: Kovács Alajos), akkor lássuk el ezeket egyedi azonosítókkal, amelyeket a felhasználó nem lát (pl. Kovács2) Módszer: a bináris fában nem csak a név mező szerepel azonosítóként, hanem a cím is. Ebben az esetben pontosabban tudunk keresni (bár lehet, hogy ez a cím is megegyezhet). A logikai törlés megvalósítása pakolás segítségével: az eredeti fájlt *.bak kiterjesztéssel elmentjük,
majd a nem törölt elemeket átmásoljuk az új fájlba, amely a régi nevét örökli. Így a teljes adatállomány csak manuális törléssel távolítható el. 7. Adatbázis-kezelés Delphivel (BDE) Lokális adatbázisok kezelése Az alkalmazások fejlesztése egy speciális programozási felületen, a BDE (Borland Database Engine) API-n (Application Programming Interface) keresztül történik. A BDE sok alapfeladat elvégzése alól mentesíti a programozót; használata a Delphi adatelérési komponensein keresztül történik (Data Access). Mivel a BDE az adatbázis-kezeléshez szükséges dinamikusan szerkeszthető könyvtárak gyűjteménye, ezért telepítése után több alkalmazás is használhatja párhuzamosan. Ezzel együtt csökken a memóriahasználat mértéke, és az alkalmazások mérete is. (nem tartalmazzák az elérést végző kódot) A BDE segítségével mind a lokális (dBase, Paradox), mind a távoli szerveren lévő, illetve az ODBC (Open DataBase
Connectivity) meghajtók által támogatott állományokat is elérhetjük. Lokális egy adatbázis, ha az adattáblák egy helyi számítógépen vagy hálózaton vannak. Ebben az esetben a műveleteket közvetlenül a gépünkön futó alkalmazás végzi. Nagyobb programrendszerek esetén az adattáblákat egy távoli kiszolgálón helyezik el. Ennek előnye, hogy az adatok központilag tárolódnak, gyengébb kliensgépek is megfelelőek, de meg kell oldani az egyidejű, ugyanarra az adatra vonatkozó hozzáférések problémáját.(A Delphi alkalmazásokból elvileg minden adatbázistípus elérhető) A Paradox vagy dBase lokális adattáblákat a BDE közvetlenül éri el. A Microsoft Access vagy a FoxPro táblákkal való kommunikációhoz pedig a Microsoft DAO (Data Access Objects) DLL-eket is telepíteni kell. A kapcsolat a többi adatforrással a megfelelő meghajtókon keresztül valósul meg (Oracle, Ms SQL Server, Interbase) A BDE minden olyan típusú fájl elérését
támogatja, amelyhez létezik egy megfelelő ODBC meghajtó. A relációs adatbázisok Ezek alapvető eleme a tábla (table), amely előre meghatározott számú oszlopot, vagyis adatmezőt ír le. Az adatmezők értékeit a tábla sorai, más néven adatrekordjai tartalmazzák. A tábla relációs volta abból adódik, hogy a több táblában tárolt adatok között különböző kapcsolatokat írhatunk le. Az adatkeresés egy másik módszere az indexelés. Az adattábla strukturálásakor létrehozott indexek, illetve az indextábla gyakorlatilag egy olyan fájl, amely tartalmazza az adatmezők bizonyos szempontból rendezett elérési sorrendjét. Az indexek nagy előnye, hogy eleve egy logikailag rendezett táblával dolgozhatunk. A relációs adatbázisok kezelése során a rendszer belső mutatója mindig az elért tábla aktuális rekordjára mutat. A BDE-t használó alkalmazás írása során nem kell kötődnünk egyik szabványhoz sem, mert az adatbázis-elérő
műveleteket a vezérlőelemek kezelőfelületén keresztül végezhetjük el. Ha később át kell térni egy másik adatbázis-típusra, az esetek többségében az alkalmazás újrafordítása megspórolható. Ez azért lehetséges, mert az adatok helyére és elérésére vonatkozó beállítások az alkalmazáson kívülről is megadhatók, a BDE Administrator alkalmazás segítségével. A logikai (alias) adatbázisnév az adathalmaz elérhetőségét és fizikai elhelyezését írja le. A logikai nevek jól használhatók, ha az adatok elérése több alkalmazásból is megtehető. (BDE Admin – Object/New) 8. Adathozzáférés, adatmegjelenítés (Táblák) Szükséges komponensek Table1, DataSource, DBGrid(a Desktop-pal hozhatók létre. megjelenítéshez). A fájlok a Database Table1.DatabaseName:=’C: UFO’; Table1.TableName:=’adatokdb’; DataSource1.DataSet:=Table1; DBGrid1.DataSource:=DataSource1; Utasítások Table1.Open; vagy Table1Active:=True; (nyitás)
Table1.Close; vagy Table1Active:=False; (zárás) Table1.First; //első rekordra Table1.Last; //utolsó rekordra Table1.Prior; //előző rekordra Table1.Next; //következő rekordra Table1.MoveBy(-2); //mozgatás a megadott irányba (vissza kettővel) Table1.BOF; //a tábla elején true Table1.EOF; //a tábla végén true Table1.Edit; //Szerkesztési műveletek engedélyezése Table1.Fields[0]:AsString:=’Elérés a mező sorszáma szerint’; Table1.FieldByName(’NEV’)AsString:=’Mező szerint(aktuális rekordból)’ Table1.FieldValues[’MERET’]:=2000; //összes ilyen mezőnek értékadás 0.Table1FieldCount-1; //a felhasználható indextartomány Table1.Append; //új üres rekord hozzáfűzése a tábla végére Table1.Insert; //új üres rekord beszúrása az éppen aktuális pozíció elé Table1.Cancel; //a rekordban történt változások elvetése Table1.Delete; //az aktuális rekord törlése Table1.Post; //a változások véglegesítése a rekordban Ahhoz, hogy a
felhasználó ne tudja módosítani az adatokat, a table vezérlőelem ReadOnly tulajdonságát true-ra kell állítani. Az Exclusive tulajdonsággal megakadályozhatjuk a tábla más alkalmazások általi elérését, amíg használjuk. A Shared engedélyezi ugyanezt A Table bezérlőelem az adattábla rekordjainak egy csoportját is képviselheti, amely csoportot különböző szűrők segítségével definiálhatunk. A szűrő karakterláncot a Filter tulajdonság értékeként kell megadni, majd pedig engedélyezni kell a szűrést a Filtered tulajdonság true-ra állításával. (Az Object Inspector-ban) Filter Filtered NEV=’Béla’ or NEV=’B*’ true Programrészletek Listázás Table1.Open; Table1.First; While not Table1.EOF do begin Kiír(Table1.FieldbyName(’NEV’)AsString); Table1.next; End; Table1.Close; Új adat felvitele Table1.Append; Table1.Edit; Table1.FieldbyName(’NEV’)AsString:=’Ottó’; Table1.Post; Table1.Close; Módosítás Módja: megkeressük
a módosítandó rekordot (szekvenciálisan vagy indexeléssel), módosítjuk, visszaírjuk, véglegesítjük. Parancs: Table1.Locate(’NEV’,’Béla’); ha nem találja, a fájl végére áll (lassú). (Ha az aktuális rekordról elmegyünk, akkor egy implicit Post hajtódik végre) A K-val kezdődő elemek listázása While copy(Table1.FieldbyName(’NEV’)AsString,1,1)=’K’ do Begin Kiír(Table1.FieldbyName(’NEV’)AsString); Table1.Next; End; //a listázás index sorrendben történik, ha van Törlés Rá kell állni, meg kell nézni, jót törlünk-e (Biztosan törli a következőt?: ’Kovács Alajos’). Edit Mode, Delete, Post/Cancel; A táblákból a delete parancs hatására fizikailag nem törlődnek az adatok. A fizikai törlést a Database Desktop-pal lehet véghezvinni Indexelés A rekordok megjelenítési sorrendjét szabályozhatjuk a Table IndexFieldName tulajdonságában megadott adatmező névvel (Paradox és dBase táblák esetén ez csak indexben
szereplő mező lehet) Ha a sorrend kialakításához az adattáblához tartozó indexfájlokat akarjuk használni, akkor a megfelelő index nevét az IndexName tulajdonságon keresztül lehet beállítani. (egyszerre csak az egyiket lehet használni) *.MDX – Maintained Index: a tábla nyitásakor az indexfájl is automatikusan megnyílik, és a használatáról sem kell külön gondoskodnunk. (Adatbázis-motorok használatakor az indexelést a kiszolgáló végzi, nem kell nekünk ezzel foglalkozni) Ha új elemet viszünk fel, vagy törlünk, stb. az indexfájl is automatikusan módosul. Parancsok indexeléshez Table1.FindKey([’A’]): az aktuális index szerint megpróbálja megkeresni a paraméterben adott sztringet. Logikai eredményt ad vissza. Ha megtalálta, rá is áll Ha index nincs beállítva, nem működik. Table1.FindNearest([’K’]): az első K betűvel kezdődő elemre áll Ha nem találja, a parancs kiadásának helyéhez képest egyet lép. Vizuális komponensek
DBNavigator, DBGrid, DBEdit, DBLabel: a DataSource segítségével kapcsolhatók a táblához. A Master-Detail kapcsolat Ha az adatok megjelenítése során két táblát össze szeretnénk kapcsolni, akkor a master-detail kapcsolatot használhatjuk. Ehhez kell két Table és két DataSource komponens. A részletező táblának be kell állítanunk az alaptábla által képviselt adatforrást, a MasterSource tulajdonságon keresztül. Továbbá meg kell adni azt a mezőt vagy indexet, amelyen értékei megfelelnek az alaptábla mezőértékeinek. Ha csak az alaptábla éppen aktuális rekordjának megfelelő bejegyzéseket szeretnénk megkapni a részletező táblából, állítsuk be a MasterFields tulajdonságát is ugyanarra az értékre, mint az IndexName. Az adatokat megjelenítő vezérlőelemnek természetesen a részletező táblához kell kapcsolódnia, a DataSource-on keresztül. A gyakorlatban, ha a master táblában lépkedünk, a detail-ben is a megfelelő rekordhoz
ugrik. Használatának előnyei: nem kell többször ugyanazt az adatot tárolni (például az emberek irányítószámai alapjának választjuk ki a települést). Vagy például a nem fontos adatokat egy másik táblában tároljuk, és csak akkor küldjük át a hálózaton, ha arra valóban szükség van. Tippek A Shared, Exclusive korlátozásokat használat után szabadítsuk fel Ne maradjon nyitva a tábla használat után, mert más nem fog tudni hozzáférni, vagy nem tudjuk majd restrukturálni sem. Ne olvassunk be DBGrid segítségével, mert tudjuk kontrollálni az adatot, hogy megfelelő-e. 9. Lekérdezések (SQL) Az SQL nyelvről A Structured Query Language a relációs adatbázisok Strukturált Lekérdező Nyelve. (1970) A lekérdezés az adatbázishoz intézett kérést jelenti, amelynek visszatérési értéke a kérés feltételeit kielégítő egy vagy több adatrekord. (szabványos, de több nyelvjárás van) A BDE-vel használható Client-Based
(Local) SQL az ANSI-92 SQL-re épül. Ennek segítségével elérhetjük a Paradox és dBase adattáblákat, az InterBase lokális kiszolgálót és távoli SQL szervereket az SQL Links meghajtókon keresztül. Az SQL utasításoknak két különböző csoportja van: Adatkezelési utasítások (DML – Data Manipulation Language) Adatdefiníciós utasítások (DDL – Data Definition Language) DML utasítások például: kiválasztás, beillesztés, módosítás, stb. DDL utasítások például: adattáblák és indexek létrehozása, stb. Általános szabályok Az SQL kulcsszavakban nem különböztetjük meg a kis –és nagybetűket; az utasítások közé pontosvesszőt teszünk. Megjegyzések: /*/ vagy {}. Karakteres értékek megadásakor az egyszeres (’) és kétszeres (”) idézőjeleket egyaránt használhatjuk. Ha programból töltjük fel az SQL Sztringlistát, csak a kettős idézőjel jöhet szóba, mert eleve egy Sztringként értelmezett parancsot adunk hozzá
a listához. A DML utasítások (lekérdezés, beszúrás, módosítás, törlés) SELECT mezőlista FROM tábla [WHERE keresési feltétel] [ORDER BY sorba rendezési lista] [GROUP BY csoportképzési lista] [HAVING feltételek csoport szinten] [UNION újabb select utasítás] INSERT INTO tábla (mező1, mező2,) VALUES (érték1, érték2,) INSERT INTO tábla1 SELECT * FROM tábla2 [WHERE tábla2.mező1] UPDATE tábla SET mező = új érték WHERE mező = régi érték DELETE FROM tábla WHERE mező = érték Az SQL utasításokkal egyszerre több adattáblát is elérhetünk, a Query vezérlőelem DatabaseName tulajdonságában megadott adatbázisból. Az SQL utasításokat a Query komponens SQL tulajdonságában lehet megadni. Ezt követően a parancs lefuttatása: Query1.Open; //ha várunk valamely „választ” Query1.ExecSQL; //ha nem várunk „választ” (pl törlés) Alapvető SQL parancsok Lekérdezés SELECT nev FROM table1 WHERE nev=’Géza’; Teljes
tábla lekérdezése SELECT * FROM Table1 Több tábla összekapcsolása (Table1: nev, irszam, ber; Table2: Irszam, Tel) SELECT nev,tel FROM Table1, Table2 WHERE nev=’Geza’ AND (Table1.irszam=Table2irszam); Ha ezt az utasítást WHERE nélkül adjuk ki, a két tábla egy-egy sorát a másikéhoz párosítja, az eredmény egy n x n –es tábla lesz. Rendezett lista SELECT * FROM tábla1 ORDER BY mező1, mező2; //elsődleges és másodlagos (Ez az utasítás lassú, kivéve ha van megfelelő indexfájl) szempont Csoportosítás SELECT count(*), Tankor FROM tábla1 GROUP BY Tankor; Megszámolja, hogy hány sor tartozik az azonos tankörű mezőkhöz, és csoportosítja azokat. (Függvények még: sum, avg, min, max, count) Új elem felvétele INSERT INTO tábla (mező1, mező2,) VALUES (érték1, érték2,) Nem muszáj minden mezőnek értéket adni. Amelyiknek nem adunk, annak értéke null lesz. Probléma: a null mezők hol így, hol úgy viselkednek, ezért ne hagyjuk, hogy
egy mező értéke null értéket vegyen fel. Lehet helyette egy „space” vagy 0; Van-e null érték valahol: SELECT * FROM Table1 WHERE Tankor is null; Módosítás UPDATE tábla SET mező = új érték WHERE mező = régi érték Where nélkül az egész tábla megfelelő mezői módosulnak. A módosítás vagy törlés előtt biztonsági okokból érdemes először egy lekérdezést intézni a táblához: biztos, hogy azt csináljuk, amit szeretnénk? SELECT * FROM Table1 WHERE szelekciós feltétel Törlés DELETE FROM Table1 WHERE szelekciós feltétel; Where nélkül mindent töröl. Létrehozás Ez az utasítás az adatmotortól függ. Példa: CREATE TABLE táblanév (mező1 típus1, mező2 típus2,) CREATE TABLE „shit-man.db” (ass CHAR(10), hit INT); Itt lehet meghatározni, hogy az adott mezőbe kerülhet-e null érték: null: igen, not null: nem. CREATE TABLE otto (bla char(10) null) Tábla törlése DROP TABLE Táblanév; Ha egy táblát létrehozunk, először
azon a néven egy drop parancsot végrehajt (hátha már létezett), aztán hozza csak létre a táblát. Azonos nevűt ne akarjunk létrehozni: adatvesztés. Jogosultságok (lekérdezés, beszúrás, törlés, módosítás,stb.) Ha egyszer szerver parancssorában adjuk ki az utasításokat, magunknak is érdemes jogosultságokat adni, hogy ne hibázzunk. SQL szerver kezelése: kliens oldalon, programnyelvbe ágyazva vagy közvetlenül a szerver (neten keresztül) a console ablakában. Az SQL paraméterezése Query1.SQLClear; Query1.SQLAdd(’SELECT * FROM Table1 WHERE NEV=:PNEV’); Query1.ParambyName(’PNEV’)AsString:=’Géza’; Query1.Open; A paraméterek típusa a Query komponensben beállítható. Megjegyzés: a Query-vel a legtöbb parancs használható, ami a Táblával. (next,eof,bof,stb.) Kivéve a módosítás/törlés/beszúrás –ra vonatkozók Üres-e az eredménylista If Query1.IsEmpty then üres else nem üres Előkészítés Query1.Prepare; //külön task-ot nyit,
gyors, nem kötelező Query1.UnPrepare; //befejezi a szálat Query1.ExecSQL; //csak futtatás, nincs válasz Tranzakció-kezelés A kiadott utasításnak kezdete és vége van, abból a szempontból, hogy mennyi idő kell a végrehajtásához. Ha a gép elkezdi a módosítást, de munkavégzés közben leáll, akkor is visszaállítható a tábla eredeti állapota (roll-back). (kozisztencia – adat összefüggőség) (Bővebben lásd a Local SQL Összefoglaló című ComputerBooks PDF kiadványt.) 10. A program készítésének folyamata, elvek A programok csoportosítása A program készítésének lépései Specifikáció: a megoldandó feladat meghatározása papíron (formalizálás, pszeudo-kód) egy, a feladathoz értő és informatikai ismeretekkel is rendelkező szervezővel. Tervezés: Az adott részfeladatok megoldásának módját határozzuk meg: algoritmusok, adatszerkezetek, operációs rendszer megválasztása, programnyelv, adatbázis-motor, stb. De nyitva
hagyunk bizonyos kérdéseket, amelyekről nem feltétlenül kell most döntenünk. (pl felhasználói felület, felépítés) Kódolás: a kiválasztott programnyelv(ek)en implementáljuk az algoritmusokat, amelynek eredménye egy futtatható program. Tesztelés, Hibakeresés: a tesztelés nem feltétlenül a programozó feladata, jobb, ha egy leendő felhasználó végzi (hiba pontos előidézési módja, javítás). Eredménye egy aránylag „jó program” Hatékonyság-minőség analízis: a megfelelő sebesség, ergonómia vizsgálata. Elkészül a program Esetleges problémák A tervezés vagy a kódolás során kiderülhet, hogy a specifikáció rossz vagy nem egyértelmű. Előfordulhat, hogy a kódolásnál vesszük észre, hogy nem megfelelő adatszerkezetet választottunk. Ilyenkor vissza kell térni az adott fázishoz, és javítani a hibát. Fontos, hogy minden egyes fázis részletesen legyen dokumentálva, hogy több ember közös munkája esetén mindenki
megértse a másik programrészletét. Ahol fontos döntés történt, ott indoklással: miért azt az adatszerkezetet választottam, stb. Programkészítési elvek Stratégiai elvek: o lépésenkénti finomítás: egyszerűbb részfeladatokra bontjuk a programot, nem törekszünk egyből a teljességre Taktikai elvek o Döntések elhalasztása: a konkrét feladatot (pl. rendezést) akkor implementáljuk, amikor az feltétlenül szükséges. Hiszen később derülhet ki, hogy melyik a legmegfelelőbb. o Párhuzamos finomítás: minden feladat kb. ugyanolyan szintig legyen kidolgozva o Nyitott rendszerre való törekvés: ne csak a konkrét feladatot oldjuk meg, hanem törekedjünk az általános, újra felhasználható elemek írására. Így a programunk egyes része könnyen lecserélhetőek, vagy képesek új kódokat befogadni. Például egy rendezés legyen paraméterezhető, hátha később növelni/csökkenteni kell a rendezendő elemek mennyiségét. o Döntések
nyilvántartása: feljegyezzük a módszer megválasztásának okát o Adatok elszigetelése: törekedjünk az adatmezők védelmére, hogy akár véletlenül se lehessen belepiszkálni az értékükbe. Strukturált programozás esetén: modulok, lokalitás, globalitás. OBJ esetén: adatmezők elérése (private, protected) Technológiai elvek o Kevés, de egyértelmű szabályok lefektetése, amelyek mindenkire érvényesek (pl. mindenki ugyanúgy tagoljon) o Bekezdéses leírás: a forma utaljon a tartalomra o Beszédes azonosítók használata a könnyű megértés miatt Technikai elvek o Barátságos felhasználói felület: de ne nézzük idiótának a felhasználót o A hibaüzenetek szövegezésének közérthetősége (+magázódás) o A hiba kijelzése már az első felismerési lehetőség során történjen meg o Minden lehetséges hasznos információ közlése o Bolondbiztosság: ne lehessen sehogyan kárt okozni: kivéve persze a kivédhetetlen eseteket (Task
Manager) o Olvashatóság, ergonómia: emeljük ki a fontos dolgokat: például a kötelezően kitöltendőeket o Színkezelés: alapszíneket használjunk, hogy aránylag beállítás-kompatibilis maradjon (minden oprendszeren ugyanúgy nézzen ki) o Konfigurációs beállítások készítése (kis munka, de hasznos) Esztétikai elvek o Menükezelés megvalósítása: összetartozó dolgok egy helyen legyenek o Ne legyen túlzsúfolt a képernyő, de túl ritka se o Ne kelljen sokat kattintani egy mezősor kitöltéséhez (automatizálás billentyűzettel: pl. Tab) o Optimális felbontás megválasztása: kis felbontásra írt program nagyon túl kicsi, fordítva meg nem fér el. 11. A dokumentáció A felhasználói dokumentáció A felhasználó Nem informatikus Minimális mértékben ismeri az adott operációs rendszert Ismeri az egér és a billentyűzet használatát Beszéli az adott nyelvet, IQ-ja mérhető (pozitív) Ismeri azt a szakterületet,
amelyre a program készült A dokumentáció tartalma A program célja, hogyan oldja meg a feladatot Minimális és ajánlott hardverigény (CPU,RAM,HDD, CD, egyéb) Felbontás, fontméret (small/large) Szükséges szoftverkörnyezet (OPrendszer, Service Pack, DirectX) Aktuális verziószám Segédprogram a működéshez: ODBC, BDE (liszensz-díj) Telepítési utasítások (nyelvi beállítás, program inkompatibilitás) Részletes használati információk az elindítástól A program használatáról Screenshot-ok magyarázattal Korlátozások az adatbevitel során Menü felépítése: blokkvázlat Hotkey és szokásos billentyűzetkiosztás leírása Hiba esetén mi történjen (mit írjon fel, kihez forduljon) +Legyen megkülönböztetett hibaüzenet: lehessen tudni, hogy a hibát a mi programunk okozta-e ha speciális vagy nem általánosan ismert algoritmust használunk egy feladat megoldásához, ismertessük, hogy a
felhasználó ellenőrizni tudja a program számításait A számításigényes műveletek kiemelése, feltüntetni, a tesztgépen meddig futott; progressbar is használható (lassít) A programmal kapcsolatos jogok (freeware, ad-ware, stb.) Továbbfejleszthető (forráskód), hány gépre telepíthető Hardverkulcs van-e (érthető módon utálják) Config állományok tartalmának leírása, hogy backup-olható legyen A fejlesztői dokumentáció A fejlesztő Informatikus szakember (mi magunk, a team-tagjai) Ismeri az adott programnyelvet és az oprendszert, az alapvető adatszerkezeteket, technikákat, stb. A dokumentáció tartalma A tesztgép hardver és szoftverkörnyezete olyan részletességgel, hogy az a továbbfejlesztéshez szükséges módon reprodukálható legyen (Service Pack, hálózat, adatbázis-motor, ODBC beállítások, kódlap, fejlesztői környezet beállításai, DLL, egyéb szoftverek) Ha komponenseket töltöttünk le,
írjuk le ezek elérhetőségét a neten és a szerzőjük nevét Feladatspecifikáció és a megvalósított részek leírása Verziókövetés (mi az új az előzőhöz képest): egy fájlban mindig felülre Felhasznált algoritmusok leírása (főleg a saját fejlesztésűek), pszeudo-kód, a programban hol van az implementáció Felhasznált adatszerkezetek, a döntés okai Objektum-hierarchia Adatbázis-terv (kapcsolatok) Forráskód Trükkös megoldások részletes leírása (pl Assembly betétek sorról sorra) *Ha kis aljasságokat teszünk bele, legalább magunknak írjuk le Írjuk le a forráskód tagolásakor használt konvenciót (tagolás, változónevek) A program korlátai (csak 1000 elemre – miért?) Ha bizonyos feladatot nem sikerült megfelelően vagy egyáltalán megoldani, írjuk le Tesztelési eredmények (igazolja a jó működést) Ha tovább kell fejleszteni, megnézzük, hogy az újabb verzión ugyanazt
az eredményt adja-e Továbbfejlesztési tanácsok, lehetőségek (pl. ha gyorsabb lenne a gép) A dokumentáció ne legyen se túl hosszú, se túl rövid 12. Hatékonyság Szempontok Sebesség (szubjektív) Tárterület, erőforrás-felhasználás Kód bonyolultsága, átláthatóság A sebesség mérése stopperrel vagy ún. profiler-rel lehetséges, amelyre sorról sorra statisztikát, hogy a program egyes részei mennyi ideig futottak. A memóriafoglalás mérés Task Managerrel lehetséges Példa (eltolás) Adott N elemű tömb. Léptessük k elemmel balra az összes elemet A, Algoritmus /k*(n+1) lépés, n+1 tárfoglalás/ Ciklus i=1-től k-ig X=A(i) //az első elemet elmentjük Ciklus j=2-től n-ig A(j-1)=A(j) Ciklus vége //visszavezetjük az eggyel balra feladatra A(n)=X Ciklus vége B, Algoritmus /(n+k) lépés, 2n tárfoglalás/ Ciklus i=1-től k-ig X(i)=A(i) Ciklus vége Ciklus j=k+1-től n-ig A(j-k)=A(j) Ciklus vége Ciklus i=1-től k-ig
A(n-k+i)=X(i) Ciklus vége //kimentjük az első k elemet egy másik tömbbe //a többit előremásoljuk az eredeti tömbben //az eredeti hátuljára beírjuk a kimentett elemeket //Gyorsabb, de nagyobb a tárfoglalása C, Algoritmus (n+1 lépés, n+1 tárfoglalás) Felhasználjuk, hogy n és k relatív prímek. Két szám relatív prím, ha legnagyobb közös osztójuk egy. /(n,k)=1/ Eljárás X=A(1) L1=1 Ciklus i=1-től n-1-ig //csak az ismétléshez kell L2=L1+k Ha L2>n akkor L2=L2-n //ha túllépnénk n-et, csökkentjük A(L1)=A(L2) //megfelelő elemek felcserélése L1=L2 //index eltárolása (lényegében növelése) Ciklus vége A(L1)=X Eljárás vége //utolsó elem behelyezése, amely még kimaradt Hatékonyság-növelő eszközök Matematikai ismeretek: erasztothenész-i szita. prímszámot találunk, Például prímszámkeresés (egy tömbben vizsgáljuk többszöröseit kihúzzuk, ha esetén az a számokat: ha nem prímszámot találunk, azt is, és
többszöröseit is kihúzzuk) (prímvizsgálat: n ig ) A hagyományos prímszámkeresés esetén, ha A> n és B> n , akkor A*B>N. Így elég n -ig vizsgálni. Tovább gyorsítás: szitával előállított prímosztókkal vizsgáljuk. Példa (rendezés) A(n) rendezendő tömb + X(k) tömb, összes eleme nulla. K a legnagyobb elem. Algoritmus (n lépés, k tárfoglalás) Ciklus i=1-től n-ig Inc(X(A(i))) Ciklus vége //X indexei adják a számokat, sorrendben (ahol 1 van) X értelmezése Ciklus i=1-től k-ig Ha X(i)=1 akkor Ki(i) Ciklus vége Példa (keresés) Szekvenciális keresés: végignézi az összeset, nem áll meg. (kivéve ha break-et használunk) While segítségével megállási feltételt is adhatunk. Logaritmikus keresés: gyors, de csak rendezett tömbre működik Átrendezéses keresés: csak kis elemszám esetén gyors (ha használunk egy elemet, utána mindig a vektor elejére tesszük, így a gyakran használtak elöl lesznek) A végrehajtás
idejének csökkentése Példa (kerekítés) Tegyük fel, hogy nincs ilyen függvényünk, csak olyan, amely csonkolni képes (int). Hogyan oldjuk meg a feladatot? A, Algoritmus Ha X-int(x)>0.5 akkor Ki(int(x)+1) különben ki(int(x)) B, Algoritmus (hatékonyabb) Ki(int(x+0.5)) //Hozzáad 0,5-öt és csonkol: marad a kerekített érték Példa (kockadobás) Lehetséges: 1,2,3,4,5,6 A, Algoritmus Ciklus i=1-től n-ig Ha ered=1 akkor DB1=DB1+1 Ha ered=2 akkor DB2=DB2+1 . ciklus vége B, Algoritmus (hatékonyabb) Ciklus i=1-től n-ig A(x)=A(x)+1 Ciklus vége //tömb megfelelő indexű eleme növekszik //ennél jobb: Inc(A(x)) Példa (Strázsa-elem) Például keresés feladatnál nem kell mindig feltételvizsgálat, hogy a végén vagyunk-e, mert a strázsa-elem megállít. Ez az elem egy, a keresési intervallumban semmiképpen sem szereplő elem. Lokális hatékonyság (ha sokszor kell végrehajtani) A2 2*A A:=A+1 A:=A+K helyett helyett helyett
helyett A*A A+A Inc(A) Inc(A,K) //A növelése K konstans értékével sin(x)/cos(x) helyett tan(x) gyors típusok használata (valós helyett egész, ha lehet) kisebb típusok esetén (byte, smallint) is integerrel számol a gép, nem érdemes ezek használatára törekedni sokszori végrehajtás előtt jó, ha a részeredményeket kiszámoljuk előre, és egy változóban tároljuk. Ha gyorsulást szeretnénk elérni, a ciklusok belsejében vizsgálódjunk: ha lehet, ne legyen függvényhívás, egyszerűsítsük a kifejezéseket Példa (Fibonacci) Ha nem kell az összes számot eltárolni n-ig, hanem csak az n-edik számot keressük, elég három változó: hatékonyabb tárfoglalás. n 1 1 5 Fib(n) round 5 2 Áttekinthetőség Eljárások, függvények használata (programsor-csökkenés) Ciklusok összeolvasztása, ha a megállási feltétel vagy a lépésszám megegyezik (kevesebb
ciklus – jobb átláthatóság) Adatok előfeldolgozása (kiszámoljuk, és változóban tároljuk, amire később szükség lesz) Program transzformációk Példa (feltételvizsgálat) (Ha A és B akkor ’valami’) Tippek: csak akkor értékeljük ki B-t, ha A igaz, mivel ha A hamis, az eredmény nem lehet igaz – ne vizsgáljunk feltételt feleslegesen Helyette: Ha A akkor Ha B akkor ’valami’ (azt tegyük előre, ami gyakrabban hamis, így időben leáll a feltétel) Delphi-ben beállítható a kiértékelés módja: balról jobbra, vagy teljes További példák (Ha A akkor ’valami1’ különben Ha B akkor ’valami2’) esetén, ha tudjuk, hogy valamelyik igaz, akkor hatékonyabb: (Ha A akkor ’valami1’ különben ’valami2’ (Ha A akkor ’valami1’; Ha A akkor ’valami2’) helyett (Ha A akkor ’valami1’,’valami2’) (Ciklus Ha f akkor ’valami’ Ciklus vége) helyett (Ha f akkor ciklus ’valami’ ciklus vége). Ez csak akkor
jó, ha f értéke állandó (Ciklus A B Ciklus vége) helyett, ha A független a ciklustól (A ciklus B ciklus vége) 13-14. Tesztelés, hibakeresés (elvek, módszerek) Példa Algoritmus (a program egy konkrét értékre mást csinál) Beolvas(A,B) Ha A=171.555432189 akkor ki(A*B2) különben ki (AB) Tesztelési elvek Szisztematikus tesztelés o Például az összes bemeneti számot megvizsgálni nagyon sok idő, és teljesen felesleges is. Az eredmény nehezen elemezhető. Az a teszt jó, amely eddig fel nem fedett típusú hibát talál (pl. ha páros számra nem jó, ne próbáljuk végig őket) A teszt legyen rekonstruálható, hogy a hiba újra előállítható legyen (így tudjuk csak javítani) A tesztelést ne a program írója végezze Próbáljunk találgatni, mi a hiba oka Hibakeresési elvek Statikus tesztelés (a program még nem fut) o A kód ellenőrzése o Formai ellenőrzés (kezdőértékek) o Kereszt-referencia tábla (melyik
változó hol kap értéket, hol használjuk) Dinamikus tesztelés (a program futása során) o Fekete-doboz módszer (nem látjuk mit, csinál, csak az eredményt) o Ekvivalencia osztályokra osztás (azonos típusú adatok különválasztása) o Határeset-elemzés (Pl. 1-6 String lehet, nézzük: 0,3,6,7) o Fehér-doboz módszer (a forrás ismeretét felhasználva választjuk ki a bemenő adatokat) Próbáljunk úgy bemenő adatokat választani, hogy minden utasítás végrehajtódjon, legalább egyszer Döntés lefedés: minden döntés hajtódjon végre (if-ek) Feltétel lefedés: az összetett feltételek (ágak) vizsgálata (H és I, I és H, stb.)