Prozeduren und Funktionen (Object Pascal)

Aus Appmethod Topics
Wechseln zu: Navigation, Suche

Nach oben zu Prozeduren und Funktionen - Index


Dieses Thema enthält Informationen zu folgenden Bereichen:

  • Deklarieren von Prozeduren und Funktionen
  • Aufrufkonventionen
  • forward- und interface-Deklarationen
  • Deklaration externer Routinen
  • Überladen von Prozeduren und Funktionen
  • Lokale Deklarationen und verschachtelte Routinen

Allgemeines zu Prozeduren und Funktionen

Prozeduren und Funktionen, die zusammenfassend auch als Routinen bezeichnet werden, sind abgeschlossene Anweisungsblöcke, die von unterschiedlichen Positionen in einem Programm aufgerufen werden können. Eine Funktion ist eine Routine, die nach Ausführung einen Wert zurückgibt. Eine Prozedur ist eine Routine, die keinen Wert zurückgibt.

Funktionsaufrufe können auch in Zuweisungen und Operationen eingesetzt werden, da sie einen Wert zurückgeben. Zum Beispiel:

 I := SomeFunction(X);

Diese Anweisung ruft die Funktion SomeFunction auf und weist das Ergebnis der Variable I zu. Funktionsaufrufe dürfen nicht auf der linken Seite einer Zuweisungsanweisung stehen.

Prozeduraufrufe können als eigenständige Anweisungen eingesetzt werden. Bei Funktionsaufrufen ist dies ebenfalls möglich, wenn die erweiterte Syntax ({$X+}) aktiviert ist. Zum Beispiel:

 DoSomething;

Diese Anweisung ruft die Routine DoSomething auf. Ist DoSomething eine Funktion, wird der Rückgabewert verworfen.

Prozeduren und Funktionen können auch rekursiv aufgerufen werden.

Deklarieren von Prozeduren und Funktionen

Beim Deklarieren einer Prozedur oder Funktion geben Sie den Namen, die Anzahl und den Typ der Parameter sowie bei Funktionen den Typ des Rückgabewertes an. Dieser Teil der Deklaration wird auch Prototyp, Kopf oder Header genannt. Anschließend schreiben Sie den Code, der beim Aufrufen der Prozedur oder Funktion ausgeführt werden soll. Dieser Teil wird auch als Rumpf oder Block der Routine bezeichnet.

Prozedurdeklarationen

Eine Prozedurdeklaration hat folgende Form:

 procedure procedureName(parameterList); directives;
    localDeclarations;
  begin
    statements
  end;

procedureName ist ein beliebiger gültiger Bezeichner, statements eine Folge von Anweisungen, die beim Aufruf der Prozedur ausgeführt werden. (parameterList), directives und localDeclarations sind optional.

Beispiel für eine Prozedurdeklaration:

 procedure NumString(N: Integer; var S: string);
  var
    V: Integer;
  begin
    V := Abs(N);
    S := '';
    repeat
      S := Chr(V mod 10 + Ord('0')) + S;
      V := V div 10;
    until V = 0;
    if N < 0 then S := '-' + S;
  end;

Nach dieser Deklaration können Sie die Prozedur NumString folgendermaßen aufrufen:

 NumString(17, MyString);

Dieser Prozeduraufruf weist der Variable MyString (es muss sich um eine Variable mit dem Typ String handeln) den Wert 17 zu.

Im Anweisungsblock einer Prozedur können Sie Variablen und Bezeichner verwenden, die im Abschnitt localDeclarations der Prozedur deklariert wurden. Sie können außerdem die Parameternamen aus der Parameterliste (N und S in diesem Beispiel) verwenden. Die Parameterliste definiert eine Gruppe lokaler Variablen. Aus diesem Grund dürfen im Abschnitt localDeclarations keine gleichlautenden Parameternamen auftauchen. Natürlich können Sie auch alle Bezeichner verwenden, in deren Gültigkeitsbereich die Prozedur deklariert wurde.

Funktionsdeklarationen

Eine Funktionsdeklaration entspricht einer Prozedurdeklaration, definiert aber zusätzlich einen Rückgabetyp und einen Rückgabewert. Funktionsdeklarationen haben folgende Form:

 function functionName(parameterList): returnType; directives;
    localDeclarations;
  begin
    statements
  end;

functionName ist ein beliebiger gültiger Bezeichner, returnType ein Typbezeichner, statements enthält die Folge von Anweisungen, die beim Aufruf der Funktion ausgeführt werden sollen. (parameterList), directives und localDeclarations sind optional.

Für den Anweisungsblock der Funktion gelten die Regeln, die bereits für Prozeduren erläutert wurden. Sie können Variablen und andere Bezeichner, die im Abschnitt localDeclarations der Funktion deklariert wurden, die Parameternamen aus der Parameterliste und alle Bezeichner verwenden, in deren Gültigkeitsbereich die Funktion deklariert wurde. Der Funktionsname dient als spezielle Variable, die wie die vordefinierte Variable Result den Rückgabewert der Funktion enthält.

Wenn die erweiterte Syntax ({$X+}) aktiviert ist, wird Result implizit in jeder Funktion deklariert. Sie dürfen diese Variable deshalb nicht manuell deklarieren.

Zum Beispiel:

 function WF: Integer;
  begin
    WF := 17;
  end;

Diese Deklaration definiert eine Konstantenfunktion namens WF, die keine Parameter entgegennimmt und immer den Integer-Wert 17 zurückgibt. Diese Deklaration ist zur folgenden äquivalent:

 function WF: Integer;
  begin
    Result := 17;
  end;

Das nächste Beispiel enthält eine etwas komplexere Funktionsdeklaration:

 function Max(A: array of Real; N: Integer): Real;
  var
    X: Real;
    I: Integer;
  begin
    X := A[0];
    for I := 1 to N - 1 do
      if X < A[I] then X := A[I];
    Max := X;
  end;

Sie können der Variable Result oder dem Funktionsnamen im Anweisungsblock mehrmals einen Wert zuweisen. Die zugewiesenen Werte müssen jedoch dem deklarierten Rückgabetyp entsprechen. Sobald die Ausführung der Funktion beendet wird, bildet der Wert, der zuletzt der Variable Result oder dem Funktionsnamen zugewiesen wurde, den Rückgabewert der Funktion. Zum Beispiel:

 function Power(X: Real; Y: Integer): Real;
  var
    I: Integer;
  begin
    Result := 1.0;
    I := Y;
    while I > 0 do
     begin
      if Odd(I) then Result := Result * X;
      I := I div 2;
      X := Sqr(X);
     end;
  end;

Result und der Funktionsname repräsentieren immer denselben Wert. Deshalb gibt die Deklaration

 function MyFunction: Integer;
  begin
    MyFunction := 5;
    Result := Result * 2;
    MyFunction := Result + 1;
  end;

den Wert 11 zurück. Result ist jedoch nicht vollständig mit dem Funktionsnamen austauschbar. Wenn der Funktionsname auf der linken Seite einer Zuweisungsanweisung angegeben ist, verwendet der Compiler den Funktionsnamen als Variable (wie Result) zur Aufzeichnung des Rückgabewerts. Taucht der Funktionsname dagegen an einer anderen Stelle im Anweisungsblock auf, wird er vom Compiler als rekursiver Aufruf der Funktion interpretiert. Result kann als Variable in Operationen eingesetzt werden, beispielsweise bei Typkonvertierungen, in Mengenkonstruktoren, Indizes und in Aufrufen anderer Routinen.

Wenn die Ausführung einer Funktion beendet wird, bevor Result oder dem Funktionsnamen ein Wert zugewiesen wurde, ist der Rückgabewert der Funktion nicht definiert.

Aufrufkonventionen

Wenn Sie eine Prozedur oder Funktion deklarieren, können Sie eine Aufrufkonvention mit einer der Direktiven register, pascal, cdecl, stdcall und safecall angeben. Zum Beispiel:

 function MyFunction(X, Y: Real): Real; cdecl;

Aufrufkonventionen bestimmen die Reihenfolge, in der Parameter an die Routine übergeben werden. Sie beeinflussen außerdem das Entfernen der Parameter vom Stack, den Einsatz von Registern zur Übergabe von Parametern sowie die Fehler- und Exception-Behandlung. Die Standard-Aufrufkonvention ist register.

  • Für die Konventionen register und pascal ist die Auswertungsreihenfolge nicht definiert.
  • Die Konventionen cdecl, stdcall und safecall übergeben die Parameter von rechts nach links.
  • Bei allen Konventionen mit Ausnahme von cdecl entfernt die Prozedur bzw. Funktion Parameter vom Stack, sobald die Steuerung zurückgegeben wird. Bei der Konvention cdecl entfernt die aufrufende Routine Parameter vom Stack, sobald sie wieder die Steuerung erhält.
  • Die Konvention register verwendet bis zu drei CPU-Register zur Übergabe von Parametern, alle anderen Konventionen übergeben alle Parameter im Stack.
  • Die Konvention safecall implementiert "Exception-Firewalls" und in Win32 die prozessübergreifende COM-Fehlerbenachrichtigung.

Die folgende Tabelle enthält einen Überblick über die Aufrufkonventionen.

Aufrufkonventionen

Direktive   Parameterreihenfolge   Bereinigung   Parameterübergabe in Registern?

register 

Undefiniert 

Routine 

Ja

pascal

Undefiniert

Routine

Nein

cdecl

Von rechts nach links

Aufrufender

Nein

stdcall

Von rechts nach links

Routine

Nein

safecall

Von rechts nach links

Routine

Nein


Die weitaus effizienteste Aufrufkonvention ist register, da hier meistens kein Stack-Frame für die Parameter angelegt werden muss. (Zugriffsmethoden für Eigenschaften, die als published deklariert sind, müssen register verwenden.) Unter Win32 verwenden die Betriebssystem-APIs die Konventionen stdcall und safecall. Andere Betriebssysteme verwenden normalerweise die Konvention cdecl (beachten Sie, dass stdcall effizienter ist als cdecl).

Die Konvention safecall muss bei der Deklaration von Methoden für duale Interfaces verwendet werden. Die Konvention pascal gewährleistet die Abwärtskompatibilität.

Die Direktiven near, far und export beziehen sich auf die Aufrufkonventionen bei der Programmierung für 16-Bit-Windows-Umgebungen. Sie dienen ausschließlich der Abwärtskompatibilität und haben in Win32-Anwendungen keine Auswirkung.

forward- und interface-Deklarationen

Die Direktive forward in einer Prozedur- oder Funktionsdeklaration wird durch einen Rumpf mit Anweisungen und lokalen Variablendeklarationen ersetzt. Zum Beispiel:

 function Calculate(X, Y: Integer): Real; forward;

Die hier deklarierte Funktion Calculate enthält die Direktive forward. An einer Position nach der forward-Deklaration muss die Routine mit dem Rumpf deklariert werden. Die definierende Deklaration für Calculate kann beispielsweise folgendermaßen aussehen:

 function Calculate;
    ... { declarations }
  begin
    ... { statement block }
  end;

Grundsätzlich muss eine definierende Deklaration die Parameterliste oder den Rückgabetyp der Routine nicht wiederholen. Werden diese Angaben jedoch wiederholt, müssen sie denen in der forward-Deklaration exakt entsprechen. Standardparameter in der definierenden Deklaration können aber weggelassen werden. Wenn die forward-Deklaration eine überladene Prozedur oder Funktion angibt, muss die Parameterliste in der definierenden Deklaration wiederholt werden.

Eine forward-Deklaration und die dazugehörige definierende Deklaration müssen in demselben type-Deklarationsabschnitt enthalten sein. Sie können also keinen neuen Abschnitt (z. B. einen var- oder const-Abschnitt) zwischen der forward-Deklaration und der definierenden Deklaration einfügen. Die definierende Deklaration kann eine external- oder assembler-Deklaration sein, es darf sich jedoch nicht um eine weitere forward-Deklaration handeln.

Mit einer forward-Deklaration kann der Gültigkeitsbereich eines Prozedur- oder Funktionsbezeichners auf einen früheren Punkt im Quelltext ausgedehnt werden. Andere Prozeduren und Funktionen können also die mit forward deklarierte Routine aufrufen, bevor sie tatsächlich definiert wurde. forward-Deklarationen bieten nicht nur mehr Flexibilität bei der Programmierung, sie werden auch gelegentlich für wechselseitige Rekursionen benötigt.

Die Direktive forward hat im interface-Abschnitt einer Unit keine Auswirkung. Prozedur- und Funktions-Header im interface-Abschnitt verhalten sich wie forward-Deklarationen. Die zugehörigen definierenden Deklarationen müssen in den implementation-Abschnitt eingefügt werden. Eine im interface-Abschnitt deklarierte Routine ist von jeder Position in der Unit und für jede Unit und jedes Programm verfügbar, die bzw. das die Unit mit der Deklaration einbindet.

external-Deklarationen

Die Direktive external ersetzt den Anweisungsblock in einer Prozedur- oder Funktionsdeklaration. Sie ermöglicht das Aufrufen von Routinen, die unabhängig vom aktuellen Programm compiliert wurden. Externe Routinen stammen aus Objektdateien oder aus dynamisch ladbaren Bibliotheken.

Verwenden Sie zum Importieren einer C-Funktion, die eine variable Anzahl von Parametern übernimmt, die Direktive varargs. Zum Beispiel:

 function printf(Format: PChar): Integer; cdecl; varargs;

Die Direktive varargs kann nur für externe Routinen und zusammen mit der Aufrufkonvention cdecl eingesetzt werden.

Linken mit Objektdateien

Um Routinen aus einer separat compilierten Objektdatei aufrufen zu können, müssen Sie die Objektdatei mit der Compiler-Direktive $L (oder $LINK) mit der Anwendung linken. Zum Beispiel:

 {$L BLOCK.OBJ}

Diese Anweisung linkt die Datei BLOCK.OBJ in das Programm bzw. die Unit, in der die Anweisung verwendet wird. Anschließend müssen Sie die aufzurufenden Funktionen und Prozeduren deklarieren:

 procedure MoveWord(var Source, Dest; Count: Integer); external;
  procedure FillWord(var Dest; Data: Integer; Count: Integer); external;

Jetzt können Sie die Routinen MoveWord und FillWord aus BLOCK.OBJ aufrufen.

Unter Win32 werden Deklarationen wie die oben gezeigten häufig verwendet, um auf externe Assembler-Routinen zuzugreifen. Assembler-Routinen können auch direkt in Object Pascal-Quelltext eingefügt werden.

Importieren von Funktionen aus Bibliotheken

Mit einer Direktive der Form

external stringConstant;

können Sie Routinen aus einer dynamisch ladbaren Bibliothek (.DLL) importieren. Die Anweisung muss an das Ende des Prozedur- bzw. Funktions-Headers angefügt werden. stringConstant gibt die Bibliotheksdatei in einfachen Anführungszeichen (Hochkommas) an. Beispielsweise wird mit der Anweisung

 function SomeFunction(S: string): string; external 'strlib.dll';

unter Win32 die Funktion SomeFunction aus der Datei strlib.dll importiert.

Sie können Routinen unter einem anderen als dem in der Bibliothek verwendeten Namen importieren, indem Sie den Originalnamen in der external-Direktive angeben:

external stringConstant1 name stringConstant2;

Die erste stringConstant gibt den Namen der Bibliotheksdatei und die zweite den Originalnamen der Routine an.

Die folgende Deklaration importiert eine Funktion aus user32.dll (Bestandteil der Win32-API):

 function MessageBox(HWnd: Integer; Text, Caption: PChar; Flags: Integer): Integer;
  stdcall; external 'user32.dll' name 'MessageBoxA';

Der Originalname der Funktion lautet MessageBoxA, sie wird jedoch unter dem Namen MessageBox importiert.

Sie können die zu importierende Routine auch über eine Nummer angeben:

external stringConstant index integerConstant;

integerConstant ist der Index der Routine in der Exporttabelle.

In der Importdeklaration müssen Sie die genaue Schreibweise des Routinennamens verwenden (einschließlich Groß-/Kleinschreibung). Beim Aufruf der importierten Routine wird die Groß-/Kleinschreibung nicht mehr berücksichtigt.

Um das Laden der Bibliothek, die die Funktion enthält, bis zu dem Moment hinauszuschieben, an dem die Funktion wirklich benötigt wird, hängen Sie die Direktive delayed an die importierte Funktion an:

 function ExternalMethod(const SomeString: PChar): Integer; stdcall; external 'cstyle.dll' '''delayed''';

delayed stellt sicher, dass die Bibliothek, die die importierte Funktion enthält, nicht beim Start der Anwendung geladen wird, sondern erst, wenn die Funktion aufgerufen wird. Weitere Informationen zu diesem Thema finden Sie unter Bibliotheken und Packages - Verzögertes Laden.

Überladen von Prozeduren und Funktionen

Sie können mehrere Routinen mit identischen Namen in demselben Gültigkeitsbereich deklarieren. Dieses Verfahren wird Überladen genannt. Überladene Routinen müssen mit der Direktive overload deklariert werden und unterschiedliche Parameterlisten haben. Beispiele:

 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 mit unterschiedlichen Typen entgegennehmen. Wenn Sie Divide aufrufen, 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, weil es sich bei den Argumenten um reelle Zahlen handelt.

Einer überladenen Routine können Parameter übergeben werden, deren Typ keinem der in den Routinendeklarationen verwendeten Typen entspricht. Voraussetzung dafür ist, dass diese Parameter zuweisungskompatibel mit den Parametern in mehr als einer Deklaration sind. Dieses Vorgehen wird meist beim Überladen von Routinen mit unterschiedlichen Integer- oder Real-Typen verwendet:

 procedure Store(X: Longint); overload;
  procedure Store(X: Shortint); overload;

Wenn die Eindeutigkeit gewährleistet ist, ruft der Compiler in diesen Fällen die Routine auf, deren Parametertyp für den Wertebereich der tatsächlich übergebenen Parameter mindestens erforderlich ist. (Beachten Sie, dass konstante Ausdrücke, die reelle Zahlen sind, immer den Typ Extended haben.)

Überladene Routinen müssen hinsichtlich der Anzahl der entgegengenommenen Parameter oder der Typen dieser Parameter eindeutig sein. Die folgenden beiden Deklarationen führen deshalb zu einem Fehler bei der Compilierung:

 function Cap(S: string): string; overload;
    ...
  procedure Cap(var Str: string); overload;
    ...

Dagegen sind die folgenden Deklarationen zulässig:

 function Func(X: Real; Y: Integer): Real; overload;
    ...
  function Func(X: Integer; Y: Real): Real; overload;
    ...


Wird eine überladene Routine mit forward oder interface deklariert, muss die Parameterliste in der definierenden Deklaration der Routine wiederholt werden.

Der Compiler kann zwischen überladenen Funktionen unterscheiden, die AnsiString/PAnsiChar, UnicodeString/PChar und WideString/PWideChar-Parameter an derselben Parameterposition enthalten. String-Konstanten oder String-Literale, die in einer derartigen überladenen Situation übergeben werden, werden in den nativen String- oder Zeichentyp (UnicodeString/PChar) übersetzt.

 procedure test(const A: AnsiString); overload;
  procedure test(const W: WideString); overload;
  procedure test(const U: UnicodeString); overload;
  procedure test(const PW: PWideChar); overload;
  var
    a: AnsiString;
    b: WideString;
    c: UnicodeString;
    d: PWideChar;
    e: string;
  begin
    a := 'a';
    b := 'b';
    c := 'c';
    d := 'd';
    e := 'e';
    test(a);    // calls AnsiString version
    test(b);    // calls WideString version
    test(c);    // calls UnicodeString version
    test(d);    // calls PWideChar version
    test(e);    // calls UnicodeString version
    test('abc');    // calls UnicodeString version
    test(AnsiString ('abc'));    // calls AnsiString version
    test(WideString('abc'));    // calls WideString version
    test(PWideChar('PWideChar'));    // calls PWideChar version
  end;

Varianten können ebenfalls als Parameter in überladenen Funktionsdeklarationen verwendet werden. Eine Variante ist allgemeiner gehalten als ein beliebiger einfacher Typ. Exakte Typübereinstimmungen werden stets gegenüber Variantenübereinstimmungen bevorzugt. Wird eine Variante in einer derartigen überladenen Situation übergeben, und ist eine overload-Anweisung an dieser Parameterposition vorhanden, die eine Variante akzeptiert, wird diese als exakte Übereinstimmung für den Variant-Typ angesehen.

Dies kann bei Gleitkommatypen einige geringe Nebeneffekte bewirken. Gleitkommatypen werden anhand der Größe verglichen. Falls es keine genaue Übereinstimmung für die Gleitkommavariable gibt, die an den overload-Aufruf übergeben wird, aber ein varianter Parameter vorhanden ist, erhält die Variante den Vorzug gegenüber dem kleineren Gleitkommatyp.

Zum Beispiel:

 procedure foo(i: integer); overload;
  procedure foo(d: double); overload;
  procedure foo(v: variant); overload;
  var
    v: variant;
  begin
    foo(1);       // integer version
    foo(v);       // variant version
    foo(1.2);     // variant version (float literals -> extended precision)
  end;

Dieses Beispiel ruft die variante Version von foo, nicht die double-Version auf, da die Konstante 1.2 implizit ein extended-Typ ist. extended ist jedoch keine genaue Übereinstimmung für double. extended ist auch keine Übereinstimmung für variant, aber variant wird als allgemeinerer Typ angesehen (während double ein kleinerer Typ als extended ist).

 foo(Double(1.2));

Diese Typumwandlung funktioniert nicht. Verwenden Sie stattdessen typisierte Konstanten:

 const  d: double = 1.2;
    begin
      foo(d);
    end;

Dieser Quelltext arbeitet korrekt und ruft die double-Version auf.

 const  s: single = 1.2;
    begin
      foo(s);
    end;

Dieser Quelltext funktioniert und ruft die double-Version von foo auf. single passt besser zu double als variant.

Bei der Deklaration von überladenen Routinen besteht die beste Möglichkeit zur Vermeidung einer Gleitkommapromotion zu variant darin, eine Version ihrer überladenen Funktion für jeden Gleitkommatyp (single, double, extended) zusammen mit der variant-Version zu deklarieren.

Bei Verwendung von Standardparametern in überladenen Routinen müssen Sie mehrdeutige Parametersignaturen vermeiden.

Die möglichen Auswirkungen des Überladens lassen sich beschränken, indem Sie beim Aufruf den qualifizierten Routinennamen verwenden. Mit Unit1.MyProcedure(X, Y) können nur die in Unit1; deklarierten Routinen aufgerufen werden. Wenn der Name und die Parameterliste keiner Routine in Unit1 entsprechen, gibt der Compiler einen Fehler aus.

Lokale Deklarationen

Der Rumpf einer Funktion oder Prozedur beginnt in vielen Fällen mit der Deklaration lokaler Variablen, die im Anweisungsblock der Routine verwendet werden. Sie können beispielsweise Konstanten, Typen und andere Routinen deklarieren. Der Gültigkeitsbereich lokaler Bezeichner ist auf die Routine beschränkt, in der sich die Deklaration befindet.

Verschachtelte Routinen

Funktionen und Prozeduren können im Abschnitt mit den lokalen Deklarationen ihrerseits Funktionen und Prozeduren enthalten. Die folgende Deklaration der Prozedur DoSomething enthält beispielsweise eine verschachtelte Prozedur:

 procedure DoSomething(S: string);
  var
    X, Y: Integer;
  
    procedure NestedProc(S: string);
    begin
    ...
    end;
  
  begin
    ...
    NestedProc(S);
    ...
  end;

Der Gültigkeitsbereich einer verschachtelten Routine ist auf die Funktion bzw. Prozedur beschränkt, in der sie deklariert ist. Im letzten Beispiel kann NestedProc nur in DoSomething aufgerufen werden.

Echte Beispiele verschachtelter Routinen sind die Prozedur DateTimeToString, die Funktion ScanDate und andere Routinen in der Unit SysUtils.

Siehe auch