Prozeduren und Funktionen (Delphi)
Nach oben zu Prozeduren und Funktionen - Index
Dieses Thema enthält Informationen zu folgenden Bereichen:
- Prozeduren und Funktionen deklarieren
- Aufrufkonventionen
- forward- und interface-Deklarationen
- external-Deklarationen
- Prozeduren und Funktionen überladen
- Lokale Deklarationen und verschachtelte Routinen
Inhaltsverzeichnis
- 1 Allgemeines zu Prozeduren und Funktionen
- 2 Prozeduren und Funktionen deklarieren
- 3 Aufrufkonventionen
- 4 forward- und interface-Deklarationen
- 5 external-Deklarationen
- 5.1 Objektdateien linken
- 5.2 Funktionen aus Bibliotheken importieren
- 5.2.1 Interne und externe Linker verwenden
- 5.2.2 Funktionen aus Objektdateien importieren (nur externer Linker)
- 5.2.3 Funktionen aus Frameworks importieren
- 5.2.4 Eine Routine unter einem anderen Namen importieren
- 5.2.5 Eine Routine über den Index importieren
- 5.2.6 Laden der Bibliothek verzögern
- 5.2.7 Abhängigkeit von der Bibliothek festlegen
- 6 Prozeduren und Funktionen überladen
- 7 Lokale Deklarationen
- 8 Siehe auch
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.
Prozeduren und Funktionen deklarieren
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;
Prozedurname ist ein beliebiger gültiger Bezeichner, Anweisungen eine Folge von Anweisungen, die beim Aufruf der Prozedur ausgeführt werden. (Parameterliste), Direktiven und LokaleDeklarationen sind optional.
Ein 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 vom 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;
Funktionsname ist ein beliebiger gültiger Bezeichner, Rückgabetyp ein Typbezeichner, Anweisungen enthält die Folge von Anweisungen, die beim Aufruf der Funktion ausgeführt werden sollen. (Parameterliste), Direktiven; und LokaleDeklarationen; 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 Integerwert 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 austauchbar. 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 Konstruktoren, 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, safecall und winapi 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 der Register 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 die Parameter vom Stack, sobald die Steuerung zurückgegeben wird. Bei der Konvention cdecl entfernt die aufrufende Routine die Parameter vom Stack, sobald sie wieder die Steuerung erhält.
- Die Konvention register verwendet bis zu drei CPU-Register zur Übergabe der Parameter, alle anderen Konventionen übergeben die Parameter an den Stack.
- Die Konvention safecall implementiert "Exception-Firewalls" und in Win32 die prozessübergreifende COM-Fehlerbenachrichtigung.
- winapi ist eigentlich keine Aufrufkonvention. winapi legt fest, dass die standardmäßige Aufrufkonvention der Plattform verwendet wird. Unter Win32 entspricht winapi der Aufrufkonvention stdcall.
Die folgende Tabelle beschreibt 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.) Die Konvention cdecl ist hilfreich, wenn Funktionen in gemeinsam benutzten Bibliotheken aufgerufen werden, die in C oder C++ geschrieben wurden. Die Konventionen stdcall und safecall sollten für Aufrufe von externem Quelltext benutzt werden. In 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 haben keine Auswirkungen auf die aktuellen Plattformziele und werden nur aus Gründen der Abwärtskompatibilität beibehalten.
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 anderen Position muss der Rumpf der Routine deklariert werden. Diese 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 Rekursionen benötigt.
Die Direktive forward hat keine Auswirkung im interface-Abschnitt einer Unit. 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 bei externen Routinen und zusammen mit der Aufrufkonvention cdecl eingesetzt werden.
Objektdateien linken
Um Routinen aus einer separat compilierten Objektdatei aufrufen zu können, müssen Sie die Objektdatei mit der Compiler-Direktive $L (oder $LINK) zur gewünschten Anwendung linken. Zum Beispiel:
{$L BLOCK.OBJ}
Diese Anweisung linkt die Datei BLOCK.OBJ zu dem Programm bzw. der 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 in BLOCK.OBJ aufrufen.
In Win32 werden Deklarationen wie die oben gezeigten häufig verwendet, um auf externe Assembler-Routinen zuzugreifen. Assembler-Routinen können auch direkt in Delphi-Quelltext eingefügt werden.
Funktionen aus Bibliotheken importieren
Mit einer Direktive der Form
external StringKonstante;
können Sie Routinen aus einer dynamisch ladbaren Bibliothek (.DLL-Datei) 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. Ein Beispiel:
function SomeFunction(S: string): string; external 'strlib.dll';
Mit dieser Anweisung wird in Win32 die Funktion SomeFunction
aus der Datei strlib.dll
importiert.
Interne und externe Linker verwenden
Der Delphi-Compiler interpretiert "External" unterschiedlich, je nachdem, ob der Compiler einen externen Linker verwendet nicht.
1. Von Delphi unterstützte Plattformen können in die zwei folgenden Gruppen unterteilt werden:
- Der Compiler verwendet den eigenen internen Linker.
- Der Compiler verwendet einen externen Linker.
Verwenden des internen Linkers |
WIN32 und WIN64 |
Verwenden eines externen Linkers |
iOS-Device, Android, Linux, macOS64 |
2. Auf Plattformen, auf denen Delphi einen internen Linker verwendet, gibt external <StringKonstante>
an, dass sich die Funktion/Prozedur in einer DLL befindet.
Auf diesen Plattformen geht Delphi davon aus, dass das Symbol aus einer .dll
importiert wird. Beim Linken wird keine Validierung vorgenommen. Delphi generiert ein Bild mit einer Referenz auf das Symbol/die Bibliothek. Wenn sich das Symbol nicht in dieser Bibliothek befindet, werden Sie bei der Ausführung darauf hingewiesen.
3. Auf Plattformen, auf denen Delphi einen externen Linker verwendet, beispielsweise wenn die iOSDevice64-Plattform das Ziel ist, wird der in external <StringKonstante>
angegebene Bezeichner an den externen Linker übergeben.
Der Delphi-Compiler übergibt <StringKonstante>
an ld.exe. Wenn der Compiler die Bibliothek nicht findet, wird die folgende Fehlermeldung angezeigt: Fehler: E2597 ld: Datei nicht gefunden: <StringKonstante>
.
Funktionen aus Objektdateien importieren (nur externer Linker)
Wenn ein externer Linker verwendet wird, können Sie die Verwendung der Compiler-Direktive $L (oder $LINK) unterdrücken, indem Sie die Objektdatei mit der external-Direktive angeben. Zum Beispiel:
procedure FunctionName; cdecl; external object 'ObjectFile.o' name '_FunctionName';
Funktionen aus Frameworks importieren
Sie können keine Routinen aus einem externen Framework importieren. Zum Beispiel:
Function foo: Integer; external framework '<framework name>'
Dies gilt nur für iOS64, macOS64 ARM und macOS64.
Eine Routine unter einem anderen Namen importieren
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 <StringKonstante1> name <StringKonstante2>;
<StringKonstante1>
gibt den Namen der Bibliotheksdatei an und <StringKonstante2>
ist der Originalname der Routine.
Die folgende Deklaration importiert eine Funktion aus user32.dll
(Bestandteil der Windows-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.
Stellen Sie in Ihrer Import-Deklaration sicher, dass die Schreibweise und der Fall des Routinenamens übereinstimmen. Beim Aufruf der importierten Routine wird die Groß-/Kleinschreibung nicht mehr berücksichtigt.
Eine Routine über den Index importieren
Sie können die zu importierende Routine auch über eine Nummer angeben:
external <StringKonstante> Index <IntegerKonstante>;
<IntegerKonstante>
ist der Index der Routine in der Exporttabelle.
Laden der Bibliothek verzögern
Um das Laden der Bibliothek, die die Funktion enthält, bis zu dem Moment zu verzögern, 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.
Abhängigkeit von der Bibliothek festlegen
Wenn die Bibliothek, die die Zielroutine beinhaltet, von anderen Bibliotheken abhängt, verwenden Sie die dependency
-Direktive, um diese Abhängigkeit festzulegen.
Um die dependency
-Direktive zu verwenden, fügen Sie das Schlüsselwort dependency
, gefolgt von einer durch ein Komma getrennten String-Liste an. Jeder String muss einen Namen einer Bibliothek enthalten, die von einer externen Zielbibliothek abhängig ist:
external <library> dependency <dependency1>, <dependency2>, …
Die folgende Deklaration gibt an, dass libmidas.a
von der Standard-C++-Bibliothek abhängig ist:
function DllGetDataSnapClassObject(const [REF] CLSID, [REF] IID: TGUID; var Obj): HResult; cdecl; external 'libmidas.a' dependency 'stdc++';
Prozeduren und Funktionen überladen
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 vom Typ Extended sind.)
Ü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 Deklarationen
function Func(X: Real; Y: Integer): Real; overload;
...
function Func(X: Integer; Y: Real): Real; overload;
...
zulässig.
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 in derselben Parameterposition enthalten. String-Konstanten oder String-Literale, die in eine solche überladene Position übergeben werden, werden in den nativen String- oder Zeichentyp übersetzt, der UnicodeString/PChar lautet.
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 wird allgemeiner angesehen als ein beliebiger einfacher Typ. Exakte Typübereinstimmungen werden stets gegenüber Variantenübereinstimmungen bevorzugt. Wird eine Variante an eine solche überladene Position ü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 variant-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 FunktionScanDate und andere Routinen in der Unit SysUtils.