Explorer les Livres électroniques
Catégories
Explorer les Livres audio
Catégories
Explorer les Magazines
Catégories
Explorer les Documents
Catégories
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.
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.
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) :
TVarRec = record
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 :
var
I: Integer;
begin
Result := 0;
vtBoolean:
vtChar:
vtExtended:
vtWideChar:
vtCurrency:
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é.
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.
procedure Hello;
begin
DoubleHello
else
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)
end;
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 :
begin
Value := Value * 3;
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 :
TForm1 = class(TForm)
...
private
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 :
begin
end;
procedure ShowMsg (FormatStr: string;
begin
end;
begin
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');
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 :
begin
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');
begin
end;
Avec cette définition, nous pouvons appeler la procédure d'une des façons
suivantes :
MessBox ('Quelque chose ne va pas ici!');
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 :
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
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.