Dinamikus tárkezelés, adatstruktúrák
A téma bevezetéseként ismételjük át, hogy eddig
milyen változókkal ismerkedtünk meg. Elsőként a globális változókkal, amelyet a
program deklarációs részében irtunk le, s amelyeket a teljes programba, bárhol
használhattunk. Aztán megismerkedtünk az eljárásokkal és függvényekkel. Ezeknek
a belsejében is deklarálhattunk változókat, ezek voltak a lokális változók. A
lokális változókat csak a deklarálás helyén, vagy az alatta lévő szinteken
lehetett használni. Az ugyanazon szinten lévő rutinok nem látták egymás
változóit, sőt a főprogram sem. Definiáltunk továbbá konstansokat és tipizált
konstansokat. A Max értékek konstansok, a menüpontok
nevei tipizált konstansok voltak. Az eddig megismert változókat és konstansokat
statikusoknak nevezzük azért, mert fordításkor a helyük létrejön és a program
futásának a végéig változatlanul meg is marad.
Nézzük ezután, hogyan helyezkednek el a pascal
program esetén a memóriában a kódok és az adatok. Kezdetben, a 4.0 verzióig a turbo pascal *.com tárgyprogramokat készített, melynek rögzített betöltési
helye volt a memóriában és mérete maximálisan egy lap, azaz 64 Kb lehetett. Ehhez kapcsolódott az adatszegmens maximum 64 Kb-tal és az Ovelay terület (azaz
átlapolási terület az Overlay-be fordított Unit-ok
részére, - mindig az aktuálisan szükséges kódot töltötte a rendszer a lemezről
a RAM-ba). A kimarad memóriaterületen volt a Stack (a verem). Ezeket a
korlátokat manapság jelentősen átléphetjük az új típusú memória kiosztás és
kezelés révén. Első fontos dolog, hogy a Turbo Pascal
7.0 már nem *.com hanem *.exe állományokat készít, melyet a
hagyományos memória bármely üres helyére betölthet, nincs rögzített helye.
Méretére nincs korlátozás, hacsak az nem, hogy a szövegszerkesztő maximálisan
64 Kb-os szöveget tud kezelni. Ezen viszont az tud
segíteni, hogy saját Unit-okat írhatunk, melynek
darabszáma nincs korlátozva. Így az összeszerkesztett exe
állomány akár több-száz Kb is lehet. Itt említenénk
meg, hogy a pascal rendszer két IDE-t tartalmaz a DOS-os
környezetre. Ezek: a Turbo és a Tpx.
A Turbo úgy dolgozik, hogy a fordítást alapértelmezésben
a RAM-ba készíti és a segédállományokat is itt hozza létre. Nagyméretű programok esetén ez akadály
lehet, hiszen DOS-ban csak maximum 10 lap (640 Kb)
áll rendelkezésre, s ha mindent a memóriában oldunk meg, hamar elfogyhat a
standard memória. A Tpx viszont a fordítást mindig
lemezre végzi és az ideiglenes állományait is itt
hozza létre. Ezzel tehát nagyobb programokat lehet irni.
Ezért használtuk eleve ezt a fejlesztéseink alkalmával. A mai rendszerben a
memória kiosztása a következő (az alacsony címtől kezdve a magasak felé
haladva):
-
*.exe állomány,
-
Unit-ok,
-
tipizált
konstansok (nem tartoznak az adatszegmenshez),
-
globális
(statikus) változók - maximum 64 Kb -,
-
Stack - maximum 64 Kb -,
-
Overlay - ha van,
-
Heap - a memória tetejéig.
Vizsgáljuk meg egy kicsit jobban a Stack-et, miért szükséges és hogyan működik. A program
futása közben gyakran előfordul, hogy a programnak alprogramhoz
kell fordulnia, amely gyakorlatilag egy objekt helyre
való ugrást jelent. Az alprogram végrehajtása után -
alapértelmezésben - a programot a hívás helye utáni memóriacímen kell
folytatni. A gép úgy tud visszatalálni, hogy minden alprogramhíváskor
lementi a hívás helyét a memóriába. Természetesen alprogramból
újra alprogramba ugorhatunk, így az eredeti helytől
már két ugrásra vagyunk. A visszajutás először a legutóbbi, aztán az eggyel
korábbi cím ismerete alapján lehetséges. Az adatokat tehát úgy kell
feljegyezni, hogy amit később irtunk le, arra hamarabb van szükség, amit
legkorábban, arra pedig legutoljára. Ennek az adattárolásnak az angol neve:
LIFO (Last Input, First
Output), magyarul: amit utoljára behelyezünk, azt vesszük ki elsőnek. Ez úgy
működik, mint egy verem (Stack), ami a verem alján
van, azt csak akkor tudjuk kivenni a veremből, ha már mindent, amit utána
tettünk bele, már kivettünk. Azt, hogy a verem véges méretű, könnyen
leellenőrizhetjük a következő kis programmal.
Program Verem;
Procedure Ide;
Ide;
End;
Begin
Ide;
End.
Ez a program gyakorlatilag semmit nem tesz, csak
azt, hogy egy rekurzív eljárást hív meg, (amely természetesen most nincs
megfelelően kidolgozva, majd a későbbiekben látunk rekurzív hívásra értelmes és
hasznos megoldásokat is). Mivel állandóan önmagát kell meghívnia, mindig menti
a visszalépési címet, amely olyan gyorsan történik, hogy a program azonnal
leáll Stack Overflow -
verem túlcsordulás hibaüzenettel. Ha már itt tartunk, említsünk meg a másik
nevezetes adattárolást és a hozzá kapcsolódó hardware megoldást. Ez pedig a
FIFO (First Input, First
Output), magyarul: amit először behelyezünk, azt vehetjük ki elsőnek. Ez egy
csőhöz hasonlítható, amelybe torlódnak az adatok (mint például a hurkatöltőben
a töltelék). Pl. (szemléletesen) a baloldalon kerülnek be a csőbe az adatok, és
jobb oldalon kerülnek ki a csőből. A számítógépeink mindegyike rendelkezik
ilyen elven működő tárral: ez CACHE memória. Ezt a memóriát a processzor és az
operatív tár közé helyezik. Működési alapelve azon alapszik, hogy ha a
processzornak adatra van szüksége a memóriából, akkor nagyon valószínű, hogy
nem csak egy-két Byte-ra, hanem a memória közeli
területéről többre is. Így nem csak a kívánt adatok indulnak el a gyorsító
tárba, hanem több is, akár 512 kb -is, a CACHE
méretétől függően. A CACHE -t ugyanis a processzor sokkal gyorsabban el tudja
érni, mint a RAM-ot. Ezáltal a gép működése
felgyorsul.
43. Írjunk
programot, amely bemutatja, hogy maximálishoz közeli statikus tömb deklarációja
után még több, szintén maximális méretű dinamikus tömb deklarálható és
kezelhető a pascal programban.
Program
Din1;
Uses NewDelay, Crt;
Var A: Integer;
AMut: ^Integer;
STomb: Array[1..800] Of String[80];
Type PTomb=^TTomb;
TTomb=Array[1..800] Of String[80];
Var
T1,T2,T3,T4:PTomb;
P: Pointer;
Begin
TextMode(CO80);
TextColor(15);
ClrScr;
A:= 123;
A:= 2*A;
WriteLn('A: ',A);
New(AMut);
AMut^:= 777;
AMut^:= 2*AMut^;
WriteLn('AMut^: ',AMut^);
AMut:= @A;
AMut^:= AMut^+4;
WriteLn('A+4:
',A);
AMut:= Nil; {Sehov sem mutat}
{Dispose(AMut); Ha egy statikus változóra állitottuk,
már nem szabaditható
fel}
New(T1); New(T2); New(T3); New(T4);
T1^[800]:= 'Nagy Medve';
T2^[799]:= T1^[800];
T3^[798]:= T2^[799];
T4^[797]:= T3^[798];
WriteLn('T4^[797]:
',T4^[797]);
WriteLn('Szabad Heap: ',MemAvail,' , maxim lis blokk: ',MaxAvail);
Dispose(T1);
WriteLn('Szabad Heap: ',MemAvail,' , maxim lis blokk: ',MaxAvail);
Dispose(T2);
WriteLn('Szabad Heap: ',MemAvail,' , maxim lis blokk: ',MaxAvail);
Dispose(T3);
WriteLn('Szabad Heap: ',MemAvail,' , maxim lis blokk: ',MaxAvail);
Dispose(T4);
WriteLn('Szabad Heap: ',MemAvail,' , maxim lis blokk: ',MaxAvail);
ReadLn;
End.
44. Írjunk
programot, amely egy egyszerű láncolt listát kezel. Üres string
végjelig bekér például neveket, majd a beirás
sorrendjében kiirja a képernyőre és folyamatosan
felszabadítja a változó területét.
Program
Din2;
Uses NewDelay, Crt, CrtPlus;
type RecMut=^Rec;
Rec=Record
Nev: String;
Ind: Integer;
KMut: RecMut;
End;
Var ElsoRec, UjRec, AktRec: RecMut;
S: String;
I: Integer;
Procedure Felfuz(UjRec: RecMut);
Begin
If UjRec= Nil Then Exit;
If ElsoRec= Nil Then
Begin ElsoRec:= UjRec; AktRec:= ElsoRec End
Else
Begin
AktReC^.KMut:= UjRec;
AktRec:= UjRec;
End;
End;
Begin
TextMode(CO80);
Szinek(1,15);
ClrScr;
I := 0;
ElsoRec:= Nil;
Write('Kérem a(z)
1. nevet: ');
ReadLn(S);
While S<>'' Do
Begin
UjRec:= New(RecMut);
With UjRec^ Do
Begin
Nev:= S;
Inc(I);
Ind:= I;
KMut:= Nil;
End;
Felfuz(UjRec);
Write('Kérem a(z)
',i+1,'. nevet: ');
ReadLn(S);
End;
WriteLn;
WriteLn('A
lista:');
AktRec:= ElsoRec;
While AktRec <> Nil Do With AktRec^ Do
Begin
WriteLn(Ind:4,'.
',Nev);
ElsoRec:= AktRec;
AktRec:= KMut;
Dispose(ElsoRec);
End;
Tunj;
Varj;
End.
45. Írjunk
programot, amely kétirányú láncolt listát kezel. Üres string
végjelig bekér például neveket, majd először a beirás
sorrendjében, másodszorra pedig ezzel fordított sorrendben kiirja
a képernyőre és folyamatosan felszabadítja a változó területét.
Program
Din3;
Uses NewDelay, Crt, CrtPlus;
type RecMut=^Rec;
Rec=Record
Nev: String;
Ind: Integer;
EMut,
KMut: RecMut;
End;
Var ElsoRec, UjRec, AktRec, UtolsoRec: RecMut;
S: String;
I: Integer;
Procedure Felfuz(UjRec: RecMut);
Begin
If UjRec= Nil Then Exit;
If ElsoRec= Nil Then
Begin ElsoRec:= UjRec; UtolsoRec:= UjRec; AktRec:= ElsoRec; Exit End
Else
Begin
AktReC^.KMut:= UjRec;
UjRec^.EMut:= AktRec;
UtolsoRec:= UjRec;
AktRec:= UjRec;
End;
End;
Begin
TextMode(CO80);
Szinek(1,15);
ClrScr;
I:= 0;
ElsoRec:= Nil;
Write('Kérem a(z)
1. nevet: ');
ReadLn(S);
While S<>'' Do
Begin
UjRec:= New(RecMut);
With UjRec^ Do
Begin
Nev:= S;
Inc(I);
Ind:= I;
EMut:= Nil;
KMut:= Nil;
End;
Felfuz(UjRec);
Write('Kérem a(z)
',i+1,'. nevet: ');
ReadLn(S);
End;
WriteLn;
WriteLn('A lista
előre:');
AktRec:= ElsoRec;
While AktRec<>Nil Do
With AktRec^ Do
Begin
WriteLn(Ind:4,'.
',Nev);
{ElsoRec:= AktRec;}
AktRec:= KMut;
{Dispose(ElsoRec);}
End;
WriteLn;
WriteLn('A lista
vissza:');
AktRec:= UtolsoRec;
While AktRec<>Nil Do
With AktRec^ Do
Begin
WriteLn(Ind:4,'.
',Nev);
UtolsoRec:= AktRec;
AktRec:= EMut;
Dispose(UtolsoRec);
End;
Tunj;
Varj;
End.
46. Írjunk
programot, amely kétirányú rendezett láncolt listát kezel. Üres string végjelig bekér például neveket, majd először a
növekedő sorrendben, másodszorra pedig csökkenő sorrendben kiirja
a képernyőre és folyamatosan felszabadítja a változó területét.
Program
Din4;
Uses NewDelay, Crt, CrtPlus;
type RecMut=^Rec;
Rec=Record
Nev: String;
Ind: Integer;
EMut,
KMut: RecMut;
End;
Var ElsoRec, UjRec, AktRec, UtolsoRec: RecMut;
S: String;
I: Integer;
Procedure Felfuz(UjRec: RecMut);
Begin
If UjRec= Nil Then Exit;
If ElsoRec= Nil Then
Begin ElsoRec:= UjRec; UtolsoRec:= UjRec;Exit End;
If ElsoRec = UtolsoRec
Then
Begin
If ElsoRec^.Nev<UjRec^.Nev Then UtolsoRec:= UjRec Else ElsoRec:= UjRec;
ElsoRec^.KMut:= UtolsoRec;
UtolsoRec^.EMut:=
ElsoRec;
Exit;
End;
AktRec:= ElsoRec;
While (AKtRec^.Nev<UjRec^.Nev) And (AktRec<>Nil)
Do
AktRec:= AktRec^.KMut;
If (AktRec<>ElsoRec) And (AktRec<>Nil)
Then
Begin
AktRec^.EMut^.KMut:=
UjRec;
UjRec^.EMut:= AktRec^.EMut;
AktRec^.EMut:= UjRec;
UjRec^.KMut:= AktRec;
Exit;
End;
If AktRec=ElsoRec Then
Begin
ElsoRec^.EMut:= UjRec;
UjRec^.KMut:= ElsoRec;
ElsoRec:=UjRec;
Exit;
End;
If AktRec=Nil Then
Begin
UtolsoRec^.KMut:=
UjRec;
UjRec^.EMut:= UtolsoRec;
UtolsoRec:= UjRec;
End;
End;
Begin
TextMode(CO80);
Szinek(1,15);
ClrScr;
I:= 0;
ElsoRec:= Nil;
Write('Kérem a(z)
1. nevet: ');
ReadLn(S);
While S<>'' Do
Begin
UjRec:= New(RecMut);
With UjRec^ Do
Begin
Nev:= S;
Inc(I);
Ind:= I;
EMut:= Nil;
KMut:= Nil;
End;
Felfuz(UjRec);
Write('Kérem a(z)
',i+1,'. nevet: ');
ReadLn(S);
End;
WriteLn;
WriteLn('A lista
előre:');
AktRec:= ElsoRec;
While AktRec<>Nil Do
With AktRec^ Do
Begin
WriteLn(Ind:4,'.
',Nev);
{ElsoRec:= AktRec;}
AktRec:= KMut;
{Dispose(ElsoRec);}
End;
WriteLn;
WriteLn('A lista
vissza:');
AktRec:= UtolsoRec;
While AktRec<>Nil Do
With AktRec^ Do
Begin
WriteLn(Ind:4,'.
',Nev);
UtolsoRec:= AktRec;
AktRec:= EMut;
Dispose(UtolsoRec);
End;
Tunj;
Varj;
End.