Inhaltsverzeichnis

2.15 Variante Records

Eine in der Praxis recht häufige Eigenschaft von Datensätzen ist es, für einzelne Ausprägungen der Daten unterschiedliche Merkmale zu enthalten. Als Beispiel betrachte man grafische Objekte. Die Aufgabe besteht darin, ein Bild durch Zusammenstellung von grafischen Primitiven (Rechtecke, Kreise, Linien und Texte) zu beschreiben (s. Deklaration in Bild 69).

CONST MAXOBJ = 50;
TYPE  TPRIMITIV = (RECK, BLOCK, KREIS, LINIE, TEXT, HINTERGRUND);
      TKOORD    = RECORD X,Y:INTEGER END;
      TOBJEKT   = RECORD
        FARBE : 1..3; (* ROT, GRUEN, BLAU *)
        INTENS: 0..1; (* HELL, DUNKEL     *)
        CASE ART: TPRIMITIV OF
          RECK, BLOCK:
           (RECHTSUNTEN,LINKSOBEN: TKOORD);
          KREIS:
           (MITTE : TKOORD;
            RADIUS: INTEGER);
          LINIE:
           (VON, BIS: TKOORD);
          TEXT:
           (POS1 : TKOORD;
            STRNG: STRING[10]);
          HINTERGRUND: ()
        END;
VAR  BILD: ARRAY[1..MAXOBJ] OF TOBJEKT;
     OBJEKT: TOBJEKT;

Bild 69: Recorddeklaration mit Varianten

In der Deklaration werden Rechtecke (RECK), ausgemalte Rechtecke (BLOCK), Kreise, Linien und Texte berücksichtigt. Alle Objekte besitzen gemeinsame Merkmale: Die Farbe (FARBE), die Helligkeit (INTENS) und eine Kennzeichnung (ART), die angibt um welches elementare Objekt es sich handelt. Diese gemeinsamen Felder werden wie in einem einfachen Record definiert.

An diesen festen Teil schließt sich der variante Teil an: Er wird durch das Wortsymbol CASE eingeleitet. Diesem folgt das sogenannte Auswahlfeld (engl. tagfield). In diesem speziellen Beispiel ist dies das Feld ART. Der Typ des Auswahlfeldes muß ein skalarer Typ (also nicht REAL oder STRING) sein, der durch einen Typ-Namen angegeben wird.

Nach dem Wortsymbol OF werden die einzelnen Varianten aufgeführt, die in Abhängigkeit von dem aktuellen Wert des Auswahlfeldes (ART) gültig sind. Die jeweiligen Konstanten des Typs (TPRIMITIV) werden durch Kommata getrennt. Nach einem Doppelpunkt folgen in Klammern die Felder für diese Variante. Die Struktur dieser Feldliste zwischen den Klammern entspricht genau der Feldliste zwischen den Wortsymbolen RECORD und END. Also könnten dort geschachtelt wiederum Varianten stehen.

Alle Varianten sind durch Semikola getrennt. Bitte beachten Sie, daß der variante Teil nach dem Symbol CASE nicht durch ein eigenes END (wie bei der Anweisung CASE im Anweisungsteil) beendet wird. Deshalb steht in Bild 69 am Ende von TOBJEKT nur ein einzelnes END, das die mit RECORD begonnene Struktur beendet.

Wie durch die Struktur der Deklaration bereits suggeriert wird, ist zu jedem Zeitpunkt nur eine einzige Variante (RECK oder BLOCK oder KREIS oder ...) gültig. Nach der Zuweisung

OBJEKT.ART:= KREIS

wären also neben den Feldern FARBE, INTENS nur die Felder der Variante KREIS gültig, die man dann wie folgt belegen kann:

OBJEKT.MITTE.X := 30;
OBJEKT.MITTE.Y := 30;
OBJEKT.RADIUS  := 15:

Die Felder der übrigen Varianten (wie z.B. POS1 und VON) dürften bei dem Wert KREIS des Auswahlfeldes ART nicht verwendet werden. Erwähnenswert ist noch die Tatsache, daß die Feldliste zu einer Varianten leer sein kann:

HINTERGRUND:()

Ist also OBJEKT.ART=HINTERGRUND, so sind nur die festen Felder (FARBE, INTENS) relevant. Die Nennung leerer Varianten dient nur der Dokumentation der Struktur und ist syntaktisch nicht verpflichtend. Wie üblich finden Sie die exakte Syntax (varianter) Records in Anhang A und zwar im Syntaxdiagramm FELDLISTE.

Es ist ein schwerer Programmierfehler, auf Varianten zuzugreifen, die nicht dem aktuellen Wert des Auswahlfeldes entsprechen:

WITH OBJECT DO
  BEGIN ART:=TEXT; VON.X:= 30 END;      FALSCH !

Um solche Probleme zu vermeiden, bietet sich bei Operationen mit varianten Records die Verwendung einer Fallunterscheidung (Case-Anweisung) an, die die Struktur des varianten Records reflektiert:

PROCEDURE READOBJECT (OBJEKTART: TPRIMITIV; VAR O: TOBJECT);
(* BENUTZEREINGABE EINES OBJEKTES VOM TYP TOBJECT *)
 
  PROCEDURE READK(WAS: STRING; VAR K: TKOORD);
  (* EINGABE EINES KOORDINATENPAARES *)
  BEGIN
    WRITE(WAS, ' (X Y) =');
    WITH K DO
      READLN(X,Y);
  END; (* READK *)
 
BEGIN
  WITH OBJECT DO
    BEGIN
      WRITE('Farbe :')     ; READLN(FARBE);
      WRITE('Intensität :'); READLN(INTENS);
      ART:= OBJEKTART;
      CASE OBJEKTART OF
        RECK,BLOCK:
          BEGIN
            READK('rechte untere Ecke', RECHTSUNTEN);
            READK('linke obere Ecke', LINKSOBEN);
          END;
        KREIS:
          BEGIN
            READK('Mitte', MITTE);
            WRITE('Radius :'); READLN(RADIUS);
          END;
        LINIE:
          BEGIN
            READK('Anfangspunkt', VON);
            READK('Endpunkt', BIS);
          END;
        TEXT:
          BEGIN
            READK('Position 1. Zeichen', POS1);
            WRITE('Text (<=10 Zeichen):'); READLN(STRING);
          END;
        HINTERGRUND: BEGIN (* NICHTS *) END
      END; (* CASE *)
    END; (* WITH *)
END; (* READOBJECT *)

Bild 70: Eingabe eines varianten Records

Für jeden Recordtyp (TKOORD und TOBJEKT) existiert also eine separate Prozedur zur Eingabe. Natürlich werden die eingelesenen Daten von den Prozeduren über Variablenparameter als Ergebnisse zurückgeliefert. Innerhalb der Case-Anweisung wird die durch den Wertparameter OBJEKTART definierte Variante eingelesen. In dieser Fallunterscheidung ist (im Gegensatz zur Deklaration des Records) die Angabe der Leeranweisung hinter der Fallmarke HINTERGRUND obligatorisch. Ansonsten würde zur Laufzeit bei dem Wert OBJEKTART=HINTERGRUND das Programm mit einer Fehlermeldung abgebrochen:

NO LABEL FOR CASE ERROR

Gerade bei großen Datenstrukturen im Hauptspeicher ist es auch für den Programmierer in einer problemorientierten »Hochsprache« sinnvoll, die Repräsentation der Daten im Computer zu kennen. Deshalb soll die Speicherverteilung (in Pascal 2.0) für variante Records kurz umrissen werden. Die exakte Definition der Speicherverteilung für beliebige Typen finden Sie in der Dokumentation in Abschnitt 4.4.2. Da zu einem Zeitpunkt das tagfield (Auswahlfeld) nur einen Wert annehmen kann, erhalten alle Varianten denselben Speicherplatz. Die Größe eines Objektes vom Typ TOBJEKT wird durch die Größe des festen Teils (FARBE und INTENS) zuzüglich der Größe der längsten Variante bestimmt. Damit ergibt sich die in Bild 71 skizzierte Speicherverteilung.

Bild 71: Struktur des Typs TOBJEKT

In diesem Fall ist TEXT die längste Variante. Alle anderen Varianten werden ebenfalls mit dieser Größe gespeichert und belegen daher ungenutzen Speicherplatz. Will man sehr große Arrays mit solchen Objekten bilden, so kann es sinnvoll sein, die größte Variante zu kürzen. Eine Möglichkeit besteht darin, das Feld STRNG auszulagern und durch einen Verweis in eine weitere Tabelle mit Strings zu ersetzen:

TYPE STRINGREF = 1..MAX;
VAR STRINGARRAY: ARRAY [STRINGREF] OF STRING[10];

Man müßte außerdem die Variante TEXT im Record TOBJEKT folgendermaßen ändern:

TEXT:
  (POS1: TKOORD;
   STRNG: STRINGREF);

Um einen Text im Array BILD (s. Deklaration in Bild 69) an der I-ten Stelle einzufügen, speichert man zunächst den String an einer freien Position im STRINGARRAY (z.B. J-tes Element). Dem Feld STRNG im Record wird dann nur der Index J zugewiesen:

WITH BILD[I] DO
  BEGIN
    FARBE := 3;
    INTENS:= 2;
    ART   := TEXT;
    POS1.X:= 0; POS1.Y:= 3;
    (* String eintragen *)
    STRINGARRAY[J]:= 'Beispieltext';
    (* Referenz notieren *)
    STRNG:=J
  END;

Damit ergibt sich eine kompaktere Speicherung der varianten Records, wie sie in Bild 72 dargestellt ist:

Bild 72: Gepackter varianter Record

Solche Speicherplatzoptimierungen sind gerade auf Mikrocomputern erforderlich und holen den angehenden software engineer allzu rasch von abstrakten objektorientierten Datenmodellen auf den Boden der Realität aus Bits und Bytes zurück.

Der Vollständigkeit halber sei noch erwähnt, daß es in Pascal zulässig ist, anstelle des Auswahlfeldes nur einen Typbezeichner zu nennen. Die aktuell gültige Variante wird also nicht in einem tagfield gespeichert. Stattdessen muß der Programmierer aus dem Kontext herleiten, welche Variante des Records momentan gültig ist. Diese Records werden praktisch nur zu schmutzigen Operationen verwendet, die normalerweise (aus gutem Grund) in Pascal verboten sind: Bei diesen Operationen nutzt man (wie bei den common areas in FORTRAN) die Tatsache aus, daß die einzelnen Varianten denselben Speicherplatz belegen, so daß die gleiche binäre Codierung bei verschiedenen Typen eine unterschiedliche Bedeutung besitzt. Daher kann man mit der folgenden Anweisung einer Variablen eines Aufzählungstyps einen Wert zuweisen, dessen Ordinalwert gleich 3 ist:

PROGRAM NASTY (OUTPUT);
  TYPE AUFZAEHL = (ROT, GRUEN, BLAU, SCHWARZ);
  VAR  SCHLIMM = RECORD
         CASE BOOLEAN OF
           TRUE:  (I: INTEGER);
           FALSE: (V: AUFZAEHL);
       END;
BEGIN
  SCHLIMM.I:=3;
  IF SCHLIMM.V = SCHWARZ THEN
    WRITELN('SCHLIMM.V IST SCHWARZ');
END.

Da solche Operationen inhärent von Eigenschaften spezieller Computer und Compiler abhängig sind, sollten Sie diese nicht in Ihren Programmen verwenden. In Pascal 2.0 gibt es explizite Operationen für solche Typumwandlungen, die in der Dokumentation behandelt werden.

Bild 69 zeigt exemplarisch, wie man in Pascal Daten-Deklarationen strukturiert: Angefangen bei den skalaren Bausteinen (Indexgrenzen, Aufzählungstypen) bildet man eine höhere Abstraktionsstufe: Man arbeitet zum Beispiel mit Koordinaten und nicht mehr mit INTEGER-Zahlen. Anschließend kann man diese abstrakteren Typen noch zu Records und Arrays (Verbunden und Feldern) geeigneter Dimensionen zusammenfassen.

Diese Vorgehensweise (von unten nach oben, bottom up) ist zwingend notwendig. Da der Compiler den Text in einem Durchgang (one pass) liest, muß jeder Typname vor der ersten Anwendung bereits deklariert sein. Konkret bedeutet dies, daß die Deklaration des Typs TKOORD vor der Anwendung des Typs in der Deklaration von TOBJEKT erfolgen muß.

Inhaltsverzeichnis