Pascal-Tutorial (Vorwort, Installation, Kapitel 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)

Prozeduren und Funktionen

In diesem Kapitel:

8.1. Prozeduren

Immer wieder kommt es, vor, dass Code mehrmals benötigt wird (Stichwort Wiederverwendbarkeit). Nicht nur um Tipparbeit zu sparen, kann man diesen in einem Unterprogramm zusammenfassen. Je nach Verwendungszweck unterscheidet man in Pascal zwischen Prozeduren und Funktionen. Die Prozedur/Funktion kann dann bei Bedarf aufgerufen werden.

Für das Endergebnis (aus Anwendersicht!) ist es gleichgültig, ob Programmierzeilen in einem Unterprogramm untergebracht werden, dieses dann aufgerufen wird, oder dieselben Zeilen "normal" im Quellcode stehen. Den großen Unterschied macht es aber für Sie als Programmierer. Unterprogramme (Prozeduren/Funktionen) helfen enorm, den Quellcode zu strukturieren, die Übersicht zu bewahren, und erhöhen - wie erwähnt - die Wiederverwendbarkeit Ihres Codes. Ein Unterprogramm erledigt üblicherweise eine klar abgegrenzte Teilaufgabe. Nehmen Sie WriteLn als Beispiel her. Wie die Ausgabe am Bildschirm - technisch im Hintergrund - funktioniert, hat uns nicht gekümmert. Wird es auch in Zukunft nicht. Das hat irgendjemand einmal definiert. Wir greifen dabei auf bestehendes Stück Code zurück und verwenden es immer wieder. Der Zweck und die Aufgabe sind auch klar abgegrenzt (Text ausgeben mit Zeilenumbruch am Ende).

Wie erwähnt, unterscheidet Pascal (!) zwischen Prozeduren und Funktionen. Diese Unterscheidung ist eine sprachspezifische, gilt nur für Pascal. In C spricht man z.B. immer von Funktionen. Eine Funktion unterscheidet sich von einer Prozedur dadurch, dass sie immer einen Rückgabewert liefert. Eine Prozedur hingegen nie. Ein typisches Anwendungsbeispiel für eine Funktion wäre eine mathematische Berechnung. Die Daten, die zur Berechnung nötig sind (quasi der Input; erinnern Sie sich an das EVA-Prinzip: Eingabe - Verarbeitung - Ausgabe), übergeben Sie an die Funktion als Parameter. Das waren in den bisherigen Beispielen die Werte, die zwischen den Klammern () standen, z.B. der Ausgabetext für WriteLn.

Mit den gegebenen Parametern führt die - hier - mathematische Funktion dann die Berechnung durch, und liefert das Ergebnis als Rückgabewert zurück. Ohne es zu wissen, haben Sie bereits unzählige Male Prozeduren und Funktionen verwendet. WriteLn/Write oder ClrScr waren Prozeduren. Funktionen kennen Sie auch bereits einige: ReadKey, Ord, usw.

Genug Einleitung. Ich beginne mit Prozeduren. Funktionen folgen unten in Abschnitt 8.2. Eine Prozedur wird wie folgt definiert:

PROCEDURE Bezeichner (Parameter);
BEGIN
  Anweisung 1;
  Anweisung 2;
  Anweisung 3;
  ...
END;

Die Definition der Prozedur muss oberhalb des Anweisungsblocks stehen, aus dem sie aufgerufen wird. Das heißt bei einem "normalen" Programm noch vor dem BEGIN. Will man eine Prozedur allerdings überall im Programm definieren können, so muss man diese extra deklarieren. Die Deklaration ist für den Compiler eine Bekanntgabe, dass die Prozedur an anderer Stelle definiert wird. Durch sie erfährt er, dass die Prozedur existiert und sucht deren Definition im ganzen Programm. Ist eine Prozedur/Funktion also deklariert, kann man Sie überall im Programm definieren, wo dies gültig ist. Nicht erlaubt ist die Definition aber innerhalb eines Anweisungsblocks!

Die Deklaration erfolgt dadurch, dass man den Funktions-/Prozedur-Kopf (das ist die Zeile, in der PROCEDURE bzw. FUNCTION steht) im Hauptprogramm noch vor BEGIN schreibt (zusätzlich natürlich):

PROCEDURE Bezeichner (Parameter);

Das nächste Beispiel demonstriert die Verwendung einer Prozedur:

PROGRAM Prozedur;
USES Crt;
 
 (* Prozedur definieren; es gibt keine extra Deklaration in diesem Beispiel *)
PROCEDURE Start;
BEGIN
  ClrScr;
  WriteLn ('Prozedur Start aufgerufen. Bitte Taste druecken.');
  ReadKey;
END;
 
 (* Hier beginnt der Anweisungsblock des Hauptprogramms.
    Beachte: Die Prozedur wurde oberhalb (!) definiert.  *)
BEGIN
  Start;  (* Prozedur aufrufen, Parameter gibt es hier keine *)
END.

Hier wurden lediglich ein paar Anweisungen aus einem - ziemlich sinnlosen - Beispiel in eine Prozedur verfrachtet und diese aufgerufen. Sie können in einer Prozedur oder Funktion auch Variablen deklarieren. Noch vor dem BEGIN in der Prozedur/Funktion können Sie einen VAR- oder CONST-Block einfügen. Auf die Variablen, die Sie in einer Prozedur/Funktion deklarieren, kann von außen (das heißt aus einer anderen Prozedur/Funktion sowie aus dem Hauptanweisungsblock) nicht zugegriffen werden. Der Gültigkeitsbereich von solchen lokalen Variablen ist auf die Prozedur/Funktion beschränkt.

Auf Variablen, die außerhalb (= globale Variablen, d.h. die im gesamten Programm gültig) deklariert wurden, kann aus der Funktion/Prozedur allerdings schon zugegriffen werden. Angenommen, man definiert in einem Unterprogramm eine Variable, die den selben Bezeichner trägt wie eine globale Variable, z.B. i für eine Zählervariable. Greift man nun aus der Funktion/Prozedur auf i zu, wird immer die lokale Variable (die des Unterprogramms) angesprochen!

Sehen Sie sich einmal folgende Anweisung an:

WriteLn ('Text');

Dass WriteLn eine Prozedur ist, habe ich bereits erwähnt. An diese Prozedur wird ein Parameter (der String Text) übergeben. Parameter ermöglichen es, dass Prozeduren/Funktionen individuell funktionieren, also je nach Input einen anderen Output erzeugen. Etwa eine mathematische Funktion, die je nach Parameter zu einem anderen Endergebnis kommt. WriteLn gibt einen Text aus, doch welchen Text WriteLn ausgibt, entscheiden die Parameter (WriteLn ist ein Sonderfall, weil beliebig viele Parameter übergeben werden können).

Die Paramter werden ähnlich angegeben wir bei der Variablendeklaration. Variablen gleichen Typs können durch Beistriche getrennt werden. Variablen unterschiedlichen Typs müssen durch Semikola getrennt werden. Zum Beispiel: (hier wird nur der Funktions-Kopf gezeigt; lassen Sie sich im Moment durch das FUNCTION nicht irritieren)

FUNCTION Addiere (summand1, summand2: Integer): Integer;

Hier wurden die beiden Parameter summand1 und summand2 als Integer-Variablen im Funktionskopf deklariert. Auf diese Variablen kann nur aus der Funktion (gleiches gilt natürlich auch für Prozeduren) heraus zugegriffen werden.

Parameter angeben können wir auf zwei Arten: Entweder erfolgt der Zugriff Call-by-Value oder Call-by-Reference. Sehen Sie sich einmal folgendes Beispiel an:

PROGRAM Call_by_Reference_Beispiel;
USES Crt;
 
VAR i: LongInt;
 
PROCEDURE Inkrementieren (VAR Variable: LongInt);  (* beachten Sie das VAR! *)
BEGIN
  Variable := Variable + 1;
      (* ändert die globale Variable i, die referenziert wird!
         sonst würde diese Zeile auch nicht viel Sinn machen,
         da der geaenderte Wert nirgendwo verwendet wird *)
END;
 
BEGIN
  i := 5; (* initialisieren *)
  ClrScr;
 
  Inkrementieren (i);   (* i an Inkrementieren uebergeben *)
 
  WriteLn ('Neuer Wert von i: ',i);    (* zeigt den geaenderten Wert an! *)
  ReadKey;
END.

Die hier definierte Prozedur macht das Gleiche wie Inc (Abkürzung für Increment). Inc erhöht den Wert einer ganzzahligen Variable um 1, z.B.:

Inc (i);

Lassen Sie testweise im oberen Beispiel das VAR im Kopf der Prozedur Inkrementieren weg. Was passiert? Am Ende wird dann immer noch 5 ausgegeben. In diesem Fall, den Normalfall eigentlich, werden die Werte der Variablen, die Sie an das Unterprogramm übergeben, kopiert. Die Prozedur/Funktion enthält also Kopien der Daten, die Sie übergeben. Das bezeichnet man als Call-by-Value.

Wird mit Call-by-Value übergeben, wird der (lokale) Wert von Variable geändert, um 1 erhöht und dann wieder aus dem Speicher gelöscht (weil er nicht mehr benötigt wird und die Prozedur beendet wird). Im Beispiel steht vor Variable aber zusätzlich ein VAR, wodurch auf diese Variable mittels Call-by-Reference zugegriffen wird. Hier wird auf die Variable, die als Parameter übergeben wurde, direkt zugegriffen!

Im Beispiel wird an die Prozedur Inkrementieren die Variable i übergeben. Auf i wird direkt zugegriffen. Die Prozedur erhöht somit den Wert in i. Weiters wichtig zu wissen ist, dass bei Call-by-Reference IMMER eine Variable als Parameter übergeben werden muss. Folgendes wäre somit auf das obere Beispiel bezogen natürlich ungültig:

Inkrementieren (5);    (* geht nicht! *)

8.2. Funktionen

Kommen wir nun zu den Funktionen. Deren Definition sieht ähnlich der der Prozeduren aus. Einige Veränderungen sind jedoch auch auf den ersten Blick erkennbar:

FUNCTION Bezeichner (Parameter) : Typ
BEGIN
  Anweisung 1;
  Anweisung 2;
  Anweisung 3;
  ...
  Bezeichner := Wert;
END;

Eine Funktion muss immer von einem bestimmten Typ sein. Hiermit legt man den Typ des Rückgabewertes fest (Rückgabetyp). Jede Funktion muss einen solchen Wert zurückliefern. Dem Funktionskopf folgt ein Doppelpunkt : und der Datentyp des Rückgabewertes. Der zurückgelieferte Wert kann dann anschließend weiterverarbeitet werden, z.B. in einer Variable gespeichert oder an eine andere Prozedur/Funktion übergeben werden. Oder auch einfach verworfen werden, wie wir es bei ReadKey gemacht haben. Will man hingegen den Tastendruck, den ReadKey eingelesen hat, weiterverarbeiten, könnte man das wie folgt tun:

Variable := ReadKey;

Es folgt ein kurzes Beispiel, das die Verwendung von Funktionen demonstriert:

PROGRAM Function_Beispiel;
USES Crt;
 
VAR summand: ARRAY [1..2] OF Integer;
 
FUNCTION Addiere (summand1, summand2: Integer): Integer;
BEGIN
  Addiere := summand1 + summand2;   (* Summe berechnen und gleich zurueckliefern *)
END;
 
BEGIN
  ClrScr;
  Write ('1. Summand: ');
  ReadLn (summand[1]);
  Write ('2. Summand: ');
  ReadLn (summand[2]);
 
  WriteLn ('Endergebnis: ', addiere(summand[1], summand[2]));
 
  ReadKey;
END.

Das Beispiel zeigt zugleich die Verwendung von Arrays (zwei normale Variablen täten's auch). Bei der Ausgabe des Endergebnisses wird die Funktion Addiere aufgerufen und an sie die beiden Parameter mit den zwei Summanden übergeben. Erster Summand ist das 1. Element von summand, zweiter Summand das 2. Element. Addiere liefert die Summe als Rückgabewert zurück. Dieser Wert wird an WriteLn als zweiter Parameter (erster Parameter ist der String Endergebnis: ) übergeben.

Die Funktion Addiere ist sehr kurz gehalten und besteht im Anweisungsblock aus einer einzigen Zeile. Den Rückgabewert liefern Sie zurück, in dem Sie dem "Namen" der Funktion einen Wert (hier: das Ergebnis aus summand1 + summand2) zuweisen.

Vorheriges Kapitel Nächstes Kapitel