Home
Up
Intro
Contents
Chapter
1
2
3
4
5
6
7
8
9
10
Design
Assert
Timing
EBNF
Report
Pas
Last Changed: Nov. 19, 1997
This is a conversion from Oberon text to HTML. The converter software is still under development,
and some features or information may be missing in this converted version.
HTML hypertext facilities are not yet active in this document.
To exploit the interactive facilities, use Oberon System 3 and the source of this text,
available as ASCII-coded Oberon System 3 documents. A previous version is also available for Oberon V4.
To access this and other additional material use
ftp.
For the convenience of our students, most of this information and the related material is in German.
Sorry if this is not one of your languages.
Einführung in die Programmiersprache Oberon.
08 Records, Arrays, Pointer, Objekte
Jede Variable in Oberon hat einen Typ. Der Typ spezifiziert den möglichen
Wertebereich für die Variable, legt fest, welche Operatoren anwendbar
sind und bestimmt mittelbar, wie die Information der Variablen gespeichert
wird und wie gespeicherte Information zu interpretieren ist. In Kapitel 5
haben wir bereits die elementaren Datentypen
BOOLEAN CHAR
SHORTINT INTEGER LONGINT
REAL LONGREAL
SET
kennengelernt. Diese Typen sind vordefiniert. Oberon erlaubt es, Typen zu
kombinieren, Typ-Definitionen zu erweitern und neue Typen zu deklarieren.
Beispiel 0: Eine "universelle"
Plot-Prozedur sollte anhand von Wertebereich und zu zeichnender Funktion
einen (automatisch skalierten) Plot erzeugen. Geeignete Deklaratione sind:
TYPE
Function=
PROCEDURE (x:LONGREAL):LONGREAL;
...
PROCEDURE Plot(from,to:LONGREAL;
f: Function);
BEGIN
...
END Plot;
Beim Aufruf von Plot kann dann irgendeine
Prozedur für f übergeben werden, die dem Muster von Function
entspricht.
Anstelle Function als neuen Typ zu deklarieren,
würde man in diesem einfachen Beispiel den Typ implizit in der Deklaration
von Plot definieren, und nur schreiben:
PROCEDURE Plot(from,to:LONGREAL;
f:
PROCEDURE (x:LONGREAL):LONGREAL);
BEGIN
...
END Plot;
Beispiel 1: Wir haben im letzten Kapitel drei Zufallsgeneratoren RandLGM,
RandPRB und RandUNIX programmiert. Alle drei haben dieselbe Aufrufstruktur:
PROCEDURE xxxxx(VAR seed:LONGINT):
LONGINT;
Wenn wir mit unterschiedlichen Generatoren in einer Simulation experimentieren
wollen, ohne jedesmal unser Programm umzuschreiben, so können wir den
jeweils aktuellen Generator in einer Variablen notieren, genau wie wir es
mit Startwerten oder Parametern machen würden. Dazu deklarieren wir
TYPE
GeneratorProc =
PROCEDURE (VAR seed:LONGINT):
LONGINT;
...
VAR
NextRandom: GeneratorProc;
GlobalSeed:LONGINT;
Mit diesen Deklarationen ist ein neuer Typ GeneratorProc
eingeführt, der gleichrangig mit allen vordefinierten Typen ist. Dieser
Typ wird dann z.B. bei der Deklaration der Variablen NextRandom benutzt.
Nach einer Zuweisung
NextRandom:=RandLGM;
liefert dann NextRandom(GlobalSeed) die nächste
Zufallszahl, berechnet mit dem LGM-Generator. Setzen wir
NextRandom:=RandPRB;
so liefern die nachfolgende Aufrufe von NextRandom(GlobalSeed)
Zufallszahlen, berechnet mit dem PRB-Generator.
Beispiel 2: Wir können weitergehen und den aktuellen Startwert eines
Zufallszahlengenerators mit der Berechnungsfunktion bündeln. Das erlaubt
es auf einfache Weise, verschiedene Zufallszahlengeneratoren reproduzierbar
zu kontrollieren. Dazu deklarieren wir:
TYPE
GeneratorProc
= PROCEDURE (VAR
seed:LONGINT): LONGINT;
GeneratorType = RECORD
seed:LONGINT;
NextRandom:
GeneratorProc
END
...
VAR
Generator,Generator1,Generator2,...:
GeneratorType;
Dann können wir die Generatoren zum Beispiel initialisieren
mit
Generator1.NextRandom:=RandPRB;
Generator1.seed:=Oberon.Time();
Generator2.NextRandom:=RandLGM;
Generator2.seed:=Oberon.Time();
und mit Generator:=Generator1 bzw. Generator:=Generator2
auf den ersten oder zweiten Generator schalten. Um auf die jeweils aktuelle
Berechnungsfunktion zuzugreifen, rufen wir jetzt
Generator.NextRandom(Generator.seed).
Beispiel 3: Um räumliche Prozesse zu simulieren, wollen wir auf Vektoren
und Matritzen zugreifen. Wir können diese wie in Beispiel 2 realisieren,
etwa mit
TYPE
VectorType = RECORD
x,y,z:REAL
END;
aber schon einfache lineare Algebra wird damit umständlich.
Wir können anstelle dessen deklarieren:
TYPE
VectorType = ARRAY
3 OF REAL;
MatrixType = ARRAY
3,3 OF REAL;
VAR
v:VectorType; m:MatrixType;
Auf die i. bzw. i,j. Komponente greifen wir dann zu mit v[i] bzw
m[i,j]. Die Komponentenzählung beginnt immer bei null, das heisst v
hat hier die Komponenten v[0], v[1], v[2].
Beispiel 4: Für jeden Typ können wir einen Zeiger-Typ definieren.
Dieser Typ kann eine Referenz aufnehmen, ähnlich einem "vorwärts"
oder "weiter"-Zeiger in einem Hypertext. In der Referenz werden nicht Daten
selbst gespeichert, sondern nur ein Verweis, mithilfe dessen auf die Daten
zugegriffen werden kann. Eine häufige Verwendung ist es, einen entsprechenden
Zeiger gleich in eine Datenstruktur einzubetten. Bei Zeiger-Deklationen ist
es ausnahmsweise möglich, ein Objekt zu benutzen, bevor es deklariert
ist.
TYPE
InfoPointer=
POINTER TO InfoPage;
InfoPage=
RECORD
..
viel Information...;
NextPage:
InfoPointer
END;
Die formalen Syntax-Definitionen sind
Typen-Deklaration:
TYPE
{Name
=
Typ;}
Typ
Typ-Name|Array-Typ|
Record-Typ | Pointer-Typ | Prozedur-Typ
Arrays (Vektoren, Matritzen, Tensoren,...) bestehen aus einer
(festen) Anzahl von Elementen eines einheitlichen Typs. Unter der Länge
eines Arrays versteht man die Anzahl seiner Elemente. Als Parametertypen
in Prozeduren können auch offene Arrays, das heisst Arrays ohne Längenangabe
benutzt werden. In Oberon-2 ist es auch möglich, offene Array-Typen
zu deklarieren.
Array-Typ:
ARRAY
[Länge {, Länge}] OF Typ
Länge :
konstanter
Ausdruck
Die einzelnen Elemente werden durch ihren Index identifiziert,
beginnend mit dem Index 0 und endend mit (Länge-1).
Array-Element:
Array-Name
[ index]
{[
index]
}
Array-Elemente sind Variable, mit denen gearbeitet werden kann wie
mit anderen Variablen auch. Der Unterschied zu üblichen Variablen ist,
dass Array-Elemente keine individuallen Namen haben: sie sind im Array zusammengefasst
und durch Arraynamen und Index innerhalb des Array identifiziert.
Die Elemente von Arrays können jeden Typ haben, insbesondere -wie hier-
auch wieder Arrays sein. Damit ist eine Konstruktion möglich wie:
TYPE
VectorType = ARRAY
maxdim OF REAL;
MatrixType = ARRAY
maxdim OF VectorType;
TensorType = ARRAY
maxdim OF MatrixType;
Damit können Arrays in höheren Dimensionen definiert werden.
Die Elemente können auch von anonymem Typ sein. Die Schreibweise
ARRAY L0, L1, ..., Ln OF T
ist eine Abkürzung für
ARRAY L0 OF
ARRAY L1 OF
...
ARRAY
Ln OF T
Für den Zugriff auf Elemente in einem mehrdimensionalen Array kann
zur Abkürzung eine Index-Liste angegeben werden; zum Beispiel A[i,j,k]
anstelle von A[i][j][k].
Beispiel:
TYPE tMATRIX= ARRAY 2,2 OF REAL;
Übungen:
Welchen Typ haben die folgenden Terme
x[i] A[i] T[i] T[i,j]
wenn die Variablen wie folgt definiert sind:
VAR x,y:
VectorType;
A: MatrixType;
T:TensorType;
i,j:INTEGER;
Schreiben Sie das Beispiel Kurs/PIO/IFS.Mod als Operation
einer 2*2-Matrix und eines 2-d Verschiebungsvektors auf 2-Vektoren.
Ein Array hat eine feste (möglicherweise offene) Anzahl von
Elementen eines einheitlichen Typs, die durch ihren Index identifiziert werden.
Im Unterschied dazu hat ein Record-Typ eine feste Anzahl von Elementen (Feldern),
möglicherweise unterschiedlichen Typs, die durch ihren Namen identifiziert
werden.
Record-Typ:
RECORD
[
(
Basis-Typ )
]
[Feld-Liste
{; Feld-Liste}]
END
Basis-Typ:
Record-Name
Feld-Liste:
Namens-Liste : Typ
Die einzelnen Elemente werden durch ihren qualifizierten Namen
identifiziert.
Record-Feld:
Variablen-Name.Feld-Name
Ein Record-Typ kann einen zuvor definierten Record-Typ erweitern.
Der neue Record-Typ "erbt" dann vom Basis-Typ alle Felder und erhält
zusätzlich die neu definierten.
Beispiel:
Time= RECORD
Hour, Min, Sec :
SHORTINT
END;
TimeAndDate = RECORD (Time)
Month,Day:SHORTINT;
Year:INTEGER
END;
In Oberon gibt es immer höchstens einen Basis-Typ für jeden Typ.
Auf Typ-Erweiterungen kommen wir in einem späteren Kapitel (Kapitel
9) zurück.
Einzelne Felder werden in der Form
RecordVarName.FeldName
angesprochen, also für eine Variable t vom Typ Time etwa t.Hour, t.Min,
t.Sec.
Wird ein Record-Typ exportiert, so sind damit noch nicht die einzelnen Felder
sichtbar. Jedes Feld, das nach ausserhalb des umgebenden Moduls sichtbar
sein soll, muss ausdrücklich gekennzeichnet sein.
Variablen vom Zeiger-Typ (Pointer-Typ) nehmen als Werte Verweise auf andere
Variablen auf. Ein Zeiger-Typ ist dabei immer an einen Bezugstyp, den Basis-Typ
des Zeigers, gebunden.
Pointer-Typ:
POINTER
TO Typ
Der Typ kann dabei ein Array-Typ oder ein Record-Typ sein, das
heisst Zeiger auf elementare Typen sind nicht vorgesehen. Alle Pointer können
als Wert auch NIL haben. NIL ist ein reservierter Wert, der in der Bedeutung
von nichts,nirgendwo benutzt wird. NIL wird häufig auch benutzt, um
eine Pointer-Variable als undefiniert zu markieren.
Standardmässig sind in Oberon alle Variable zunächst mit (zufälligen)
undefinierten Werten besetzt. POINTER sind die Ausnahme. POINTER-Variable
werden immer mit dem Wert NIL vorbesetzt.
Variable vom Prozedur-Typ haben eine Prozedur oder NIL als Wert. Wenn eine
Prozedur als Wert zugewiesen wird, muss die formale Parameterliste zu der
bei der Typdeklaration angegebenen passen.
Prozedur-Typ
PROCEDURE
[ Formale
Parameterliste ]
Wird ein Typ in einer Typ-Deklaration eingeführt, so bekommt der Typ
einen Namen, der ihn identifiziert. Typen, die einen Namen bekommen haben,
nennen wir deklarierte Typen. Ein Typ kann auch ad-hoc in einer Variablen-Deklaration
oder einer Parameterliste eingeführt werden. Dann bleibt der Typ anonym,
nur die deklarierte Variable hat einen Namen.
Beispiel:
VAR W:
ARRAY 3 OF REAL;
...
W[0]:=0; W[1]:=0; W[2]:=2.5;
Durch die Typen ist festgelegt, welche Verträglichkeiten zwischen Variablen
bestehen. Dabei gibt es abgestufte Anforderungen für Zuweisungen, Verwendung
in Operationen und Verwendung als Parameter.
Zuweisungs-Verträglichkeit
Für Zuweisungen gilt als Faustregel: Ein Ausdruck ist mit
einer Variablen zuweisungsverträglich, wenn die Zuweisung
Variable:=Ausdruck
eine sichere natürliche Interpretation hat. Zuweisungen zwischen zwei
Variablen mit demselben erklärten Typ sind immer möglich.
1) wenn die Variable und der Ausdruck denselben Typ haben
2) wenn Variable und Ausdruck numerisch sind, und der Typ der Variablen den
des Ausdrucks umfasst. (Ein ganzzahliger Ausdruck kann einer reellen Variablen
zugewiesen werden. Umgekehrt nicht: eine reeller Wert muss erst gerundet
oder abgeschnitten werden, bevor er einer ganzzahligen Variablen zugewiesen
wird).
3) wenn Ausdruck und Variable einen Record-Typ haben, und der Typ des Ausdrucks
eine Erweiterung des Variablen-Typs ist. (Umgekehrt nicht: eine Erweiterung
kann Felder haben, die im ursprünglichen Typ nicht definierte sind.
Der Grund-Typ kann nicht einer echten Erweiterung zugewiesen werden.
4) wenn Ausdruck und Variable Zeiger auf einen Record-Typ sind und 3) entsprechend
gilt.
5) wenn der Ausdruck NIL ist und die Variable einen Zeiger- oder Prozedurtyp
hat
6) wenn die Variable einen Typ ARRAY OF CHAR hat und der Ausdruck eine String-Konstante
ist, die höchstens soviele Stellen hat, wie die Variable aufnehmen
kann.
7) wenn die Variable von einem Prozedur-Typ und der Ausdruck der Name einer
Funktion mit passender formaler Parameterliste ist.
Die Zuweisung ist in der Regel nicht möglich, wenn Variablen nicht
denselben erklärten Typ haben, selbst dann, wenn sie die gleiche Struktur
haben.
Beispiel:
TYPE
Location =
ARRAY [3] OF REAL;
Speed =ARRAY
[3] OF REAL;
NewLocation = Location;
VAR
PosA,PosB :
Location;
VA,VB :
Speed;
NPos :
NewLocation;
...
:::
PosA[0]:=0; PosA[1]:=0;
PosA[2]:=2.5;
...
PosB := PosA;
(* erlaubt.
Typen sind zuweisungs-verträglich *)
(* VA:=PosA; nicht
erlaubt. Die gleiche Struktur,
aber
nicht zuweisungs-verträglich *)
NPos := PosB;
(* erlaubt.
Typen sind zuweisungs-verträglich *)
VA[0]:=PosA[0];
(* erlaubt.
Typen der Elemente sind
zuweisungs-verträglich
*)
Ergänzungen zu Basistypen
Ein Grundtyp T2 ist zuweisungs-verträglich zu einem Grundtyp
T1, wenn T2 in T1 "enthalten" ist, entsprechend der folgenden Ordnung:
LONGREAL >= REAL >= LONGINT >= INTEGER >= SHORTINT
Bei arithmetischen Ausdrücken findet eine automatische Umwandlung statt,
wenn diese eindeutig ist. In diesem Fall nennt man die Typen ausdrucks-verträglich.
Das Ergebnis ist vom kleinsten Grundtyp, der das Resultat darstellen kann.
Mit der vordefinierten Funktion SHORT wird -falls möglich- eine Konversion
zu einem kleineren Zahlentyp erzwungen, mit LONG eine Expansion zu einem
grösseren Zahlentyp. Mit ENTIER erhält man zu einer reellen Zahl
x die grösste ganze Zahl <= x.
Beispiel: ENTIER(17/4) ergibt
4
Ergänzungen zu Arrays
Die vordefinierte Funktion
LEN(a,n)
gibt die Länge von a in der Dimension n. LEN(a) ist eine Kurzform von
LEN(a,0). In Oberon ist also die Grösse eines Arrays zur Ausführungszeit
eines Moduls mit Hilfe von LEN abfragbar.
Eine typische Schleife über alle Elemente eines Vektors, geschrieben
mit der LEN Funktion ist etwa:
i:=0;
WHILE i< LEN(a) DO
a[i]:=a[i]*2;
INC(i)
END;
Diese Schleife ist äquivalent zu:
FOR i:=0 TO LEN(a)-1 DO a[i]:=a[i]*2
END;
Die Verwendung der WHILE-Konstruktion ist in Oberon für den Durchlauf
von Arrays empfohlen.
Array-Typen können auch ohne Angabe der Länge deklariert werden.
Arrays ohne explizite Längenangaben heissen offene Arrays. In Oberon
können offene Array als Parameter für Funktionen benutzt werden.
Oberon-2 erlaubt auch die Definition von Variablen als offene Arrays. Offene
Arrays können in Zusammenhang mit der Funktion LEN benutzt werden,
um flexible Prozeduren für die lineare Algebra zu implementieren.
Beispiel:
PROCEDURE MaxDiag(A: ARRAY OF ARRAY OF REAL;
VAR
Val:REAL);
VAR i:INTEGER;
BEGIN
Val:=0;
i:=0;
WHILE i< LEN(A)
DO
IF ABS(A[i,i])>Val
THEN Val:=ABS(A[i,i]) END;
INC(i)
END
END MaxDiag;
Wird ein Array wie in diesem Beispiel als Wert-Parameter übergeben,
so wird die gesamte Information bei Aufruf der Prozedur auf eine lokale Variable
kopiert. Dies ist bei allen Wert-Parametern der Fall. Im Gegensatz dazu wird
bei VAR-Parametern immer nur ein Verweis übergeben. Insbesondere Arrays
beinhalten oft grosse Informationsmengen. Das Kopieren ist dann zeit- und
platzaufwendig. Deshalb ist es üblich, sie auch dann als VAR-Parameter
zu übergeben, wenn sie nicht verändert werden.
Beispiel:
PROCEDURE MaxDiag(VAR A: ARRAY
OF ARRAY OF REAL; VAR Val:REAL);
Übungen:
Schreiben Sie eine Prozedur, die das Skalarprodukt
von zwei Vektoren A,B ausrechnet und das Resultat einer Variablen C zuweist:
PROCEDURE SKALPROD(VAR A,B: ARRAY
OF REAL;
VAR C: REAL);
Benutzen Sie diese Prozedur, um das Skalarprodukt der folgenden Vektoren
zu berechnen:
A[i]:= sin(i* (2*pi)/n) B[i]:=
cos(i* (2*pi)/n)
für n=1, ..., 10 mit i=0,...,n-1.
Hinweis: Sinus und Cosinus finden Sie im Modul Math.
Neben der Repräsentation von geometrischen Objekten wie Vektoren ist
eine typische Anwendung von Arrays die Repräsentation von (rechteckigen)
Tabellen mit Information. Im Unterschied zu den bisherigen Beispielen muss
dabei nicht immer die gesamte Tabelle durchgearbeitet werden, sondern Schleifen
laufen zum Beispiel nur so lange bis ein bestimmtes Element gefunden ist.
Beispiel: Suche den Index j eines Eintrags mit dem Wert x in einer Tabelle
t
TYPE Table= ARRAY n OF INTEGER;
VAR t:Table; j,x:INTEGER;
..
j:=0;
WHILE (j<n) & (t[j]#x)
DO INC(j) END;
Im obigen Beispiel ist
j:=0;
WHILE (t[j]#x) & (j<n)
DO INC(j) END;
nicht korrekt. Warum?
Ergänzungen zu Records
Zuweisungen von einem Record-Typ T1 zu einem Typ T0 sind möglich, wenn
T1 den Typ T0 erweitert. Die Zuweisung überträgt dann nur die
Felder, die bereits in T0 enthalten sind. Beispiel:
VAR
t: Time;
ta,tb: TimeAndDate;
...
t:=ta; (*
erlaubt. Überträgt Hour, Min, Sec *)
...
tb:=ta; (*
erlaubt. Überträgt alle Felder *)
...
(* ta:=t nicht
erlaubt *)
ta.Hour:=t.Hour; (*
erlaubt.
Zuweisung
von Variablen des
gleichen
Basistyps *)
Die Regeln der Typ-Erweiterung gelten analog für Zeiger: ist Typ1 eine
Erweiterung von Typ0, so ist ein Zeiger auf Typ1 eine Erweiterung eines Zeigers
auf Typ0.
Ergänzungen zu Pointer-Variablen
Wird eine Variable P vom Zeiger-Typ deklariert, so wird damit nur der Speicherplatz
für den Verweis bereitgestellt, und noch kein Platz für eine
Variable vom Basis-Typ. Der Platz für die bezeichnete Variable wird
erst durch einen Aufruf der vordefinierten Prozedur NEW bereitgestellt, zum
Beispiel NEW(P).
NEW versucht, den Platz für die Variable bereitzustellen. Konnte der
Platz bereitgestellt werden, so wird in P ein Verweis auf diese neue Variable
gespeichert. Auf die bezeichnete Variable kann dann mit P^ zugegriffen werden.
Konnte kein Platz bereitgestellt werden, so ist das weitere Verhalten implementationsabhängig.
Konnte der Platz nicht bereitgestellt werden, so hat P den Wert NIL.
Der einfache Aufruf von NEW stellt eine Variable fester Länge bereit.
Ist der Basis-Typ ein offenes ARRAY, so wird mit NEW(P,e0, ..., en-1) eine
Matrix der Längen e0, ..., en-1 bereitgestellt.
Jeder Variablen vom Zeiger-Typ kann der Wert NIL zugewiesen werden; NIL verweist
auf keine Variable.
Ist der Basis-Typ ein RECORD-Typ, so kann auf die Komponenten mit einer Kurzschreibweise
zugegriffen werden, zum Beispiel mit
P: POINTER
TO Time; (* Time wie oben *)
ist
P.Hour kurz
für P^.Hour.
(Analog kann für den Zugriff auf Felder die Kurzschreibweise verwandt
werden, zum Beispiel p[i,j,k] anstelle von p^[i,j,k]).
Einführung in die Programmiersprache Oberon. Kurs/Kap08.Text
gs (c) G. Sawitzki, StatLab Heidelberg
<http://statlab.uni-heidelberg.de/projects/oberon/kurs/>
Home
Up
Intro
Contents
Chapter
1
2
3
4
5
6
7
8
9
10
Design
Assert
Timing
EBNF
Report
Pas