2 Dynamische variabelen (bereikbaar via `pointers`)

advertisement
B2: Algoritmiek, Datastructuren en Objectprogrammeren
Dynamische variabelen
2 Dynamische variabelen (bereikbaar via ‘pointers’)
2.1 Inleiding
In het algemeen willen wij zo min mogelijk te maken hebben met de hardware van een specifieke computer (o.a.
omdat die zo snel verandert). Toch is het voor het begrip noodzakelijk op de hoogte te zijn van een aantal
elementaire zaken.
Tegenwoordig heeft ook een PC megabytes werkgeheugen. Vaak
1 MB
wordt dit geheugen aangeduid met RAM. RAM staat voor Random
Acces Memory. Dit betekent dat het geheugen willekeurig
640 kB
toegankelijk is. Als de processor een bepaald geheugenadres op de
o.a. MSDOS-deel
adresbus zet, krijgt hij toegang tot een geheugenwoord. Dit (RAM) geheugenwoord kan de processor zowel lezen als schrijven. Dit
Heap
‘vrije geheugen’
hangt af van de soort instructie die de computer moet uitvoeren.
Alleen toegankelijk
Gelukkig programmeren wij in een hogere programmeertaal en
via ‘dynamische
variabelen’
hoeven niet zoals een assembly programmeur van alle details op de
(met ‘pointers’)
“Heap”
hoogte te zijn. Wanneer wij de variabele getal van het type REAL
declareren dan zorgt de compiler ervoor dat er op een geschikte
Stacksegment
Stack
adres in het werkgeheugen 6 bytes ruimte wordt gereserveerd voor
Default: <=16 kB
tbv lokale variabelen
deze variabele. Nu kunnen wij een realwaarde toekennen aan getal
Maximaal: 64 kB
en value-parameter
en bewerkingen uitvoeren met getal. Hierbij is getal een surrogaat
Globale variabelen
<= 64 kB
voor de waarde. De variabele getal vervult dezelfde rol als x of y in
Getypeerde
Datasegment
de middelbare school wiskunde. Een opdracht als write(getal)
constanten
schrijft de huidige waarde van getal naar het scherm.
<= 64 kB
Unit 1
Een erfenis uit het eerste begin van het MSDOS-tijdperk is de
“image”
beperktheid tot 64 kiloBytes van het zogenaamde ‘datasegment’,
Unit 2
van
waar de waarden van de in de B1-cursus behandelde ‘statische
.EXE-file
Unit . .
variabelen’ in werden geplaatst. Zeeën van geheugenruimte bleven
op zo’n manier onbenut. In de vroege MSDOS-tijd betekende die
(hoofd) programma
onbenutte ‘zee van ruimte’ het RAM-geheugen tot maximaal 640
256 Bytes
kB. De sleutel tot die vergrootte benutting van ruimte, was het
MSDOS-deel(tje)
gebruik van zogenaamde dynamische variabelen in ‘de Heap’.
Daarvoor was het gebruik van pointers (zie volgende paragraaf)
Figure 1 Geheugengebruik onder MSDOS
nodig.
In de MSDOS-tijd kon door toepassing van wat truukjes (lees: hulprogramma’s) de benutting van het RAMgeheugen worden uitgebreid; bij gebruik van het Windows-besturingssysteem zijn die ‘truukjes’ niet nodig.
2.2 Pointers
Bij programmeertalen als Pascal is het mogelijk om in dat relatief kleine datasegment (64 kB) verwijzingen (van
telkens b.v. slechts 4 bytes grootte) op te nemen naar veel grotere datastructuren op de Heap. Zo’n verwijzing
bestaat eigenlijk uit het adres van de geheugenplaats waar de ‘echte gegevens’ staan opgeslagen.1
Maar wat moet echter het type van zo’n verwijzing zijn?
Pascal beschikt over een geschikt type hiervoor:
het Pointer type. Het woord zegt het al, het is
Pointer
een aanwijzer. De waarde van pointer wijst naar
Gegeven
het adres van getal. Pascal is echter een strikt
In ‘datasegment’
getypeerde taal en normaal neemt Pascal geen
Op ‘Heap’
genoegen met een algemeen type Pointer. De
waarde van een pointer is immers een
Figure 2 Pointer in ' Datasegment' naar variabele in ‘Heap’
1
We kunnen, als we dat willen, het geheugenadres waar een variabele (neem:) getal staat opvragen. Hiervoor beschikt
Borland Pascal over de operator @.
De opdracht:
getaladreswijzer := @getal
kent aan getaladreswijzer het geheugenadres van de variabele ‘getal’ toe, dit is een 4 bytes adreswaarde (vaak
hexadecimaal aangegeven).
7
B2: Algoritmiek, Datastructuren en Objectprogrammeren
Dynamische variabelen
geheugenadres en op dit geheugenadres kan van alles staan. Om fouten door slordigheid met pointers te
vermijden verwacht Pascal pointers van een bepaald type. Bij compilatie wordt ook voor wat betreft pointers een
type check doorgevoerd. Zo is een pointer naar een INTEGER van een geheel ander type dan een pointer naar
een STRING.
De declaratie van een (getypeerde) pointer in Pascal gaat als volgt:
VAR getal_ptr : ^INTEGER ;
Lees dit als: ‘er is een variabele van het pointertype (4 bytes) die alleen naar een INTEGER (2 bytes) mag
wijzen’. 2 De procedure New ( VAR P : Pointer ) maakt een nieuwe dynamische variabele en zet een pointer
die naar de locatie van die variabele wijst. De opdracht:
New ( getal_ptr ) ;
reserveert 2 bytes (correcter: 8) geheugen voor de INTEGER en zorgt ervoor dat getal_ptr hier naar wijst. Dat
de procedure New twee bytes geheugen moest reserveren kon afgeleid worden uit het pointertype. De opdracht:
Dispose ( getal_ptr ) ;
geeft het voor de variabele gereserveerde geheugen (2 bytes; of dus beter: 8 bytes!) weer vrij; de getal_ptr
pointer zal ‘ergens’ naar blijven wijzen!
Onbegrip ontstaat wanneer je niet beseft dat er steeds twee verschillende dingen gebeuren, nl. een pointer krijgt
een adreswaarde toegewezen en er wordt geheugen gereserveerd of vrijgegeven.3
Om te kijken of je het begrijpt, bespreken we onderstaand programmafragment:
VAR
Tekstwijzer : ^STRING ;
Regel
: STRING;
BEGIN
Tekstwijzer := NIL ;
Regel := ‘Hatsekidee, daar gaan we dan’ ;
Tekstwijzer := @Regel ;
Writeln ( ‘De variabele ‘’Regel’’ heeft als inhoud: ‘, Regel ) ;
Writeln ( ‘‘’Tekstwijzer’’ verwijst naar de tekst: ‘, Tekstwijzer^ )
END.
In dit programmavoorbeeld zullen beide Writeln-opdrachten dezelfde tekst (‘Hatsekidee, ...’) tonen.
Regel is een gedeclareerde STRING -variabele waarvoor is de dataspace (‘het datasegment’) van het programma door de compiler
256 Bytes geheugen zijn gereserveerd. In eerste instantie hebben we op het moment van declaratie aan de variabele Tekstwijzer
‘alleen’ wat ruimte (4 bytes) gereserveerd. In de eerste toekenning ( := NIL) geven we aan, dat die Tekstwijzer beslist ‘nergens’
naar toe wijst. Vervolgens kennen we aan de variabele Regel de tekstwaarde ‘Hatsekidee, ... ’ toe. Na de laatste toekenning aan
Tekstwijzer, wordt nu het geheugenadres van waar Regel in het geheugen zit, aan de pointer Tekstwijzer toegekend; deze pointer
verwijst nu naar een geheugenplaats waar de bekende tekst staat. We hebben de procedure New niet gebruikt (voor het creëren
van ruimte voor de Tekstwijzer) omdat we die lieten gaan wijzen naar de variabele Regel, die al bestond. Regel is een statische
variabele, waarvoor in dit programma-fragment in de dataspace van het programma door de compiler al ruimte was gereserveerd.
Pointers worden vooral gebruikt met dynamische variabelen. Dit zijn variabelen waarvoor niet door de compiler,
maar tijdens de uitvoering van het programma geheugen wordt ingeruimd en vaak na een aantal bewerkingen
ook weer wordt vrijgemaakt. Deze dynamische variabelen worden niet in de dataspace van het programma
geplaatst maar op de Heap. De Heap is doorgaans vele malen groter dan de dataspace. We zullen later zien, dat
‘objecten’ typisch dit dynamische gedrag vertonen, ze worden gemaakt, bestaan een tijdje en gaan weer verloren
(lees: ze worden ‘opgeruimd’).
2.2.1
Geheugenallocatie via ‘New’
De procedure New (…) maakt op de heap een nieuwe dynamische variabele, reserveert daarbij zoveel bytes
geheugen als voor dat datatype nodig is, en zet een pointer die naar de locatie van die variabele wijst.
2.2.2
Geheugen vrijgeven via ‘Dispose’
Indien eerst geheugen ‘gealloceerd’ is met behulp van ‘ New (…)’, kan dat heap-geheugendeel later weer
vrijgegeven worden via ‘Dispose (…)’.
2
Het reserveren van benodigde ruimte gebeurt steeds in blokjes van 8 bytes; ook voor een INTEGER worden dus 8 bytes op
de Heap gereserveerd.
3
Een veel voorkomende fout is te vergeten om een pointer op Nil te initialiseren, waardoor later niet meer gecontroleerd
kan worden of de aangetroffen adres/pointerwaarde naar een gealloceerd stuk geheugen op de Heap wijst.
8
B2: Algoritmiek, Datastructuren en Objectprogrammeren
Dynamische variabelen
2.3 Kleine voorbeelden met dynamische variabelen
Om het gebruik de procedure New te illustreren behandelen we eerst een aantal kleinere voorbeelden. Daarbij
besteden we vooral aandacht aan:
• het werken met pointers en dynamische variabelen,
• het gebruik van typedeclaraties voor samengestelde datatypen, zoals records en arrays
2.3.1
Een dynamische ‘losse’ getalsvariabele en een dynamische STRING-variabele
program dynamische_variabelen;
Uses Crt;
{onder Windows: WinCrt}
TYPE GETALWIJZER = ^INTEGER ;
STRINGWIJZER = ^STRING ;
VAR
GWijzer
SWijzer
TussenVrij
: GETALWIJZER ;
: STRINGWIJZER ;
: LONGINT ;
PROCEDURE BepaalVrijGeheugen ( message: STRING; VAR NuVrij: LONGINT ) ;
BEGIN
writeln('Door ', message, ' verminderd met: ', NuVrij - MemAvail, ‘ bytes’ );
NuVrij := MemAvail
END;
BEGIN
TussenVrij := MemAvail ;
writeln ('We starten met aantal bytes vrij geheugen: ', TussenVrij);
GWijzer := NIL ;
SWijzer := NIL ;
BepaalVrijGeheugen ( '2x NIL-maken', TussenVrij );
New ( GWijzer );
BepaalVrijGeheugen ( 'New(GWijzer)', TussenVrij );
New ( SWijzer );
BepaalVrijGeheugen ( 'New(SWijzer)', TussenVrij );
GWijzer^ := 12345 ;
BepaalVrijGeheugen ( 'GWijzer^:=12345', TussenVrij );
SWijzer^ := 'Hatsekidee';
BepaalVrijGeheugen ( 'SWijzer^:=''Hatsekidee''', TussenVrij );
Dispose ( GWijzer ); BepaalVrijGeheugen ( 'Dispose(GWijzer)', TussenVrij );
Dispose ( SWijzer ); BepaalVrijGeheugen ( 'Dispose(SWijzer)', TussenVrij );
writeln ('We eindigen met aantal bytes vrij geheugen: ', MemAvail )
END.
Kun je goed het effect op het heap-geheugendeel nagaan van de operatie New, toekenningen van enerzijds ‘NIL’
en anderzijds een ‘echte’ waarde aan dynamische variabelen en tot slot van de operatie Dispose.
De uitvoer van dit programma naar het beeldscherm is:
a) Onder Turbo/Borland Pascal voor MSDOS:
We starten met aantal bytes vrij geheugen: 603296
Door 2x NIL-maken verminderd met:
0 bytes
Door New(GWijzer) verminderd met:
8 bytes
Door New(SWijzer) verminderd met:
256 bytes
Door GWijzer^:=12345 verminderd met:
0 bytes
Door SWijzer^:='Hatsekidee' verminderd met:
0 bytes
Door Dispose(GWijzer) verminderd met:
-8 bytes
Door Dispose(SWijzer) verminderd met:
-256 bytes
We eindigen met aantal bytes vrij geheugen: 603296
N.B. ook bij gebruik van REAL-waarden, net als bij INTEGERS, wordt door New(Gwijzer) dus 8 bytes geallocceerd!
b) Onder Borland Pascal voor Windows krijgen we (helaas) een niet zo mooi overzicht … :
We starten met aantal bytes vrij geheugen: 33067008
Door 2x NIL-maken verminderd met:
0 bytes
Door New(GWijzer) verminderd met:
-8176 bytes
Door New(SWijzer) verminderd met:
256 bytes
Door GWijzer^:=12345 verminderd met:
0 bytes
Door SWijzer^:='Hatsekidee' verminderd met:
0 bytes
Door Dispose(GWijzer) verminderd met:
-4 bytes
Door Dispose(SWijzer) verminderd met:
7924 bytes
We eindigen met aantal bytes vrij geheugen: 33067008
(Een wat vreemd rijtje getallen, maar een aantal conclusies kun je er toch wel aan verbinden.)
9
B2: Algoritmiek, Datastructuren en Objectprogrammeren
2.3.2
Dynamische variabelen
Een (statische) RIJ met pointers naar (dynamische) STRINGs
We kunnen de uit de B1-cursus bekende Rij-datastructuur (ARRAY [...] OF ... ) ook gebruiken om niet
‘rechtstreeks’ statische waarden in op te slaan (zoals bij een ARRAY [...] OF STRING ; of een rij van een of
ander RECORD-soort), maar om pointers naar een bepaald datatype in op te slaan.
Voordelen daarvan zijn o.a.: - je hebt meer geheugen beschikbaar voor gegevenswaarden (in het volledige
datasegment van 64 K kun je bijvoorbeeld maximaal 250 STRING’s kwijt ....) ; - bij verwisselingsoperaties
(zoals bij sorteren) hoef je niet de waarden van b.v. gehele RECORD’s met elkaar te verwisselen, maar is het
voldoende de pointers naar die waarden/RECORD’s te verwisselen (en van 2 adreswaarden van bijvoorbeeld elk
4 bytes gaat het verwisselen heel wat sneller dan dat van de inhoud van 2 STRING’s van elk 256 bytes.)
Een voorbeeld:
program rij_met_dynamische_variabelen;
Uses Crt;
{onder Windows: WinCrt}
CONST Max_Aantal = 2000 ;
TYPE STRINGWIJZER = ^STRING;
DYNAMISCHE_STRING_RIJ = ARRAY [ 1 .. Max_Aantal ] OF STRINGWIJZER ;
VAR
SWijzer
: STRINGWIJZER ;
MijnRij
: DYNAMISCHE_STRING_RIJ ;
TussenVrij
: LONGINT ;
Teller
: INTEGER ;
PROCEDURE BepaalVrijGeheugen ( message: STRING; VAR NuVrij: LONGINT ) ;
BEGIN
writeln('Door ', message, ' verminderd met: ', NuVrij - MemAvail, ' bytes' );
NuVrij := MemAvail
END;
BEGIN
TussenVrij := MemAvail ;
writeln ('We starten met aantal bytes vrij geheugen: ', TussenVrij);
FOR Teller := 1 TO Max_Aantal DO MijnRij [ Teller ] := NIL ;
BepaalVrijGeheugen ( 'alles NIL-maken', TussenVrij );
FOR Teller := 1 TO Max_Aantal DO New ( MijnRij [ Teller ]) ;
BepaalVrijGeheugen ( 'alle New(MijnRij[Teller])', TussenVrij );
FOR Teller := 1 TO Max_Aantal DO MijnRij [ Teller ]^ := 'Hatsekidee' ;
BepaalVrijGeheugen ( 'SWijzer^:=''Hatsekidee''', TussenVrij );
FOR Teller := 1 TO Max_Aantal DO Dispose( MijnRij [ Teller ]) ;
BepaalVrijGeheugen ( 'Dispose(MijnRij[Teller])', TussenVrij );
writeln ('We eindigen met aantal bytes vrij geheugen: ', MemAvail )
END.
Weer kun je goed het effect op het Heap-geheugendeel nagaan van de operatie New, toekenningen van enerzijds
‘NIL’ en anderzijds een ‘echte’ waarde aan dynamische variabelen en tot slot van de operatie Dispose.
De uitvoer van dit programma naar het beeldscherm is:
a) Onder Turbo/Borland Pascal voor MSDOS:
We starten met aantal bytes vrij geheugen:
595312
Door alles NIL-maken verminderd met:
0 bytes
Door alle New(MijnRij[Teller]) verminderd met: 512000 bytes
Door SWijzer^:='Hatsekidee' verminderd met:
0 bytes
Door Dispose(MijnRij[Teller]) verminderd met: -512000 bytes
We eindigen met aantal bytes vrij geheugen:
595312
Duidelijk is, dat die 2000 Strings een halve MegaByte aan heap-geheugen innemen; in het normale MSDOS
‘data-segment’ van 64 kB zouden ze nooit ‘gepast’ hebben.
b) Onder Borland Pascal voor Windows krijgen we weer niet zo’n mooi overzicht … :
We starten met aantal bytes vrij geheugen:
33067008
Door alles NIL-maken verminderd met:
Door alle New(MijnRij[Teller]) verminderd met: -19700
Door SWijzer^:='Hatsekidee' verminderd met:
Door Dispose(MijnRij[Teller]) verminderd met:
19700
We eindigen met aantal bytes vrij geheugen: 33067008
0 bytes
bytes
0 bytes
bytes
(Wederom een vreemd rijtje getallen, maar je conclusies kun je er toch wel uit trekken.)
10
B2: Algoritmiek, Datastructuren en Objectprogrammeren
Dynamische variabelen
2.4 ‘Pointer’-operaties
Let op het totaal verschillende effect van de volgende ‘pogingen’ om ‘pointer’ -waarden te veranderen:
Effect van: P1 := P2
2.4.1
Het effect van Pointer1 := Pointer2
Het effect is, dat pointer Pointer1 de waarde krijgt van het
geheugenadres waar ook Pointer2 naar wijst.
Met andere woorden: Pointer1 en Pointer2 wijzen na
afloop van deze toekenning naar dezelfde geheugenlocatie!
Een onplezierig bijeffect is, dat nergens meer bekend is
waar de ‘oude waarde’ van P1 ergens zit en dat die
geheugenlocatie niet meer via ‘Dispose(..) kan worden
vrijgemaakt.
Vóór verwisseling
Ná verwisseling
3
P1
3
P1
A
P2
4
A
P2
4
B
B
Effect van: P1^ := P2^
2.4.2
Het effect van Pointer1^ := Pointer2^
Vóór verwisseling
Het effect van deze operatie is, dat de geheugenlocatie
waar Pointer1^ naar wijst, dezelfde waarde zal krijgen als
de geheugenlocatie waar Pointer2^ naar wijst.
P1
4
P1
A
P2
2.4.3
Ná verwisseling
3
Het verwisselen van pointer-waarden
4
B
P2
B
Ga voor jezelf in het volgende voorbeeld het effect na van
de procedures Verwissel1 en Verwissel2:
program verwisselen_waarden_dynamische_variabelen;
TYPE
COMBINATIE = RECORD
Getal : INTEGER ;
Teken : CHAR
END ;
COMBINATIEWIJZER = ^COMBINATIE ;
PROCEDURE Toon ( Wijzer : COMBINATIEWIJZER ) ;
BEGIN
write ( Wijzer^.Getal, Wijzer^.Teken, ' ')
END ;
PROCEDURE Verwissel1 ( VAR Wijzer1, Wijzer2 : COMBINATIEWIJZER) ;
VAR Hulp : COMBINATIEWIJZER ;
BEGIN
Hulp
:= Wijzer1 ;
{ pointerwaarden verwisselen }
Wijzer1 := Wijzer2 ;
Wijzer2 := Hulp
END ;
PROCEDURE Verwissel2 ( VAR Comb1, Comb2 : COMBINATIE ) ;
VAR Hulp : COMBINATIE ;
BEGIN
Hulp := Comb1 ;
{ record-inhoud verwisselen }
Comb1 := Comb2 ;
Comb2 := Hulp
END ;
VAR
Wijzer1, Wijzer2
: COMBINATIEWIJZER ;
BEGIN
Writeln ( 'MemAvail = ', MemAvail );
New ( Wijzer1 ) ;
Wijzer1^.Getal := 3 ; Wijzer1^.Teken := 'A' ;
New ( Wijzer2 ) ;
WITH Wijzer2^ DO BEGIN Getal := 4 ; Teken := 'B'
Toon ( Wijzer1 ) ; Toon ( Wijzer2 ) ;
Verwissel1 ( Wijzer1, Wijzer2 ) ;
Toon ( Wijzer1 ) ; Toon ( Wijzer2 ) ;
Verwissel2 ( Wijzer1^, Wijzer2^ ) ;
Toon ( Wijzer1 ) ; Toon ( Wijzer2 ) ;
11
END;
4
B
B2: Algoritmiek, Datastructuren en Objectprogrammeren
Dynamische variabelen
Dispose ( Wijzer1 ) ; Dispose ( Wijzer2 ) ;
Writeln;Writeln ( 'MemAvail = ', MemAvail )
END.
Het getoonde resultaat op het beeldscherm (in een MSDOS-box) is:
MemAvail = 535040
3A 4B 4B 3A 3A 4B
MemAvail = 535040
Het effect van de verschillende Verwisselx-procedures blijkt (zoals verwacht) hetzelfde te zijn. Een voordeel van
het verwisselen van de adres-waarden in de pointers (dus met Verwissel1) is, dat dan slechts 3 waarden van elk 4
bytes verwisseld hoeven te worden, terwijl bij het verwisselen van de waarden op de aangewezen
geheugenlocaties er 3x het totaal aantal bytes van het gehele te verwisselen record versleept moet worden
(toevallig is dat in voorgaand voorbeeld weinig, maae als het b.v. over ‘persoonsrecords’ met b.v. 200 bytes
grootte gaat, gaat het verwisselen van de pointerwaarden veel sneller dan het verwisselen van de recordinhouden
zelf.
Met behulp van zo’n Verwissel-procedure is het uiteraard mogelijk om een een sorteer-proces toe te passen op
een rij van pointers naar (elk) een record.
2.5 Een uitgebreid voorbeeld: Studentenadministratie
Als een uitgebreider voorbeeld van het werken met dynamische variabelen (lees: pointers) maken we een
eenvoudig administratief programma voor het opslaan van studentengegevens bij een cursus. We besteden
daarbij speciale aandacht aan:
• het werken met pointers en dynamische variabelen,
• het gebruik van typedeclaraties voor samengestelde datatypen, zoals records en arrays
• de omgang met typed files,
• het werken met Units.
In het te ontwikkelen programma moeten van elke cursist de volgende gegevens worden opgenomen:
• studentnummer
• naam
• studierichting
• practicumcijfer
• tentamencijfer
• cursuscijfer
Dit leidt tot de volgende typedeclaraties:
TYPE
NaamStr
= STRING [ NaamLgt ];
StudieStr = STRING [ StudieLgt ];
PSTUDENT = ^RSTUDENT;
{N.B. naar nog ongedefinieerd recordtype ‘mag’! }
RSTUDENT = RECORD
nummer : LongInt;
naam
: NaamStr;
studierichting : StudieStr;
prakticumcijfer,
tentamencijfer : REAL ;
cursuscijfer
: INTEGER ;
END;
FStudent: FILE OF RSTUDENT ;
12
B2: Algoritmiek, Datastructuren en Objectprogrammeren
Dynamische variabelen
De gegevens worden als dynamische recordvariabelen op de Heap gezet. Als globale variabelen worden lijsten
met pointers gebruikt. Er moeten drie lijsten komen, die naar de studentenrecords wijzen op resp. naamvolgorde,
nummervolgorde en studierichting.
De declaratie van het lijsttype luidt:
D yn am isch e Heap space
ALijst = ARRAY[1..MaxAantal] of PSTUDENT;
en er zijn drie globale variabelen van dit type gedeclareerd:
VAR
Statisch e dataspace
n aam lijst
naamlijst, nummerlijst,
studielijst : ALijst;
Poin ter 1
Poin ter 2
Alle drie deze ALijst-variabelen kunnen geïnitialiseerd worden
met behulp van de volgende procedure:
procedure InitLijst ( VAR Lst : ALijst );
VAR i : INTEGER ;
BEGIN
FOR i := 1 TO MaxAantal DO Lst[i] := NIL;
END {InitLijst};
Als je dat zou willen, zou je bijvoorbeeld de naamlijst –
variabele kunnen initialiseren (alle pointers in die rij op NIL
zetten) via de aanroep: InitLijst( naamlijst ).
Jan Jansen
9500100
Cog
0.0
0.0
0
C laas Z ee
95 0002 0
T SI
0.0
0.0
0
Pointer 2
Figure 3 Statische en Dynamische Data
nummerlijst
Pointer 1
Pointer 1
Pietje Puk
9500010
Cog
0.0
0.0
0
Pointer 2
Pointer n
Pointer n
Claas Zee
9500020
TSI
0.0
0.0
0
Figure 4 Naam- en nummerlijsten naar studenten
13
Pietje Pu k
95 0001 0
C og
0.0
0.0
0
Poin ter n
Let op het gebruik van de NIL-waarde (om aan te geven, dat de
pointer in een bepaalde rijcel nog niet naar een gealloceerd
record wijst).
naamlijst
Jan Jan sen
95 0010 0
C og
0.0
0.0
0
B2: Algoritmiek, Datastructuren en Objectprogrammeren
Dynamische variabelen
Programma’s opsplitsen in Units
De voorgaande TYPE-declaraties zijn geplaatst
in de Unit Student in het bestand Student.pas.
Het programma heet Studenten_administratie en
is te vinden in het hoofdbestand StudAdm.pas, de
verdere programmadelen, komen in de Units
Namen, Nummers en Studies, in de
respectievelijke bestanden Namen.pas,
Nummers.pas en Studies.pas
Unit Student
Student.pas
Unit Namen
Namen.pas
We zullen het programma implementeren als een
eenvoudig programma onder Windows, waarbij
gebruik gemaakt wordt van de standaard unit Crt
(of bij gebruik van Pascal for Windows:
WinCrt), en waarbij minimale eisen gesteld
worden aan de invoer vanaf het toetsenbord. De
recordvelden mogen met een readln van het
toetsenbord gelezen worden.
Unit Nummers
Nummers.pas
Unit Studies
Studies.pas
Program Studenten_Administratie
StudAdm.pas
Figure 5 Units in Studenten Administratie
Program Studenten_administratie;
{In StudAdm.pas}
Uses {Win}Crt, Student, Namen, Nummers, Studies;
VAR naamlijst, nummerlijst, studielijst : ALijst;
BEGIN
{ In de Unit Namen }
MaakNaamLijst( naamlijst );
ToonNaamLijst( naamlijst );
{ In de Unit Nummers }
MaakNummerLijst( nummerlijst, naamlijst );
ToonNummerLijst( nummerlijst );
{In de Unit Studies }
MaakStudieLijst( studielijst, naamlijst );
ToonStudieLijst( studielijst );
END.
Programma 1 Studenten administratie
In de Unit Namen komen de procedures MaakNaamlijst en ToonNaamLijst. De interface van de unit is:
Unit Namen;
INTERFACE
Uses Student;
PROCEDURE MaakNaamLijst( VAR opnaam : ALijst );
PROCEDURE ToonNaamLijst( opnaam : ALijst );
Het top down ontwerp en de implementatie van MaakNaamLijst moet de eerste keer een aantal studentrecords
maken en opslaan in een bestand, en vervolgens, als het bestand bestaat, de studentenrecords lezen en dynamisch
in het geheugen plaatsen:
14
B2: Algoritmiek, Datastructuren en Objectprogrammeren
Dynamische variabelen
PROCEDURE MaakNaamLijst( VAR opnaam : ALijst );
VAR bestand : FStudent;
{ FStudent: FILE OF RSTUDENT }
BEGIN
InitLijst ( opnaam ) ; Aantal := 0 ;
IF OpenStudentBestand ( bestand ) THEN
LeesStudentBestand ( bestand, opnaam )
ELSE BEGIN
LeesNaamLijst ( opnaam );
BewaarStudentBestand ( bestand, opnaam );
END;
END;
De uitwerking van LeesStudentBestand is rechttoe rechtaan. Zolang het einde van het geopende bestand niet
bereikt is, worden studentrecords, stud, ingelezen, de in het implementatie deel van de unit gedeclareerde
variabele Aantal wordt verhoogd, een nieuw dynamisch object wordt gecreëerd met de tijdelijke pointer, phulp,
en de inhoud van het studentrecord, r, toegekend aan phulp^. Steeds wordt de waarde van phulp op de juiste
plek in de lijst geplaatst en tot slot wordt het bestand gesloten.
PROCEDURE LeesStudentBestand ( VAR bestand : FStudent; VAR opnaam : ALijst );
VAR stud : RStudent; phulp : PStudent;
BEGIN
WHILE NOT EOF( bestand ) DO BEGIN
Read ( bestand, stud );
{Lees een studentrecord }
New ( phulp );
{maak ruimte voor een nieuw studentrecord}
phulp^ := stud ;
{ken er de inhoud van stud aan toe}
VoegIn( opnaam, phulp ); {Voeg de pointer alfabetisch toe}
END {While};
Close(bestand) ;
{Sluit het bestand}
END {LeesStudentBestand};
De procedure VoegIn plaatst de student in de lijst op de alfabetisch juiste plaats door de lijst van boven naar
beneden af te lopen tot de eerste naam gevonden wordt die alfabetisch groter is dan de naam van de te plaatsen
student of het einde van de lijst bereikt is. Nadat alle studenten op en na deze positie één plaats naar beneden
geschoven zijn, kan de nieuwe student tussengevoegd worden:
PROCEDURE VoegIn ( VAR opnaam : ALijst ; stud : PStudent );
VAR positie, j : INTEGER ;
BEGIN
{bepaal ‘positie’ voor invoegen }
positie := 1;
WHILE ( positie < MaxAantal) AND ( opnaam[positie] <> NIL ) AND
( opnaam[ positie ]^.naam < stud^.naam ) DO inc ( positie ) ;
{maak ruimte door naar beneden te schuiven}
FOR j := Aantal DOWNTO positie DO opnaam [ j+1 ] := opnaam [ j ] ;
{voeg in}
inc ( Aantal ) ;
{ Oei; toegrijpen op unit-globale variabele ...}
opnaam [ positie ] := stud ;
END {voegin};
Deze procedure is iets te simpel, want er volgt geen foutmelding als de lijst vol is. De procedure LeesNaamLijst
bevat een herhaallus waarbij de gegevens van een studentrecord van het toetsenbord gelezen worden met de
procedure LeesStudent. Als de naam niet leeg is wordt Aantal met één verhoogd en een nieuwe dynamische
variabele gecreëerd en toegewezen aan de eerste vrije studentpointer in de naamlijst opnaam.
15
B2: Algoritmiek, Datastructuren en Objectprogrammeren
Dynamische variabelen
PROCEDURE LeesNaamLijst ( VAR opnaam : ALijst );
VAR stud : RStudent ;
BEGIN
REPEAT
LeesStudent ( stud );
IF IsGeldig ( stud ) THEN BEGIN
Inc ( Aantal );
New ( opnaam [ Aantal ] );
{ vraag ruimte op de Heap }
opnaam [ Aantal ]^ := stud
{ kopieer gegevens naar Heap }
END;
UNTIL ( NOT IsGeldig ( stud ) ) OR ( Aantal = MaxAantal );
END {LeesNaamLijst};
De boolse functie IsGeldig test de studentgegevens op geldigheid. We maken ons er op dit moment een beetje
vanaf door te stellen dat studentengegevens geldig zijn als de naam uit minstens één teken bestaat en het
studentnummer groter dan 0 is:
FUNCTION IsGeldig( stud : Rstudent ) : BOOLEAN ;
BEGIN
IsGeldig:= ( Length ( stud.naam ) > 0 ) AND ( stud.nummer > 0 );
END {IsGeldig};
De procedure BewaarStudentBestand opent het bestand om te schrijven en schrijft vervolgens Aantal
studentrecords naar het bestand f en sluit tot slot het bestand.
PROCEDURE BewaarStudentBestand ( VAR bestand : FStudent; opnaam : ALijst );
VAR index : INTEGER ;
BEGIN
Rewrite ( bestand );
FOR index := 1 to Aantal DO BEGIN
IF opnaam[index] <> Nil THEN write ( bestand, opnaam[index]^ );
END {for};
Close ( bestand );
END {BewaarStudentBestand};
Het ontwerp en de implementatie van de procedure ToonNaamLijst in de unit Namen moet de studenten aan de
hand van de naamlijst in alfabetische volgorde tonen Deze procedure ToonNaamLijst wordt eenvoudig gehouden
door de algemene ToonLijst aan te roepen.
PROCEDURE ToonNaamLijst ( opnaam : ALijst );
BEGIN
ToonLijst ( opnaam );
END;
Het ontwerp en de implementatie van de procedure MaakNummerLijst in de unit Nummers, die de nummerlijst
maakt en daarbij studenten aanwijst op nummervolgorde. Eerst wordt de nummerlijst Opnummer geïnitialiseerd
en vervolgens wordt de naamlijst opnaam afgelopen en de pointers uit de naamlijst op nummervolgorde
ingevoegd in de nummerlijst.
Unit Nummers;
INTERFACE
Uses Student;
PROCEDURE MaakNummerLijst ( VAR opnummer : ALijst; opnaam : ALijst );
PROCEDURE ToonNummerLijst ( opnummer : ALijst );
IMPLEMENTATION
VAR Aantal : INTEGER ;
{ bereikbaar voor alle procedures binnen deze unit }
16
B2: Algoritmiek, Datastructuren en Objectprogrammeren
Dynamische variabelen
PROCEDURE MaakNummerLijst( VAR opnummer : Alijst ; opnaam : ALijst );
VAR positie : INTEGER ;
BEGIN
InitLijst ( opnummer ); Aantal := 0 ;
positie := 1;
WHILE ( opnaam [ positie ] <> NIL ) DO BEGIN
VoegIn ( opnummer, opnaam [ positie ] ) ;
Inc (positie );
END {while};
END;
MaakNummerLijst maakt gebruik van een aangepaste voegin procedure die invoegt op nummervolgorde.
PROCEDURE VoegIn ( VAR opnummer : ALijst; stud : PStudent );
VAR positie, j : INTEGER ;
BEGIN
{Alleen als er nog plaats is:}
IF Aantal < MaxAantal THEN BEGIN
{bepaal invoegpositie }
positie := 1;
WHILE (positie < Aantal ) AND ( opnummer [ positie ] <> NIL )
AND (opnummer[positie]^.nummer < stud^.nummer ) DO inc(positie);
{maak ruimte door naar beneden verschuiven}
FOR j := Aantal-1 DOWNTO positie DO opnummer [j+1] := opnummer [j];
{voeg in}
Inc ( Aantal );
opnummer [ positie ] := stud ;
END {if};
END {voegin};
Deze VoegIn procedure mag er eigenlijk van uit gaan dat de naamlijst niet te vol kan zijn, zolang de naam- en
nummerlijst op hetzelfde type gebaseerd zijn. Als verzekering tegen toekomstige wijzigingen is er toch maar een
begrenzende voorwaarde gesteld. De procedure ToonNummerLijst heeft niets nieuws te melden en
implementeert met behulp van de procedure ToonLijst de procedure die de studenten toont op nummervolgorde
aan de hand van de nummerlijst.
PROCEDURE ToonNummerLijst( opnummer: ALijst );
BEGIN
ToonLijst( opnummer );
END;
Het ontwerp en de implementatie van de procedures MaakStudieLijst en ToonStudieLijst in de unit Studies, die
de studenten groepeert naar studierichting verschilt niet van die van Opnummer,
Unit Studies;
INTERFACE
Uses Student;
PROCEDURE MaakStudieLijst ( VAR st : Alijst ; opnaam : ALijst );
PROCEDURE ToonStudieLijst ( st : ALijst );
slechts de VoegIn procedure is aangepast en sorteert op studierichting in alfabetische volgorde middels de
opdracht::
WHILE ( st [ positie ] <> NIL ) AND
( st[positie]^.studierichting < stud^.studierichting ) DO Inc (positie);
17
B2: Algoritmiek, Datastructuren en Objectprogrammeren
Dynamische variabelen
2.6 Practicumopdracht
Je gebruikt in deze opdracht een rij (uiteindelijke rijdimensie: 5000, maar start met b.v. een rijlengte van 10 !)
van pointers naar records, die elk als volgt zijn opgebouwd:
GEGEVEN
=
RECORD
getal
teken
:
:
INTEGER ;
CHAR
END
Nà opstarten van het programma moeten de records van de rij random (50% wèl en 50% nìet) worden opgevuld
met een willekeurig geheel getal èn een willekeurig teken uit het alfabet (hoofd en/of kleine letter).
De (50%) pointers die nìet verwijzen naar zo’n opgevuld record, moeten een ‘ Nil’-waarde krijgen.
Laat de inhoud van de bij de rij behorende records op het scherm zien; laat bij Nil-pointers het woord ‘NIL’ op
het scherm verschijnen.
Hierna moet de rij met pointers gesorteerd worden volgens de getalswaarde uit het geassocieerde record.
Gebruik voor het benodigde sorteerproces een aanpassing van de selectiemethode (kijk eens in je B1-Reader of
in een boek zoals Savitch of kijk naar het ‘sjabloon’ hieronder); verwissel bij het sorteerproces de
pointerwaarden en nìet de recordinhouden en let op de Nil-pointers (die moeten ‘naar achteren’ in de rij)!
Laat tot slot weer alle recordinhouden zien volgens de nu aangebrachte ordening van de rij, waarbij wederom
Nil-pointers het woord ‘NIL’ op het scherm moeten tonen.
Zorg ervoor, dat door controle van het in het begin èn op het einde beschikbare werkgeheugen, vóór het
beëindigen van het programma, alle eerder dynamisch bezette geheugenruimte inderdaad weer is vrijgegegeven.
Laat het evt. verschil in beschikbare begin- en eindwaarde op het beeldscherm verschijnen.
Schematisch:
325
‘C ’
27
938
127
‘z ’
‘k ’
‘m ’
rij vóór sorteren:
N IL
Gewenste uitvoer:
325 C
NIL
325
‘C ’
N IL
N IL
NIL
27 z
938 k
NIL
N IL
127 m
27
938
127
‘z ’
‘k ’
‘m ’
NIL
rij ná sorteren:
Gewenste uitvoer:
27 z
127 m
325 C
938 k
N IL
N IL
N IL
NIL
NIL
NIL
N IL
NIL
Sjabloon ‘selectie-sorteren’:
PROCEDURE selection sort
BEGIN
eerst is het gesorteerde deel leeg
{ sorteer_vanaf := 0 }
WHILE ongesorteerde deel nog niet leeg
{ sorteer_vanaf < aantal-1 }
DO BEGIN
zoek het kleinste element uit het nog ongesorteerde deel ;
verwissel dit met het eerste element uit ongesorteerde deel
END {WHILE}
END
18
Download