Inhaltsverzeichnis

2.14 Der Datentyp Record

In einem Array und einer Menge werden Elemente eines einzigen Typs zu einer Datenstruktur zusammengefaßt. Um Werte verschiedener Typen zu verbinden, benutzt man in Pascal Recordvariablen:

TYPE KENNZEICHEN = RECORD
       KREIS: STRING[3];
       B    : STRING[2];
       NR   : 1..9999;
     END;
     ADRESSE = RECORD
       NAME, VORNAME: STRING[20];
       ORT, STRASSE : STRING[15];
       HAUSNR, PLZ  : INTEGER;
     END;
     KRAFTFAHRZEUGSCHEIN = RECORD
       WAGEN            : KENNZEICHEN;
       WOHNORT,STANDORT : ADRESSE;
       LEISTUNG         : INTEGER;
       ABGEMELDET       : BOOLEAN
     END;
VAR  HALTER          : ADRESSE;
     AUTO1, AUTO2    : KENNZEICHEN;
     SCHEIN1, SCHEIN2: KRAFTFAHRZEUGSCHEIN;

Bild 65: Record-Typen

Dieses etwas ausführlichere Beispiel zeigt, wie man Attribute eines realen Objektes (Fahrzeugkennzeichen, Adresse, Fahrzeugschein) in einer Variablen vom Typ Record speichert: Ein Kennzeichen besteht aus einem dreistelligen Kürzel für den Kreis, zwei Buchstaben und einer Nummer. Im Fahrzeugschein werden für ein Fahrzeug der Halter und der Standort des Fahrzeugs eingetragen.

Man kann sich einen Record wie eine »Karteikarte« mit vordefinierten Feldern vorstellen, die nur Werte bestimmter Typen beinhalten dürfen. Die in Bild 65 definierten Typen bezeichnet man auch als Verbundtypen. Auf die Felder einer Record-Variablen greift man durch Nennung des Variablennamens und des Feldnamens getrennt durch einen Punkt zu:

AUTO1.KREIS:= 'M';
AUTO2.KREIS:= 'HH';
SCHEIN1.WOHNORT.ORT:='NEW YORK';
SCHEIN2.STANDORT.ORT:= HALTER.ORT;
IF SCHEIN1.LEISTUNG < 28 THEN ...

Der Recordtyp KRAFTFAHRZEUGSCHEIN beinhaltet seinerseits die Recordtypen KENNZEICHEN und ADRESSE als Felder. Daher kann man auch über mehrere Stufen auf Record-Felder zugreifen:

SCHEIN1.WAGEN.KREIS:= AUTO1.KREIS;
SCHEIN2.STANDORT.ORT:= SCHEIN2.WOHNORT.ORT;

In der ersten Zuweisung wird also der Inhalt des Feldes KREIS im Kennzeichen AUTO1 in dem Feld KREIS des Feldes WAGEN in der Recordvariablen SCHEIN1 gespeichert. Wirklich nützlich wird das Konzept der Records in Pascal durch die Tatsache, daß man Zuweisungen zwischen kompletten Records des gleichen Typs vornehmen kann:

AUTO1:= AUTO2;
SCHEIN1.WOHNORT:= HALTER;
SCHEIN1:= SCHEIN2;

Durch solche Block-Zuweisungen werden alle Felder eines Records kopiert. Die Feldnamen (Selektoren), wie ORT, B und NR, sind Namen, deren Sichtbarkeit auf den Record ihrer Deklaration beschränkt ist. Deshalb könnte man also durchaus ohne Namenskonflikte eine Variable KREIS deklarieren, da Feldnamen vom Compiler an dem vorausgehenden Punkt erkannt werden können.

Eine ähnliche Bedeutung wie die For-Anweisung für Arrays besitzt die sogenannte With-Anweisung (Inspektionsanweisung) für Variablen vom Typ Record. Sie vereinfacht Ausdrücke, die mit vielen Feldern eines Records arbeiten.

Bild 66: Struktur der With-Anweisung

WITH SCHEIN1 DO
  BEGIN
    WITH WAGEN DO
      BEGIN KREIS:='MTK'; B:='M'; NR:=939 END;
    WITH WOHNORT DO
      BEGIN
        NAME   := 'MUELLER THURGAU';
        VORNAME:= 'HANS PETER';
        PLZ    := 6232;
        HAUSNR := 4;
      END;
    STANDORT:= WOHNORT; LEISTUNG:=45;
    ABGEMELDET:= FALSE;
  END;

In der Anweisung, die nach dem Wortsymbol DO der With-Anweisung folgt, ist also die Angabe Variablenbezeichner vor dem Feldnamen nicht erforderlich. Geschachtelte With-Anweisungen beziehen sich aber immer auf dieselbe Record-Variable. Die folgende Schachtelung ist also verboten, da der Record AUTO1 nicht zum Record SCHEIN1 gehört:

WITH SCHEIN1 DO
  BEGIN
    WOHNORT:= HALTER;
    WITH AUTO1 DO          FALSCH !
      B:=WAGEN.B
  END;

Bei vielen Compilern (auch Pascal 2.0) bringt die With-Anweisung außerdem noch Geschwindigkeitsvorteile, da alle Operationen zum Zugriff auf die Record-Variable nur einmal benötigt werden.

WITH SCHEIN1.WAGEN DO
  BEGIN
    FOR I:= 1 TO 3 DO
      KREIS[I]:= ' ';
    KREIS[0]:=CHR(3);
  END;

ist also schneller als die Anweisungsfolge

FOR I:= 1 TO 3 DO
  SCHEIN1.WAGEN.KREIS[I]:=' ';
SCHEIN1.WAGEN.KREIS[0]:=CHR(3);

In Bild 65 wurden Arrays und Records als Teile von Records deklariert. Natürlich ist es auch erlaubt, Arrays mit Records als Elementen zu benutzen:

VAR ZULASSUNGEN : ARRAY[1..200] OF KRAFTFAHRZEUGSCHEIN;
ZULASSUNGEN[30].WAGEN.KREIS:= 'B';

Dies ist einer der Gründe für die Flexibilität der Sprache Pascal. Die Standard- und Aufzählungstypen bilden die elementaren Bausteine, mit denen man je nach Bedarf hierarchisch strukturierte zusammengesetzte Datentypen definiert.

Als ein vollständiges Beispielprogramm für die Verwendung von Records zeigt Bild 67 die Darstellung einer dreidimensionalen Netzgrafik. Grundsätzlich ist das Zeichnen eines solchen Netzes sehr einfach: In zwei geschachtelten For-Anweisungen (ROW, POINT) berechnet man N * N Stützstellen. Jede Stützstelle wird mit ihrem (bereits gezeichneten) »linken« und »vorderen« Vorgänger durch eine Linie verbunden. Dabei müssen nur der erste Punkt jeder X- und Y-Reihe gesondert behandelt werden.

Aus den gegebenen X- und Y-Koordinaten wird nun mit einer (beliebigen) Funktion F eine zugehörige Z-Koordinate berechnet. Diese drei reellen Koordinaten zusammen werden mit einer Formel, die die perspektivische Verzerrung der Y-Achse berücksichtigt, in ganzzahlige Bildschirmkoordinaten umgewandelt.

Das Programm in Bild 68 ist etwas umfangreicher, da einerseits einige Zwischenergebnisse aus Geschwindigkeitsgründen aus den inneren For-Schleifen ausgelagert wurden und andererseits »verdeckte« Linien nicht gezeichnet werden. Normalerweise stellt das hidden line Problem sehr hohe Anforderungen an die Rechenleistung des Computers. In diesem Fall ist jedoch die relative Lage der Punkte zueinander bereits bekannt. Um zu bestimmen, wie zwei Punkte P und Q verbunden werden, genügt in diesem Fall die Kenntnis über die Sichtbarkeit dieser beiden Punkte (s. Prozedur DRAWLINE).

Ein Punkt kann folgende Lagen besitzen (Aufzählungstyp VISIBILITY):

Insgesamt gibt es damit beim Verbinden der Punkte P und Q 16 verschiedene Kombinationen der Sichtbarkeit von P und Q. Diese werden mit der brute force Methode in der Prozedur DRAWLINE geprüft. Dabei ist es nötig, für jeden Punkt die höchste und niedrigste Y-Koordinate zu kennen, die gerade noch durch die Oberfläche verdeckt wird (YMAX, YMIN).

Daher wird jeweils für die zuletzt gezeichnete Reihe und den neu berechneten Punkt ein Record mit folgenden Feldern benötigt:

TYPE LOCATION = RECORD
   X,Y      : INTEGER;
   WHERE    : VISIBILITY;
   YMAX,YMIN: INTEGER
END;

Mit diesen Erläuterungen können Sie das Programm in Bild 67 sicher nachvollziehen.

Bild 67: Das Programm NETZ

Bild 68: Ein dreidimensionales Netz

Aufgaben

1. Schreiben Sie ein Paket mit Prozeduren, das die Bearbeitung von Bruchzahlen ermöglicht. Dabei sollen Brüche nicht als Zahlen vom Typ REAL dargestellt werden, sondern als Paare ganzer Zahlen:

TYPE BRUCH = RECORD
  ZAEHLER: INTEGER;
  NENNER : INTEGER;
END;

Damit der Zahlenbereich der ganzen Zahlen nicht bei den einfachsten Operationen überschritten wird, sollen Zähler und Nenner immer gekürzt vorliegen (also 1 / 134 und nicht 2345 / 314230). Zum Kürzen können Sie die Funktion GGT aus Abschnitt 2.11 verwenden.

PROCEDURE KUERZE(VAR ZAEHLER,NENNER: INTEGER);
 
PROCEDURE LESEN(VAR B: BRUCH); 
 
PROCEDURE SCHREIBEN(B: BRUCH); 
  
PROCEDURE PLUS(A,B: BRUCH; VAR C:BRUCH);
 
PROCEDURE MAL (A,B: BRUCH; VAR C: BRUCH);
 
PROCEDURE KEHRWERT(A: BRUCH; VAR C: BRUCH);
 
FUNCTION GROESSER(A,B:BRUCH): BOOLEAN;
 
FUNCTION GLEICH(A,B: BRUCH): BOOLEAN;
 
FUNCTION WERT(A:BRUCH): REAL; 
(* Liefert den reellen Quotienten ZAEHLER/NENNER *)

Zwei Beispiele sollen Ihnen das Prinzip verdeutlichen:

PROCEDURE MAL (A,B:BRUCH; VAR C: BRUCH);
(* C:= A * B. Da A und B jeweils gekürzt vorliegen, *)
(* genügt ein kreuzweises Kürzen vor der Mult.      *)
BEGIN
  KUERZE(A.ZAEHLER, B.NENNER);
  KUERZE(B.ZAEHLER, A.NENNER);
  C.ZAEHLER:= A.ZAEHLER * B.ZAEHLER;
  C.NENNER := A.NENNER  * B.NENNER;
  (* Zähler und Nenner von C sind jetzt teilerfremd *)
END; (* MAL *)
 
PROCEDURE SCHREIBEN(A: BRUCH);
(* Ausgabe des Bruches A *)
BEGIN
  WITH A DO
    IF NENNER = 1 THEN WRITE(ZAEHLER)
    ELSE WRITE(ZAEHLER, '/', NENNER);
END; (* SCHREIBEN *)

2. Sollten Sie ab und zu mit komplexen Zahlen arbeiten (müssen), ist es vielleicht interessanter, den Typ KOMPLEX mit seinen Operationen zu implementieren

TYPE KOMPLEX = RECORD RE,IM: REAL END;

Als Operationen bieten sich Addition, Multiplikation, Bildung der konjugiert komplexen Zahl, Betragsfunktion sowie die Umwandlung in Polarkoordinaten an.

3.Bisher bestand bei allen Sortierprogrammen das zu sortierende Array alleine aus den Schlüsseln, die sortiert werden sollten. Normalerweise möchte man jedoch Daten sortieren, bei denen die Elemente die Struktur eines Records besitzen.

TYPE ELEMENT = RECORD
  NAME     : STRING[30];
  EINKOMMEN: REAL;
  STADT    : STRING[30]
END;
  
VAR A: ARRAY OF ELEMENT;

Ändern Sie die Sortierprogramme BUBBLESORT bzw. QUICKSORT, so daß die Daten nach folgenden Kriterien sortiert werden:

3.1 Namen alphabetisch aufsteigend

3.2 Einkommen absteigend

3.3 Namen alphabetisch aufsteigend. Bei Namensgleichheit weiter nach Städten aufsteigend.

Bei den Austauschoperationen werden weiterhin ganze Elemente zugewiesen, während die Vergleichsoperationen auf die Schlüsselfelder (NAME, EINKOMMEN bzw. STADT) eingeschränkt werden müssen.

Inhaltsverzeichnis