Verkettete Listen: Aufbau


Wenn wir den abstrakten Datentyp list in Modula 2 nachbilden wollen, so haben wir das Problem, daß die bisher besprochenen Mechanismen alle Objekte fester Länge beschreiben. Die Objekte des Datentyps list  haben aber offensichtlich variablen Umfang, und daher kann man ihnen in einer konkreten Realisierung keine Speicherobjekte fester Länge zuordnen; es bleibt nur die Möglichkeit, sie durch ein Geflecht von Knoten zu realisieren, dessen Elemente auf der Halde liegen. Die Knoten müssen Verbunde sein, da sie neben den eigentlichen Nutzdaten vom Typ T auch noch die Strukturinformation über ihren Zusammenhang tragen müssen.

In unserem Fall ist dies jeweils ein Verweis auf den Nachfolger, und am Ende der Liste steht stattdessen ein NIL-Wert. Die Liste als Ganzes wird angesprochen über einen Anker, also eine Variable, deren Inhalt ein Verweis auf das erste Listenelement ist, oder NIL bei einer leeren Liste.

Listen

Wir definieren daher:

TYPE atom = ? (* irgend ein Typ *)
     liste = POINTER TO knoten;
     knoten = RECORD wert: atom;
                     nach: liste
              END;
Aus historischen Gründen erlaubt Modula 2 nicht beliebige Typen als Funktionsresultate, daher ersetzen wir einige Funktionen durch Prozeduren mit Ergebnisparametern:
PROCEDURE newlist(VAR l: liste):
BEGIN l := NIL;
END newlist;

PROCEDURE cons(a: atom; l: liste; VAR r: liste);
(* r: = cons(a,l) *)
BEGIN NEW(r);
      r^.wert := a; r^.nach := l;
END cons;

PROCEDURE isempty(l: liste): BOOLEAN;
BEGIN RETURN l = NIL;
END isempty;

PROCEDURE head(l: liste; VAR a: atom);
BEGIN a := l^.wert;
END head;

PROCEDURE tail(l: liste; VAR r: liste);
BEGIN r := l^.nach;
END tail;
Diese Prozeduren sind extrem einfach; und hat man sich ihren Aufbau einmal eingeprägt, so wird man auf ihren weiteren Gebrauch verzichten und die betreffenden Anweisungen jeweils direkt hinschreiben.

Allerdings haben wir noch nicht berücksichtigt, daß head  und tail  partiell definiert sind und auf eine leere Liste nicht angewandt werden dürfen. Dies müssen wir noch korrigieren; und weil Modula 2, anders als Sprachen wie Ada und Java, keinen Standard-Fehlermechanismus anbietet, rufen wir hier eine Prozedur error  auf, die noch geeignet zu definieren wäre. Eine Fortsetzung des Programmlaufs nach ihrem Aufruf macht natürlich wenig Sinn, da ja der Aufruf von head  bzw. tail  unzulässig war; es muß also ein logischer Fehler  vorliegen, den man aufsuchen und beseitigen sollte.

PROCEDURE head(l: liste; VAR a: atom);
BEGIN IF l = NIL THEN error("access to empty list")
      ELSE a := l^.wert END;
END head;

PROCEDURE tail(l: liste; VAR r: liste);
BEGIN IF l = NIL THEN error("access to empty list")
      ELSE r := l^.nach END;
END tail;
Durch wiederholte Aufrufe von newlist  und cons  können wir nun Listen von beliebigem Inhalt aufbauen; für einige Standard-Anwendungen, die sich wieder als abstrakte Datentypen gut modellieren lassen, werden wir spezielle Mechanismen kennenlernen.


zurück | Inhalt | Index | vor | Vorlesung

Klaus Lagally, 22. Februar 2000, 19:36