![]() |
|
Marco Cantù
Traduit de l'anglais par Iannis Papageorgiadis [email protected] |
Chapitre 4
|
Avec la notion de type, une des grandes idées introduites par le langage Pascal est la possibilité de définir dans un programme de nouveaux types de données. Les programmeurs peuvent définir leurs propres types de données au moyen de constructeurs de types, tels des types sous-ensemble, des tableaux, des enregistrements, des types énumérés, des pointeurs et des ensembles. Le type de données le plus important défini par l'utilisateur est la classe; il fait partie des extensions orientées objet de Pascal Objet (point qui n'est pas traité dans cet ouvrage).
Si vous pensez que les constructeurs de type sont courants dans la plupart des langages de programmation, vous avez raison, mais Pascal a été le premier langage à introduire cette idée dans une voie formelle et très précise. Rares sont les langages possédant tant de mécanismes pour définir de nouveaux types.
type
// définition d'un intervalle
Uppercase = 'A'..'Z';
// définition d'un tableau
Temperatures = array [1..24] of Integer;
// définition d'un enregistrement
Date = record
Month: Byte;
Day: Byte;
Year: Integer;
end; // définition d'un type énuméré
Colors = (Red, Yellow, Green, Cyan, Blue, Violet);
// définition d'un ensemble
Letters = set of Char;Des constructions similaires de définition de type peuvent être utilisées directement pour définir une variable sans qu'elle ait un nom de type explicite, comme on le voit dans le code suivant :
var
DecemberTemperature: array [1..31] of Byte;
ColorCode: array [Red..Violet] of Word;
Palette: set of Colors;
type
Ten = 1..10; OverHundred = 100..1000; Uppercase = 'A'..'Z';Dans la définition d'un intervalle, on ne doit pas spécifier le nom du type de base. On doit seulement fournir deux constantes de ce type. Le type d'origine doit être un type scalaire et le type dérivé sera un autre type scalaire.
Quand vous avez défini un intervalle, vous pouvez légalement lui assigner une valeur à l'intérieur de ce type. Le code suivant est valide:
var
UppLetter: Uppercase;
begin
UppLetter := 'F';Mais celui-ci ne l'est pas:
var
UppLetter: Uppercase;
begin
UppLetter := 'e'; // erreur de compilationEn écrivant le code ci-dessus, vous provoquez une erreur de compilation: "L'expression constante dépasse les limites de sous étendu". Si par contre vous écrivez le code suivant:
var
UppLetter: Uppercase;
Letter: Char;
begin
Letter :='e';
UppLetter := Letter;Delphi le compilera. A l'exécution, si vous avez activé l'option Vérification des limites (dans la page Compilateur de la boîte de dialogue Options du menu Projet), vous obtiendrez un message d'erreur Erreur de vérification d'étendue.
Remarque : Nous suggérons d'activer cette option du compilateur pendant le développement de votre programme; il sera ainsi plus robuste et plus facile à déboguer puisqu'en cas d'erreur, vous aboutirez à un message d'erreur explicite et non à un comportement indéterminé. Vous pouvez éventuellement désactiver l'option pour la construction finale du programme afin de le rendre un peu plus rapide. Cependant, la différence est vraiment minime; c'est pourquoi nous suggérons de laisser activés tous ces contrôles, même dans un programme de navigation. Cela vaut aussi pour toutes les autres options de contrôle à l'exécution, tels que dépassement de capacité et contrôle de pile.
type
Colors = (Red, Yellow, Green, Cyan, Blue, Violet);
Suit = (Club, Diamond, Heart, Spade);Chaque valeur de la liste possède un rang associé, commençant à zéro. Si vous appliquez la fonction Ord à une valeur d'un type énuméré, vous obtiendrez cette valeur sur base zéro. Par exemple, Ord
type
TFormBorderStyle = (bsNone, bsSingle, bsSizeable,
bsDialog, bsSizeToolWin, bsToolWindow);Quand la valeur d'une propriété est une énumération, vous pouvez habituellement choisir dans la liste de valeurs affichées dans l'Inspecteur d'Objets, comme le montre la figure 4.1.
Figure 4.1: Une propriété de type énuméré dans l'inspecteur d'objets
Le fichier d'aide de Delphi donne généralement la liste des valeurs possibles d'une énumération. Comme alternative vous pouvez utiliser le programme OrdType, dans le répertoire TOOLS du code source du livre, pour voir la liste des valeurs de chaque énumération Delphi, ensemble, intervalle et tout autre type scalaire. Vous trouverez un exemple de l'affichage de ce programme à la figure 4.2.
FIGURE 4.2 : Information détaillée à propos d'un type énuméré, comme l'affiche le programme OrdType du répertoire TOOLS du code source du livre.
Une variable contient habituellement une des valeurs possibles de l'intervalle
de son type. Une variable de type ensemble, par contre, peut ne contenir
aucune, ou contenir une, deux, trois ou plusieurs valeurs de l'intervalle.
Elle peut même comprendre toutes les valeurs. Voici un exemple d'un
ensemble
type
Letters = set of Uppercase;Maintenant nous pouvons définir une variable de ce type et lui assigner quelques valeurs du type de base. Pour indiquer quelques valeurs dans un ensemble, on écrit une liste d'éléments, séparés par une virgule, encadrés par des crochets carrés. Le code suivant montre l'assignation à une variable de plusieurs valeurs, d'une seule valeur et d'une valeur vide
var Letters1, Letters2, Letters3: Letters;
begin
Letters1 := ['A', 'B', 'C'];
Letters2 := ['K'];
Letters3 := [];En Delphi, un ensemble est généralement utilisé pour indiquer des symboles non exclusifs. Par exemple, les deux lignes de code suivantes (qui font partie de la bibliothèque Delphi) déclarent une énumération d'icônes possibles pour une fenêtre et le type ensemble correspondant
type
TBorderIcon = (biSystemMenu, biMinimize, biMaximize, biHelp);
TBorderIcons = set of TBorderIcon;En fait, une fenêtre donnée pourrait ne comporter aucune de ces icônes, ou l'une d'elles, ou plus d'une. Lorsque on travaille dans l'inspecteur d'objets (Figure 4.3), on peut fournir les valeurs d'un ensemble en étendant la sélection (double cliquez sur le nom de la propriété ou cliquez sur le signe plus à sa gauche) et en insérant ou en éliminant chacune des valeurs.
Figure 4.3 : Une propriété de type ensemble dans l'inspecteur d'objets
Une autre propriété basée sur un type ensemble est le style d'une fonte (police). Ces valeurs indiquent une police en gras, en italique, en souligné ou en barré. Bien sûr la même fonte peut avoir les deux attributs italique et gras, ne pas avoir d'attribut ou les avoir tous. Pour cette raison le style est déclaré comme étant un ensemble.
Font.Style := []; // aucun style
Font.Style := [fsBold]; // gras uniquement
Font.Style := [fsBold, fsItalic]; // deux stylesVous pouvez également travailler de plusieurs manières différentes sur un ensemble, y compris en ajoutant deux variables du même type ensemble (ou, pour être plus précis, en calculant l'union de deux variables ensemble) :
Font.Style := OldStyle + [fsUnderline]; // deux ensemblesEn outre, vous pouvez utiliser les exemples OrdType inclus dans le répertoire TOOLS du code source pour voir la liste des valeurs possibles de plusieurs ensembles définis par la bibliothèque des composants Delphi.
type
DayTemperatures = array [1..24] of Integer;Dans la définition du tableau, vous devez indiquer un type intervalle à l'intérieur des crochets, ou définir un nouveau type intervalle spécifique en utilisant deux constantes d'un type scalaire. Cet intervalle spécifie les indices valides du tableau. Puisque vous spécifiez aussi bien l'indice maximum que l'indice minimum du tableau, les indices ne doivent pas commencer à zéro, comme c'est le cas en C, C++, Java et dans d'autres langages de programmation.
Puisque les indices des tableaux sont basés sur les intervalles, Delphi peut contrôler leur étendue comme nous l'avons déjà dit. Une constante d'intervalle invalide aboutit à une erreur de compilation ; et un indice hors intervalle utilisé à l'exécution aboutit à une erreur d'exécution si l'option correspondante du compilateur est activée.
En utilisant la définition de tableau ci-dessus, vous pouvez affecter la valeur d'une variable DayTemp1 de type DayTemperatures comme ci-après:
type
DayTemperatures = array [1..24] of Integer; var
DayTemp1: DayTemperatures; procedure AssignTemp;
begin
DayTemp1 [1] := 54;
DayTemp1 [2] := 52;
...
DayTemp1 [24] := 66;
DayTemp1 [25] := 67; // erreur de compilationUn tableau peut avoir plusieurs dimensions, comme dans les exemples suivants :
type
MonthTemps = array [1..24, 1..31] of Integer;
YearTemps = array [1..24, 1..31, Jan..Dec] of Integer;Ces deux types tableau sont construits sur les mêmes types de noyau. Ainsi vous pouvez les déclarer en utilisant les types de données précédentes, comme dans le code suivant :
type
MonthTemps = array [1..31] of DayTemperatures;
YearTemps = array [Jan..Dec] of MonthTemps;Cette déclaration inverse l'ordre des indices présentés ci-dessus, mais elle permet aussi l'assignation de blocs entiers entre des variables. Par exemple, l'instruction suivante copie les températures de Jan dans Feb :
var
ThisYear: YearTemps;
begin
...
ThisYear[Feb] := ThisYear[Jan];On peut également définir un tableau à base zéro, un type tableau avec l'indice inférieur à zéro. Généralement l'utilisation de limites plus logiques est un avantage, puisque vous ne devez pas utiliser l'indice 2 pour accéder au troisième élément, etc. Windows, cependant, utilise invariablement les tableaux à base zéro (parce qu'il est construit sur le langage C), et la bibliothèque de composants Delphi a tendance à faire de même.
Si vous devez travailler sur un tableau, vous pouvez toujours tester
ses limites en utilisant les fonctions standard Low et High,
qui retournent les limites inférieure et supérieure. Il est
vivement recommandé d'utiliser Low et High lorsqu'on
travaille sur un tableau, spécialement dans les boucles, puisque
cela rend le code indépendant de l'intervalle du tableau. Par la
suite, on peut modifier l'intervalle déclaré des indices
du tableau, et le code qui utilise Low et High fonctionnera
encore.
Voici un petit listing avec la définition d'un type enregistrement, la déclaration d'une variable de ce type et quelques instructions utilisant cette variable :
type Date = record Year: Integer; Month: Byte; Day: Byte; end; var BirthDay: Date; begin BirthDay.Year := 1997; BirthDay.Month := 2; BirthDay.Day := 14;Les classes et les objets peuvent être considérés comme une extension du type enregistrement. Les bibliothèques Delphi ont tendance à utiliser les types classe plutôt que les types enregistrement, mais il existe de nombreux types enregistrement définis par l'API Windows.
L'utilisation d'un type enregistrement variable n'est pas sans danger et ne constitue pas une pratique de programmation à recommander, spécialement pour les débutants. Les programmeurs chevronnés peuvent par contre utiliser les types enregistrement variable, et le noyau des bibliothèques Delphi les utilise. De toute façon, vous n'aurez pas besoin de vous attaquer à ceux-ci tant que vous n'êtes pas un expert Delphi.
type
PointerToInt = ^Integer;Une fois que l'on a défini une variable pointeur, on peut lui affecter l'adresse d'une autre variable du même type en utilisant l'opérateur @
var
P: ^Integer;
X: Integer;
begin
P := @X;
// modifier la valeur de deux façons différentes
X := 10;
P^ := 20;Lorsqu' on a un pointeur P, avec l'expression P on fait référence à l'adresse de l'emplacement mémoire auquel se réfère aussi le pointeur; par l'expression P^ on fait référence au contenu réel de cet emplacement mémoire. C'est la raison pour laquelle dans le fragment de code ci-dessus P^ correspond à X.
Au lieu de faire référence à un emplacement mémoire existant, un pointeur peut faire référence à un bloc mémoire alloué dynamiquement (dans le tas (heap)) à l'aide de la procédure New. Dans ce cas quand on n'a plus besoin du pointeur, on doit aussi se débarrasser de la mémoire qu'on avait allouée dynamiquement, en faisant appel à la procédure Dispose.
var
P: ^Integer;
begin
// initialization
New (P);
// operations
P^ := 20;
ShowMessage (IntToStr (P^));
// termination
Dispose (P);
end;Si un pointeur n'a pas de valeur, on peut lui affecter la valeur nil. On peut alors tester si un pointeur est nil pour voir s'il fait à ce moment référence à une valeur. Ceci est souvent utilisé, parce que le fait de déréférencer un pointeur non valide provoque une violation d'accès (connue également sous le nom de faute générale de protection, GPF) :
procedure TFormGPF.BtnGpfClick(Sender: TObject);
var
P: ^Integer;
begin
P := nil;
ShowMessage (IntToStr (P^));
end;Vous pouvez voir un exemple de l'effet de ce code en exécutant l'exemple GPF (ou en examinant la figure correspondante 4.4). L'exemple contient également des fragments du code ci-dessus.
Figure 4.4 : L'erreur système provoquée par l'accès à un pointeur nil, dans l'exemple GPF.
Dans le même programme vous pouvez trouver un exemple d'accès sûr aux données. Dans ce deuxième cas, le pointeur est assigné à une variable locale et il peut être utilisé sans danger, mais nous avons quand même ajouté un contrôle de sécurité:
procedure TFormGPF.BtnSafeClick(Sender: TObject); var P: ^Integer; X: Integer; begin P := @X; X := 100; if P <> nil then ShowMessage (IntToStr (P^)); end;Delphi définit aussi un type de données Pointer qui indique des pointeurs sans type (comme void* en C). Si vous utilisez un pointeur sans type, vous devez utiliser GetMem au lieu de New. La procédure GetMem est requise chaque fois que la taille de la variable mémoire à allouer n'est pas définie.
Le fait que les pointeurs sont rarement nécessaires en Delphi
est un avantage considérable de cet environnement. Néanmoins,
comprendre les pointeurs est important pour la programmation avancée
et pour la compréhension totale du modèle objet de Delphi,
qui utilise les pointeurs "dans les coulisses".
type
IntFile = file of Integer;On peut ensuite ouvrir un fichier physique associé à cette structure et y écrire des valeurs entières ou lire les valeurs courantes de ce fichier. (Les exemples concernant les fichiers figuraient dans les éditions plus anciennes de Mastering Delphi et nous avons prévu de les annexer également ici).
L'utilisation des fichiers en Pascal est très aisée, mais dans Delphi il existe aussi des composants capables de stocker leur contenu dans un fichier ou de le charger depuis un fichier. Il existe un support de suivi, sous la forme de flux, et également un support base de données.
© Copyright Marco Cantù, Wintech Italia Srl 1995-99 |