Inhaltsverzeichnis

2.12 Skalare Typen und ihre Operationen

Dieser Abschnitt stellt eine Fortsetzung von Kapitel 2.6 über die Typen der Sprache Pascal fort. Es werden Methoden vorgestellt, um neue skalare Typen zu deklarieren. Die so definierten Typen erlauben es, im Computer ein möglichst exaktes Modell der zu bearbeitenden Daten zu bilden. Auch wenn auf der Ebene der Maschinensprache alle Daten als binäre Zahlenfolgen dargestellt werden, sollte der Programmierer problemorientierte Objekte definieren und manipulieren können.

2.12.1 Aufzählungstypen

Im Rahmen einer Typdeklaration kann man eine Menge von Werten aufzählen und zu einem Typ zusamenfassen:

TYPE WOTAG    =(MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG,
                FREITAG, SAMSTAG, SONNTAG);
     FAMSTAND =(LEDIG, VERHEIRATET, GETRENNT, GESCHIEDEN, 
                VERWITWET);
     FRUCHT   =(APFEL, BIRNE, ORANGE);
 
VAR  HEUTE, MORGEN  : WOTAG;
     LIEBLINGSFRUCHT: FRUCHT;
     SPEISEPLAN     : ARRAY [WOTAG] OF FRUCHT;
     STATUS         : FAMSTAND;

Bild 56: Aufzählungstypen

Die Variablen HEUTE und MORGEN können nur die Werte Montag bis Sonntag annehmen. WOTAG, FAMSTAND und FRUCHT bezeichnet man als Aufzählungstypen. Die Namen in Klammern sind Konstanten des jeweiligen Aufzählunstyps. MONTAG ist also eine Konstante vom Typ WOTAG.

Zwar kann man mit Werten eines Aufzählungstyps nicht »rechnen«, jedoch sind Zuweisungen und Vergleiche zwischen Variablen eines Aufzählungstyps möglich:

HEUTE:= SAMSTAG;
IF HEUTE = MORGEN THEN WRITELN('???');
CASE SPEISEPLAN[SAMSTAG] OF
 APFEL : WRITELN('Apfel');
 BIRNE : WRITELN('Birne');
 ORANGE: WRITELN('Orange');
END;
SPEISEPLAN[HEUTE]:= SPEISEPLAN[MORGEN];

Wegen der Typbindung in Pascal sind folgende Operationen fehlerhaft:

HEUTE:= LIEBLINGSFRUCHT;
SPEISEPLAN[3]:= APFEL;
IF HEUTE = LEDIG THEN ...

Durch die Reihenfolge der Konstanten bei der Typdeklaration wird eine Ordnung definiert, so daß auch Vergleiche mit »<« und »>« sinnvoll sind:

ORD(MONTAG)   = 0
ORD(DIENSTAG) = 1
ORD(SONNTAG)  = 6
MONTAG<DIENSTAG       
MITTWOCH>MONTAG      
LEDIG < VERWITWET
IF HEUTE<=FREITAG THEN WRITELN('Heute ist ein Werktag')
IF LIEBLINGSFRUCHT > APFEL THEN WRITELN('Birne oder Orange')

Die Standardfunktionen SUCC und PRED liefern zu jedem skalaren Typ den Nachfolger und Vorgänger im Wertebereich.

SUCC(DIENSTAG)    = MITTWOCH
PRED(SONNTAG)     = SAMSTAG
SUCC(APFEL)       = BIRNE<
PRED(VERHEIRATET) = LEDIG

aber auch

SUCC(1)           = 2
SUCC(-2)          = -1
SUCC(FALSE)       = TRUE
SUCC('A')         = 'B'

Dementsprechend läßt sich die FOR-Anweisung auch mit einer Laufvariablen eines Aufzählungstyps verwenden:

FOR HEUTE:= MONTAG TO FREITAG DO
  FOR MORGEN:= SUCC(HEUTE) TO SONNTAG DO
    WRITELN(ORD(HEUTE):4, ORD(MORGEN):4);

Im Computer existiert zur Laufzeit eines Objektprogrammes nur die kompakte codierte Darstellung der Aufzählungswerte durch ihre Ordinalwerte. Deshalb kann man die Namen der Werte des Aufzählungstyps nicht direkt ausgeben:

WRITE('Heute ist ', HEUTE)                FALSCH!

Stattdessen muß man die Ausgabe explizit programmieren:

PROGRAM TAGE (OUTPUT);
  TYPE WOTAG = (MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG, 
                FREITAG, SAMSTAG, SONNTAG);
  VAR  HEUTE : WOTAG;
 
PROCEDURE WRITEWOTAG (TAG: WOTAG);
BEGIN
  CASE TAG OF
    MONTAG   : WRITE('Montag');
    DIENSTAG : WRITE('Dienstag');
    ...
    SONNTAG  : WRITE('Sonntag');
  END; (* CASE *)
END; (* WRITEWOTAG *)
 
BEGIN
  FOR HEUTE:= SONNTAG DOWNTO MONTAG DO
    BEGIN WRITEWOTAG(HEUTE); WRITE(',') END
END.

Bild 57: Ausgabe eines Aufzählungstyps

Aufzählungstypen erhöhen die Lesbarkeit eines Programmes enorm und sollten wo immer möglich verwendet werden. Ein etwas spezielleres Beispiel zeigt die Verwendung eines Aufzählungstyps als Indextyp: Im Programm soll die Anzahl der Bürger jedes Familienstandes gezählt werden.

Die Idee besteht darin, ein Array von Zählern zu deklarieren, das direkt durch Werte des Typs FAMSTAND indiziert wird. Damit kann man in einer Schleife, die alle Bürger erfaßt, mit einer einzigen Zuweisung den jeweiligen Zähler erhöhen (s. Bild 58):

PROGRAM VOLKSZAEHLUNG (INPUT, OUTPUT);
(* Dieses Programm skizziert die Verwendung von Auf- *)
(* zählungstypen als Indextypen.                *)
  CONST ANZAHLBUERGER = 300;
  TYPE FAMSTAND = (LEDIG, VERHEIRATET, GETRENNT, GESCHIEDEN,
                   VERWITWET);
  VAR  STAMMBUCH: ARRAY [1..ANZAHLBUERGER] OF FAMSTAND;
       STATISTIK: ARRAY [FAMSTAND] OF INTEGER;
       STATUS   : FAMSTAND;
  BUERGER  : INTEGER;
 
  PROCEDURE STAMMBUCHBELEGEN;
  (* Fuer jeden Bürger wird in dieser Prozedur der   *)
  (* Familienstand im Stammbuch zufällig belegt.     *)
    VAR I: INTEGER;
  BEGIN
    FOR I:= 1 TO ANZAHLBUERGER DO
      STAMMBUCH[I]:= FAMSTAND (INT(RANDOM(0) * (ORD(VERWITWET)+1)));
  END; (* STAMMBUCHBELEGEN *)
 
 BEGIN
  STAMMBUCHBELEGEN;
  (* zunächst alle Zähler zurücksetzen: *)
  FOR STATUS:= LEDIG TO VERWITWET DO
    STATISTIK[STATUS]:= 0;
  (* jetzt Stammbuch durchlaufen und zählen *)
  FOR BUERGER:= 1 TO ANZAHLBUERGER DO
    BEGIN
      STATUS:= STAMMBUCH[BUERGER];
      STATISTIK[STATUS]:= STATISTIK[STATUS] + 1
    END;
  (* Ergebnisse drucken: *)
  FOR STATUS:= LEDIG TO VERWITWET DO
    WRITELN( ORD(STATUS):4, STATISTIK[STATUS]:8);
END.

Bild 58: Aufzählungstypen als Indizes

Die Prozedur STAMMBUCHBELEGEN verwendet das Gegenstück zur Prozedur ORD, das jedoch nicht im report definiert ist: Um einen zufälligen Familienstand zu definieren, wird die Funktion RANDOM(0) verwendet. Sie liefert eine reelle Zahl X, die aus dem Intervall 0<=X<1 stammt. Dieser Wert wird durch die Mulitplikation mit der Zahl

(ORD(VERWITWET)+1) = 5

auf das Intervall 0 bis 4.999999 abgebildet. Nachdem mit der Funktion INT die Nachkommastellen abgeschnitten wurden, wird die so berechnete INTEGER-Zahl zwischen 0 und 4 mit dem Typnamen FAMSTAND in den Typ FAMSTAND konvertiert. Es gilt nämlich:

FAMSTAND(0) = LEDIG
FAMSTAND(1) = VERHEIRATET
...
FAMSTAND(4) = VERWITWET
 
WOTAG(0)    = MONTAG
WOTAG(1)    = DIENSTAG
FRUCHT(2)   = ORANGE

2.12.2 Unterbereichstypen

Gibt es in einem Programm Variablen, die nur Werte aus einem Teilbereich des Wertebereichs eines Typs annehmen (oder annehmen sollen), so läßt sich diese Information bei der Deklaration einer Variablen angeben.

  CONST N=3; M=4;
  TYPE  ZEILENINDEX  = 1..N;
   SPALTENINDEX = 1..M;
   DATEITYP     = (SEQ, INDSEQ, REL, ERASED);
   XKOORDINATE  = 0..319;
   YKOORDINATE  = 0..199;
  VAR   M: ARRAY[ZEILENINDEX, SPALTENINDEX] OF REAL;
   I, I1: ZEILENINDEX;
   J, J1: SPALTENINDEX;
   B, BUCHSTABE:  'A'..'Z';
   ZIFFER      :  '0'..'9';
   X,CENTERX   : XKOORDINATE;
   Y,CENTERY   : YKOORDINATE;
   ARBEITSDATEI: SEQ..REL;

Bild 59: Unterbereichstypen

Bei einer Typ- oder Variablendeklaration kann man einen sogenannten Unterbereichstyp deklarieren, indem man getrennt durch das Auslassungssymbol »..« die untere und obere Grenze innerhalb eines anderen Typs nennt. Der so definierte Typ kann wie jeder skalare Typ in Variablendeklarationen oder zusammengesetzten Typ- deklarationen verwendet werden.

Werte eines Unterbereichstyps können an jeder Stelle verwendet werden, an der auch ein Wert des zugrundeliegenden Basistyps zulässig ist. Außerdem sind mit Variablen eines Unterbereichstyps alle Operationen des Basistyps möglich:

I:= I1 * 2;
A[I,J]:= I + J * 4 - 5;
B:= CHR(68);
ARBEITSDATEI:= SEQ;
X:= X DIV 2;

Obwohl die Einführung von Unterbereichstypen etwas mehr (Schreib-) Aufwand bei der Programmierung erfordert, ermöglicht sie eine zusätzliche Sicherheit vor unzulässigen Zuweisungen und erlaubt so eine einfachere Fehlersuche. Mindestens ebenso wichtig ist der erhöhte Dokumentationswert einer Variablendeklaration:

Wählen Sie bei der Compilation die Option range-check (Bereichs- überprüfung) des Compilers, indem Sie den aktiven Kommentar

(*$R+ *)

im Programm verwenden, so wird das Programm nachfolgend um Codesequenzen erweitert, die bei der Laufzeit auch die Einhaltung der Intervallgrenzen bei Unterbereichstypen prüfen. Dabei müssen nur die Zuweisungen an Variablen eines Unterbereichs geprüft werden:

I:= I1 * 2;
READLN(BUCHSTABE);
ZIFFER:= SUCC(ZIFFER);
FOR J:= -3 TO 3 DO WRITE(J);
     M[I+1,J+1]:= 30;

Verwendet man jedoch durchgängig im gesamten Programm Unterbereichstypen, so sinkt die Anzahl der Stellen, an denen Code zur Überprüfung erzeugt werden muß. Bei Zuweisungen der folgenden Art sind nämlich keine Überprüfungen erforderlich:

J:= J1;
I:= I1;
B:= BUCHSTABE;
M[I,J]:= M[I1,J1]

Wurde die Bereichsüberprüfung eingeschaltet, wird bei einer illegalen Zuweisung im Objektprogramm eine Fehlermeldung erzeugt:

ZIFFER:= SUCC('9');

VALUE OUT OF BOUNDS: 58  48  57

Dies bedeutet, daß der Nachfolger des Zeichens »9« mit dem Ordinalwert 58 nicht im Bereich der Ziffern »0« bis »9« mit den Ordinalwerten 48 und 57 liegt. Also:

Fehlerhafter Wert  ORD(SUCC('9')) = 58
untere Bereichsgrenze  ORD('0') = 48
obere Bereichsgrenze  ORD('9') = 57

Generell werden bei dieser Fehlermeldung nur die Ordinalwerte angegeben, da z.B. für Aufzählungstypen im Objektprogramm keine Namen vorhanden sind.

Zumindest in der Testphase ist die Option range check (s. 4.4.6.1) sehr zu empfehlen, um Indizierungsfehler und Bereichsüberschreitungen zu entdecken. Die Ausführungsgeschwindigkeit und Codegröße steigt durch diese Option nur gerinfügig an. Im endgültigen Programm kann man die Option wieder ausschalten, um optimalen Code zu erhalten.

Abschließend sollte erwähnt werden, daß in einzelnen Fällen die Verwendung von Unterbereichstypen den Speicherplatzbedarf für Variablen verringern kann. Um das Alter von 100 Personen zu speichern, sind folgende zwei Arraydeklarationen möglich:

VAR ALTER1: ARRAY[1..100] OF 0..130;
    ALTER2: ARRAY[1..100] OF INTEGER;

Während die Variable ALTER1 100 Bytes belegt, werden durch die Variable ALTER2 200 Bytes belegt. Gerade bei großen Datenstrukturen sind solche Optimierungsmöglichkeiten zu beachten. In Pascal 2.0 existiert bereits der vordefinierte Unterbereichstyp BYTE, der genau ein Byte Speicherplatz belegt:

TYPE BYTE = 0..255;

Aufgaben

1. Untersuchen Sie alle bisher im Text angegebenen Beispielprogramme. Prüfen Sie, ob in den Variablendeklarationen die Möglichkeit besteht, Unterbereichstypen zu verwenden. Prädestiniert für solche Verbesserungen sind Variablen, die zur Indizierung verwendet werden. (Diese Variablen heißen meist I oder J.)

Compilieren Sie ein Beispielprogramm mit der range-check Option und prüfen Sie die Reaktion auf Bereichsüberschreitungen!

2. Modifizieren Sie das Programm QUICKSORT aus Bild 52 durch die Einführung von Typnamen (INDEX und ITEM) im Hauptprogramm, so daß beliebige Indexbereiche und Elementtypen sortiert werden können. Prüfen Sie die Richtigkeit der Änderungen, indem Sie folgendes Array sortieren:

TYPE INDEX = 10..20;
     ITEM  = CHAR;
VAR  A : ARRAY [INDEX] OF ITEM;

Inhaltsverzeichnis