Programmierkurs: Delphi: Pascal: Records
Aus Wikibooks
Inhaltsverzeichnis |
[Bearbeiten] Records
[Bearbeiten] Was sind Records?
Records ermöglichen es, mehrere Variablen zu gruppieren. Dies ist beispielsweise dann hilfreich, wenn oft die gleiche Menge an Variablen benötigt wird, oder eine Menge Variablen logisch zusammengefasst werden soll. Eine weitere Situation in der Records unverzichtbar sind ist, wenn im Programm mehrere Datensätze gespeichert und verwaltet werden sollen, beispielsweise in einem Adressbuch
[Bearbeiten] Wie funktionieren Records?
Um zu unserem Beispiel vom Adressbuch zurückzukommen: Wir wollen alle Daten, also Vorname, Nachname, etc. in einem Record speichern. Dazu legen wir einen neuen Typ TPerson an, in dem wir alle Variablen auflisten:
type TPerson = record Vorname: string; Nachname: string; Anschrift: string; TelNr: string; end;
Wenn jetzt eine Variable vom Typ TPerson deklariert wird, enthält diese all diese Variablen:
var Person: TPerson; begin Person.Vorname := 'Hans'; Person.Nachname := 'Müller'; ... end;
Die Variablen im Record verhalten sich genauso wie „normale“ Variablen. Benötigt man ein Record nur zur einmaligen Strukturierung von Daten, ist es nicht nötigt, einen Verbund-Typ anzulegen:
var Datei: record Name, Pfad: string; end;
[Bearbeiten] Die with-Anweisung
Falls Sie mit mehreren Record-Feldern nacheinander arbeiten wollen, ist es sehr mühselig, immer den Namen der Variablen vornweg zu schreiben. Diese Aufrufe lassen sich mithilfe der with-Anweisung ebenfalls logisch gruppieren:
with Person do begin Vorname := 'Hans'; Nachname := 'Müller'; Anschrift := 'Im Himmelsschloss 1, 12345 Wolkenstadt'; TelNr := '03417/123456'; end;
Es ist auch möglich, die With-Anweisung auf mehrere Records anzuwenden. Dazu müssen die Bezeichner zwischen with und do mit Kommas getrennt aufgezählt werden. Natürlich müssen die Records zum gleichen Typ gehören.
[Bearbeiten] Variante Teile in Records
Ein Record kann so genannte variante Teile enthalten. Dies sind Felder eines Records, die den gleichen Speicherplatz belegen, aber unterschiedliche Typen haben und/oder in verschiedener Anzahl vorhanden sind. Je nach verwendetem Bezeichner werden beim Schreiben und Lesen die Daten im Speicher entsprechend seines Typs anders interpretiert.
[Bearbeiten] Deklaration
Der variante Teil eines Records ähnelt dabei einer case-Anweisung bei der verzweigten Datenverarbeitung (siehe Abschnitt „Verzweigungen“). Der variante Teil wird ebenfalls mit case eingeleitet und steht immer am Ende der Record-Deklaration. Ein solches Record ist daher so aufgebaut:
type
Typname = record
Feld_1: Typ_1;
Feld_2: Typ_2;
...
Feld_n: Typ_n;
case [Markierung:] Typ of
Wert_1: (Feldliste_1);
Wert_2: (Feldliste_2);
...
Wert_n: (Feldliste_n);
end;
Von Feld_1 bis Feld_n erstreckt sich der statische Teil des Records wie in den oberen Abschnitten beschrieben. Vom Schlüsselwort case bis zum abschließenden end; folgt der variante Teil. Anders als bei den Verzweigungen schließt hierbei das end sowohl den varianten Teil als auch das gesamte Record ab. Daraus ergibt sich, dass die varianten Teile immer am Ende stehen und keine statischen Teile mehr folgen können.
Die Markierung muss hierbei nicht angegeben werden. Sie dient zur Unterscheidung, welche der varianten Feldlisten die gültigen Daten enthält und kann daher wie die statischen Felder mit einem Wert belegt werden. Man sollte der Markierung immer einen eindeutigen Wert für die jeweilige Liste zuweisen, sobald man das Record mit Daten füllt. Beim Auslesen der Daten entscheidet dann der Wert der Markierung, welche varianten Felder abgefragt werden. In manchen Fällen werden Sie vielleicht keine Markierung benötigen, Sie können dann den Bezeichner und den Doppelpunkt weglassen. Ein Typ und eine Wertliste muss aber in jedem Falle (sozusagen fiktiv) angegeben werden, wobei Sie jeden Aufzählungstyp verwenden können, also auch selbst definierte.
Die Feldlisten werden für jeden Wert von Klammern eingeschlossen. Diese Liste selbst unterscheidet sich nicht von anderen Felddeklarationen, sie entspricht also der Form
VarFeld_1: Typ_1; VarFeld_2: Typ_2; ... VarFeld_n: Typ_n;
Dabei müssen Sie jedoch beachten, dass kein Typ mit variabler Größe verwendet werden darf, da der variante Teil immer einen festen Speicherplatz belegt. Es ist daher kein string (mit Ausnahme von ShortString), dynamisches Array und keine Varianten (Datentyp Variant) erlaubt. Es darf auch kein Record verwendet werden, dass einen solchen Typ enthält. Sie können stattdessen jedoch einen Zeiger auf solche Typen verwenden, da Zeiger immer eine feste Größe von 4 Byte haben.
[Bearbeiten] Speicherbelegung
Die Größe des varianten Teils bestimmt sich nach der größten Feldliste. Um Anordnung und Größe der Daten zu verstehen, betrachten Sie bitte folgendes Beispiel:
type TVarRecord = record StatischesFeld1: Byte; StatischesFeld2: Boolean; case Byte of 0: (VariantesFeld1: Byte; VariantesFeld2: Integer); 1: (VariantesFeld3: array[1..10] of Char); 2: (VariantesFeld4: Double; VariantesFeld5: Boolean); end;
Dieses Record wird im Speicher folgendermaßen abgelegt:
Falls Sie eine Markierung verwenden, wird diese zwischen dem letzten statischen und dem Beginn der varianten Feldliste gespeichert, das Record vergrößert sich entsprechend dem Typ der Markierung.
Wichtig ist für die Verwendung von varianten Records auch, dass zu jeder Zeit jedes der varianten Felder gelesen und geschrieben werden kann. Es kann daher immer nur eine der Feldlisten gültige Werte enthalten; Sie können also nicht die verschiedenen Werte gleichzeitig speichern. Sobald Sie im obigen Beispiel VariantesFeld3 mit Daten füllen, ändert sich automatisch der Wert aller anderen varianten Felder. Sie müssen daher besonders aufpassen, dass Sie keine benötigten Daten mit ungültigen überschreiben, weil Sie versehentlich einen falschen Feldnamen benutzen. Delphi wird Sie hiervor weder bei der Kompilierung noch bei der Ausführung Ihres Programms warnen!
Geben Sie zur Verdeutlichung einmal folgendes Programm ein und starten Sie es. Die Ausgabe wird Sie überraschen!
program VariantRecord; {$APPTYPE CONSOLE} type TVarRec = packed record case Byte of 0: (FByte: Byte; FDouble: Double); 1: (FStr: ShortString); end; var rec: TVarRec; begin rec.FByte := 6; rec.FDouble := 1.81630607010916E-0310; // Der Speicherbereich von FByte und FDouble // wird jetzt als Zeichenkette interpretiert. Writeln(rec.FStr); Readln; end.
Hinweis: Das Schlüsselwort packed sorgt dafür, dass die Felder des Records lückenlos im Speicher aneinander gereiht und nicht für einen schnelleren Zugriff optimiert abgelegt werden.
[Bearbeiten] Beispiel
In einem Adressbuch gibt es leider keine voneinander abhängigen Einträge, daher wenden wir uns wieder unserer Gastliste zu und erweitern diese.
Statt nur den Namen des Gastes zu speichern, wollen wir auch aufnehmen, ob der jeweilige Gast eingeladen ist, welche Gastnummer und welcher Platz ihm in diesem Falle zugewiesen wurde, oder ob sein Besuch andernfalls erwünscht ist. Wir können davon ausgehen, dass ein eingeladener Gast in jedem Fall erwünscht ist, benötigen hierzu also keine Information. Andererseits kann ein nicht eingeladener Gast keinen Platz und keine Gastnummer zugewiesen bekommen haben.
Diese Situation können wir in einem varianten Record darstellen:
type TGast = record Name, Vorname: string; // bei statischen Feldern erlaubt case eingeladen: Boolean of True: (Platz, Gastnummer: Byte); False: (erwuenscht: Boolean); end; TGastListe = array of TGast; var GastListe: TGastListe; Zaehler: Integer; Gast: TGast;
Name, Vorname und eingeladen sind bei jedem Gast vorhanden, die anderen Felder sollen (und dürfen) nur abhängig vom Wert des Feldes eingeladen verwendet werden. Nun können wir einige Gäste erfassen:
// Anzahl der Gäste festlegen SetLength(GastListe, 4); GastListe[0].Name := 'Schweiss'; GastListe[0].Vorname := 'Axel'; GastListe[0].eingeladen := False; GastListe[0].erwuenscht := False; GastListe[1].Name := 'Silie'; GastListe[1].Vorname := 'Peter'; GastListe[1].eingeladen := True; GastListe[1].Platz := 42; GastListe[1].Gastnummer := 1; GastListe[2].Name := 'Pot'; GastListe[2].Vorname := 'Jack'; GastListe[2].eingeladen := False; GastListe[2].erwuenscht := True; GastListe[3].Name := 'Schluepfer'; GastListe[3].Vorname := 'Rosa'; GastListe[3].eingeladen := True; GastListe[3].Platz := 14; GastListe[3].Gastnummer := 2;
Anschließend wollen wir noch einen Türsteher beauftragen, die Meute am Eingang zu sortieren. Dazu schreiben wir in den Programmrumpf eine Funktion, die prüft, ob der Gast eingelassen werden kann (Hinweis: Zu der Verwendung von Funktionen, Schleifen und Verzweigungen erfahren Sie später mehr. Mit ein paar grundlegenden Englischkenntnissen lässt sich jedoch herausfinden, was die einzelnen Anweisungen bewirken.)
// Gast nur einlassen, wenn er eingeladen oder als nicht Eingeladener erwünscht ist function GastEinlassen(AGast: TGast): Boolean; begin if AGast.eingeladen then Result := True else Result := AGast.erwuenscht; end;
Wenn der Gast eingeladen wurde, gibt diese Funktion True zurück, wenn er nicht eingeladen wurde, entsprechend, ob er erwünscht ist oder nicht.
Im Hauptprogramm läuft dann eine Schleife, die alle Gäste prüfen lässt und uns anzeigt, was unser Türsteher festgestellt hat.
// GastListe von Index 0 bis 3 durchlaufen for Zaehler := 0 to 3 do begin Gast := GastListe[Zaehler]; Writeln('Name: ', Gast.Vorname, ' ', Gast.Name); if GastEinlassen(Gast) then Writeln('einlassen: ja') else Writeln('einlassen: nein'); if Gast.eingeladen then begin Writeln('Gastnummer: ', Gast.Gastnummer); Writeln('Sitzplatz: ', Gast.Platz); end; Writeln; end;
Vergessen Sie nicht, am Ende des Programms den vom dynamischen Array verwendeten Speicher wieder freizugeben, indem Sie SetLength(GastListe, 0); aufrufen.
Wenn Sie das Programm ausführen, werden Sie folgende Ausgabe erhalten:
Name: Axel Schweiss einlassen: nein Name: Peter Silie einlassen: ja Gastnummer: 1 Sitzplatz: 42 Name: Jack Pot einlassen: ja Name: Rosa Schluepfer einlassen: ja Gastnummer: 2 Sitzplatz: 14
Wie Sie sehen, erlaubt diese Art von Records größere Flexibilität für die Programmierung. Sie birgt jedoch auch die Gefahr schwer zu entdeckender Fehler, wenn man nicht sorgfältig genug programmiert.
| Inhaltsverzeichnis | Pascal: Varianten |