Inhaltsverzeichnis

2.11.3 Funktionen

Neben den Variablenparametern gibt es in Pascal eine weitere Möglichkeit, Resultate aus Unterprogrammen zurückzuliefern. Diese Methode lehnt sich an die Notation von Funktionen in der Mathematik an. Dort schreibt man zum Beispiel

x = ggT (7,29)

Der Name der Funktion ggT repräsentiert also gleichzeitig den Wert der Berechnung. In Pascal definiert man solche Prozeduren, die nur einen skalaren Wert als Ergebnis liefern, als Funktionen:

PROGRAM FUNKTION(OUTPUT);
  VAR W: INTEGER;
 
  FUNCTION GGT(A,B: INTEGER): INTEGER;
  BEGIN
    WHILE A<>B DO
      IF A>B THEN A:= A-B ELSE B:= B-A;
    GGT:= A
  END; (* GGT *)
 
BEGIN (* HAUPTPROGRAMM *)
  W:= GGT(12345, 25325);
  WRITELN(W, GGT(9,3));
  W:= W + GGT( GGT(1234, 432), 444);
END.

Bild 44: Der ggT als Funktion

Wenn Sie Bild 44 mit Bild 40 vergleichen, so werden Sie folgende Unterschiede zwischen einer Funktion und einer »normalen« Prozedur feststellen: Im Prozedurkopf wird das Wortsymbol PROCEDURE durch FUNCTION ersetzt und am Ende der Parameterliste steht zusätzlich nach einem Doppelpunkt der Ergebnistyp der Funktion. An dieser Stelle muß (wie bei einer Parameterliste) ein Typ-Name stehen:

TYPE SHORTSTRING = STRING[16];

FUNCTION REVERSE(S: SHORTSTRING): SHORTSTRING; RICHTIG!

also nicht:

FUNCTION REVERSE(S: STRING[16]): STRING[16];   FALSCH!

Als Ergebnistyp einer Funktion sind im report nur skalare Typen und Zeiger (s. Abschnitt 2.18) erlaubt, während Pascal 2.0 auch beliebige zusammengesetzte Typen als Funktionsergebnis unterstützt.

Die exakte Syntax einer Prozedur- und Funktionsdeklaration ist natürlich wieder dem Anhang A (Syntaxdiagramm BLOCK und PARAMETERLISTE) zu entnehmen.

Innerhalb der Funktion muß das Funktionsergebnis an mindestens einer Stelle durch eine Zuweisung an den Funktionsnamen festgelegt werden. In der Funktion GGT geschieht dies mit der Zuweisung GGT:=A. GGT ist also nicht nur der Name der Funktion, sondern auch der Name für das Ergebnis der Funktion. Die Anweisungen im Hauptprogramm in Bild 44 zeigen, daß man Funktionsaufrufe wie in der Mathematik üblich beliebig geschachtelt innerhalb von Ausdrücken verwenden kann.

Funktionsaufrufe sind nicht wie Prozeduraufrufe als eigenständige Anweisungen möglich:

W:= GGT(3,4)         RICHTIG!
BEGIN GGT(3,4) END   FALSCH !

Das folgende Programm beinhaltet mehrere Funktionsdeklarationen, die recht häufig in alltäglichen Programmen verwendet werden.

PROGRAM FUNCTIONS (INPUT,OUTPUT);
  CONST VEKTORLAENGE = 3;
  TYPE  VEKTOR       = ARRAY [1..VEKTORLAENGE] OF INTEGER;
  VAR   V            : VEKTOR;
        S            : STRING;
        I            : INTEGER;
 
  FUNCTION MAX(A,B: INTEGER): INTEGER;
  (* BESTIMME DAS MAXIMUM DER BEIDEN ZAHLEN *)
  BEGIN
    IF A>B THEN MAX:=A ELSE MAX:=B;
  END; (* MAX *)
  
  FUNCTION INTRANDOM(LIMIT: INTEGER): INTEGER;
  (* GANZE ZUFALLSZAHL ZWISCHEN 1 UND LIMIT ERZEUGEN *)
  BEGIN 
    INTRANDOM:= 1 + INT(RANDOM(0) * LIMIT) 
  END; (* INTRANDOM *)
  
  FUNCTION FAKULTAET(N: INTEGER): REAL;
  (* N ! = 1 * 2 * 3 * ... * N BERECHNEN *)
    VAR P: REAL;
  BEGIN 
    P:=1; 
    WHILE N>1 DO
      BEGIN P:= P*N; N:= N-1 END;
    FAKULTAET:= P;
  END; (* FAKULTAET *)
  
  FUNCTION CENTER (S: STRING; FIELD: INTEGER): STRING;
  (* ZENTRIERE S IN EINEM STRING DER LAENGE FIELD *)
  BEGIN
    WHILE LENGTH(S)<FIELD-1 DO
      S:= ' ' + S + ' ';
    IF LENGTH(S)=FIELD-1 THEN CENTER:= S + ' ' ELSE CENTER:= S
  END; (* CENTER *)
 
  FUNCTION BETRAG (V: VEKTOR): REAL;
  (* BERECHNE DEN BETRAG (EUKLIDISCHE NORM / LAENGE) DES *)
  (* VEKTORS V                                           *)
    VAR I    : INTEGER;
        SIGMA: REAL;
  BEGIN
    SIGMA:= 0.0;
    FOR I:= 1 TO VEKTORLAENGE DO
      SIGMA:= SIGMA + SQR(V[I]);
    BETRAG:= SQRT(SIGMA)
  END; (* BETRAG *)
 
BEGIN (* HAUPTPROGRAMM *)
  FOR I:= 1 TO VEKTORLAENGE DO
    BEGIN
      V[I]:= INTRANDOM(200); WRITE(V[I]:4)
    END;
  WRITELN;
  WRITELN('Das Maximum ist:', MAX(MAX(V[1],V[2]),V[3]));
 
  WRITELN('Der Betrag ist:',  BETRAG(V));
 
  S:= CENTER('*', 40); 
  WRITELN( CENTER('Dieser Text wird auf ',40)); 
  WRITELN( CENTER('40 Spalten zentriert', 40));
  WRITELN; WRITELN(S);
  FOR I:= 0 TO 15 DO
    WRITELN(I:5, '! =', FAKULTAET(I));
END.

Bild 45: Beispiele für Funktionen

2.11.4 Standardprozeduren

Bereits bei Ihren ersten Schritten in Pascal hatten Sie die Anweisungen WRITE und READ sowie arithmetische Funktionen wie SIN, COS und ABS verwendet.

Im Unterschied zu den Wortsymbolen (BEGIN, END, WHILE, OR, MOD ...) handelt es sich hierbei um vordefinierte Namen für Prozeduren und Funktionen. Aus den in Abschnitt 2.11.1 beschriebenen Sichtbarkeitsregeln für Namen geht hervor, daß diese Standardbezeichner auch durch benutzerdefinierte Namen verdeckt werden können. Diese Tatsache ist besonders wichtig, wenn es um die Verwendung von Namen geht, die nur in Pascal 2.0 eine vordefinierte Bedeutung besitzen: Falls Sie z.B. aus der Literatur ein Programm übernehmen wollen, das den Namen CIRCLE vewendet, so können Sie nicht in Konflikte mit der Prozedur CIRCLE von Pascal 2.0 geraten, da die Prozedur CIRCLE in einem imaginären Block deklariert ist, der das gesamte Programm umgibt.

Als Beispiel zeigt Bild 46 eine Deklaration der Quadratwurzelfunktion SQRT. Durch die Vereinbarung der Funktion SQRT wird die Standardfunktion SQRT verdeckt, so daß spätere Aufrufe der Funktion SQRT nur die benutzerdefinierte Funktion aktivieren:

PROGRAM NEWSQRT (INPUT,OUTPUT);
  CONST EPS = 1.0E-7;   (* Genauigkeit *)
  VAR   X: REAL;
 
  FUNCTION SQRT(X: REAL): REAL;
    VAR Y,Z: REAL;
  BEGIN
    IF X<0 THEN
      BEGIN
        WRITELN('Fehler in SQRT'); HALT
      END
    ELSE
      BEGIN
        Y:= 2; (* Startwert Z berechnen *)
        REPEAT
          Z:= Y; Y:= Y*Y
        UNTIL Y>X
        REPEAT   (* Iteration *)
          Y:= Z; Z:= 0.5 * (Y + X/Y)
        UNTIL ABS(Y-Z)<= EPS
      END;
    SQRT:= Z
  END; (* SQRT *)
 
BEGIN
  X:= 3.0;
  REPEAT
    WRITELN(X:13, SQRT(X):13); X:= 10 * X;
  UNTIL X>1E9;
END.

Bild 46: Deklaration der Quadratwurzelfunktion

Durch eine Erniedrigung der Genauigkeit EPS läßt sich bei Bedarf die Geschwindigkeit der Routine erhöhen. Die Eigenschaft mancher vordefinierter Prozeduren, eine variable Anzahl von Parametern zu besitzen (z.B. WRITE, READ), läßt sich mit benutzerdefinierten Prozeduren nicht simulieren.

Die Wirkung jeder einzelnen Standardprozedur und -funktion wird in Kapitel 4.4.4 erläutert.

2.11.5 Rekursion

In Abschnitt 2.11.1 über die Schachtelung von Prozeduren wurde erklärt, daß in einem Block jede Prozedur aufgerufen werden kann, deren Name sichtbar ist. Dies bedeutet, daß sich eine Prozedur auch selbst aufrufen kann. Dieser Vorgang wird Rekursion genannt.

Durch die Tatsache, daß bei jedem Aufruf einer Prozedur Speicherplatz für die lokalen Objekte bereitgestellt wird, werden bei rekursiven Aufrufen einer Prozedur P verschiedene Inkarnationen der lokalen Variablen von P erzeugt.

Wegen dieser Methode der Speicherverwaltung werden beim rekursiven Aufruf einer Prozedur die alten Werte der lokalen Variablen nicht überschrieben. Dies läßt sich am besten mit einem einfachen Beispielprogramm verdeutlichen (Bild 47).

PROGRAM REKURSION (OUTPUT);
 
  PROCEDURE REKURSIV (N: INTEGER);
    VAR LOKAL: INTEGER;
  BEGIN
    LOKAL:= N; 
    WRITELN (' ': N, 'LOKAL =', LOKAL); 
    IF N<4 THEN REKURSIV(N+1);        (* <<-------- *)
    WRITELN (' ': N, 'LOKAL =', LOKAL);
  END; (* REKURSIV *)
 
BEGIN 
  REKURSIV(1)
END.

Bild 47: Eine einfache Rekursion

Die Prozedur REKURSIV speichert zunächst (zu Demonstrationszwecken) den Wert des Parameters N in einer lokalen Variablen (LOKAL). Dieser Wert wird nun in zwei identischen Ausgabeanweisungen um N Stellen eingerückt gedruckt. Die eigentlich interessante Anweisung ist mit einem Pfeil markiert: Ist beim Aufruf der Prozedur der Parameter noch nicht gleich 4, so ruft sich die Prozedur selbst auf. Als Parameter für diesen Selbstaufruf wird die Zahl N+1 verwendet.

Da jeder Aufruf der Prozedur REKURSIV seine eigenen Variablen N und LOKAL besitzt, wird durch diesen rekursiven Aufruf der Inhalt der Variablen N und LOKAL in der aufrufenden Umgebung nicht verändert. Deshalb ergibt sich folgende Ausgabe:

LOKAL = 1
 LOKAL = 2
  LOKAL = 3
   LOKAL = 4
   LOKAL = 4
  LOKAL = 3
 LOKAL = 2
LOKAL = 1

Jeweils zwei Zeilen, die gleich weit eingerückt sind, stammen von derselben Inkarnation der Prozedur REKURSIV. Dieses Programm ist zwar nicht sehr sinnvoll, zeigt aber deutlich die geschachtelten Aufrufe:

REKURSIV(1)   ruft
  REKURSIV(2)   ruft
    REKURSIV(3)   ruft
      REKURSIV(4)

Außerdem sehen Sie, daß diese Folge rekursiver Aufrufe irgendwann beendet werden muß. Deshalb werden rekursive Aufrufe immer durch eine Bedingung kontrolliert. Im Beispiel aus Bild 47 ist dies die If-Anweisung IF N<4 THEN.

Schon jetzt möchte ich Sie davor warnen, rekursive Prozeduren Schritt für Schritt nachzuvollziehen, indem Sie sich die Werte aller lokalen Variablen notieren und so den Programmablauf zu analysieren versuchen. Dabei werden Sie nach wenigen Schritten schon an einem heillosen Durcheinander verzweifeln. Vielmehr muß man rekursive Prozeduren deklarativ verstehen. So kann man z.B. die Prozedur REKURSIV wie folgt beschreiben:

  1. Drucke N Stellen eingerückt die Zahl N.
  2. Ist der Text noch nicht vier Stellen eingerückt, so drucke einen Block, der N+1 Stellen eingerückt ist.
  3. Drucke N Stellen eingerückt die Zahl N.

Um Ihr deklaratives Verständnis zu trainieren, sollten Sie jetzt ein Blatt Papier zur Hand nehmen und die Ausgabe notieren, die Sie beim Aufruf REKURSIV2(1) in Bild 48 erwarten. Im Unterschied zur Prozedur REKURSIV druckt REKURSIV2 den eingerückten Block zweimal. (Ein Tip: Die Ausgabe ist genau 30 Zeilen lang!)

PROGRAM REKURSION2 (OUTPUT);
 
  PROCEDURE REKURSIV2 (N: INTEGER); 
    VAR LOKAL: INTEGER;
  BEGIN
    LOKAL:= N;
    WRITELN (' ': N, 'LOKAL =', LOKAL);
    IF N<4 THEN REKURSIV2(N+1);   (* <<-------- *)
    IF N<4 THEN REKURSIV2(N+1);   (* <<-------- *)
    WRITELN (' ': N, 'LOKAL =', LOKAL);
  END; (* REKURSIV *)
 
BEGIN
  REKURSIV2(1)
END.

Bild 48: Rekursion (2)

Das Prinzip der Rekursion besteht darin, daß man zur Lösung einer großen Aufgabe die Lösung der Aufgabe für kleinere Werte benutzt. Diese etwas tautologisch anmutende Strategie soll ein kleines grafisches Beispielprogramm illustrieren:

PROGRAM DRAWCIRCLES (INPUT,OUTPUT, GRAPHIC);
 
  CONST XMAX = 319;
        YMAX = 199;
 
  VAR   T    : INTEGER;
 
  PROCEDURE QUADCIRCLE (TIEFE: INTEGER; MX, MY, R : INTEGER);
  (* ZEICHNE REKURSIV EINEN KREIS, DER VIER KREISE *)
  (* ENTHÄLT, DIE SICH GEGENSEITIG BERÜHREN.       *)
    VAR R2: INTEGER;
  BEGIN
    CIRCLE (1, MX, MY, R,,0,360,0,10);
    IF TIEFE>1 THEN
      BEGIN (* Bilde einen Kreis aus 4 kleineren Kreisen *)
        R2:= R DIV 2;
        QUADCIRCLE(TIEFE-1, MX-R2, MY    ,R2);
        QUADCIRCLE(TIEFE-1, MX   , MY-R2 ,R2);
        QUADCIRCLE(TIEFE-1, MX+R2, MY    ,R2);
        QUADCIRCLE(TIEFE-1, MX   , MY+R2, R2)
      END;
    (* --- Punkt 1 -- *)
  END; (* QUADCIRCLE *)
 
BEGIN
  COLOR(0,1); COLOR(1,2); COLOR(5,5);
  WRITE('Welche Tiefe? :4' #157);
  READLN(T);
  GRAPHIC(1,1);
  QUADCIRCLE(T, XMAX DIV 2, YMAX DIV 2, YMAX DIV 2);
  REPEAT UNTIL KEYPRESSED;
  GRAPHIC(0)
END.

Bild 49: Rekursives Grafikprogramm

Dieses Programm zeichnet ein Muster aus geschachtelten Kreisen. Ein Muster der Tiefe N wird durch folgende (rekursive) Regel beschrieben:

  1. Zeichne einen Kreis
  2. Ist die Tiefe N größer als 1, so zeichne das Muster viermal (links, oben, rechts und unten) in halber Größe mit der Tiefe N-1.

Wenn Sie das Programm nacheinander für die Tiefen 1, 2, 3 und 4 ausführen lassen, werden Sie das obige Konstruktionsprinzip sehr deutlich erkennen. Außerdem werden Sie feststellen, daß durch Rekursion die Programmlaufzeit nach dem Schneeballsystem exponentiell ansteigen kann:

Tiefe Anzahl der Kreise
1 1= 1
2 1 + 4= 5
3 1 + 4 ( 1 + 4)= 21
4 1 + 4 ( 1 + 4 ( 1 + 4))= 85
5 1 + 4 ( 1 + 4 ( 1 + 4 ( 1 + 4)))= 341

Grafische Programme sind sehr gut geeignet, das Prinzip der Rekursion anschaulich darzustellen, da optisch die Selbstbezüglichkeit einer Struktur unmittelbar zu erkennen ist. Eine Rekursion wie in Bild 49 läßt sich auch »von hinten« aufzäumen, indem man zunächst die Muster niedriger Tiefe zeichnet. Sie sollten deshalb zum Vergleich die Anweisung

CIRCLE (1, MX, MY, R,,0,360,0,10);

an die Stelle (* --- Punkt1 --- *) stellen. Zwar ist das endgül- tige Muster unverändert, jedoch werden Sie erkennen, daß die Zeichenstrategie sich geändert hat. Auf der mitgelieferten Pro- grammdiskette findet sich das Programm »TRIA.P«. Es arbeitet nach dem gleichen Rekursionsschema wie das Programm in Bild 49, um geschachtelte Dreiecke wählbarer Verzerrung zu zeichnen.

Nachdem Sie einmal das Prinzip der Rekursion verstanden haben, werden Sie eine Vielzahl von Problemlösungen durch rekursive Algorithmem formulieren. Ein sinnvolles Beispiel für die Verwendung der Rekursion zeigt Bild 50. Das Programm soll alle möglichen Anordungen (Permutationen) eines Strings mit N Zeichen Länge drucken. Für den String 'ABC' mit drei Zeichen gibt es diese 6 Anordnungen:

ABC BAC CBA BCA ACB CAB

Der rekursive Algorithmus zur Ausgabe lautet folgendermaßen:

  1. Ist die Länge N gleich 1, so gibt es nur diese Anordnung. Drucke diese Anordnung.
  2. Ansonsten drucke wie folgt alle Möglichkeiten, die ersten (N-1) Zeichen im String anzuordnen, ohne das N-te Zeichen zu verändern:

    Für alle Positionen i von 1 bis N-1: Tausche das i. Zeichen mit dem letzen (N-ten) Zeichen im String. Drucke alle Möglichkeiten für diese Anordnung. Mache die Vertauschung rückgängig.

So formuliert läßt sich der Algorithmus direkt in ein Pascal-Programm umsetzen (s. Bild 50).

PROGRAM ANORDNUNGEN (INPUT,OUTPUT);
  VAR I: INTEGER;
      A: STRING;
 
  PROCEDURE ANORDNUNG(S: STRING; N: INTEGER);
  (* Drucke alle Anordnungen der ersten N Zeichen im  *) 
  (* String S, ohne die übrigen Zeichen zu verändern. *)
    VAR C: CHAR;
        I: INTEGER;
  BEGIN
    IF N=1 THEN WRITE(S,' ')
    ELSE
      BEGIN
        ANORDNUNG (S,N-1);
        FOR I:= 1 TO N-1 DO
          BEGIN
            C:= S[I]; S[I]:= S[N]; S[N]:= C;
            ANORDNUNG(S,N-1);
            C:= S[I]; S[I]:= S[N]; S[N]:= C;
          END
      END
  END; (* ANORDNUNG *)
 
BEGIN
  READLN(A);
  ANORDNUNG (A, LENGTH(A)); 
  WRITELN
END.

Bild 50: Permutationen

Zugegebenermaßen erfordern rekursive Algorithmen eine gewisse geistige Leistung, da es bei ihnen im allgemeinen nicht mehr möglich ist, Anweisung für Anweisung den Programmablauf zu verfolgen. Vielmehr muß man eine rekursive Prozedur folgendermaßen »verstehen«:

  1. Für Strings der Länge 1 arbeitet die Prozedur ANORDNUNG offensichtlich korrekt.
  2. Angenommen, die Prozedur ANORDNUNG arbeitet für Strings der Länge N-1 korrekt, so werden durch die For-Anweisung auch alle Anordnungen der Länge N korrekt erzeugt.

Aus 1 und 2 zusammen folgt, daß für Strings beliebiger Länge alle Anordnungen bestimmt werden.

Natürlich gibt es auch Fälle, in denen eine Rekursion unnötig ist, da ein gleichwertige iterative Lösung existiert. Man könnte z.B. die Funktionen CENTER und FAKULTAET aus Bild 45 auch folgendermaßen rekursiv definieren:

  FUNCTION FAKULTAET(N: INTEGER): REAL;
  (* N ! = 1 * 2 * 3 * ... * N BERECHNEN *)
  BEGIN
    IF N=0 THEN FAKULTAET:= 1.0
    ELSE FAKULTAET:= N * FAKULTAET(N-1);
  END; (* FAKULTAET *)
 
  FUNCTION CENTER (S: STRING; FIELD: INTEGER): STRING;
  (* ZENTRIERE S IN EINEM STRING DER LAENGE FIELD *)
  BEGIN
    IF LENGTH(S)<FIELD-1 THEN
      CENTER:= CENTER(' ' + S + ' ') 
    ELSE 
      IF LENGTH(S)=FIELD-1 THEN
        CENTER:= S + ' '
      ELSE
        CENTER:= S
  END; (* CENTER *)

Bild 51: Unnötige Rekursionen

Jedoch erfordert jeder rekursive Aufruf abgesehen von dem Speicherplatz für die lokalen Variablen auch einen gewissen Verwaltungsaufwand, der bei zusammengesetzten Anweisungen (For-, While- und Repeat-Schleifen) nicht auftritt.

Zum Abschluß der Ausführungen über Rekursion wird der in Kapitel 2.9.1 angekündigte verbesserte Sortieralgorithmus vorgestellt. Die Aufgabe ist wiederum, ein Array A mit N (ganzen) Zahlen aufsteigend zu sortieren.

Die Idee zu einer rekursiven Lösung besteht darin, das Array A in zwei Teile A[1..K] und A[K+1..N] aufzuteilen, so daß jeder Teil für sich, ohne Kenntnis der Zahlen im anderen Teil, sortiert werden kann. Diese Zerlegung und den Index K findet man mit der folgenden Strategie:

  1. Man wählt einen (zufälligen) Wert X aus dem Array. Dies kann zum Beispiel der Wert in der Mitte des Arrays sein.
  2. Anschließend bestimmt man den Index K, so daß die linke Hälfte A[1..K] des Arrays nur Werte kleiner oder gleich X enthält, während die rechte Hälfte A[K+1..N] nur Werte größer oder gleich X enthält.
  3. Sortiert man anschließend beide Teilarrays, die mindestens ein Element weniger als das ursprüngliche Array enthalten, so ist schließlich das gesamte Array A[1..N] sortiert.

Die Tatsache, daß in jedem Schritt die Arraygröße um mindestens ein Element sinkt, ist wichtig, damit die Rekursion auch korrekt terminiert. Betrachten Sie diese Strategie an einem Zahlenbeispiel:

8 5 7 6 3 4 8 4

Da acht Elemente vorliegen, wählt man in Schritt 1 das 4. Element (X=6) im Array. Die Zerlegung in zwei Teile sieht nach dem 2. Schritt wie folgt aus:

4 5 4 3     6      7 8 8

Links und rechts von der Zahl 6 sind (in beliebiger Reihenfolge) die Zahlen kleiner bzw. größer als 6 aufgeführt. Da es vier Zahlen kleiner als 6 gibt, besitzt der Index K den Wert K= 4 + 1 = 5. Im dritten Schritt werden nun die Teilarrays getrennt sortiert

3 4 4 5     6      7 8 8

Damit ist die gesamte Folge sortiert. Für Schritt 2 (die Zerlegung in zwei Teilarrays) muß noch ein effizienter Algorithmus angegeben werden:

2.1 Setze zwei Zeiger I und J auf das erste und letzte Element im Array.

2.2 Lasse I nach rechts wandern, bis es auf ein Element zeigt, das größer als X ist. Lasse J nach links wandern, bis es auf ein Element zeigt, das kleiner als X ist.

2.3 Da A[I] und A[J] jeweils auf der falschen Seite von X stehen, tausche beide Elemente aus.

2.4 Wiederhole diese Schritte, bis sich beide Zeiger I und J treffen.

Der skizzierte Algorithmus wurde von seinem Erfinder C.A.R.Hoare Quicksort getauft und findet sich in Bild 52 als ein vollständiges Pascalprogramm. Um die Korrektheit für alle Belegungen des Arrays A zu sichern, ist die exakte Formulierung der Bedingungen in den Schleifen nötig. Die Diskussion solcher Details und die Berechnung der Rechenzeit finden Sie in [2] (s. Anhang E).

Die Sortierung erfolgt in der rekursiven Prozedur QUICK, deren Parameter L und R den Index des ersten und letzten Elementes im zu sortierenden Array A enthalten. In Bild 52 sind zur Verdeutlichung die Nummern der obigen Schritte als Kommentare angegeben.

PROGRAM SORTIEREN (INPUT,OUTPUT);
  CONST N = 16;
  TYPE  ELEMENT = INTEGER;
  VAR   A: ARRAY [1..N] OF ELEMENT;
 
  PROCEDURE ERZEUGEN;
    VAR I: INTEGER;
  BEGIN
    WRITELN('Geben Sie eine unsortierte Folge');
    WRITELN('von ', N, ' ganzen Zahlen an:');
    FOR I:= 1 TO N DO READ(A[I]);
    READLN;
  END; (* ERZEUGEN *)
 
  PROCEDURE AUSGEBEN;
    VAR I: INTEGER;
  BEGIN
    WRITELN('Die Folge lautet:');
    FOR I:= 1 TO N DO WRITE(A[I]:5);
    WRITELN
  END; (* AUSGEBEN *)
 
  PROCEDURE QUICK (L,R: INTEGER);
  (* Sortiere das Array im Bereich A[L..R] *)
    VAR I, J: INTEGER;
        X, Y: ELEMENT;
  BEGIN
    X:= A[(L+R) DIV 2]; (* 1.   *)
    I:=L; J:=R;
    REPEAT
      WHILE A[I]<X DO I:= I+1;(* 2.1  *)
      WHILE A[J]>X DO J:= J-1:(* 2.2  *)
      IF I<=J THEN
        BEGIN
          Y:= A[I]; A[I]:= A[J]; A[J]:= Y; (* 2.3  *)
          I:= I+1; J:= J-1
        END;
    UNTIL I>J;(* 2.4   *)
    IF I<R THEN QUICK(I,R); (* 3     *)
    IF L<J THEN QUICK(L,J); (* 3     *)
  END; (* QUICK *)
 
BEGIN
  ERZEUGEN; AUSGEBEN;
  QUICK(1, N); (* vom 1. bis zum N. Element *)
  AUSGEBEN
END.

Bild 52: Quicksort

2.11.6 FORWARD-Deklarationen

In sehr speziellen Anwendungen kann eine indirekte Rekursion der Prozeduren A,B C in der folgenden Form notwendig sein:

A ruft B
A ruft C
     C ruft B
B ruft C
In diesem Fall läßt sich keine Reihenfolge der Prozedurdeklarationen finden, in der die Deklaration jeder Prozedur vor ihrer Anwendung steht. Für diese zyklischen Referenzen existiert in Pascal die FORWARD Deklaration von Prozeduren und Funktionen:

PROGRAM FORWARDPROCEDURES (OUTPUT);
 
  PROCEDURE B(I: INTEGER); FORWARD;
 
  PROCEDURE C(X: CHAR);
  BEGIN
    IF X = '+' THEN B(ORD(X)); 
  END; (* C *)
 
  PROCEDURE B;
  BEGIN
    IF CHR(I)<>'+' THEN C(CHR(I));
  END; (* B *)
 
  PROCEDURE A; 
  BEGIN
    B(13); C('+');
  END; (* A *)
 
BEGIN
  A
END.

Bild 53: Beispiel für FORWARD

Indem man nur den Kopf der Prozedur B gefolgt von dem Wortsymbol FORWARD nennt, kann man B in der Prozedur C verwenden, obwohl der eigentliche Anweisungsteil der Prozedur B erst hinter der Prozedur C steht. Der Anweisungsteil jeder FORWARD-deklarierten Prozedur muß nachträglich hinter einem Prozedurkopf ohne Parameterliste aufgeführt werden. Ansonsten meldet der Compiler

119   UNSATISFIED 'FORWARD' DECLARATION IN PROGRAM

Inhaltsverzeichnis