Komplexe Datentypen

Zum Pascal-Sprachumfang gehören nicht nur einfache Datentypen, wie sie im Abschnitt Variablen und Konstanten beschrieben sind, sondern auch zusammengesetzte.

Mengentypen

Um in einer einzigen Variablen eine unterschiedliche Menge an Werten des gleichen Typs zu speichern, gibt es Mengentypen. Es ist eine Menge an möglichen Werten vorgegeben, aus der eine beliebige Anzahl (keiner bis alle) in der Variablen abgelegt werden kann. Folgendermaßen wird eine solche Mengenvariable deklariert:

var zahlen: set of 1..10;

Damit können der Variablen zahlen Werte aus der Menge der Zahlen von 1 bis 10 zugewiesen werden - mehrere gleichzeitig oder auch gar keiner:

zahlen := [5, 9];           // zahlen enthält die Zahlen 5 und 9
zahlen := []; // zahlen enthält überhaupt keine Werte
zahlen := [1..3]; // zahlen enthält die Zahlen von 1 bis 3
zahlen := zahlen + [5]; // zahlen enthält die Zahlen 1, 2, 3, 5
zahlen := zahlen - [3..10]; // die Zahlen von 3 bis 10 werden aus der Menge
// entfernt, es bleiben 1 und 2

Um nun zu prüfen, ob ein bestimmter Wert in der Menge enthalten ist, wird der Operator in verwendet:

if 7 in zahlen then ...

In einem Set sind nur Werte mit der Ordnungsposition von 0 bis 255 möglich.

Arrays

Müssen mehrere Werte des gleichen Typs gespeichert werden, ist ein Array (zu deutsch Feld oder Liste) eine praktische Lösung. Ein Array hat einen Namen wie eine Variable, jedoch gefolgt von einem Index in eckigen Klammern. Über diesen Index kann man auf die einzelnen Werte zugreifen. Am einfachsten lässt sich das mit einer Straße vergleichen, in der sich lauter gleiche Häuser befinden, die sich jedoch durch ihre Hausnummer (den Index) unterscheiden.

Eine Deklaration der Art

var testwert: array [0..10] of integer;

bewirkt also, dass wir quasi elf verschiedene Integer-Variablen bekommen. Man kann auf sie über den Indexwert zugreifen:

testwert[0] := 15;
testwert[1] := 234;

usw.

Vorteil dieses Indexes ist, dass man ihn durch eine weitere Variable ersetzen kann, die dann in einer Schleife hochgezählt wird. Folgendes Beispiel belegt alle Elemente mit dem Wert 1:

for i := 0 to 10 do
testwert[i] := 1;

Auf Schleifen wird jedoch in einem gesonderten Kapitel eingegangen.

Bei dem vorgestellten Array handelt es sich genauer gesagt um ein eindimensionales, statisches Array. "Eindimensional", weil die Elemente über nur einen Index identifiziert werden, und "statisch", weil Speicher für alle Elemente reserviert wird. Legt man also ein Array für Indexwerte von 1 bis 10000 an, so wird für 10000 Werte Speicher reserviert, auch wenn während des Programmablaufs nur auf zwei Elemente zugegriffen wird. Außerdem kann die Array-Größe zur Programmlaufzeit nicht verändert werden.

Dynamische Arrays

Wenn schon so viel Wert auf die Eigenschaft statisch gelegt wird, muss es ja eigentlich auch etwas Dynamisches geben. Und das gibt es auch, zumindest seit Delphi 4: die dynamischen Arrays.

Der erste Unterschied findet sich in der Deklaration: Es werden keine Grenzen angegeben.

var dynArray: array of integer;

dynArray ist nun prinzipiell eine Liste von Integer-Werten, die bei Index Null beginnt.

Zum Hintergrundverständnis: Während statische Arrays direkt die einzelnen Werte beinhalten, enthält ein dynamisches Array nur Zeiger auf einen Arbeitsspeicherbereich. In der Anwendung ist das nicht zu merken, es ist keine spezielle Zeigerschreibweise nötig. Nur an einer Stelle bemerkt man das interne Vorgehen: Bevor man Werte in das Array stecken kann, muss man Speicher für die Elemente reservieren. Dabei gibt man an, wie groß das Array sein soll:

SetLength(dynArray, 5);

Nun kann das Array fünf Elemente (hier Integer-Zahlen) aufnehmen.

Man beachte: Da die Zählung bei Null beginnt, befindet sich das fünfte Element bei Indexposition 4!

Der Zugriff erfolgt ganz normal:

dynArray[0] := 321;

Damit es mit der Unter- und vor allem der Obergrenze, die ja jederzeit verändert werden kann, keine Probleme gibt (Zugriffe auf nicht (mehr) reservierten Speicher), lassen sich Schleifen am einfachsten so realisieren:

for i := 0 to high(dynArray) do
dynArray[i] := 0;

Dadurch werden alle Elemente auf 0 gesetzt. high(dynArray) entspricht dem höchstmöglichen Index. Über length(a) lässt sich die Länge des Arrays ermitteln, welche immer high(dynArray)+1 ist. Dabei handelt es sich um den Wert, den man mit SetLength gesetzt hat.

Da die Länge des Arrays jederzeit verändert werden kann, könnten wir sie jetzt mit

SetLength(dynArray, 2);

auf zwei verkleinern. Die drei hinteren Werte fallen dadurch weg. Würden wir das Array dagegen vergrößern, würden sich am Ende Elemente mit undefiniertem Wert befinden.

Mehrdimensionale Arrays

Wenn es eindimensionale Arrays gibt, muss es auch mehrdimensionale geben. Am häufigsten sind hier wohl die zweidimensionalen. Man kann mit ihnen z. B. ein Koordinatensystem oder Schachbrett abbilden. Die Deklaration ist wie folgt:

var koordinate: array [1..10, 1..10] of integer;

Es werden also 10x10=100 Elemente angelegt, die jeweils einen Integer-Wert aufnehmen können. Für den Zugriff auf einzelne Werte sind zwei Schreibweisen möglich:

  koordinate[1,6] := 34;
koordinate[7][3] := 42;

Records

Records entsprechen von der Struktur her einem Datensatz einer Datenbank - nur dass sie, wie alle bisher aufgeführten Variablen, nur zur Laufzeit vorhanden sind. Wenn wir unterschiedliche Daten haben, die logisch zusammengehören, können wir sie sinnvollerweise zu einem Record zusammenfassen. Beispiel: Adressen.

var Adresse: record
name: string;
plz: integer;
ort: string;
end;

Die Variable Adresse wird also in drei "Untervariablen" aufgegliedert.

Folgendermaßen greift man auf die einzelnen Felder zu:

adresse.name := 'Hans Müller';
adresse.plz := 12345;
adresse.ort := 'Irgendwo';

Damit das nicht (besonders bei vielen Elementen) in enorme Tipparbeit ausartet, gibt es dafür auch eine Abkürzung:

with adresse do begin
name := 'Hans Müller';
plz := 12345;
ort := 'Irgendwo';
end;

Variante Records

Eine besondere Form der Records sind die varianten Records. Hier wird mittels case-Unterscheidung immer nur ein bestimmter Datenteil berücksichtigt.

Beispiel:

type
person = record
name: string;
case erwachsen: boolean of
true: (personalausweisnr: integer);
false: (kinderausweisnr: integer;
erziehungsberechtigte: string);
end;

Das Record speichert in jedem Fall die Daten name und erwachsen. Abhängig von dem Wert erwachsen wird dann entweder die personalausweisnr (bei erwachsen=true) oder kinderausweisnr und erziehungsberechtigte (bei erwachsen=false) gespeichert. Ist erwachsen=false, sollte nicht auf den Wert personalausweisnr zugegriffen werden.

Dadurch hat man eindeutige Beschreibungen von Feldwerten und kommt nicht in Gefahr, die Kinderausweisnr versehentlich als Personalausweisnr zu speichern.

Wichtig ist noch zu bemerken, dass der variante Teil eines Records (also der case-Teil) immer am Ende stehen muss. Und case hat in diesem Fall auch kein schließendes end, wie sonst üblich.

Die Teile hinter case teilen sich denselben Speicherbereich. Der Compiler reserviert so viel Platz wie die größte Variante benötigt. Deshalb können auch variante Records in typisierten Dateien verwendet werden.

Variante Records können auch in Delphi für .NET-Programmen verwendet werden, werden dort allerdings als "unsicherer Typ" mit einer Warnung versehen.

Nun wäre es praktisch, eine größere Menge solcher Variablen zu haben, da eine Adressverwaltung üblicherweise mehr als nur eine Adresse enthält. Die Lösung: Wir kombinieren Array und Record. Damit wir unseren neuen Adresstyp aber einfach weiterverwenden können, wollen wir zuerst einen eigenen Typ anlegen.

Eigene Typen definieren

Wir erinnern uns: Variablendeklarationen enthalten auf der linken Seite einen Variablennamen und rechts vom Doppelpunkt einen zugehörigen Datentyp. Solche Typen können wir auch selbst definieren, beispielsweise obiges Record:

type TAdressRecord = record
name: string;
plz: integer;
ort: string;
end;

Eine Typdefinition besteht also aus dem Schlüsselwort type gefolgt von einer Konstruktion, die einer Variablendeklaration ähnelt. Da aber statt einer Variablen ein Typ deklariert wird, ist das Folgende auch kein Variablen- sondern ein Typname. Typnamen beginnen in Delphi mit einem großen T, wenn sich der Programmierer an den Styleguide gehalten hat.

Statt eines Doppelpunkts wird ein Gleichheitszeichen verwendet.

Auf die folgende Weise kann solch ein Typ dann verwendet werden:

var adresse: TAdressRecord;

Diese Variablendeklaration entspricht der im obigen Beispiel für Records.

Wollen wir nun noch ein Array daraus machen, dann geht das so:

var adresse: array [1..50] of TAdressRecord;

Der Zugriff erfolgt streng nach den Regeln für Arrays und Records:

adresse[1].name := 'Hans Müller';
adresse[2].name := 'Susi Sorglos';