Vous êtes sur la page 1sur 18

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".
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.
 
 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');

 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 :

 Les paramètres par défaut doivent se trouver à la fin de la liste des paramètres.
 Les valeurs par défaut doivent être des constantes. Ceci limite évidemment les types que l'on
peut utiliser avec les paramètres par défaut. Par exemple, un tableau dynamique ou un type
interface ne peut avoir un paramètre par défaut autre que nil; les enregistrements ne peuvent
pas du tout être utilisés.
 Les paramètres par défaut doivent être passés par valeur ou en tant que constante. Un
paramètre par référence (var) ne peut pas avoir de valeur 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

Vous aimerez peut-être aussi