Procédures et fonctions (Object Pascal)

De Appmethod Topics
Aller à : navigation, rechercher

Remonter à Procédures et fonctions - Index


Cette rubrique traite les sujets suivants :

  • Déclaration de procédures et de fonctions
  • Conventions d'appel
  • Déclarations forward et interface
  • Déclaration de routines externes
  • Surcharge de procédures et de fonctions
  • Déclarations locales et routines imbriquées

A propos des procédures et des fonctions

Les procédures et fonctions, désignées collectivement par le terme routines, sont des blocs d'instructions autonomes qui peuvent être appelés depuis divers endroits d'un programme. Une fonction est une routine qui renvoie une valeur quand elle est exécutée. Une procédure est une routine qui ne renvoie pas de valeur.

Les appels de fonctions peuvent être utilisés comme expression dans les affectations et les opérations car ils renvoient une valeur. Par exemple :

 I := SomeFunction(X);

appelle SomeFunction et assigne le résultat à I. Les appels de fonctions ne peuvent pas apparaître du côté gauche d'une instruction d'affectation.

Les appels de procédures -- et, quand la syntaxe étendue est activée, ({$X+}), les appels de fonctions -- peuvent s'utiliser comme des instructions à part entière. Par exemple :

 DoSomething;

appelle la routine DoSomething ; si DoSomething est une fonction, sa valeur de retour est perdue.

Les procédures et les fonctions peuvent s'appeler elles-mêmes de manière récursive.

Déclaration de procédures et de fonctions

Quand vous déclarez une procédure ou une fonction, vous spécifiez son nom, le nombre et le type de ses paramètres et, dans le cas d'une fonction, le type de la valeur qu'elle renvoie. Cette partie de la déclaration est parfois appelée le prototype ou l'en-tête. Puis, vous écrivez un bloc de code qui s'exécute à chaque fois que la procédure ou la fonction est appelée. Cette partie est parfois appelée le corps ou le bloc de la routine.

Déclarations de procédures

Une déclaration de procédure a la forme :

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

procedureName est un identificateur valide, statements une série d'instructions qui s'exécute quand la procédure est appelée. Les éléments (parameterList), directives; et localDeclarations; sont facultatifs.

Voici un exemple de déclaration de procédure :

 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;

Etant donné cette déclaration, vous pouvez appeler la procédure NumString de la manière suivante :

 NumString(17, MyString);

Cet appel de procédure assigne la valeur '17' à MyString (qui doit être une variable chaîne).

Dans le bloc d'instructions d'une procédure, vous pouvez utiliser des variables et d'autres identificateurs déclarés dans la partie localDeclarations de la procédure. Vous pouvez aussi utiliser les noms de paramètres de la liste de paramètres (comme N et S dans l'exemple précédent). La liste de paramètres définit un ensemble de variables locales, vous ne devez donc pas redéclarer le nom des paramètres dans la section localDeclarations. Vous pouvez, enfin, utiliser tous les identificateurs qui sont dans la portée de la déclaration de la procédure.

Déclarations de fonctions

Une déclaration de fonction est similaire à la déclaration de procédure, mais elle spécifie un type de retour et une valeur de retour. Les déclarations de fonctions ont la forme suivante :

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

functionName est un identificateur valide, returnType est un identificateur de type, statements est une séquence d'instructions qui s'exécute quand la fonction est appelée. Les éléments (parameterList), directives; et localDeclarations; sont facultatifs.

Le bloc d'instructions d'une fonction respecte les mêmes règles que celles qui s'appliquent aux procédures. A l'intérieur du bloc d'instructions, vous pouvez utiliser les variables et autres identificateurs déclarés dans la partie localDeclarations de la fonction, les noms de paramètres de la liste de paramètres et les identificateurs dont la portée est dans la déclaration de la fonction. De plus, le nom de la fonction se comporte comme une variable spéciale contenant la valeur de retour de la fonction, à l'image de la variable prédéfinie Result.

Si la syntaxe étendue est activée ({$X+}), Result est déclarée implicitement dans chaque fonction. Vous ne devez donc pas la redéclarer.

Par exemple :

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

définit une fonction constante appelée WF qui n'attend pas de paramètre et renvoie toujours la valeur entière 17. Cette déclaration est équivalente à :

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

Voici un exemple de déclaration de fonction plus compliquée :

 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;

Vous pouvez assigner une valeur à Result ou au nom de la fonction de façon répétitive au sein d'un bloc d'instructions, tant que vous n'assignez que des valeurs qui correspondent au type de retour déclaré. Quand l'exécution de la fonction s'achève, la dernière valeur affectée à Result ou au nom de la fonction devient la valeur de retour de la fonction. Par exemple :

 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 et le nom de la fonction représentent toujours la même valeur. Ainsi, la fonction :

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

renvoie la valeur 11. Cependant, Result n'est pas totalement interchangeable avec le nom de la fonction. Quand le nom de la fonction apparaît sur la gauche d'une instruction d'affectation, le compilateur suppose qu'il est utilisé (comme Result) pour indiquer la valeur de retour. Par contre, quand le nom de la fonction apparaît n'importe où ailleurs dans le bloc d'instructions, le compilateur l'interprète comme un appel récursif à la fonction. Result, quant à lui, s'utilise comme une variable dans les opérations, les transtypages, les constructeurs d'ensembles, les index et les appels à d'autres routines.

Si la fonction s'achève sans qu'une valeur ne soit assignée à Result ou au nom de la fonction, la valeur de retour de la fonction est indéfinie.

Conventions d'appel

Dans la déclaration d'une procédure ou d'une fonction, vous pouvez spécifier une convention d'appel en utilisant l'une des directives register, pascal, cdecl, stdcall et safecall. Par exemple,

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

Les conventions d'appel déterminent l'ordre dans lequel les paramètres sont transmis à la routine. Elles affectent aussi le retrait des paramètres de la pile, l'utilisation de registres pour transmettre les paramètres, ainsi que la gestion des erreurs et des exceptions. register est la convention d'appel par défaut.

  • Pour les conventions register et pascal, l'ordre de l'évaluation n'est pas défini.
  • Les conventions cdecl, stdcall et safecall transmettent les paramètres de droite à gauche.
  • Pour toutes les conventions à l'exception de cdecl, la procédure ou la fonction retire les paramètres de la pile lors de la sortie. Avec la convention cdecl, l'appelant retire les paramètres de la pile au retour de l'appel.
  • La convention register utilise jusqu'à trois registres CPU pour transmettre des paramètres, alors que toutes les autres conventions transmettent tous les paramètres sur la pile.
  • La convention safecall implémente les 'coupe-feu' d'exceptions. Sur Win32, cela implémente la notification d'erreurs COM interprocessus.

Le tableau suivant résume les conventions d'appel.

Conventions d'appel :

Directive   Ordre des paramètres   Nettoyage   Transfert des paramètres dans les registres ?

register 

Non défini 

Routine 

Oui

pascal

Non défini

Routine

Non

cdecl

De droite à gauche

Appelant

Non

stdcall

De droite à gauche

Routine

Non

safecall

De droite à gauche

Routine

Non


La convention par défaut register est la plus efficace car elle évite habituellement la création d'un cadre de pile. (Les méthodes d'accès aux propriétés publiées doivent utiliser register.) Sur Win32, les API du système d'exploitation sont stdcall et safecall.- Les autres systèmes d'exploitation utilisent généralement cdecl. (Notez que stdcall est plus efficace que cdecl.)

La convention safecall doit être utilisée pour déclarer les méthodes des interfaces doubles. La convention pascal est conservée dans un souci de compatibilité descendante.

Les directives near, far et export se réfèrent aux conventions d'appel en programmation Windows 16 bits. Elles sont sans effet dans Win32 et sont conservées uniquement dans un souci de compatibilité descendante.

Déclarations forward et interface

Dans une déclaration de procédure ou de fonction, la directive forward remplace le bloc, y compris les instructions et les déclarations des variables locales. Par exemple :

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

déclare une fonction appelée Calculate. Quelque part après la déclaration forward, la routine doit être redéclarée dans une déclaration de définition qui inclut un bloc. La déclaration de définition de Calculate peut être :

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

Généralement, une déclaration de définition ne répète pas la liste des paramètres ou le type de retour de la routine. Mais s'ils sont répétés, ils doivent correspondre exactement à ceux de la déclaration forward (à cette différence que les paramètres par défaut peuvent être omis). Si la déclaration forward spécifie une procédure ou une fonction surchargée, la déclaration de définition doit alors répéter la liste des paramètres.

Une déclaration forward et sa déclaration de définition doivent apparaître dans la même section de déclaration de type. C'est-à-dire que vous ne pouvez pas ajouter une nouvelle section (telle qu'une section var ou const) entre la déclaration forward et la déclaration de définition. La déclaration de définition peut être une déclaration external ou assembler, mais pas une autre déclaration forward.

Le but d'une déclaration forward est d'étendre la portée d'un identificateur de procédure ou de fonction à un point antérieur du code source. Cela permet à d'autres procédures et fonctions d'appeler la routine déclarée forward avant qu'elle ne soit effectivement définie. En dehors du fait que cela permet d'organiser le code de manière plus souple, les déclarations forward sont parfois indispensables dans le cas de récursions mutuelles.

La directive forward n'a pas d'effet dans la section interface d'une unité. Les en-têtes de procédures et de fonctions de la section interface se comportent comme des déclarations forward et doivent avoir des déclarations de définition dans la section implementation. Une routine déclarée dans la section interface est disponible partout ailleurs dans l'unité et dans toute unité ou tout programme qui utilise l'unité où elle est déclarée.

Déclarations externes

La directive external qui remplace le bloc dans une déclaration de procédure ou de fonction permet d'appeler des routines compilées séparément de votre programme. Les routines externes peuvent être issues de fichiers objet ou de bibliothèques à chargement dynamique.

Quand vous importez une fonction C qui prend un nombre variable de paramètres, utilisez la directive varargs. Par exemple :

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

La directive varargs fonctionne seulement avec des routines externes et seulement avec la convention d'appel cdecl.

Liaison aux fichiers objet

Pour appeler des routines depuis un fichier objet compilé séparément, commencez par lier le fichier objet à votre application en utilisant la directive de compilation $L (ou $LINK). Par exemple :

 {$L BLOCK.OBJ}

lie BLOCK.OBJ au programme ou à l'unité dans lequel elle apparaît. Ensuite, déclarez les fonctions et procédures que vous voulez appeler :

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

Vous pouvez ensuite appeler les routines MoveWord et FillWord depuis BLOCK.OBJ.

Sur la plate-forme Win32, de telles déclarations sont fréquemment utilisées pour accéder à des routines externes écrites en assembleur. Vous pouvez aussi placer directement des routines en assembleur dans votre code source Object Pascal.

Importation de fonctions depuis des bibliothèques

Pour importer des routines depuis une bibliothèque à chargement dynamique (.DLL), attachez une directive de la forme

external stringConstant;

à la fin de l'en-tête de la fonction ou de la procédure, où stringConstant est le nom du fichier bibliothèque placé entre apostrophes. Par exemple, sur Win32

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

importe une fonction appelée SomeFunction depuis strlib.dll.

Vous pouvez importer une routine sous un nom différent de celui qu'elle a dans la bibliothèque. Si vous le faites, spécifiez le nom original dans la directive external :

external stringConstant1 name stringConstant2;

où le premier stringConstant spécifie le nom du fichier bibliothèque et le deuxième stringConstant est le nom original de la routine.

La déclaration suivante importe une fonction depuis user32.dll (partie de l'API Win32) :

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

Le nom original de la fonction est MessageBoxA, mais elle est importée sous le nom MessageBox.

A la place du nom, vous pouvez utiliser un numéro pour identifier la routine à importer :

external stringConstant index integerConstant;

integerConstant est l'index de la routine dans la table d'exportation.

Dans la déclaration d'importation, assurez-vous de respecter exactement l'orthographe et la casse du nom de la routine. Par la suite, quand vous appelez la routine importée, le nom est insensible à la casse.

Pour différer le chargement de la bibliothèque qui contient la fonction au moment où la fonction est réellement nécessaire, ajoutez la directive delayed à la fonction importée :

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

delayed garantit que la bibliothèque qui contient la fonction importée n'est pas chargée au démarrage de l'application, mais plutôt au premier appel de la fonction. Pour de plus amples informations sur cette rubrique, voir la rubrique Bibliothèques et packages - Chargement différé.

Surcharge de procédures et de fonctions

Vous pouvez déclarer plusieurs fois une routine dans la même portée sous le même nom. C'est ce que l'on appelle la surcharge. Les routines surchargées doivent être déclarées avec la directive overload et doivent utiliser des listes de paramètres différentes. Par exemple, soit les déclarations :

 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;

Ces déclarations créent deux fonctions appelées toutes les deux Divide qui prennent des paramètres de types différents. Quand vous appelez Divide, le compilateur détermine la fonction à invoquer en examinant les paramètres effectivement transmis dans l'appel. Ainsi, Divide(6.0, 3.0) appelle la première fonction Divide car ses arguments sont des valeurs réelles.

Vous pouvez transmettre à une routine surchargée des paramètres qui ne sont pas du même type que ceux d'une des déclarations de la routine, mais qui sont compatibles au niveau de l'affectation avec des paramètres d'une ou de plusieurs déclarations. Cela arrive plus souvent quand une routine est surchargée avec différents types entiers ou différents types réels, par exemple :

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

Dans ces cas, quand c'est possible de le faire sans ambiguïté, le compilateur invoque la routine dont les paramètres sont du type du plus petit intervalle qui convienne aux paramètres réels de l'appel. (N'oubliez pas que les expressions de constantes réelles sont toujours de type Extended.)

Les routines surchargées doivent pouvoir se distinguer par le nombre ou le type de leurs paramètres. Ainsi, la paire de déclarations suivante déclenche une erreur de compilation :

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

Alors que les déclarations :

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

sont correctes.

Quand une routine surchargée est déclarée dans une déclaration forward ou interface, la déclaration de définition doit répéter la liste des paramètres de la routine.

Le compilateur peut distinguer les fonctions surchargées qui contiennent les paramètres AnsiString/PAnsiChar, UnicodeString/PChar et WideString/PWideChar à la même position de paramètre. Les constantes de chaînes et les littéraux transmis dans cette situation de surcharge sont traduits en chaîne ou type de caractère natif, c'est-à-dire UnicodeString/PChar.

 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;

Les variants peuvent aussi être utilisés en tant que paramètres dans des déclarations de fonctions surchargées. Le variant est considéré comme étant plus général que tout type simple. La préférence est toujours donnée aux correspondances de type exact sur les correspondances de variant. Si un variant est transmis dans cette situation de surcharge, et si une surcharge qui prend un variant existe à cette position de paramètre, elle est considérée comme étant une correspondance exacte pour le type Variant.

Cela peut provoquer des effets secondaires mineurs avec les types flottants. Les types flottants correspondent par la taille. S'il n'y a pas de correspondance exacte pour la variable flottante transmise à l'appel surchargé, mais qu'un paramètre variant est disponible, le variant est pris par rapport à tout type flottant plus petit.

Par exemple :

 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;

Cet exemple appelle la version variant de foo, et pas la version double, car la constante 1.2 est implicitement un type extended, et ce type n'est pas une correspondance exacte pour double. Extended n'est pas non plus une correspondance exacte pour Variant, mais Variant est considéré comme un type plus général (alors que double est un type plus petit que extended).

 foo(Double(1.2));

Ce transtypage ne fonctionne pas. Vous devriez utiliser des constantes typées à la place :

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

Le code ci-dessus fonctionne correctement, et il appelle la version double.

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

Le code ci-dessus appelle aussi la version double de foo. Single convient mieux à double qu'à variant.

Lors de la déclaration d'un ensemble de routines surchargées, la meilleure façon d'éviter le passage de flottant à variant consiste à déclarer une version de votre fonction surchargée pour chaque type flottant (Single, Double, Extended) avec la version variant.

Si vous utilisez des paramètres par défaut dans des routines surchargées, faites attention à ne pas introduire de signatures de paramètres ambiguës.

Vous pouvez limiter les effets potentiels de la surcharge en qualifiant le nom d'une routine lors de son appel. Par exemple, Unit1.MyProcedure(X, Y) peut n'appeler que les routines déclarées dans Unit1; si aucune routine de Unit1 ne correspond au nom et à la liste de paramètres de l'appel, une erreur de compilation apparaît.

Déclarations locales

Le corps d'une fonction ou d'une procédure commence souvent par la déclaration de variables locales utilisées dans le bloc d'instructions de la routine. Ces déclarations peuvent aussi contenir des constantes, des types ou d'autres routines. La portée d'un identificateur local est limitée à la routine où il est déclaré.

Routines imbriquées

Les fonctions et procédures contiennent parfois d'autres fonctions ou procédures dans la section des déclarations locales de leurs blocs. Par exemple, la déclaration suivante d'une procédure appelée DoSomething contient une procédure imbriquée.

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

La portée d'une routine imbriquée est limitée à la fonction ou la procédure dans laquelle elle est déclarée. Dans notre exemple, NestedProc peut seulement être appelée dans DoSomething.

Pour des exemples réels de routines imbriquées, examinez la procédure DateTimeToString, la fonction ScanDate et d'autres routines de l'unité SysUtils.

Voir aussi