Programmierkurs: Delphi: Pascal: Records

Aus Wikibooks

Wechseln zu: Navigation, Suche

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:

VariantesRecord.png

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.


Arrow left.png Pascal: Arrays Inhaltsverzeichnis Pascal: Varianten Arrow right.png
Persönliche Werkzeuge