PI1-Kap3g

Werbung
Strukturierte Datentypen - Überblick
Modula-2-Typen und benutzerdefinierte Typen
elementare Typen
Aufzählungstypen (enumeration types)
INTEGER
Ampel = (rot,gelb,gruen);
REAL
CARDINAL
Tag = (Mo,Di,Mi,Do,Fr,Sa,So);
CHAR ...
BOOLEAN = (FALSE,TRUE);
Strukturierte Typen
Bereichstypen (range types)
VektorTyp=[1..10] OF INTEGER;
Wochenende = [Sa..So];
Matrix=ARRAY[1..4],[1..10] OF INTEGER;
KleineZahlen = [0..100];
Datum = RECORD
Tag :INTEGER
Monat:INTEGER;
Jahr :INTEGER;
END;
Stop =[rot..gelb];
All diese Typen können bei der Definition strukturierter Datentypen verwendet werden.
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 141
Dynamische Datenstrukturen
Zeiger (Pointer) und dynamische Datenstrukturen
Mit Feldern (Array) und Verbunden (Record) ist es
möglich komplexe Datenstrukturen zu definieren.
Allerdings sind diese Strukturen statisch (d.h. von fester Größe).
Zur Definition von Datenstrukturen, bei denen zur Laufzeit nicht
nur die zugeordneten Werte, sondern auch der Aufbau und die
Größe variabel sein sollen, braucht man dynamische Strukturen.
Beispiele: Listen, Bäume oder Graphen
Modula-2 bietet ein einfaches Werkzeug zur Definition
beliebiger dynamischer Datenstrukturen: den Zeiger (Pointer)
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 142
Zeiger (Pointer)
Was sind Zeiger ?
Der Wert (Inhalt) einer Zeigervariablen dient als Referenz auf eine
Variable seines Bezugstyps (referenced type).
Ein Zeiger (Pointer) "zeigt" auf eine andere Variable.
Man kann den Wert einer Zeigervariable auch als Anfangsadresse eines
bestimmten Speicherbereiches auffassen.
Abstraktes Modell
Speichermodell
0
my_pointer
Blabla
Zeigervariable
enthält Referenz
(Adresse)
Bezugsvariable
enthält Wert
(des Bezugstyps)
Praktische Informatik 1
my_pointer
4711
R.Zicari / JWG-Universität Frankfurt
4711
Blabla
Kap.3 - Modula-2 S. 143
Zeiger (Pointer)
Formale Definition für einen Zeigertyp in Modula-2:
TYPE PointerType
= POINTER TO Type;
Beispiel:
TYPE string_pointer = POINTER TO string;
• “Type“ stellt den Bezugstyp, also den Typ der
referenzierten Variable (referenced variable), dar.
• Ein Zeiger (Pointer) kann in Modula-2 nicht auf Variablen
beliebigen (unterschiedlichen) Typs zeigen.
Dies soll die Programmsicherheit erhöhen, da nichttypisierte
Zeigervariablen schwer lokalisierbare Fehler zur Folge
haben können.
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 144
Zeiger (Pointer)
Wie werden Zeiger verwaltet ?
Beispiel:
Mit dem Befehl
VAR my_pointer : string_pointer;
wird eine Zeigervariable vom Typ string_pointer deklariert.
Die zugehörige Bezugsvariable (referenced variable) muss allerdings
zur Laufzeit (also innerhalb des Programms) explizit erzeugt werden.
Hierfür steht die Standardprozedur NEW(...) zur Verfügung:
NEW(my_pointer);
Dieser Befehl hat zur Folge, dass der entsprechende Speicherplatz für die
benötigte Bezugsvariable zur Laufzeit allokiert wird. Zusätzlich wird
in my_pointer die Adresse auf diesen Speicherplatz eingetragen.
DISPOSE(my_pointer);
gibt den allokierten Speicherbereich wieder frei, er wird deallokiert.
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 145
Zeiger (Pointer)
Die Operationen NEW( ) bzw. DISPOSE( ) werden in Aufrufe der
Funktionen ALLOCATE( ) bzw. DEALLOCATE( ) übersetzt:
NEW (p)
ALLOCATE(p,SIZE(XT))
Mit ALLOCATE(p,SIZE(XT)) wird für eine Variable des Typs XT ein
Speicherbereich der Größe SIZE(XT) angefordert, und dessen
Anfangsadresse wird in p hinterlegt.
DISPOSE(p)
DEALLOCATE(p,SIZE(XT))
DEALLOCATE(p,SIZE(XT)) bewirkt, dass der Speicherbereich mit
der Länge SIZE(XT) ab der Adresse p freigegeben wird.
Da Pointer immer einem bestimmten Typ XT zugeordnet sind, kann man
direkt aus dem Pointertyp die Länge SIZE(XT) bestimmen.
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 146
Zeiger (Pointer)
Hierzu muss man allerdings das Modul Storage
importieren, da die Funktionen ALLOCATE( ) und
DEALLOCATE( ) dort definiert sind.
FROM Storage IMPORT ALLOCATE,DEALLOCATE;
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 147
Zeiger (Pointer)
Die Variablen, auf die my_pointer zeigt, ist anonym
(hat also keinen Namen) und kann nur indirekt über den
Zeiger my_pointer angesprochen werden!
Es ist nicht möglich, die Bezugsvariable
ohne den zugehörigen Zeiger anzusprechen.
Der Wert (Inhalt) der Bezugsvariablen kann über den sog.
Dereferenzierungs-Operator (dereferencing operator) ^
angesprochen werden.
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 148
Zeiger (Pointer)
Beispiel:
Die Bezugsvariable von my_pointer wird also
mit my_pointer^ angesprochen.
MODULE Pointer_example;
...
TYPE string_pointer = Pointer TO string;
VAR my_pointer:string_pointer
BEGIN
Zeigervariable
NEW(my_pointer);
my_pointer^:='geschafft!';
my_pointer
END Pointer_example.
Bezugsvariable
geschafft!
my_pointer^
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 149
Zeiger (Pointer)
Frage: Warum sind Zeiger so wichtig ?
• Man kann mit Zeigern auf Variablen zeigen,
die wiederum auch Zeiger enthalten.
Das ermöglicht den Aufbau dynamischer Strukturen!
• Analog zu den rekursiven Prozeduren, kann man mit
Zeigern rekursive Datenstrukturen konstruieren.
• Beispiele hierfür sind Listen oder Bäume.
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 150
Lineare Liste
Beispiel: Listenerzeugung mit Zeigern
Anfang
der Erste
der Zweite
der Dritte
Deklaration einer linearen Liste
TYPE ListPointer = POINTER TO ListNode;
TYPE ListNode = RECORD
next:ListPointer;
Data:string;
END
VAR Anfang,p,Listend : ListPointer;
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 151
Lineare Liste
Frage: Wie beende ich eine Liste oder eine andere
dynamische rekursive Datenstruktur?
• Wir benötigen ein Mittel, um einen Pointer auf "nichts"
zeigen lassen zu können, damit die rekursive Definition
der Datenstruktur terminiert.
(termination of data structure)
• Für Zeigervariablen existiert ein spezieller Wert:
NIL (Not In List)
• NIL zeigt auf kein Objekt.
Damit ist es offensichtlich, dass der Ausdruck p^
nicht ausgewertet werden darf, falls p=NIL ist.
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 152
Lineare Liste
Wie muss ein Programm zur Erzeugung einer Liste aussehen?
• Zunächst müssen wir die Liste initialisieren.
Das bedeutet, dass wir die Variable Anfang auf NIL
setzen müssen, damit sie nicht auf eine undefinierte
Speicherposition zeigt:
Anfang := NIL;
Damit haben wir die leere Liste erzeugt.
NIL
Anfang
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 153
Lineare Liste
Jetzt erzeugen wir das erste Element:
• new(p);
• Da der Zeiger p^.next auf eine undefinierte
Speicherposition zeigen könnte, wird er auf NIL gesetzt:
p^.next := NIL;
• Wir setzen die gewünschten Daten in unser neues Element:
p^.Data := "der Erste";
Anfang
NIL
p
Praktische Informatik 1
der Erste
p^.next
p^.Data
R.Zicari / JWG-Universität Frankfurt
NIL
Kap.3 - Modula-2 S. 154
Lineare Liste
Jetzt müssen wir das neue Element in die leere Liste einfügen:
• Dazu setzen wir den Zeiger Anfang so,
dass er auf das Element zeigt, auf welches auch p zeigt:
Anfang := p;
Anfang
NIL
p
der Erste
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 155
Lineare Liste
Wir “merken“ uns das aktuelle Ende der Liste mit einem
Hilfszeiger, den wir z.B. Listend nennen.
Listend := p;
Anfang
NIL
p
der Erste
Listend
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 156
Lineare Liste
Wir erzeugen einen weiteren Knoten:
new(p);
p^.next := NIL;
p^.DATA := "der Zweite";
Anfang
Listend
NIL
der Erste
NIL
p
der Zweite
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 157
Lineare Liste
Wir fügen auch diesen Knoten am Ende der Liste ein und
aktualisieren unseren Zeiger auf das Listenende:
(i)
(ii)
Listend^.next := p;
Listend
:= Listend^.next;
Anfang
Listend
(ii)
(i)
der Erste
NIL
der Zweite
p
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 158
Lineare Liste
Frage: Wie durchsucht man eine lineare Liste?
• Um eine lineare Liste durchsuchen zu können, brauchen wir
eine Laufvariable (hier current), die angibt, an welcher
Listenposition wir uns aktuell befinden.
Anfang
...
der Erste
der Zweite
der Dritte
current
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 159
Lineare Liste
Diese Laufvariable wird als Zeiger des Listentyps deklariert,
VAR current : ListPointer;
und auf den Listenanfang gesetzt (Initialisierung).
current := Anfang;
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 160
Lineare Liste
NIL
Anfang
ich nicht
ich nicht
Such mich!
current
Danach muss man in einer Schleife die Liste durchlaufen, bei
jedem Schritt die Suchbedingung überprüfen und die Laufvariable
current gegebenenfalls ein Listenelement weiterbewegen.
WHILE (current^.Data<>"Such mich!") AND
(current^.Data<>NIL) (* Auf Listenende achten *)
DO current:=current^.next
END;
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 161
Lineare Liste
• Man kann also eine Funktion schreiben,
die eine Liste durchsucht.
• Weiterhin ist es möglich, die Elemente an jedem Punkt
der Liste einfügen oder löschen zu können.
Dazu kann man die Such-Funktion verwenden.
• Bei der Implementierung von Einfüge- bzw.
Löschoperationen muss man aufpassen,
dass man die Liste wieder korrekt zusammenfügt.
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 162
Lineare Liste
Beispiel:
Löschen des Listenelements mit dem Inhalt "der Zweite"
Zunächst werden zwei Hilfszeiger lauf1 und lauf2 deklariert.
VAR lauf1, lauf2 : Listpointer;
Ziel ist es, lauf2 auf das zu löschende Element und
lauf1 auf dessen Vorgänger zeigen zu lassen.
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 163
Lineare Liste
Beide Zeiger werden auf den Listenanfang gesetzt.
lauf1:=Anfang;
lauf2:=Anfang;
.....
Anfang
lauf2
Praktische Informatik 1
der Erste
der Zweite
der Dritte
lauf1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 164
Lineare Liste
Danach wird das entsprechende Element gesucht:
REPEAT
lauf1 := lauf2;
lauf2 := lauf2^.next;
UNTIL lauf2^.Data = "der Zweite";
.....
Anfang
der Erste
der Zweite
lauf1
lauf2
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
der Dritte
Kap.3 - Modula-2 S. 165
Lineare Liste
Jetzt wird das Element aus der Kette heraus genommen:
lauf1^.next := lauf2^.next;
.....
Anfang
Praktische Informatik 1
der Erste
der Zweite
lauf1
lauf2
R.Zicari / JWG-Universität Frankfurt
der Dritte
Kap.3 - Modula-2 S. 166
Lineare Liste
Das Element kann nun auch physikalisch gelöscht werden.
(Sein Speicherplatz wird wieder deallokiert (freigegeben).
DISPOSE (lauf2);
.....
Anfang
der Erste
NIL
lauf1
lauf2
Praktische Informatik 1
der Dritte
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 167
Lineare Liste
Problem: Lineare Listen können immer nur
in einer Richtung durchlaufen werden.
ONE WAY
.....
Anfang
Praktische Informatik 1
der Erste
der Zweite
R.Zicari / JWG-Universität Frankfurt
der Dritte
Kap.3 - Modula-2 S. 168
Stapel (stack)
Eine oft genutzte Datenstruktur, die mit einer linearen Liste
realisiert werden kann, ist der Stapel (stack), der manchmal auch
als Keller bezeichnet wird.
Beim Stapel sind nur die folgenden beiden Operationen erlaubt:
•
pop = hole das oberste Element vom Stapel
•
push(x) = lege das Element x oben auf den Stapel
Beim Stapel kann man immer nur Zugriff auf das oberste Element.
Um ein Element aus einem Stapel zu löschen, muss man zuvor alle
Elemente, die "über" dem gesuchten Element liegen, entfernen.
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 169
Stapel (stack)
Beispiel eines Stapels:
Abstraktes Modell
push(x)
pop
x
Ein Stapel arbeiten nach dem LIFO-Prinzip (Last In First Out).
Praktische Informatik 1
R.Zicari / JWG-Universität Frankfurt
Kap.3 - Modula-2 S. 170
Herunterladen