Inhaltsverzeichnis

2.11 Prozeduren

Eine besonders erfolgreiche Strategie beim Programmentwurf besteht darin, das Ausgangsproblem in geeignet gewählte kleinere Teilprobleme zu zerlegen. Ziel dieser Zerlegung ist es, überschaubare Programmteile zu erhalten, die mit dem restlichen Programm über wohldefinierte Schnittstellen kommunizieren, so daß die Korrektheit der Programmteile ohne Kenntnis der Umgebung gesichert werden kann. Als Beispiel ist in Bild 34 die Grobstruktur eines Programmes für ein Brettspiel mit dem Computer angegeben.

Ausgangsstellung aufbauen
REPEAT
  Spielstellung anzeigen
  Eingabe Spielerzug
  Spielstellung anzeigen
  Computerzug berechnen
UNTIL Spielende
Gewinner anzeigen

Bild 34: Teilprobleme eines Brettspiels

Jede der im Klartext angegebenen Teilaufgaben läßt sich logisch von den anderen trennen. Ein Beispiel für eine Schnittstelle zwischen den Teilen ist die Spielstellung: Sie darf nur bei der Eingabe des Spielerzuges und des Computerzuges verändert werden. Die Teilaufgabe Spielstellung anzeigen kann hingegen auf die Spielstellung nur lesend zugreifen.

Pascal unterstüzt diese Modularisierung des Programmes durch das Konzept der Prozeduren und Funktionen. Die Schnittstellen werden durch Parameterlisten und die Sichtbarkeitsregeln für Namen innerhalb des Programmtextes festgelegt.

In Pascal würde man jede der obigen Teilaufgaben durch ein separates Programmstück, eine Prozedur, definieren. Jede Prozedur erhält in der Prozedurdeklaration einen Namen (Bezeichner). Im Anweisungsteil wird durch die Angabe des Namens der Prozedur die Ausführung der genannten Prozedur veranlaßt. Diese Art von Anweisungen nennt man Prozeduraufrufe.

Besonders nützlich sind Prozeduren, die von verschiedenen Stellen aufgerufen werden (z.B. Spielstellung anzeigen in Bild 34). Durch die Tatsache, daß die Prozedur nur einmal deklariert werden muß, spart man nicht nur Schreibarbeit und Speicherplatz, sondern man muß spätere Änderungen nur an einer Stelle durchführen.

Ein konkretes Beispiel soll die einfachste Form einer Prozedur vorstellen: Es ist der größte gemeinsame Teiler (ggT) der ganzen Zahlen A und B zu bestimmen. Diese Berechnung soll den ggT in der Variablen ERG ablegen. Die dazugehörige Prozedur GGT ist in Bild 35 angegeben:

PROCEDURE GGT;
(* Berechne ERG, den ggT der Zahlen A und B *)
BEGIN
  X:= A; Y:= B;
  WHILE X<>Y DO
    IF X>Y THEN X:= X-Y ELSE Y:= Y-X;
  ERG:= X;
END; (* GGT *)

Bild 35: Die Prozedur GGT

Dies ist eine Prozedurdeklaration. Sie besteht aus dem Wortsymbol PROCEDURE und dem Prozedurnamen GGT, die zusammen den Prozedurkopf bilden. Zwischen den Wortsymbolen BEGIN und END steht der Anweisungsteil der Prozedur. Der Aufbau einer Prozedur ähnelt also der Struktur eines vollständigen Programmes.

Die Prozedurdeklarationen eines Programmes stehen am Ende des Vereinbarungsteils (s. auch das allgemeine Programmschema in Bild 9). Dort werden alle Prozeduren, die das Hauptprogramm benutzt, in der Form von Bild 36 aufgeführt. In diesem Programm steht an zwei Stellen der Name GGT. Dort wird also die Prozedur GGT aufgerufen, um den ggT der Werte in den Variablen A und B zu berechnen. Der ggT ist bei der Rückkehr aus der Prozedur in der Variablen ERG gespeichert.

Beim Aufruf einer Prozedur werden alle Befehle im Anweisungsteil der Prozedur ausgeführt. Nach der Ausführung der letzen Anweisung der Prozedur (dort steht das Wortsymbol END) wird die Programm- ausführung mit dem nächsten Befehl im Hauptprogramm fortgesetzt. In den nächsten Abschnitten wird die Prozedur GGT noch mehrmals verändert und verbessert, um die weiteren Möglichkeitn von Pascal vorzustellen, die es erlauben, Prozeduren noch flexibler einzusetzen.

PROGRAM CALLGGT (OUTPUT);
  VAR A, B, ERG, X, Y: INTEGER;

  PROCEDURE GGT;
  BEGIN
    X:= A; Y:= B;
    WHILE X<>Y DO
      IF X>Y THEN X:= X-Y ELSE Y:= Y-X;
    ERG:= X;
  END; (* GGT *)

BEGIN  (* Hauptprogramm *)
  A:= 9; B:= 3; GGT; WRITELN(ERG);
  A:= 8; B:= 3; GGT; WRITELN(ERG);
END.
Bild 36: Prozeduraufrufe

2.11.1 Lokalität von Namen

In Bild 36 wurden die Variablen X und Y nur innerhalb der Prozedur GGT verwendet. Diese Zugehörigkeit kann man in Pascal dadurch unterstreichen, daß die Variablen innerhalb der Prozedur deklariert werden:

PROGRAM LOCALGGT (OUTPUT);
  VAR A, B, ERG: INTEGER;

  PROCEDURE GGT;
    VAR X,Y: INTEGER;
  BEGIN
    X:= A; Y:= B;
    WHILE X<>Y DO
      IF X>Y THEN X:= X-Y ELSE Y:= Y-X;
    ERG:= X
  END; (* GGT *)

BEGIN  (* Hauptprogramm *)
  A:= 9; B:= 3; GGT; WRITELN(ERG);
  A:= 8; B:= 3; GGT; WRITELN(ERG);
  (* X:= 3  ist hier nicht möglich *)
END.

Bild 37: Lokale Variablen

Man bezeichnet X und Y als lokale Variablen der Prozedur GGT, da sie jetzt außerhalb der Prozedur nicht mehr sichtbar sind. D.h. die in Kommentarklammern angegebene Zuweisung X:=3 im Hauptprogramm würde der Compiler mit der Fehlermeldung

104 Identifier not declared

markieren. Durch diese lokalen Deklaration nimmt eine Prozedur die Form eines eigenständigen Programmes an. Tatsächlich sind alle Deklarationen, die im Hauptprogramm erlaubt sind, auch lokal möglich. Wenn Sie wieder einmal die Syntaxdiagramme im Anhang A zu Rate ziehen, werden Sie sehen, daß sich an den Programm- und den Prozedurkopf jeweils ein BLOCK anschließt. Das Syntaxdiagramm BLOCK enthält sowohl den Vereinbarungsteil als auch den Anweisungsteil.

Grundsätzlich versucht man, den Sichtbarkeitsbereich (engl. scope) eines Namens (Konstante, Variable etc.) möglichst klein zu halten. Diese Strategie wird manchmal auch als Geheimnisprinzip bezeichnet: Eine Prozedur verbirgt vor ihrer Umgebung nicht nur die Details ihres Anweisungsteils, sondern auch die lokalen Objekte.

Ein angenehmer Nebeneffekt dieses Prinzips der Lokalität ist eine Speicherplatzersparnis. Bei der Programmausführung wird erst beim Aufruf der Prozedur (GGT) Speicherplatz für die lokalen Objekte (die Variablen X und Y) reserviert. Am Ende der Ausführung der Prozedur wird dieser Speicherplatz wieder freigegeben und steht anderen Prozeduren zur Verfügung. Eine direkte Folge dieser Speicherorganisation ist, daß bei jedem neuen Aufruf einer Prozedur alle lokalen Variablen (wie die Variablen des Hauptpro- grammes beim Programmstart) undefiniert sind.

Wird beim Aufruf einer Prozedur oder am Programmanfang festgestellt, daß nicht genügend Speicherplatz für die lokalen Variablen vorhanden ist, wird das Programm mit einer Fehlermeldung beendet:

STACK OVERFLOW ERROR

Soll eine Variable ihren Wert zwischen zwei Aufrufen beibehalten, so muß man sie außerhalb der Prozedur (global) deklarieren. Deshalb wird die Variable G, die in Bild 38 die Anzahl der Aufrufe der Prozedur GLOBAL zählen soll, im Hauptprogramm deklariert.

PROGRAM GLOBALTEST(OUTPUT);
  VAR G: INTEGER;
 
  PROCEDURE GLOBAL;
  BEGIN
    G:= G+1; WRITELN('AUFRUF NUMMER', G);
  END; (* GLOBAL *)
 
BEGIN
  G:=0; GLOBAL; GLOBAL; GLOBAL
END.

Bild 38: Globale Variable

In Bild 36 waren auch X und Y globale Variablen. Gerade in großen Programmen ist die Verwendung von globalen Variablen eine schwer zu entdeckende Fehlerquelle: Hätte man im Hauptprogramm in Bild 36 die Variablen X und Y benutzt, so würden als Seiteneffekt bei jedem Aufruf von GGT die Werte von X und Y zerstört werden.

Nachdem der Unterschied zwischen globalen und lokalen Objekten einer Prozedur bekannt ist, wird nun die Schachtelung von Prozeduren behandelt. Da alle Arten von Deklarationen in einer Prozedur erlaubt sind, kann eine Prozedur auch weitere Prozedurdeklarationen enthalten. Die Sichtbarkeitsregeln für lokale und globale Variablen gelten dann analog:

PROGRAM SCHACHTELUNG (OUTPUT);
  VAR A: REAL; B: REAL;
 
  PROCEDURE AUSSEN;
    VAR A: INTEGER;
 
    PROCEDURE INNEN;
      VAR I: INTEGER;
    BEGIN
      WRITELN('INNEN')
    END; (* INNEN *)
 
  BEGIN (* AUSSEN *)
    A:= 30; B:= 30;
    WRITELN('AUSSEN: A B =', A, B);
    INNEN; INNEN; INNEN
  END; (* AUSSEN *)
 
BEGIN (* HAUPTPROGRAMM *)
  A:= 1; B:= 1;
  AUSSEN; AUSSEN;
  WRITELN('HAUPTPROGRAMM: A, B=', A,B);
END.

Bild 39: Geschachtelte Prozeduren

Lokale Prozeduren (wie die Prozedur INNEN) verwendet man aus dem gleichen Grund wie lokale Variablen: Die Prozedur INNEN wird nur innerhalb der Prozedur AUSSEN benötigt. Deshalb sollte die Umgebung (in diesem Fall das Hauptprogramm) keinen Zugriff auf die lokale Prozedur besitzen. Konkret gesprochen ist es nicht möglich, die Prozedur INNEN im Anweisungsteil des Hauptprogrammes zu verwenden.

Wie Ihnen sicher aufgefallen ist, werden in Bild 39 außerdem Variablen in verschiedenen Schachtelungsebenen deklariert, um die folgenden Regeln über die Sichtbarkeit von Namen in Pascal zu illustrieren:

  1. Ein Name ist in dem Block P, in dem er deklariert wurde, sichtbar. Außerdem ist er in jedem Block sichtbar, der von P eingeschlossen wird, solange nicht Regel 2 gilt.
  2. Eine Deklaration eines Namens X in einem Block macht alle Deklarationen des Namens X in äußeren Blöcken unsichtbar.
  3. Die Standardnamen (wie z.B. READ, WRITE, SIN, INPUT, INTEGER) sind in einem imaginären Block deklariert, der das gesamte Programm umschließt.

Durch die Regel 1 ist die Variable B im Block SCHACHTELUNG, in der Prozedur AUSSEN und auch in der Prozedur INNEN sichtbar. Nach Regel 2 überdeckt die lokale Deklaration von A (INTEGER) die globale Variable A (REAL). In AUSSEN und INNEN ist also nur A (INTEGER) sichtbar. Aufgrund von Regel 3 sind z.B. die Standardbezeichner TRUE und FALSE im gesamten Programm sichtbar.

In der Prozedur AUSSEN finden sich folgende Zuweisungen:

A:= 30; B:= 30

Gemäß der obigen Sichtbarkeitsregeln wird damit der lokalen Variablen A und der globalen Variablen B der Wert 30 zugewiesen. Damit bleibt insbesondere der Wert der Variablen A (REAL) im Hauptprogramm unverändert.

Sollten Ihnen die Regeln (1) bis (3) etwas kompliziert erscheinen, so sollten Sie sich für den Augenblick nur merken, daß man in einer Prozedur Namen völlig unabhängig vom übrigen Programm wählen kann.

2.11.2 Parameter für Prozeduren

Die Version der Prozedur GGT mit lokalen Variablen (Bild 37) ist immer noch nicht optimal in Pascal formuliert. Dort werden die Eingabewerte A und B sowie das Ergebnis ERG über globale Variablen übergeben. Durch die Benutzung von Parametern wird die Prozedur universeller und übersichtlicher:

PROGRAM PARAMETER (OUTPUT);
  VAR V: INTEGER;

  PROCEDURE GGT(A,B: INTEGER; VAR ERG: INTEGER);
  BEGIN
    WHILE A<>B DO
      IF A>B THEN A:= A-B ELSE B:= B-A;
    ERG:=A;
  END; (* GGT *)

BEGIN (* HAUPTPROGRAMM *)
  GGT(9, 3, V);
  WRITELN(V);
  GGT(17+4, 3, V);
  WRITELN(V)
END.

Bild 40: GGT mit Parametern

Der Prozedurkopf von GGT wird um eine Parameterliste erweitert. Dort werden die Übergabeparameter mit Namen und Typ angegeben. Die formalen Parameter A, B und ERG werden dadurch wie lokale Variablen der Prozedur GGT deklariert.

Beim Aufruf der Prozedur GGT müssen diesen formalen Parametern aktuelle Parameter übergeben werden. Dies geschieht in einer Parameterliste in Klammern hinter dem Prozeduraufruf. Beim Aufruf müssen Anzahl und Typ der aktuellen und formalen Parameter übereinstimmen. Es gibt zwei verschiedene Typen von Parametern, die beide in Bild 40 verwendet wurden.

Im Beispiel sind A und B Wertparameter. Sie verhalten sich innerhalb der Prozedur wie normale lokale Variablen. Jedoch werden sie zusätzlich beim Aufruf der Prozedur durch Ausdrücke (17+4 oder 3) als aktuelle Parameter initialisiert. Die Prozedur kann die Variablen A und B beliebig benutzen und ihnen auch Werte zuweisen, ohne daß in der aufrufenden Umgebung irgendeine Änderung bewirkt würde.

PROGRAM VALUEPARAMETER (OUTPUT);
  VAR AKTUELLERPARAMETER : INTEGER;
 
  PROCEDURE P (FORMALERPARAMETER: INTEGER);
  BEGIN
    WRITELN(FORMALERPARAMETER);
    FORMALERPARAMETER:= 0;
    WRITELN(FORMALERPARAMTER);
  END; (* P *)
 
BEGIN
  AKTUELLERPARAMETER:= 100;
  WRITELN(AKTUELLERPARAMETER);
  P (AKTUELLERPARAMETER);
  WRITELN(AKTUELLERPARAMETER);
END.

Bild 41: Wertparameter

Durch die Entkopplung von aktuellem und formalem Parameter konnten im Beispiel (Bild 40) die Hilfsvariablen X und Y eingespart werden. Wertparameter sind die vorherrschende Parameterart, da beliebige Ausdrücke als aktuelle Parameter auftreten können:

PROGRAM PARAMETERDEMO (OUTPUT);
  VAR I: INTEGER;
 
  PROCEDURE KASTEN (LAENGE, BREITE: INTEGER; ZEICHEN: CHAR);
  (* ZEICHNE EINEN KASTEN DIESER LAENGE UND BREITE, DER *)
  (* AUS DEM ZEICHEN GEBILDET WIRD.                     *)
    VAR I,J: INTEGER;
  BEGIN
    FOR I:= 1 TO BREITE DO
      BEGIN
        FOR J:= 1 TO LAENGE DO WRITE(ZEICHEN);
        WRITELN
      END
  END; (* KASTEN *)
 
BEGIN
  (* ZEICHNE EIN PAAR KÄSTEN: *)
  FOR I:= 1 TO 10 DO
    KASTEN (2*I,                  (* LAENGE  *)
            I+2,                  (* BREITE  *)
            CHR(I+ ORD('A')));    (* ZEICHEN *)
END.

Bild 42: Parameter verschiedener Typen

Wertparameter können keine Ergebnisse aus der Prozedur an die aufrufende Umgebung zurückliefern. In solchen Fällen benutzt man Variablenparameter. Hier ist der aktuelle Parameter immer eine Variable vom Typ des formalen Parameters. Variablenparameter werden bei der Deklaration in der Parameterliste von Wertparametern durch Voranstellen des Wortsymbols VAR unterschieden.

Die Prozedur GGT liefert das Ergebnis (den ggT) über den Varia- blenparameter ERG zurück. Deshalb muß beim Aufruf der dritte aktuelle Parameter immer eine Variable vom Typ INTEGER sein. Im Beispiel ist dies die Variable V.

Innerhalb der Prozedur ändert jede Zuweisung an einen formalen Variablenparameter unmittelbar den Wert des zugehörigen Parameters. Somit wird durch die Zuweisung ERG:=A (in Bild 40) der Variablen V der Wert A zugewiesen. Sie sollten sich den Unter- schied nochmals dadurch verdeutlichen, daß Sie in Bild 41 den Prozedurkopf um das Wortsymbol VAR erweitern und das Programm compilieren und ausführen:

PROCEDURE P (VAR FORMALERPARAMETER: INTEGER);

Der Aufruf mit Wertparametern wird auch als call by value bezeichnet. Den Aufruf mit Variablenparametern bezeichnet man dann als call by reference. Dies deutet bereits auf die Realisierung der Parameter auf dem Computer hin: Bei Wertparametern wird der Wert des Ausdruckes in den lokalen Speicherbereich der Prozedur kopiert (wie bei einer Zuweisung). Bei Variablenparametern wird hingegen nur ein Verweis (in Form einer Adresse) auf eine globale Variable übergeben.

Viele weitere Beispiele werden Sie in den nächsten Abschnitten finden. Am Ende dieses Abschnittes soll noch betont werden, daß alle Typen (auch zusammengesetzte) als Parameter auftreten können. Im Bild 43 werden zwei Arrays A und B mit je 5 Elementen elementweise addiert und das Ergebnis (5 Werte) in einem Array C gespeichert. Solche Arrays werden in der Mathematik als Vektoren bezeichnet, so daß die Summe von A und B eine Vektorsumme ist.

PROGRAM VEKTORSUMME (INPUT,OUTPUT);
  CONST N=5;
  TYPE VEKTOR = ARRAY [1..N] OF REAL;
  VAR X,Y,Z: VEKTOR;
      M    : ARRAY [1..N] OF VEKTOR; (* M ist eine Matrix! *)
 
  PROCEDURE HOLE(VAR A: VEKTOR);
  (* Vektor A einlesen *)
    VAR I: INTEGER;
  BEGIN
    FOR I:= 1 TO N DO READ (A[I]);
    READLN
  END; (* HOLE *)
 
  PROCEDURE DRUCKE(A: VEKTOR);
  (* A ausgeben *)
    VAR I: INTEGER;
  BEGIN
    FOR I:= 1 TO N DO WRITE (A[I],' ');
    WRITELN
  END; (* DRUCKE *)
 
  PROCEDURE ADD(A,B: VEKTOR; VAR C: VEKTOR);
  (* C:= A + B komponentenweise *)
    VAR I: INTEGER;
  BEGIN
    FOR I:= 1 TO N DO
      C[I]:= A[I] + B[I]
  END; (* ADD *)
 
BEGIN
  HOLE(X); HOLE(Y);
  ADD(X,Y,Z);
  DRUCKE(X); DRUCKE(Y);
  WRITELN('Summe:');
  DRUCKE(Z);
 
  M[1]:= X; M[2]:= Z;
  ADD(M[1], M[2], M[3]);
  WRITELN('M[3] =');
  DRUCKE(M[3])
END.

Bild 43: Vektorsumme

Hier werden also Vektoren von fünf Zahlen als Parameter übergeben. Aktuelle und formale Parameter müssen natürlich auch im Falle zusammengesetzter Variablen übereinstimmen. Dabei ist es wichtig, daß Sie einen Typ-Namen im Prozedurkopf angeben. Verboten ist also die Parameterliste

PROCEDURE (A,B: ARRAY[1..N] OF REAL; VAR C: VEKTOR);

Inhaltsverzeichnis