Home » Object Pascal » Prozeduren und Funktionen

Prozeduren und Funktionen

Was sind Prozeduren und Funktionen?

Prozeduren und Funktionen, auch „Unterprogramme“ oder Routinen genannt, haben die Aufgabe, öfter wiederkehrenden Programmcode sozusagen als Baustein zusammenzufassen. Dieser Baustein erhält einen eindeutigen Namen, über den er ausgeführt werden kann.

Aufbau einer Prozedur

Jede Prozedur besteht aus dem Schlüsselwort procedure, gefolgt von einem gültigen Namen und evtl. einer Parameterliste in runden Klammern. Sind keine Parameter vorhanden, können die Klammern sowohl bei der Deklaration als auch beim Aufruf weggelassen werden. Diesen Teil nennt man Kopf der Prozedur. Es folgen Variablen- und Konstantendeklarationen und anschließend zwischen begin und end die Anweisungen, die die Prozedur durchführen soll:

 procedure ();
 
 begin
 
 end;

Beispiel: Die folgende Prozedur gibt so viele Töne über den PC-Lautsprecher aus, wie über den Parameter Anzahl angegeben.

 procedure Toene(Anzahl: integer);
 var i: integer;
 begin
 for i := 1 to Anzahl do
 beep;
 end;

Der Aufruf für fünf Töne geschieht so:

 Toene(5);

Aufbau einer Funktion

Eine Funktion unterscheidet sich nur geringfügig von einer Prozedur. Sie besitzt einen Rückgabewert und wird mit dem Schlüsselwort function deklariert anstelle von procedure.

 function (): ;
 
 begin
 
 end;

Beispiel: Eine Funktion, die drei Zahlen addiert und das Ergebnis zurückliefert.

 function SummeAusDrei(zahl1, zahl2, zahl3: integer): integer;
 begin
 result := zahl1 + zahl2 + zahl3;
 end;

Bei result handelt es sich um eine vordefinierte Variable, der der Rückgabewert zugewiesen wird, den die Funktion haben soll. Alternativ kann dafür auch der Funktionsname verwendet werden. Mit result kann innerhalb der Funktion jedoch gerechnet werden, während der Funktionsname nur auf der linken Seite einer Zuweisung stehen darf, da ansonsten von einem rekursiven Aufruf der Funktion (Funktion ruft sich selbst auf) ausgegangen wird.
Es ist möglich, result oder dem Funktionsnamen öfters einen Wert zuzuweisen. Letztlich bildet der Wert den Rückgabewert der Funktion, der der Variablen result oder dem Funktionsnamen als letztes zugewiesen wurde.

Ab Delphi 2009

Im Gegensatz zu return in andern Sprachen bricht result den Ablauf in einer Routine nicht an dieser Stelle ab. Allerdings ist es seit Delphi 2009 möglich, den Befehl exit, der normalerweise nur die Ausführung einer Routine abbricht, mit einem Rückgabewert zu versehen:

function MyFunction(x: Integer): Integer;
begin
  if (x < 0) then Exit(0);
  ...
end;

In diesem Beispiel gibt die Funktion den Wert 0 zurück, wenn der Aufrufparameter x kleiner als 0 ist. Der Code, der anstelle der drei Punkte folgt, wird dann nicht mehr ausgeführt. Er wird nur durchlaufen, wenn x größer oder gleich 0 ist.
Der Rückgabewert kann dann an der Aufrufstelle ausgewertet werden (ergebnis sei eine Integer-Variable):

 ergebnis := SummeAusDrei(3,5,9);

forward- und interface-Deklarationen

Prozeduren und Funktionen können nur aufgerufen werden, wenn ihr Name im Code bekannt ist. Und dieser Gültigkeitsbereich beginnt erst an der Stelle der Definition. Soll eine Routine bereits vorher bekannt sein, wird eine forward-Deklaration eingesetzt.
Dabei wird lediglich der Kopf einer Routine, versehen mit der Direktive forward, an eine frühere Stelle im Code gesetzt. Für die Funktion SummeAusDrei sähe das so aus:

function SummeAusDrei(zahl1, zahl2, zahl3: integer): integer; forward;

Die eigentliche Funktion, wie sie im letzten Abschnitt dargestellt ist, muss dann später im Code folgen.
Soll eine Prozedur oder Funktion auch aus einer anderen Unit aufrufbar sein, muss ihr Kopf im Interface-Teil der Unit stehen. Die Definition folgt im Implementation-Teil. Das Verhalten entspricht einer forward-Deklaration, die Direktive forward darf hierbei aber nicht verwendet werden.
Folgendes Beispiel zeigt eine interface-Deklaration, Implementierung sowie Aufruf einer Funktion:

unit Unit1;

interface

uses
 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
 TForm1 = class(TForm)
 private
 { Private-Deklarationen }
 public
 { Public-Deklarationen }
 end;
 function SummeAusDrei(zahl1, zahl2, zahl3: integer): integer;
 //Deklaration nur mit dem Kopf der Funktion

var
 Form1: TForm1;

implementation

{$R *.DFM}

function SummeAusDrei(zahl1, zahl2, zahl3: integer): integer;
begin
  result := zahl1 + zahl2 + zahl3;
end;

procedure TForm1.Button1Click(sender: TObject);
var ergebnis: integer;
begin
  ergebnis := SummeAusDrei(3, 5, 9);  //Aufruf der Funktion
end;

Zu beachten ist: Das Gerüst der zweiten "Prozedur" (TForm1.Button1Click usw.) haben wir nicht selbst erstellt. Hierbei handelt es sich um eine Ereignisbehandlungsmethode für das Ereignis OnClick eines Buttons. Dazu mehr in unseren Einsteiger-Tutorials. Von uns stammt nur der Funktionsaufruf darin.

Parameter

In den meisten Fällen wird eine Routine zwar öfters gebraucht, allerdings - z. B. bei Berechnungen - nicht immer mit den gleichen Werten. Deshalb gibt es, wie oben bereits gesehen, die Möglichkeit, Routinen Werte beim Aufruf zu übergeben.
Beim Aufruf einer Prozedur/Funktion mit Parametern muss beachtet werden, dass Anzahl und Typ der Werte übereinstimmen.
Anhand der Reihenfolge der Werte steht in obigem Beispiel fest, dass die Variable zahl1 den Wert 3, zahl2 den Wert 5 und zahl3 den Wert 9 erhält. Diese Variablen werden nicht wie üblich über var deklariert. Ihre Deklaration erfolgt durch die Nennung im Funktionskopf. Außerdem gelten sie nur innerhalb der Funktion. Von außerhalb (z. B. nach Beendigung der Funktion) kann nicht mehr auf sie zugegriffen werden.
Um das Ganze etwas komplizierter zu machen, gibt es verschiedene Arten von Parametern, die durch var, const oder out gekennzeichnet werden.

Wert- und Variablenparameter

In obigen Beispielen wird immer eine Kopie eines Wertes an die Prozedur/Funktion übergeben. Wenn dieser Wert also innerhalb der Prozedur/Funktion verändert wird, ändert sich nicht die Variable, die beim Aufruf verwendet wurde:

 procedure machwas(zahl: integer);
 begin
   zahl := zahl+5;
 end;

 procedure aufruf;
 var einezahl: integer;
 begin
   einezahl := 5;
   machwas(einezahl);
 end;

Im Beispiel ruft also die Prozedur aufruf die Prozedur machwas mit dem Wert der Variable einezahl auf. In machwas wird dieser Wert über zahl angesprochen. Und obwohl zahl nun verändert wird, ändert sich der Wert in einezahl nicht. Er ist am Ende immer noch 5. Man spricht von einem Wertparameter, es wird nur der Inhalt der Variablen übergeben.
Im Fall des Variablenparameters wird das "Original" übergeben. Ein solcher Parameter wird mit dem Schlüsselwort var gekennzeichnet.

 procedure machwas(var zahl: integer);
 begin
   zahl := zahl+5;
   ...
 end;

 procedure aufruf;
 var einezahl: integer;
 begin
   einezahl := 5;
   machwas(einezahl);
 end;

Hier wird keine Kopie des Variableninhalts übergeben, sondern eine Referenz (also die Speicheradresse) der Variablen einezahl. Wird der Wert in machwas nun um 5 erhöht, geschieht dies auch mit der Variablen einezahl, weil es sich um dieselbe Variable im Speicher handelt. Sie wird nur von den beiden Prozeduren mit anderen Namen angesprochen. Im Gegensatz zum Fall der Wertparameter braucht ein Referenzparemeter in einer Prozedur also keinen zusätzlichen Speicherplatz, da die "originale Variable" weiterverwendet wird.
Über den Umweg des var-Parameters kann man sogar Prozeduren dazu bewegen, Werte zurückzugeben:

 procedure machwas2(var wert1, wert2: integer);
 begin
   wert1 := 2;
   wert2 := 3;
 end;

 procedure aufrufen;
 var zahl1, zahl2: integer;
 begin
   machwas2(zahl1, zahl2);
 end;

Dass die Variablen zahl1 und zahl2 vor der Übergabe an machwas2 nicht initialisiert wurden, macht nichts, da sie dort sowieso nicht ausgelesen werden. In machwas2 werden wert1 und wert2 Werte zugewiesen - und da es sich dabei um Referenzparameter handelt, automatisch auch zahl1 und zahl2. Wenn machwas2 abgearbeitet wurde, enthält zahl1 also den Wert 2 und zahl2 den Wert 3.

Konstantenparameter

Wird ein übergebener Wert in der Funktion/Prozedur nicht verändert und auch nicht als var-Parameter zum Aufruf einer weiteren Routine verwendet, kann man ihn als Konstantenparameter (const) deklarieren:

 procedure machwas(const zahl: integer);

Das ermöglicht dem Compiler eine bessere Optimierung, außerdem wird nun nicht mehr zugelassen, dass der Wert innerhalb der Prozedur verändert wird.

Ausgabeparameter

Ausgabeparameter werden mit dem Schlüsselwort out deklariert. Wie der Name bereits sagt, können solche Parameter nur zur Zuweisung eines Ausgabewerts verwendet werden. Eine Übergabe von Werten an eine Routine ist damit nicht möglich. Ansonsten entspricht der Ausgabeparameter einem Variablenparameter.

Array-Parameter

Es ist natürlich auch möglich, Arrays als Parameter einer Routine zu verwenden. Jedoch nicht auf die Art, die man intuitiv wählen würde:

 procedure MachWasAnderes(feld: array [1..20] of integer); //falsch!

Stattdessen muss zunächst ein eigener Typ definiert werden. Dieser kann dann in Prozedur- oder Funktionsköpfen verwendet werden:

 type TMeinFeld = array [1..20] of integer;
 procedure MachWasAnderes(feld: TMeinFeld);

Default-Parameter

In Delphi können Parameter optional gemacht werden, so dass beim Aufruf auf sie verzichtet werden kann. Dafür müssen zwei Bedingungen erfüllt sein:

  • Der optionale Paramter benötigt einen Default-Wert, der dann verwendet wird, wenn der Parameter beim Aufruf nicht angegeben wird.
  • Die optionalen Parameter müssen am Ende der Parameterliste stehen. Das Weglassen beim Aufruf ist nur von hinten her möglich.

Beispiel:

function MyTest(a: Integer; b: Integer = 42);

In diesem Beispiel muss der zweite Parameter (b) nicht übergeben werden. Dann wird der Wert 42 verwendet. Es sind also folgende Aufrufe möglich:

MyTest(100); // a = 100, b = 42
MyTest(100, 100); // a = 100, b = 100

Prozeduren und Funktionen überladen

In Delphi ist es möglich, im selben Gültigkeitsbereich mehrere Routinen mit identischem Namen zu deklarieren. Dieses Verfahren wird "Überladen" genannt.
Überladene Routinen müssen mit der Direktiven overload deklariert werden und unterschiedliche Parameterlisten haben.

 function Divide(x, y: real): real; overload;
 begin
   result := x / y;
 end;

 function Divide(x, y: integer): integer; overload;
 begin
   result := x div y;
 end;

Diese Deklarationen definieren zwei Funktionen namens Divide, die Parameter unterschiedlicher Typen entgegennehmen. Wenn Divide aufgerufen wird, ermittelt der Compiler die zu verwendende Funktion durch Prüfung des übergebenen Parametertyps.
Divide(6.0, 3.0) ruft beispielsweise die erste Divide-Funktion auf, da es sich bei den Argumenten um reelle Zahlen handelt, auch wenn der Nachkommateil Null ist.
Überladene Methoden müssen sich deshalb entweder in der Anzahl der Parameter oder in den Typen dieser Parameter signifikant unterscheiden.

Prozeduren und Funktionen abbrechen

Nach dem Ausführen einer Prozedur bzw. Funktion wird die Programmausführung an der aufrufenden Stelle fortgesetzt. Wenn man dort aber weitermachen will, bevor die Prozedur/Funktion vollständig ausgeführt wurde, kann man exit verwenden. exit bricht eine Prozedur/Funktion ab und setzt das Programm an der aufrufenden Stelle fort. Bei Funktionen ist darauf zu achten, dass bereits ein Rückgabewert definiert wurde.

Ab Delphi 2005

inline-Funktionen und -Prozeduren

Zur Verbesserung des Laufzeitverhaltens bietet Delphi die Direktive inline. Wenn eine Funktion/Prozedur bestimmten Kriterien entspricht, fügt der Compiler sie automatisch an der Aufrufstelle ein, anstatt einen Aufruf zu generieren. Die Kriterien sind in der Delphi-Hilfe zu finden.
Die inline-Direktive wird in den Routinenkopf geschrieben:

function MyInlineFunction(parameter: Integer): Integer; inline;
begin
  ...
end;