Logo

Marco Cantù
L'essentiel sur Pascal

Traduit de l'anglais par Iannis Papageorgiadis ipapag@village.uunet.be

Chapitre 6
Procédures et fonctions

Un autre concept important mis en évidence par Pascal est celui de routine; il s'agit fondamentalement d'une série d'instructions sous un nom unique, que l'on peut activer plusieurs fois en utilisant leur nom. Cette façon de faire évite de répéter les mêmes instructions à plusieurs reprises, et, en disposant d'une seule version du code, on peut aisément le modifier partout dans le programme. De ce point de vue, on peut considérer les routines comme le mécanisme fondamental de l'encapsulation de code. Nous reviendrons sur ce sujet avec un exemple après une introduction à la syntaxe des routines Pascal.

Les procédures et les fonctions Pascal

En Pascal, une routine peut prendre deux formes : procédure et fonction. Théoriquement, une procédure est un service que l'on demande au compilateur, une fonction est un bloc qui calcule et retourne une valeur. Cette différence est mise en évidence par le fait qu'une fonction a un résultat, une valeur de retour, tandis qu'une procédure n'en a pas. Les deux types de routine peuvent comporter des paramètres multiples de types de données donnés.

En pratique, cependant, la différence entre fonctions et procédures est très limitée : on peut appeler une fonction pour améliorer un travail et se passer du résultat (qui pourrait être un code d'erreur facultatif ou quelque chose de semblable) ou bien appeler une procédure qui retourne un résultat via ses paramètres (on en dira davantage plus loin sur les paramètres par référence).

Voici les définitions d'une procédure et deux versions de la même fonction utilisant une syntaxe légèrement différente:

procedure Hello;
begin
  ShowMessage ('Hello world!');
end;

function Double (Value: Integer) : Integer;
begin
  Double := Value * 2;
end;

// ou, deuxième version
function Double2 (Value: Integer) : Integer;
begin
  Result := Value * 2;
end;
L'utilisation de Result plutôt que du nom de la fonction pour affecter la valeur de retour d'une fonction devient très populaire et tend, à notre avis, à rendre le code plus lisible.

Une fois ces routines définies, on peut les appeler une ou plusieurs fois. On appelle la procédure pour qu'elle effectue sa tâche, et on appelle une fonction pour calculer la valeur :

procedure TForm1.Button1Click (Sender: TObject);
begin
  Hello;
end;
procedure TForm1.Button2Click (Sender: TObject);
var
  X, Y: Integer;
begin
  X := Double (StrToInt (Edit1.Text));
  Y := Double (X);
  ShowMessage (IntToStr (Y));
end;
Remarque : Pour le moment,  ne vous occupez pas de la syntaxe des deux procédures ci-dessus, qui sont en fait des méthodes. Placez simplement deux boutons sur une fiche Delphi, double-cliquez sur eux pendant la conception, et l'EDI Delphi générera le code approprié. Il ne vous reste maintenant qu'à remplir les lignes entre begin et end. Pour compiler le code ci-dessus, vous devez également ajouter un contrôle de saisie (TEdit) à la fiche.
 
Revenons à présent au concept d' encapsulation de code que nous avons introduit plus haut. Lors de l'appel de la fonction Double, on ne doit pas connaître l'algorithme utilisé pour l' implémenter. Si plus tard on découvre une meilleure façon de faire pour doubler des nombres, on peut facilement modifier le code de la fonction, mais le code appelant restera inchangé (quoique, à l'exécution, il sera plus rapide!). Le même principe peut être appliqué à la procédure Hello : Nous pouvons modifier le résultat du programme en changeant le code de cette procédure, et la méthode Button2Click changera automatiquement son effet. Voici comment nous pouvons modifier le code :
procedure Hello;
begin
  MessageDlg ('Hello world!', mtInformation, [mbOK]);
end;
Conseil : Quand on appelle une fonction ou une procédure Delphi, ou une méthode VCL, il faut se souvenir du nombre et du type de ses paramètres. L'éditeur Delphi y aide en présentant la liste des paramètres de la fonction ou de la procédure, avec une indication aérienne, dès que l'on tape son nom et la parenthèse ouvrante. Cette fonctionnalité appelée Paramètres du code fait partie de l'Audit de code.

Les paramètres passés par référence

Les routines Pascal permettent le passage de paramètres par valeur et par référence. Par défaut, le passage des paramètres s'effectue par valeur : la valeur est copiée sur la pile et les routines utilisent et manipulent la copie, et non la valeur d'origine.

Passer un paramètre par référence signifie que sa valeur n'est pas copiée sur la pile dans le paramètre formel de la routine (éviter une copie signifie souvent que le programme s'exécute plus rapidement). Par contre, le programme se réfère à la valeur d'origine, même dans le code de la routine. Ceci permet à la procédure ou à la fonction de modifier la valeur du paramètre. Le paramètre passé par référence est signalé par le mot réservé var.
 

Cette technique est disponible dans plusieurs langages de programmation. Elle n'est pas présente en C, mais elle a été introduite en C++, où on utilise le symbole & (passé par référence). En Visual Basic, chaque paramètre non spécifié comme étant ByVal est passé par référence. Voici un exemple de passage d'un paramètre par référence en utilisant le mot réservé var :
procedure DoubleTheValue (var Value: Integer);
begin
  Value := Value * 2;
end;
Dans ce cas, le paramètre est utilisé aussi bien pour passer une valeur à la procédure que pour retourner une nouvelle valeur au code appelant. Quand vous écrivez :
var
  X: Integer;
begin
  X := 10;
  DoubleTheValue (X);
la valeur de la variable X devient 20, parce que la fonction utilise une référence vers l'emplacement mémoire d'origine de X, en affectant sa valeur initiale.

Passer des paramètres par référence a du sens pour les types ordinaux, pour les anciennes chaînes et pour les grands enregistrements. Les objets Delphi, en fait, sont invariablement passés par valeur, parce qu'ils sont eux-mêmes des références. Pour cette raison, passer un objet par référence a peu de sens (mis à part des cas très spéciaux), parce que c'est comme si on passait "une référence à une référence".

Les chaînes longues Delphi ont un comportement légèrement différent : elles se comportent comme des références, mais si on modifie une des variables chaîne en faisant référence à la même chaîne en mémoire, elle est copiée avant sa mise à jour. Mais si on modifie la valeur de la chaîne, la valeur d'origine n'est pas affectée. Par contre si on passe la chaîne longue par référence, on peut modifier sa valeur d'origine.
 

Delphi 3 a introduit un nouveau type de paramètre, le paramètre out. Il s'agit d'un paramètre qui n'a pas de valeur initiale et qui est utilisé uniquement pour retourner une valeur. Ces paramètres ne devraient être utilisés qu'avec les procédures et fonctions COM; en général, il vaut mieux utiliser les paramètres var qui sont plus efficaces. A part le fait qu'ils n'ont pas de valeur initiale, les paramètres out se comportent comme des paramètres var.

Les paramètres constante

Comme alternative aux paramètres passés par référence, on peut utiliser un paramètre constante. Puisque, à l'intérieur de la routine, on ne peut pas affecter une nouvelle valeur à un paramètre constante, le compilateur peut optimiser le passage par paramètre. Le compilateur peut choisir une approche semblable à celle utilisée pour les paramètres passés par référence (ou, en termes de C++, une référence const), mais le comportement restera semblable à celui des paramètres valeur, parce que la valeur d'origine n'est pas affectée par la routine.

En fait, si vous essayez de compiler le code (idiot) suivant, Delphi détectera une erreur :

function DoubleTheValue (const Value: Integer): Integer;
begin
  Value := Value * 2;      // erreur de compilation
  Result := Value;
end;

Les paramètres tableau ouvert

Contrairement au C, les procédures et les fonctions Pascal ont toujours un nombre fixe de paramètres. Cependant, il existe un moyen pour passer un nombre variable de paramètres à une routine en utilisant un tableau ouvert.

La définition de base du paramètre tableau ouvert est celui d'un tableau ouvert typé. Ceci veut dire que l'on indique le type de paramètre, mais on ne connaît pas le nombre d'éléments de ce type que comportera le tableau. Voici un exemple d'une telle définition:

function Sum (const A: array of Integer): Integer;
var
  I: Integer;
begin
  Result := 0;
  for I := Low(A) to High(A) do
    Result := Result + A[I];
end;
En utilisant High(A) nous pouvons obtenir la taille du tableau. Notez aussi l'utilisation de la valeur retournée par la fonction, Result, pour stocker les valeurs intermédiaires. On peut appeler cette fonction en lui passant un tableau d'expressions de types Integer :
X := Sum ([10, Y, 27*I]);
Si on a un tableau d'Integer, de n'importe quelle taille, on peut le passer directement à une routine possédant un paramètre tableau ouvert, ou plutôt, on peut appeler la fonction Slice pour passer uniquement une partie du tableau (comme indiqué par son deuxième paramètre). Voici un exemple où le tableau complet est passé par paramètre.
var
  List: array [1..10] of Integer;
  X, I: Integer;
begin
  // initialiser le tableau
  for I := Low (List) to High (List) do
    List [I] := I * 2;
  // appel
  X := Sum (List);
Si vous souhaitez passer uniquement une partie du tableau à la fonction Slice, appelez-la simplement de cette façon :
X := Sum (Slice (List, 5));
On trouvera tous les fragments du code présenté dans cette section dans l'exemple OpenArr (pour la fiche, voir fig. 6.1, plus loin).

Figure 6.1 : L'exemple OpenArr lorsqu'on presse le bouton Partial Slice

En Delphi 4, les tableaux ouverts typés sont entièrement compatibles avec les tableaux dynamiques (introduits en Delphi 4 et traités dans le chapitre 8). Les tableaux dynamiques utilisent la même syntaxe que les tableaux ouverts avec cette différence qu'on peut utiliser une notation comme array of Integer pour déclarer une variable et non plus uniquement pour passer un paramètre.

Les paramètres tableau ouvert de type variant

En plus des tableaux ouverts typés, Delphi offre la possibilité de définir des tableaux ouverts sans type ou de type variant. Cette sorte spéciale de tableau possède un type indéfini de valeurs et peut être pratique pour le passage de paramètres.

Techniquement, la construction array of const permet, en une fois, de passer à une routine un tableau avec un nombre indéfini d'éléments de types différents. Voici par exemple la définition de la fonction Format (nous verrons, au chapitre 7 qui traite des chaînes de caractères, comment utiliser cette fonction) :

function Format (const Format: string;
  const Args: array of const): string;
Le second paramètre est un tableau ouvert, qui reçoit un nombre indéfini de valeurs. En fait, vous pouvez appeler cette fonction selon les façons suivantes :
N := 20;
S := 'Total:';
Label1.Caption := Format ('Total: %d', [N]);
Label2.Caption := Format ('Int: %d, Float: %f', [N, 12.4]);
Label3.Caption := Format ('%s %d', [S, N * 2]);
Remarquez qu'on peut passer un paramètre soit en tant que valeur constante, la valeur de la variable, soit en tant qu'expression. Déclarer une fonction de ce genre est simple, mais comment l'encoder? Comment connaître les types des paramètres? Les valeurs d'un paramètre tableau ouvert de type variant sont compatibles avec les éléments de type TVarRec.
 
Remarque : Il ne faut pas confondre l'enregistrement TVarRec avec l'enregistrement TVarData utilisé par le type Variant lui-même. Ces deux structures ont un but différent et ne sont pas compatibles. Même les listes des types possibles sont différentes, parce que TVarRec accepte les types de données Delphi, tandis que TVarData accepte les types de données OLE.
 
L'enregistrement TVarRec a la structure suivante :
type

  TVarRec = record

    case Byte of

      vtInteger:    (VInteger: Integer; VType: Byte);

      vtBoolean:    (VBoolean: Boolean);

      vtChar:       (VChar: Char);

      vtExtended:   (VExtended: PExtended);

      vtString:     (VString: PShortString);

      vtPointer:    (VPointer: Pointer);

      vtPChar:      (VPChar: PChar);

      vtObject:     (VObject: TObject);

      vtClass:      (VClass: TClass);

      vtWideChar:   (VWideChar: WideChar);

      vtPWideChar:  (VPWideChar: PWideChar);

      vtAnsiString: (VAnsiString: Pointer);

      vtCurrency:   (VCurrency: PCurrency);

      vtVariant:    (VVariant: PVariant);

      vtInterface:  (VInterface: Pointer);

  end;
Chaque enregistrement possède le champ VType, bien qu'il ne soit pas facile à voir parce qu'il est déclaré une seule fois, ainsi que la vraie donnée taille de type Integer (généralement une référence ou un pointeur).

Grâce à cette information, nous pouvons en fait écrire une fonction capable de fonctionner avec différents types de données. Dans la fonction SumAll, nous souhaitons pouvoir additionner des valeurs de types différents, transformer des chaînes en entiers, des caractères en leur valeur d'ordre correspondant, et additionner 1 pour les valeurs booléennes vraies. Le code est basé sur une instruction case et il est très simple, bien que nous ayons dû déréférencer très souvent des pointeurs :

function SumAll (const Args: array of const): Extended;

var

  I: Integer;

begin

  Result := 0;

  for I := Low(Args) to High (Args) do

    case Args [I].VType of

      vtInteger: Result :=

        Result + Args [I].VInteger;

      vtBoolean:

        if Args [I].VBoolean then

          Result := Result + 1;

      vtChar:

        Result := Result + Ord (Args [I].VChar);

      vtExtended:

        Result := Result + Args [I].VExtended^;

      vtString, vtAnsiString:

        Result := Result + StrToIntDef ((Args [I].VString^), 0);

      vtWideChar:

        Result := Result + Ord (Args [I].VWideChar);

      vtCurrency:

        Result := Result + Args [I].VCurrency^;

    end; // case

end;
Nous avons ajouté ce code dans l'exemple OpenArr qui appelle la fonction SumAll lorsqu'un bouton donné est pressé:
procedure TForm1.Button4Click(Sender: TObject);

var

  X: Extended;

  Y: Integer;

begin

  Y := 10;

  X := SumAll ([Y * Y, 'k', True, 10.34, '99999']);

  ShowMessage (Format (

    'SumAll ([Y*Y, ''k'', True, 10.34, ''99999'']) => %n', [X]));

end;
On peut voir le résultat de cet appel ainsi que la fiche de l'exemple OpenArr à la fig. 6.2.

FIGURE 6.2 : La fiche de l'exemple OpenArr avec la boîte de message ouverte lorsque le bouton Untyped a été pressé.

Les conventions d'appel Delphi

La version 32 bits de Delphi a introduit une nouvelle approche pour le passage de paramètres, l'appel rapide ( fastcall) : chaque fois que c'est possible, on peut passer jusqu'à trois paramètres dans les registres du CPU, de sorte que l'appel à la fonction devient beaucoup plus rapide. La convention d'appel rapide (utilisée par défaut en Delphi 3) est indiquée par la directive register.

Le problème est qu'il s'agit de la convention par défaut et que les fonctions qui l'utilisent ne sont pas compatibles avec Windows : les fonctions API de Win32 doivent être déclarées en utilisant la convention d'appel stdcall, un mélange de la convention d'appel Pascal originale de l'API Win 16 et de la convention d'appel cdecl du langage C.

En général, il n'y a pas de raison de ne pas utiliser la nouvelle convention d'appel rapide, à moins qu'on n'effectue des appels externes Windows ou qu'on ne définisse des fonctions de rappel Windows. Nous verrons un exemple utilisant la convention stdcall avant la fin de ce chapitre. On trouvera un résumé des conventions d'appel de Delphi dans la rubrique Conventions d'appel de l'aide Delphi.

Qu'est-ce qu'une méthode?

Qui a déjà travaillé avec Delphi ou lu les manuels a probablement rencontré le terme méthode. Une méthode est une sorte particulière de fonction ou de procédure qui est relative à un type de données, à une classe. En Delphi, chaque fois que nous gérons un événement, nous devons définir une méthode, habituellement une procédure. En général, cependant, le terme méthode est utilisé pour indiquer soit les procédures soit les fonctions relatives à une classe.

Nous avons déjà vu quelques méthodes dans les exemples de ce chapitre et du chapitre précédent. Voici une méthode vide ajoutée automatiquement au code source d'une fiche par Delphi :

procedure TForm1.Button1Click(Sender: TObject);
begin
  {c'est ici que vous placerez votre code}
end;

Les déclarations forward

Lorsqu'on doit utiliser un identificateur (de n'importe quel genre), le compilateur doit avoir déjà vu une déclaration pour savoir à quoi se réfère l'identificateur. Pour cette raison, on effectue habituellement une déclaration complète avant d'utiliser une routine. Cependant, il existe des cas où ce n'est pas possible. Si une procédure A appelle une procédure B et que la procédure B appelle la procédure A, lorsqu'on commence à écrire le code, on devra appeler une routine pour laquelle le compilateur n'a encore vu aucune déclaration.

Si on souhaite déclarer l'existence d'une procédure ou d'une fonction avec un certain nom et des paramètres donnés, sans fournir son code réel, on peut écrire la procédure ou la fonction suivie du mot clé forward :

procedure Hello; forward;
Par la suite le code devrait fournir une définition complète de la procédure, mais celle-ci peut être appelée avant d'être complètement définie. Voici un exemple idiot, juste pour donner une idée :
procedure DoubleHello; forward;



procedure Hello;

begin

  if MessageDlg ('Do you want a double message?',

      mtConfirmation, [mbYes, mbNo], 0) = mrYes then

    DoubleHello

  else

    ShowMessage ('Hello');

end;



procedure DoubleHello;

begin

  Hello;

  Hello;

end;
Cette approche permet d'écrire de la récursivité indirecte : DoubleHello appelle Hello, mais Hello peut aussi appeler DoubleHello. Évidemment il doit y avoir une condition pour terminer la récursion, pour éviter le dépassement de la pile. On peut trouver ce code avec de légères modifications dans l'exemple DoubleH.

Bien qu'une déclaration de procédure forward ne soit pas très courante en Delphi, il existe un cas similaire beaucoup plus fréquent. La déclaration d'une procédure ou d'une fonction dans la partie interface d'une unité (on en dira plus sur les unités au chapitre suivant) est considérée comme une déclaration forward, même si le mot forward n'est pas présent. En fait, on ne peut pas écrire le corps d'une routine dans l'interface d'une unité. On doit fournir en même temps dans la même unité l'implémentation réelle de chaque routine que l'on a déclarée.

La même chose vaut pour la déclaration d'une méthode dans un type class générée automatiquement par Delphi (puisqu'on a ajouté un événement à la fiche ou à ses composants). Les gestionnaires d'événement déclarés dans une classe TForm sont des déclarations forward : le code sera fourni dans la partie implémentation de l'unité. Voici un extrait du code source d'un exemple précédent, avec la déclaration de la méthode Button1Click:

type

  TForm1 = class(TForm)

    ListBox1: TListBox;

    Button1: TButton;

    procedure Button1Click(Sender: TObject);

  end;

Les types procéduraux

Une autre caractéristique de Pascal Objet est la présence de types procéduraux (N.D.T. : types sous-programme). Il s'agit d'un élément vraiment avancé du langage, que seuls quelques programmeurs Delphi utilisent régulièrement. Cependant, puisque nous discuterons de sujets voisins dans les prochains chapitres (plus précisément des pointeurs de méthode, une technique très utilisée par Delphi), il vaut la peine de l'examiner ici rapidement. Un programmeur novice peut sauter cette section pour le moment et y revenir quand il se sentira prêt.

En Pascal existe le concept de type procédural (semblable au concept de pointeur de fonction du C). La déclaration d'un type procédural présente la liste de paramètres et éventuellement le type retourné, dans le cas d'une fonction. Par exemple, vous pouvez déclarer le type procédure avec un paramètre Integer passé par référence comme suit :

type
  IntProc = procedure (var Num: Integer);
Ce type procédural est compatible avec toute routine comportant exactement les mêmes paramètres (ou la même signature de fonction pour utiliser le jargon C). Voici un exemple d'une routine compatible :
procedure DoubleTheValue (var Value: Integer);
begin
  Value := Value * 2;
end;
Remarque : Dans la version 16 bits de Delphi, les routines doivent être déclarées avec la directive far pour être utilisées en tant que vraies valeurs d'un type procédural.

Les types procéduraux peuvent être utilisés pour deux raisons différentes : on peut déclarer des variables d'un type procédural ou passer un type procédural - un pointeur de fonction - en tant que paramètre à une autre routine. Étant donné les déclarations de type et de procédure précédentes, nous pouvons écrire ce code :

var

  IP: IntProc;

  X: Integer;

begin

  IP := DoubleTheValue;

  X := 5;

  IP (X);

end;
Ce code produit le même effet que la version plus courte ci-après:
var

  X: Integer;

begin

  X := 5;

  DoubleTheValue (X);

end;
La première version est évidemment plus complexe; alors pourquoi l'utiliser? Dans certains cas, il peut être utile de pouvoir décider quelle fonction appeler et l'appeler réellement plus tard. On pourrait construire un exemple complexe pour illustrer cette approche. Cependant, nous préférons vous laisser en explorer un très simple, nommé ProcType. Pour rendre la situation un peu plus réaliste, cet exemple sera plus complexe que ceux que nous avons vus jusqu'ici.

Créez simplement un nouveau projet et placez sur la fiche deux boutons radio et un bouton de commande, comme l'indique la figure 6.3. Cet exemple est basé sur deux procédures. L'une est utilisée pour doubler la valeur du paramètre. Cette procédure est semblable à celle que nous avons déjà utilisée dans cette section. La seconde procédure est utilisée pour tripler la valeur du paramètre, elle s'appelle donc TripleTheValue :

FIGURE 6.3 : La fiche de l'exemple ProcType.

procedure TripleTheValue (var Value: Integer);

begin

  Value := Value * 3;

  ShowMessage ('Value tripled: ' + IntToStr (Value));

end;
Les deux procédures affichent ce qui se passe pour nous montrer qu'elles ont été appelées. On peut utiliser cette simple fonctionnalité de débogage pour tester si, ou quand, une portion de code est exécutée, au lieu d'y insérer des points d'arrêt.

A chaque pression sur le bouton Apply, une des deux procédures est exécutée en fonction de l'état des boutons radio. En fait, si on a deux boutons radio sur une fiche, on ne peut en sélectionner qu'un à la fois. Ce code pourrait être implémenté en testant la valeur des boutons radio à l'intérieur du code pour l'événement OnClick du bouton Apply. Pour montrer l'utilisation des types procéduraux, nous avons par contre utilisé une approche plus longue mais intéressante. A chaque clic sur l'un des boutons radio, une des procédures est stockée dans une variable :

procedure TForm1.DoubleRadioButtonClick(Sender: TObject);
begin
  IP := DoubleTheValue;
end;
Lorsque l'utilisateur clique sur le bouton de commande, la procédure que nous avons stockée est exécutée :
procedure TForm1.ApplyButtonClick(Sender: TObject);
begin
  IP (X);
end;
Pour permettre à trois fonctions différentes d'accéder aux variables IP et X, nous devons les rendre visibles dans toute la fiche; elles ne doivent pas être déclarées localement (à l'intérieur d'une des méthodes). Une solution est de placer ces variables dans la déclaration de la fiche:
type

  TForm1 = class(TForm)

    ...

  private

    { Private declarations }

    IP: IntProc;

    X: Integer;

  end;
Nous verrons exactement ce que cela signifie dans le prochain chapitre. Pour le moment, nous devons modifier le code généré par Delphi pour le type class comme indiqué ci-dessus, et ajouter la définition du type procédural que nous avons montrée ci-dessus. Pour initialiser ces deux variables avec des valeurs convenables, nous pouvons gérer l'événement OnCreate de la fiche (sélectionnez cet événement dans l'inspecteur d'objets après avoir choisi la fiche, ou en double-cliquant simplement sur la fiche). Nous suggérons de regarder le listing sur le CD d'accompagnement pour étudier en détails le code source de cet exemple.
 
Un exemple pratique de l'utilisation des types procéduraux est présenté au Chapitre 9 dans la section Une fonction de rappel Windows.
 

new La surcharge de fonctions

Le concept de surcharge est simple : le compilateur permet de définir deux fonctions ou deux procédures ayant le même nom à condition que les paramètres soient différents. En vérifiant les paramètres, le compilateur détermine quelle version de la routine on souhaite appeler.

Considérons cette série de fonctions extraites de l'unité Math de la VCL :

function Min (A,B: Integer): Integer; overload;

function Min (A,B: Int64): Int64; overload;

function Min (A,B: Single): Single; overload;

function Min (A,B: Double): Double; overload;

function Min (A,B: Extended): Extended; overload;
Lorsque vous appelez Min(10, 20), le compilateur détermine que vous appelez la première fonction du groupe; la valeur retournée sera ainsi un Integer.

Il y a deux règles fondamentales:

  • Chaque version de la routine doit être suivie de la directive overload.
  • Les différences doivent concerner le nombre ou le type des paramètres, ou les deux. Le type retourné, par contre, ne peut pas être utilisé pour distinguer les deux routines.
  • Voici trois versions surchargées de la procédure ShowMsg, ajoutées à l'exemple Overdef (une application illustrant la surcharge et les paramètres par défaut) :
    procedure ShowMsg (str: string); overload;
    
    begin
    
      MessageDlg (str, mtInformation, [mbOK], 0);
    
    end;
    
    
    
    procedure ShowMsg (FormatStr: string;
    
      Params: array of const); overload;
    
    begin
    
      MessageDlg (Format (FormatStr, Params),
    
        mtInformation, [mbOK], 0);
    
    end;
    
    
    
    procedure ShowMsg (I: Integer; Str: string); overload;
    
    begin
    
      ShowMsg (IntToStr (I) + ' ' + Str);
    
    end;
    Les trois fonctions affichent une boîte de message contenant une chaîne, en la formatant éventuellement de différentes manières. Voici les trois appels du programme :
    ShowMsg ('Hello');
    
    ShowMsg ('Total = %d.', [100]);
    
    ShowMsg (10, 'MBytes');
    Nous sommes agréablement surpris de constater que la technologie Paramètres du code fonctionne bien avec les procédures et fonctions surchargées. Dès que vous tapez la parenthèse ouverte après le nom de la routine, toutes les possibilités disponibles sont affichées. Dès que vous entrez les paramètres, Delphi utilise leur type pour déterminer quelle possibilité est encore disponible. La figure 6.4 montre que si vous commencez à taper une constante chaîne, Delphi affiche uniquement les versions compatibles (en omettant la version de la procédure ShowMsg qui comporte un premier paramètre de type Integer).
     
    Figure 6.4 : Les différentes possibilités offertes par Paramètres du code pour routines surchargées sont filtrées en fonction des paramètres déjà disponibles.


     

    Le fait que chaque version d'une routine surchargée doit être correctement marquée implique que l'on ne peut pas surcharger une routine existante de la même unité si celle-ci n'est pas marquée à l'aide de la directive overload. (Le message d'erreur que l'on reçoit est alors: 'La déclaration précédente de '<nom>' n'a pas été marquée de la directive 'overload'). Cependant, on peut surcharger une routine qui a été initialement déclarée dans une autre unité, et ce pour assurer la compatibilité avec les versions précédentes de Delphi qui permettaient à différentes unités de réutiliser le même nom de routine. On remarquera, en tout cas, que ce cas spécial ne constitue pas une fonctionnalité supplémentaire de la surcharge mais n'est qu'une indication des problèmes que l'on peut rencontrer.

    Par exemple, vous pouvez ajouter à une unité le code suivant :

    procedure MessageDlg (str: string); overload;
    
    begin
    
      Dialogs.MessageDlg (str, mtInformation, [mbOK], 0);
    
    end;
    Ce code ne surcharge pas véritablement la routine initiale MesageDlg. En effet, si vous écrivez :
    MessageDlg ('Hello');
    vous obtiendrez un petit message d'erreur signalant que quelques paramètres font défaut. La seule façon d'appeler la version locale au lieu de celle de la VCL est de faire explicitement référence à l'unité locale, ce qui contrecarre l'idée de surcharge :
    OverDefF.MessageDlg ('Hello');

    new Les paramètres par défaut

    Une nouvelle fonctionnalité de Delphi 4 est la possibilité de donner une valeur par défaut aux paramètres d'une fonction et d'appeler la fonction avec ou sans le paramètre. Prenons un exemple. Nous pouvons définir l'encapsulation suivante de la méthode MessageBox de l'objet global Application, qui utilise les PChars au lieu des strings, en prévoyant deux paramètres par défaut :
    procedure MessBox (Msg: string;
    
      Caption: string = 'Warning';
    
      Flags: LongInt = mb_OK or mb_IconHand);
    
    begin
    
      Application.MessageBox (PChar (Msg),
    
        PChar (Caption), Flags);
    
    end;
    Avec cette définition, nous pouvons appeler la procédure d'une des façons suivantes :
    MessBox ('Quelque chose ne va pas ici!');
    
    MessBox ('Quelque chose ne va pas ici!', 'Attention');
    
    MessBox ('Hello', 'Message', mb_OK);
    La figure 6.5 montre que Paramètres du code de Delphi utilise à bon escient un style différent pour indiquer les paramètres qui ont une valeur par défaut, permettant ainsi de déterminer facilement quel paramètre a été omis.
     
    Figure 6.5 : Le Paramètres du code de Delphi délimite par des crochets les paramètres possédant des valeurs par défaut; on peut les omettre lors de l'appel.


     

    Notez que Delphi ne génère pas de code spécial pour supporter les paramètres par défaut; il ne crée pas non plus de copies multiples des routines. Les paramètres absents sont simplement ajoutés au code appelant par le compilateur.

    Une restriction importante concernant l'utilisation des paramètres par défaut est que l'on ne peut pas "sauter" des paramètres. Par exemple, on ne peut pas passer le troisième paramètre à la fonction après avoir omis le deuxième :

    MessBox ('Hello', mb_OK); // erreur
    La règle principale pour les paramètres par défaut est la suivante : dans un appel, on ne peut omettre les paramètres qu'à partir du dernier. Autrement dit, si l'on omet un paramètre, on doit aussi omettre ceux qui le suivent.

    Voici quelques autres règles concernant les paramètres par défaut :

    Utiliser en même temps des paramètres par défaut et la surcharge peut provoquer quelques problèmes, vu que les deux fonctionnalités peuvent entrer en conflit. Si, par exemple, nous ajoutons à l'exemple précédent la nouvelle version de la procedure ShowMsg que voici :
    procedure ShowMsg (Str: string; I: Integer = 0); overload;
    
    begin
    
      MessageDlg (Str + ': ' + IntToStr (I),
    
        mtInformation, [mbOK], 0);
    
    end;
    le compilateur ne protestera pas; la définition est légale. Cependant, l'appel :
    ShowMsg ('Hello');
    est signalé par le compilateur comme appel surchargé Ambigu de ShowMsg. Notez que cette erreur survient dans une ligne de code correctement compilée avant la nouvelle définition de surcharge. En pratique, il n'est pas possible d'appeler la procédure ShowMsg avec un paramètre de type string, car le compilateur ne peut pas savoir si on veut appeler la version ayant uniquement le paramètre de type string ou celle ayant le paramètre de type string et le paramètre de type Integer avec une valeur par défaut. Devant un tel doute, le compilateur s'arrête et demande au programmeur de fixer ses intentions plus clairement.

    Conclusion

    Écrire des procédures et des fonctions est un élément fondamental de la programmation, bien que, en Delphi, vous écriviez plutôt des méthodes qui sont des procédures et des fonctions liées à des classes et à des objets.

    Maintenant, au lieu d'aborder les caractéristiques de l'orienté objet, quelques chapitres seront consacrés à des détails sur d'autres éléments de programmation Pascal, à commencer par les chaînes.

    Chapitre suivant: La gestion des chaînes
     
    © Copyright Marco Cantù, Wintech Italia Srl 1995-99