Vous êtes sur la page 1sur 138

Chapitre 1 ENVIRONNEMENT DE TRAVAIL

A la fin de cette partie, vous serez capable de:

• connaître quelques composants de base de Delphi


• utiliser quelques outils,
• créer, ouvrir et sauvegarder un projet,
• distinguer entre propriétés et événements concernant les composants Delphi

1.1 Introduction

Après son lancement Delphi se présente sous la forme de 4 fenêtres. Cette présentation n'est pas
courante parmi les applications Windows. Toutefois elle se révèle relativement pratique.
La première fenêtre occupe la partie supérieure de l'écran; elle correspond à l'environnement de
programmation proprement dit:

Fig. 1.1
Cette fenêtre contient:
• la barre de titre
• la barre de menu de Delphi
• une zone "barre d'outils" (sur la gauche)
• une zone contenant les divers composants regroupés par familles.

La seconde fenêtre se trouve par défaut à gauche de


l'écran: c'est l'inspecteur d'objets. Il permet de visualiser,
pour chaque objet ou composant, les propriétés et les
événements auxquels l'objet peut répondre:

fig. 1.2

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 1-1


La troisième fenêtre constitue la fiche principale de la future application Delphi. Il s'agit, au départ,
d'une fenêtre vide dans laquelle on placera les divers objets:

fig. 1.3

La dernière fenêtre, cachée sous la précédente constitue l'"éditeur" proprement dit, contenant le
code source de l'application:

fig. 1.4

On peut déjà constater que Delphi génère automatiquement le code correspondant à la fiche
principale Form1. Dans Delphi, chaque fiche est décrite comme un objet (associé à une classe).
A ce point, une remarque s'impose. Du fait de différentes traductions de l'anglais on peut trouver
plusieurs termes décrivant le même objet à savoir une fenêtre: forme, feuille, fiche, fenêtre. Nous
utiliserons le terme "fiche".
Delphi applique un principe fondamental lors de la création d'applications:

Chaque fiche correspond à une unité

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 1-2


On peut voir sur la figure 1.4 que Delphi a automatiquement déclaré la variable Form1. Cette
variable définit un objet qui est une instance de la classe Tform1.
Avant d'aller plus loin, il faut insister sur un point essentiel de l'utilisation de Delphi. Pour le confort
de l'utilisateur, Delphi donne automatiquement un nom à chaque nouvel objet ou composant. Il
s'agit d'un nom par défaut. Par exemple Form1, Button1, Button2, Listbox1... On peut bien
entendu travailler avec ces noms ou donner un nom (explicite) à chaque nouvel objet. Au lieu de
Button1 on aura, par exemple, Quitter; au lieu de Listbox1 on aura, par exemple Couleurs.

1.2 Quelques options des menus et quelques outils

Dans le menu Outils, l'option Options d'environnement, permet d'activer les options d'auto-
enregistrement, ce qui force l'enregistrement du projet avant chaque exécution. Lors des premiers
pas en Delphi, il est vivement conseillé d'enclencher les options d'auto-enregistrement.

fig 1.5

Sur la fiche (voir figure 1.3) une grille en pointillés est affichée. Dans le concepteur de fiche, on
peut choisir d'afficher ou non la grille, d'aligner ou non les objets sur la grille et choisir également
la taille des mailles de la grille.
Lors du premier démarrage de Delphi, une nouvelle application est crée. Lors des démarrages
suivants, la dernière application utilisée dans Delphi est automatiquement rechargée.
Pour démarrer une nouvelle application, il faut choisir l'option Nouvelle application du menu
Fichier.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 1-3


fig 1.6

Pour sauvegarder une application, il faut choisir l'option Tout enregistrer du menu Fichier. Une
règle à suivre absolument est de créer un répertoire par application. Delphi créant plusieurs
fichiers pour une application donnée, il plus facile de les retrouver s'il ne sont pas enregistrés avec
d'autres fichiers de noms pratiquement identiques.
Lors du premier "tout enregistrement" de l'application, une fenêtre permet de choisir
l'emplacement de sauvegarde et même de le créer.
L'icône Tout enregistrer se retrouve dans la barre d'outils.

fig 1.7

Pour exécuter une application, il faut choisir l'option Exécuter du menu Exécuter. Si les options
d'auto-enregistrement ont été sélectionnées et que l'application n'a encore jamais été
sauvegardée, la fenêtre d'enregistrement s'affiche. L'application est ensuite compilée puis
exécutée, si elle ne contient pas d'erreur. L'icône Exécuter se retrouve dans la barre d'outils.

fig 1.8

Quitter
L'option Quitter du menu Fichier permet de quitter Delphi, après les vérifications d'usage, à
savoir la sauvegarde éventuelle des fichiers.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 1-4


1.3 Premier projet

Ce premier projet en Delphi a l'allure suivante:

fig 1.9

La fiche contient cinq boutons:


• Un clic sur les boutons Rouge, Bleu ou Jaune change la couleur de la fiche en celle
indiquée
• Un clic sur le bouton Reset remet la fiche à sa couleur initiale
• Un clic sur le bouton Fin permet de quitter l'application.
Nous allons voir, pas à pas, comment réaliser cette application.
Le titre qui s'affiche dans la barre de titre de la fiche est "Premier projet". Il faut l'indiquer dans la
propriété Caption de la fiche:

fig 1.10
Delphi baptise la première fiche d'un projet du nom de Form1. Pour la sélectionner, il faut cliquer
n'importe où sur la fiche. Son nom apparaît alors dans le haut de l'Inspecteur d'objets. Il faut que
l'onglet Propriétés soit sélectionné pour pouvoir modifier les propriétés de la fiche. Le texte
indiqué pour la propriété Caption apparaît dans la barre de titre de la fiche.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 1-5


Une autre propriété nous intéresse pour cette application, c'est la propriété Color. La valeur par
défaut choisie par Delphi est "clBtnFace". Un clic sur la flèche fait apparaître une liste de couleurs
prédéfinies, qui sont soit des "vraies" couleurs, comme par exemple clBlue, clRed, etc. soit des
couleurs définies dans la section Couleur du Panneau de configuration Windows (par exemple,
clBtnFace renvoie à la couleur système des faces de boutons).

fig 1.11

Pour l'instant, il ne faut pas modifier cette propriété.


Il faut ensuite ajouter 5 boutons sur la fiche. L'onglet Standard de la palette des composants étant
sélectionné, cliquez sur l'icône du bouton puis sur la fiche à l'emplacement choisi (coin supérieur
gauche). Il faut répéter ces opérations pour les quatre autres boutons. Delphi leur donne les noms
Button1,.., Button5 qui sont également affichés sur chacun des boutons.

fig 1.12

Quelques propriétés des boutons:


• Caption: texte affiché sur le bouton. Une esperluette ( & ) précède le raccourci
• Name: nom du bouton, utilisé par le programmeur
• Top: position verticale, nombre de pixels le séparant du haut de la fiche
• Left: position horizontale, nombre de pixels le séparant de la gauche de la fiche
• Height: hauteur du bouton, en pixels
• Width: largeur du bouton, en pixels
Il faut changer le nom (propriété Name) de chaque bouton, ce qui modifie automatiquement la
propriété Caption si elle n'a pas déjà été modifiée. Il faut ensuite ajouter l'& dans la propriété
Caption, devant la lettre raccourci.
L'application n'est pas terminée, mais nous allons déjà l'exécuter pour voir ce que Delphi génère.
La touche <F9> est le raccourci pour Exécuter. Etant donné que nous avions sélectionné les
options d'auto-enregistrement, il faut maintenant indiquer le répertoire où stocker l'application.
ATTENTION!!! Une seule application par répertoire !!!
Delphi propose le nom Unit1.pas, pour l'unité Pascal attachée à la fiche, et project1.dpr pour le
projet. Il est conseillé de choisir un autre nom pour le projet (par exemple: exemple1), car c'est ce
nom que portera l'exécutable créé par Delphi.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 1-6


Le programme s'exécute. Il est possible de cliquer sur les boutons, mais ça n'a aucun effet, Il est
possible de maximiser, minimiser et fermer la fenêtre, et ça a de l'effet… A la fermeture de la
fenêtre, nous nous retrouvons dans l'environnement de développement.
Il manque à l'application, une action derrière chaque bouton. Je veux que quand l'utilisateur clique
sur le bouton Rouge, la fiche devienne rouge. L'événement déclencheur est: l'utilisateur clique
sur le bouton Rouge. Ce qui se produit est: la fiche devient rouge. Pour le programmer:
• Sélectionner le bouton Rouge
• Choisir l'onglet Evénements dans l'inspecteur d'objets
• Double-cliquer à côté de l'événement OnClick, l'éditeur apparaît:

fig 1.13

Le curseur se retrouve dans la procédure Tform1.RougeClick (la procédure qui sera exécutée
lorsque le bouton Rouge de la fiche Form1 sera cliqué). Le code à y écrire est:
form1.color := clred;
la valeur clRed (rouge) est assignée ( := ) à la propriété Color de la fiche Form1. Vous
noterez qu'il n'y a pas de distinction majuscule/minuscule au niveau du langage de
programmation.
Pour les boutons Bleu, Jaune et Reset, la manière de procéder est semblable, la couleur
assignée lors du Reset est clBtnFace.
Un clic sur le bouton Fin doit provoquer la fermeture de l'application, ce qui correspond au code:
Application.terminate;
Note: la touche <F11> commute entre la fiche et l'éditeur.

Voici le code de ce premier exemple en Delphi:


procedure TForm1.RougeClick(Sender: TObject);
begin
form1.Color := clred;
end;

procedure TForm1.BleuClick(Sender: TObject);


begin
form1.color := clblue;
end;

procedure TForm1.JauneClick(Sender: TObject);


begin
form1.color := clyellow;
end;

procedure TForm1.ResetClick(Sender: TObject);


begin
form1.color := clBtnFace;
end;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 1-7


procedure TForm1.FinClick(Sender: TObject);
begin
application.Terminate;
end;

1.4 Labels et Edits

Label
Une étiquette (Label) sert à afficher du texte fixe, c'est-à-dire non modifiable par l'utilisateur.
Cependant il arrive assez souvent de modifier un label au moment de l'exécution du programme.

Label
Edit

fig 1.14
Dans cet exemple (figure 1.14) on peut voir un cas courant: l'association d'un Label et d'un Edit.
Mais un Label peut aussi servir à placer des titres sur une fiche.

Quelques propriétés des labels:


• Caption: texte constituant le Label
• Color: couleur de fond
• Font: choix du style de l'écriture (fonte, taille, couleur du texte...)
• Name: nom du composant
• Visible indique si le composant est visible ou non lors de l'exécution

Edit
Un Edit est un composant utilisé chaque fois que l'utilisateur doit fournir une information sous
forme de texte (voir figure 1.14). Généralement il est utilisé lorsque le texte à saisir n'est pas trop
long, car un Edit ne peut pas avoir de Scroll bar ni être "multilignes". Pour saisir de longs
fragments de texte on utilisera plutôt un composant Memo.

Quelques propriétés des Edits:


• BorderStyle: style de bordure autour de l'Edit.
• Color: couleur de fond.
• Enabled: actif (si oui l'utilisateur peut y accéder, sinon il n'est pas possible
d'atteindre l'Edit)
• Font: choix du style de l'écriture (fonte, taille, couleur du texte...)

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 1-8


• MaxLength: limite le nombre de caractère à saisir (0 signifie qu'il n'y a pas de limite)
• Name: nom du composant
• TabStop: si oui, on peut se placer sur le composant en appuyant sur la touche
<Tab>
• Text: texte qui apparaît par défaut dans l'Edit
• Visible: indique si le composant est visible ou non lors de l'exécution

Evénements importants:
• OnChange: lorsque le contenu de l'Edit change. Par exemple quand l'utilisateur tape,
supprime, modifie des caractères dans l'Edit.
• OnEnter: lorsque le focus arrive sur l'Edit. En d'autres termes, lorsque l'Edit devient
actif (le curseur y arrive).
• OnExit: lorsque l'Edit perd le focus.
• OnKeyPress: lorsque l'utilisateur tape sur une touche dans l'Edit.

La propriété Text de l'Edit peut contenir uniquement du texte. Pour afficher la valeur d'un nombre,
il faut d'abord le convertir en texte ou chaîne de caractères.
Si i est un nombre de type entier: Edit1.Text := IntToStr (i);

Si x est un nombre de type réel: Edit1.Text := FloatToStr (x);

Ou Edit1.Text := FloatToStrF (x, ffFixed, 10, 4);


Lorsque l'utilisateur saisit un nombre dans un Edit, Editx.Text contient une chaîne de caractères.
Pour pouvoir effectuer des calculs, il faut convertir la chaîne de caractères.
Pour obtenir une valeur entière, à condition que la chaîne de caractères à convertir ne contienne
pas de caractères illicites: i := StrToInt (Edit1.Text);
Pour obtenir une valeur réelle, à condition que la chaîne de caractères à convertir ne contienne
pas de caractères illicites: x := StrToFloat (Edit1.Text);

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 1-9


Exercice 1.1:
Reprendre les mêmes objets que dans l'exemple de la page 1.7 et y ajouter un Edit qui est de
même couleur que la fiche et ne contient pas de texte. Un clic sur un bouton doit provoquer le
changement de couleur de l'Edit et non plus de la fiche.
La propriété Text de l'Edit contient le texte affiché dans l'Edit.
Voici comment se présente le programme:

Exercice 1.2:
Placer quatre boutons (Matin, Midi, Soir, Nuit) et un Edit sous chacun d'eux. Au départ, seuls les
boutons sont visibles. Un clic sur le bouton Matin fait apparaître l'Edit situé en-dessous et où il est
écrit Matin, si un autre Edit est visible, il doit disparaître. Idem pour les trois autres boutons.
Pour rendre l'Edit1 visible:
Edit1.Visible := true;
Pour rendre l'Edit1 invisible:
Edit1.Visible := false;
Pour mettre un texte par programmation dans l'Edit1:
Edit1.Text := 'Bonjour';
Voici comment se présente le programme:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 1-10


Delphi (J-C Armici janvier 2003 www.unvrai.com) page 1-11
Chapitre 2 LE LANGAGE PASCAL DE BASE

A la fin de cette partie, vous serez capable de:

• connaître la structure du langage Pascal (non objet);


• utiliser les divers types de données du Pascal;
• utiliser des instruction séquentielles, itératives et sélectives

2.1 La programmation structurée

Un ordinateur peut être assimilé à un système produisant des résultats à partir d'informations
fournies et de "marches à suivre" permettant de les traiter. Les informations sont constituées par
des données, et les méthodes de traitement par des algorithmes. Pour obtenir des résultats, la
description des données et les algorithmes doivent être codés sous forme de programmes
interprétables par l'ordinateur. En effet, le processeur de celui-ci ne peut exécuter qu'un nombre
relativement restreint d'instructions élémentaires (le code machine).
Les programmes sont donc le résultat d'une succession d'étapes comprises entre la spécification
informelle du problème et sa codification. Il y a ainsi entre ces deux pôles un "trou" qu'il s'agit de
combler. Parmi les moyens ou les outils permettant d'y parvenir on peut citer notamment des
environnements de production de logiciel (par exemple Delphi), des méthodes fournissant un
encadrement au concepteur ou encore des langages de spécification permettant de préciser les
étapes intermédiaires. Un autre aspect du rapprochement de la phase de codification vers la
spécification du problème est constitué par le développement ou l'utilisation de langages de
programmation permettant un niveau d'abstraction plus élevé.
Un des objectifs de la programmation structurée est la conception de logiciel fiable, efficace et
d'une maintenance plus aisée. Il peut être atteint de manière asymptotique et par divers moyens.
Les trois caractéristiques citées peuvent difficilement être évaluées dans l'absolu, car elles
dépendent souvent de critères relatifs et subjectifs. Un programme n'est pas juste ou faux; sa
qualité est une notion globale, constituée par plusieurs éléments, dont nous allons étudier les plus
importants.
La fiabilité est une propriété informelle et parfois difficile à cerner. Cette propriété peut être atteinte
grâce à deux qualités du langage de programmation. D'abord, la facilité d'écriture doit permettre
d'exprimer un programme de façon naturelle ou en termes du problème à résoudre. Le
programmeur ne doit pas être dérangé par des détails ou des habitudes du langage, mais doit
pouvoir se concentrer sur la solution recherchée. Les langages modernes de haut niveau tendent
vers cet objectif. Ensuite, la lisibilité du programme doit permettre d'en saisir aisément la
construction logique et de détecter plus facilement la présence d'erreurs. Dans cette optique,
l'instruction goto, par exemple, rend difficile la lecture du programme de façon descendante.
Toutefois, dans certains cas, les objectifs énoncés au début de cette section peuvent être atteints
dans de meilleures conditions par l'utilisation d'un goto (bien placé et bien documenté) plutôt que
par une construction structurée; sa présence est alors acceptable. De telles situations sont
toutefois extrêmement rares.
Si l'efficacité était au début l'objectif principal de la conception d'un programme, actuellement cette
notion a évolué pour englober non seulement des critères de vitesse d'exécution et de place
mémoire, mais aussi l'effort requis pour la maintenance du logiciel.
En effet, la nécessité de la maintenance impose au logiciel qu'il soit lisible et modifiable. Ces
qualités sont souvent liées à des critères esthétiques. On peut néanmoins citer quelques facteurs
qui facilitent les interventions dans un programme: un découpage approprié, une mise en page

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-1


claire, un choix adéquat des noms d'objets utilisés, des commentaires cohérents placés
judicieusement.

2.2 Forme générale d'un programme Delphi

Dans Delphi on est peu confronté au programme proprement dit, mais plutôt à des unités, ce qui
constitue une grande différence par rapport au Pascal traditionnel. De plus, le programme
(principal), appelé projet:
• est généralement sauvegardé dans un fichier .dpr (pour Delphi Project);
• est toujours de petite taille;
• est automatiquement créé par Delphi;
• contient les références aux unités qui constituent l'application;
• initialise l'application, crée les différentes fiches et lance l'exécution de l'application.
En voici un exemple

program Project2;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2};

{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TForm2, Form2);
Application.Run;
end.
Ainsi, la ligne marquée d'une flèche signifie que le programme utilise l'unité Unit1 stockée dans le
fichier Unit1.pas et concerne la fiche Form1.
Lors du développement on est sans cesse en train de travailler dans des unités de Delphi. En voici
un exemple:

unit Unit1;

interface

uses Windows, Messages, SysUtils, Classes, Graphics,


Controls, Forms, Dialogs, StdCtrls;

Const MAX = 10;


type
TForm1 = class(TForm)
Button1: TButton;
Partie procedure Button1Click(Sender: TObject);
interface private
{ Déclarations privées }
public
{ Déclarations publiques }
end;

var Form1: TForm1;


tab : array[1..MAX] of integer;

procedure Titre (taille: integer);

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-2


implementation

uses Unit2;
Partie {$R *.DFM}
implémentation
procedure TForm1.Button1Click(Sender: TObject);
begin
form2.show;
end;

end.

Une unité comporte:


• un en-tête
• une partie interface dans laquelle figurent les objets exportables (visibles de l'extérieur
de l'unité):
• déclaration de constantes, de types (par exemple les objets) et de variables
• des déclarations de procédures et/ou fonctions exportables
• une partie implémentation dans laquelle figurent les objets privés de l'unité, ainsi que
le code des procédures et/ou fonctions déclarées dans la partie interface ainsi que de
celles strictement privées;
• le corps de l'unité, ou partie d'initialisation, qui est souvent vide et réduite à "end.".
En Pascal, il n'est pas possible de définir ou déclarer un objet à n'importe quel emplacement du
programme. Ces définitions doivent être regroupées dans la partie réservée aux déclarations. De
plus:
Tout objet référencé dans un programme doit avoir été préalablement défini.
Cette règle traduit une logique qui veut que des objets nouveaux soient construits uniquement à
l'aide d'objets connus.
Enfin, on peut signaler une particularité intéressante: des déclarations de constantes, types et
variables peuvent être placées entre les procédures. Dans ce cas, le domaine de visibilité de ces
objets est constitué par la portion du programme située après ces déclarations. Cette possibilité
permettant, par exemple, de disposer de variables "semi-globales" tend à rendre les programmes
moins lisibles et va à l'encontre des principes de base liés à la notion de procédure.

2.3 Différents objets d'un programme

Si l'on répertorie les différents objets d'un programme, on obtient les catégories suivantes:

identificateurs

Chaque fois que l'on fait référence à un objet du programme (une variable, une constante, le nom
d'une procédure...), c'est par l'intermédiaire d'un nom, appelé identificateur. Un identificateur est
une suite de caractères de longueur non limitée dont le premier doit obligatoirement être une lettre
(non accentuée). Après cette première lettre peuvent figurer exclusivement des chiffres, des
lettres (non accentuées) ou des caractères de soulignement (dans un ordre quelconque). Le
caractère de soulignement est souvent utilisé dans le but d'améliorer la lisibilité. Voici quelques
exemples d'identificateurs:
A Epsilon45 revenu_brut

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-3


Le choix des identificateurs peut également favoriser la lecture et la compréhension d'un
programme. Il est déconseillé d'utiliser des identificateurs trop courts, ne suggérant aucune
signification (par exemple A, X2, z), ainsi que des identificateurs trop longs, nécessitant plus de
travail lors de l'écriture, et surtout n'offrant pas forcément une meilleure lisibilité.
Delphi ne distingue pas les minuscules des majuscules formant un identificateur. Ainsi les trois
noms qui suivent désignent le même objet:
Rendement rendement RENDEMENT

mots réservés du langage

Il s'agit de mots ou de symboles qu'il n'est pas possible d'utiliser comme identificateurs déclarés
dans le programme. En voici des exemples:
begin program and until

constantes

Il s'agit, comme leur nom l'indique, d'objets qui gardent leur valeur tout au long de l'exécution d'un
programme. Les constantes peuvent être de différents types, et constituées, entre autres, de
nombres, de chaînes de caractères ou de caractères. Voici quelques exemples de constantes:
128 15.625 'A' 'Début'

identificateurs standard

Ce sont des identificateurs connus du langage Pascal, mais qui peuvent être redéfinis par
l'utilisateur. On trouve parmi les identificateurs standard:
• les types standard
exemple: integer real byte
• les procédures standard
exemple: reset
• les fonctions standard
exemple: sin ord chr round eoln

2.4 Constantes et variables

Le langage Pascal fait une distinction entre constantes et variables (ce qui n'est pas le cas pour
des langages comme le BASIC). On utilise une constante chaque fois qu'un objet garde la même
valeur tout au long d'un programme. Une constante reçoit une valeur au moment de la compilation
du programme et cette valeur ne peut pas être modifiée. Une variable sera au contraire utilisée
comme un objet dont la valeur peut être modifiée durant l'exécution du programme. Comme tous
les objets définis par le programmeur, les constantes et les variables doivent être déclarées avant
leur utilisation. Voici un exemple de déclaration de constantes et de variables:
procedure premier;
const nul = 0;
code = 'secret';
zede = 'z';
var age : integer;
salaire : real;
sexe : char;
...
Dans le cas des constantes, l'identificateur est suivi du signe "=" et de la valeur que l'on associe à
la constante. Pour les variables, l'identificateur est suivi du signe ":" et du type de la variable.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-4


Chaque déclaration de constante ou de variable se termine par un point virgule. Si plusieurs
variables sont du même type, il est possible de les déclarer sous forme de liste d'identificateurs
(séparés par une virgule) suivie du signe ":" et du type des variables:

var age, enfants, voitures : integer;


salaire, taille : real;
Les déclarations précédentes peuvent également s'écrire:

var age : integer; { du père }


enfants : integer; { à charge }
voitures : integer; { disponibles }
salaire : real; { maximum }
taille : real; { du capitaine }

2.5 Commentaires

Dans le but d'améliorer la lisibilité et la compréhension des programmes, il est fortement conseillé
d'introduire des commentaires. Un commentaire est un texte explicatif plus ou moins long, placé
dans le programme, et ignoré par le compilateur. Les commentaires sont donc totalement
invisibles et inutiles dans la phase de compilation et d'exécution d'un programme, mais d'une
importance primordiale dans les phases de conception, de mise au point et de maintenance.
Ces explications, visibles uniquement dans les fichiers source (contenant le texte du programme),
sont essentiellement destinées aux personnes susceptibles d'analyser un programme ou de lui
apporter des modifications. Dans la plupart des cas, les commentaires sont destinés à l'auteur du
programme. Mais dans tous les cas où un programme est réalisé en équipe, ou repris par d'autres
personnes que l'auteur, les commentaires doivent être une aide à la compréhension, surtout dans
certains passages peu évidents. Il n'est pas rare de rencontrer des programmes où les
commentaires occupent plus de place que les instructions elles-mêmes. Il ne faut cependant pas
oublier de mettre à jour les commentaires lors de la modification de tout ou partie du programme.
Un commentaire devenu caduque ou qui n'a pas été mis à jour perd son utilité et peut même
devenir un obstacle à la compréhension d'un programme.
En Pascal, les commentaires sont reconnus comme tels par le compilateur grâce à des marques
de début et de fin qui sont soit des accolades { }, soit les symboles (* et *). Il est possible
d'imbriquer un type de commentaire dans l'autre type de commentaire. En voici quelques
exemples:

(* Programme écrit par : Durant Eva *)


{ imbrication de commentaires (* très drôles *)}
Delphi ajoute également un troisième type de commentaire issu du langage C: chaque ligne
commençant par // est considérée comme un commentaire. Dans ce cas, le commentaire ne peut
s'étendre que sur une ligne et ne possède qu'une marque de début:
//ceci est un commentaire du troisième type

2.6 Affectation

L'affectation (ou assignation) est l'une des instructions les plus importantes en Pascal. Elle permet
de placer une valeur, qui est le résultat de l'évaluation d'une expression, dans une position
mémoire référencée par une variable:
variable := expression;
où variable est l'identificateur d'une variable qui a été déclarée
auparavant.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-5


L'expression peut être une simple variable ou constante, ou bien une combinaison de constantes,
variables et opérateurs arithmétiques. Le symbole := indique l'affectation et pourrait être traduit
par "prend la valeur de". Les deux signes qui le composent doivent être accolés.
En Pascal, on a voulu éviter toute confusion entre le symbole de l'affectation et celui de l'égalité
(représenté par le signe "="). Lors d'une affectation, l'expression qui se trouve à droite du symbole
:= est évaluée, ensuite seulement le résultat de l'évaluation est affecté à la variable située à
gauche du symbole :=. De plus, il est impératif que le type de l'expression soit le même que le
type de la variable à laquelle on affecte le résultat de l'expression.

2.7 Instructions et blocs d'instructions

Nous avons vu précédemment que les instructions sont séparées par des points virgules. Lorsque
plusieurs instructions forment logiquement un tout, on est souvent amené à les grouper. On
obtient alors un bloc d'instructions. Le début d'un tel bloc est indiqué par le mot réservé begin,
alors que sa fin est indiquée par le mot réservé end. On parle parfois d'"instruction composée" au
lieu de "bloc d'instructions". Ceci exprime bien le fait qu'un groupement de plusieurs instructions
peut être vu comme une seule instruction (indivisible). Le corps d'un programme Pascal est lui-
même un bloc d'instructions. Voici un exemple de bloc d'instructions:

begin
age := 55;
no := age * 10;
end;
Lorsque l'on écrit ses premiers programmes, les points virgules posent parfois des problèmes. En
fait, ce qui peut sembler au début une contrainte, devient naturel après un temps d'adaptation. La
règle concernant les points virgules est la suivante:

Chaque instruction doit se terminer par un point-virgule. Toutefois, le point-virgule peut être omis
s'il est suivi des mots réservés end ou until. Il doit être omis s'il est suivi du mot réservé else
(sauf s'il s'agit d'une structure sélective case...of).
En suivant cette règle, l'exemple précédent aurait pu s'écrire de la manière suivante

begin
age := 55;
no := age * 10
end;
Bien que cette forme d'écriture soit tout à fait correcte, il est conseillé de l'éviter au profit de la
première citée. L'économie de points virgules peut parfois conduire à des erreurs de syntaxe lors
de la modification d'un programme. Ajoutons, par exemple, une ligne à la fin du bloc d'instructions
(avant le end). Le risque d'oublier de placer un point virgule à la fin de la ligne qui précède donne
le résultat suivant:

begin
age := 55;
no := age * 10 (* il manque un ; ici *)
no := no - age (* ligne ajoutée *)
end;
Ce fragment de programme n'est pas correct, car il manque le séparateur entre les deux dernières
instructions. Le lecteur jugera lui-même, expérience faite, de l'opportunité de placer un séparateur
d'instructions avant un end ou un until.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-6


2.8 Types scalaires et expressions

Parmi les avantages qu'offre le langage Pascal, on trouve une grande richesse de types de
données. Nous avons vu précédemment qu'à chaque variable correspond un type. La notion de
type est très importante puisqu'elle détermine la nature et l'ensemble des valeurs que peut
prendre une variable. Dans cette section nous nous intéressons aux types scalaires, caractérisés
par un ensemble de valeurs ordonnées et non structurées.
Parmi les types scalaires on trouve les types entiers, réels, booléen et caractère, ainsi que les
types énumérés ou définis par l'utilisateur. Nous étudierons également les expressions qu'il est
possible de construire sur la base de chacun de ces types. Une expression est une combinaison
d'objets qui sont des opérateurs, des opérandes et des parenthèses.
Nous serons amenés à évoquer la représentation interne des nombres, ou du moins la place
mémoire occupée par une variable d'un type donné. En informatique, l'unité de mesure de la
capacité mémoire est l'octet (byte); un octet étant lui-même composé de 8 chiffres binaires (bits)
pouvant prendre chacun la valeur 0 ou 1.

Les types entiers

Il existe plusieurs type permettant de stocker des valeurs entière. Voici leurs caractéristiques:

Type Intervalle Format/taille


Byte 0..255 non signé, 1 octet
Word 0..65535 non signé, 2 octets
Shortint -128..127 signé, 1 octet
Smallint -32768..32767 signé, 2 octets
Integer -2147483648.. 2147483647 signé, 4 octets
Cardinal 0.. 4294967295 non signé, 4 octets
Longint -2147483648.. 2147483647 signé, 4 octets
Longword 0..4294967295 non signé, 4 octets
Int64 2^63..2^63–1 Signé, 8 octets
Tableau 2.1
Il convient de signaler que les types integer et cardinal sont stockés sur 2 octets sous Windows
3.x et sur 4 octets sous Windows 95 ou NT.
Malgré leurs particularités, les différents types entiers se comportent de la même manière. Pour
garder une cohérence dans les types de données, une opération entre deux nombres entiers doit
fournir un résultat de type entier. Ceci est effectivement le cas pour:
• l'addition 4+5 donne 9
• la soustraction 12 - 20 donne -8
• la multiplication 6*7 donne 42
Mais la division pose un problème:
le quotient de 6 par 4 donne 1.5 qui n'est pas un nombre entier.
Il a donc fallu implémenter une division, spécifique aux nombres entiers, qui conserve le type des
opérandes. En Pascal, cette division entière s'écrit div :
6 div 4 donne 1
10 div 3 donne 3

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-7


Le résultat de cette division correspond à la troncation de la partie décimale. Parallèlement à cette
division entière, il est souvent utile de connaître le reste de la division entre deux nombres entiers.
L'opérateur mod (abréviation de modulo) permet le calcul de ce résultat:
10 mod 3 donne 1
23 mod 4 donne 3

Lorsque des opérateurs et des opérandes sont combinés de manière à former une expression, il
est nécessaire d'avoir une convention permettant de l'évaluer. Ainsi l'expression 4 * 2 + 3
donnera-t-elle le résultat 11 ou bien 20 ? Tout dépend de l'ordre dans lequel sont effectuées les
opérations. Les conventions d'évaluation en vigueur en Pascal correspondent à des règles de
priorité, semblables à celles qui existent en mathématique:
• les opérateurs div, mod, et * sont prioritaires par rapport aux opérateurs + et - ;
• dans chacune de ces deux catégories les opérateurs ont la même priorité;
• en cas d'égalité de priorité, les opérations concernées sont effectuées de gauche à
droite.
Ainsi:
4*2+3 donne 11
8 + 4 * 3 div 2 donne 14
6 mod 4 * 2 div 3 donne 1

Pour modifier ces règles de priorité, il est toujours possible d'utiliser les parenthèses:
4 * (2 + 3) donne 20
7 div ((5 mod 3) mod 4) donne 3

Il est également possible de faire précéder un nombre par l'opérateur unaire "-":
-4 * 12 donne -48
4 * (-5) donne -20

Dans les programmes écrits en TURBO Pascal, les nombres entiers peuvent également être
exprimés en notation hexadécimale, autrement dit en base 16. Cette possibilité est appréciée par
certains programmeurs, car elle leur permet, dans des situations bien particulières, une
représentation des nombres plus proche de celle rencontrée en langage machine.
Chaque fois qu'une constante numérique entière intervient dans une expression, il est possible de
l'exprimer sous forme hexadécimale. Pour cela il faut placer le signe $ immédiatement devant le
nombre exprimé en base 16. Ainsi l'expression
total := 2 * 30;
peut s'écrire
total := $2 * $1E;
Cette notation est utilisable pour tous les types entiers.

Les types réels

Les différents types réels se différencient par leur domaine de définition, le nombre de chiffres
significatifs (précision) et la place mémoire occupée. Le tableau suivant résume ces différentes
caractéristiques:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-8


Type Intervalle Chiffres Taille en
octets
Real48 2.9 10-39..1.7 1038 11-12 6
Single 1.5 10-45..3.4 10 38 7-8 4
Double
5.0 10-324..1.7 10308 15-16 8
Real
Extended 1.9 10-4951..1.1 104932 19-20 10
63 63
Comp -2 +1..2 -1 19-20 8
Currency -922337203685477.5808.. 19-20 8
922337203685477.5807
Tableau 2.2

Conversion réels/entiers et entiers/réels

Il existe deux manières de convertir une valeur réelle en valeur entière: l'arrondi et la troncation.
Le Pascal dispose de deux fonctions standard qui effectuent ces opérations sur les nombres réels.

La troncation
La fonction trunc agit de manière à tronquer la partie décimale d'un nombre réel. Son utilisation
est illustrée par les exemples qui suivent:
trunc (4.2) donne 4
trunc (4.99) donne 4
trunc (-12.6) donne -12
trunc (3.01) donne 3

r := 5.67; { r est de type réel }


i := trunc (r); { i est de type entier et contient 5}

L'arrondi
La fonction round permet d'arrondir un nombre réel à l'entier le plus proche:
round (2.99) donne 3
round (2.5) donne 3
round (2.499) donne 2
round (-7.8) donne -8

r := 4.77; { r est de type réel }


i := round (r); { i est de type entier et contient 5}

Cette fonction possède également la propriété que round(-x) est équivalent à -round(x).
Les fonctions prédéfinies s'expriment par un identificateur (le nom de la fonction) suivi d'un ou
plusieurs arguments placés entre parenthèses. L'argument est la valeur transmise à la fonction en
vue d'un traitement. Le résultat en retour est porté par l'identificateur de fonction et peut, par
exemple, être affiché ou intervenir dans une expression.
Edit1.Text := IntToStr (trunc (3.6 * (round (1.4) - 2.5)));

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-9


La conversion d'une valeur entière en une valeur réelle s'effectue sans passer par des fonctions
spécifiques. Lors de l'affectation d'une expression de type entier à une variable de type réel, la
conversion de type est implicite. Dans les instructions qui suivent, a et b sont des variables de
type entier et c de type réel:

a := 6;
b := 2;
c := 2 * a + b; {c contiendra la valeur 14.0}
Très souvent le programmeur est confronté des expressions mixtes, contenant aussi bien des
valeurs entières que réelles. Dans ce cas, le résultat sera de type réel.
c := 15 div a; c contiendra 2.0
15 divisé par 6 donne 2 (division entière);
c := 15 / a; c contiendra 2.5
la division "réelle" de deux entiers donne un résultat réel;
c := 1.5 * (a + b); c contiendra 12.0
il s'agit d'une expression mixte dont le résultat est réel.

Type booléen (boolean)

L'ensemble des valeurs constituant le type booléen (boolean) est réduit à deux identificateurs de
constantes prédéfinies: true (vrai) et false (faux). On parle souvent de variables ou d'expressions
logiques, car le langage Pascal est fortement inspiré de la logique mathématique en ce qui
concerne les opérations associées au type booléen. Les constantes et variables de ce type se
déclarent de la manière suivante:
const demo = true;
var majuscules, termine : boolean;

La constante demo a la valeur true; les variables majuscules et termine peuvent recevoir les
valeurs true ou false, selon leur utilisation dans le programme.
Parmi les opérateurs booléens on trouve, dans une première catégorie, les opérateurs
relationnels. La liste qui suit présente les différents opérateurs relationnels utilisables en Pascal,
avec leur symbole et leur signification:
< plus petit
> plus grand
= égal
<> différent
<= inférieur ou égal
>= supérieur ou égal
Ces opérateurs se rencontrent dans les expressions booléennes correspondant à des conditions.
Comme nous le verrons plus loin, les conditions interviennent notamment dans les instructions
sélectives et répétitives. Voici un exemple illustrant l'utilisation d'opérateurs relationnels:

begin
...
vieux := age >= 60;
...
end.

Dans ce programme, age >= 60 est une expression booléenne. Selon le contenu de la variable
age, le résultat de cette expression est true ou false. La variable vieux étant de type booléen,
elle peut recevoir ce résultat.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-10


La seconde catégorie d'opérateurs booléens est constituée par des opérateurs purement logiques.
Ils sont au nombre de quatre, et sont illustrés ci-dessous par des exemples:
not négation logique (non)
si x > 12 est vrai, alors not (x > 12) est faux

and conjonction logique (et)


(x > 2) and (x < 10) est vrai, si (x > 2) est vrai et (x < 10) est vrai

or disjonction logique (ou)


(x < 2) or (x > 10) est vrai, si (x < 2) est vrai ou (x > 10) est vrai

xor disjonction logique exclusive


a xor b est vrai si a et b n'ont pas la même valeur logique

Comme il est possible, et même très courant, de construire des expressions logiques contenant
aussi bien des opérateurs arithmétiques que logiques, il convient d'étendre les conventions de
priorité établies précédemment. Nous avons quatre classes d'opérateurs indiquées dans la liste
qui suit, en partant de la plus prioritaire:
1) not
2) * / div mod and
3) + - or xor
4) < <= = <> >= >
Dans chacune des classes, les opérateurs ont la même priorité. En cas d'égalité de priorité, les
opérations correspondantes sont effectuées de gauche à droite. Les parenthèses peuvent servir à
forcer la priorité. Voici, à titre d'exemple, comment s'exprimerait en Pascal l'expression "somme
est comprise entre 10 et 35":
(somme > 10) and (somme < 35)
Examinons deux autres exemples d'expressions booléennes:
age < date + 100
il s'agit ici de comparer le contenu de la variable age avec le résultat de date + 100. L'opérateur +
a une plus grande priorité que l'opérateur <. De ce fait, cette expression booléenne est
équivalente à age < (date + 100);

age < 40 and revenu > 6000

cette expression provoquera un message d'erreur de la part du compilateur, car elle présente un
conflit de types. En effet, la priorité de l'opérateur and étant plus élevée que celle des opérateurs
relationnels < et >, elle sera interprétée comme
age < (40 and revenu) > 6000
qui n'est pas une expression correcte.
En Pascal, l'affectation d'une expression booléenne à une variable de type booléen permet
souvent d'économiser une instruction sélective if. Le programme qui suit est tout à fait correct:

var riche : boolean;


revenu : real;
...
revenu := StrToFloat (Edit1.Text);
if revenu > 4000.0 then

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-11


riche := true
else
riche := false;
...
mais il est plus concis d'écrire:

var riche : boolean;


revenu : real;
...
revenu := StrToFloat (Edit1.Text);
riche := revenu > 4000.0;
...
Ces deux programmes sont équivalents et riche vaut true ou false selon la valeur introduite par
l'utilisateur. Malheureusement la seconde forme est fréquemment négligée au profit de la
première.

Type caractère (char)

Ce type dénote un ensemble de caractères, fini et ordonné. Chacun de ces caractères peut être
exprimé grâce à son code ASCII. Une variable de type caractère (char) peut contenir un seul
caractère, généralement spécifié entre apostrophes. Comme nous le verrons plus loin, il est
possible de constituer des suites de caractères appelées chaînes de caractères. Voici un petit
programme qui met en évidence l'emploi des variables et constantes de type caractère:

const effe = 'F';


var lettre : char;
bip : char;
...
bip := chr(7);
lettre := 'h';
mot.text := effe;
...
Dans cet exemple, la constante effe contient une lettre majuscule indiquée entre apostrophes et la
variable bip contient le caractère dont le code ASCII est 7. Ce code correspond à l'émission d'un
bref signal sonore par le haut-parleur de l'ordinateur.
La fonction prédéfinie chr permet de référencer tous les caractères, y compris certains caractères
du code ASCII qui ne sont pas affichables. L'argument de cette fonction est précisément le code
ASCII du caractère désiré. Pour connaître le code des caractères disponibles, il convient de se
reporter au manuel de référence de l'ordinateur utilisé.
Delphi dispose de deux facilités supplémentaires pour désigner les caractères. L'une est
d'indiquer le symbole # suivi du code du caractère. Cette notation est équivalente à la fonction
chr et permet, par exemple, d'incorporer des caractères particuliers dans une chaîne de
caractères. L'autre concerne uniquement les caractères de contrôle (non affichables). On peut
les spécifier en les faisant précéder du symbole '^'.
#27'G'
cette suite de caractères comprend le caractère correspondant à la touche <Esc>, suivi du
caractère G. Cette séquence de caractères pourrait, si elle était par exemple envoyée à une
imprimante, servir à placer celle-ci dans un mode d'impression donné;
if c = #13 then...
cette instruction permet de déterminer si la variable c (de type caractère) contient le caractère
correspondant à la touche <Return>. On aurait également pu écrire:
if c = chr(13) then...

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-12


Type énuméré

En Pascal, l'utilisateur peut définir de nouveaux types de données, qui n'existent pas
intrinsèquement dans le langage. On parle de types énumérés, ou types scalaires définis par
l'utilisateur, ou encore types scalaires déclarés. Le programmeur spécifie lui-même l'ensemble des
valeurs appartenant au type qu'il définit. Ces valeurs constituent une liste d'identificateurs de
constantes que le programmeur doit indiquer dans la partie du programme réservée aux
déclarations. Lors de la déclaration d'un type énuméré on indique, entre parenthèses, toutes les
valeurs possibles constituant ce type. Voici comment définir un type énuméré dont les valeurs sont
les quatre saisons:

type saisons = (printemps, ete, automne, hiver);


var periode : saisons;
Le type saisons est déclaré à l'aide du mot réservé type. Dès ce moment, il peut figurer dans des
déclarations ultérieures, au même titre qu'un type prédéfini.
La possibilité de définir ses propres types de données et de les adjoindre aux types prédéfinis
permet une plus grande souplesse du langage et du traitement des données. En voici un exemple:
...
type fruits = (pommes, poires, ananas, peches, amandes, noix);
jours = (lundi, mardi, mercredi, jeudi, vendredi,
samedi, dimanche);
langages= (VisualBasic, Delphi, Pascal, inconnu,Francais);
var dessert, fruit : fruits;
aujourdhui : jours;
langue : langages;

...
dessert := amandes;
if aujourdhui = dimanche then
liste.items.add ('Congé');
...
fig. 2.3

Les types énumérés sont toutefois soumis à un certain nombre de restrictions. Par exemple, la
lecture et l'affichage des valeurs figurant dans un type énuméré ne sont pas autorisés, car il ne
s'agit pas de chaînes de caractères, mais d'identificateurs. Une autre contrainte est qu'une même
valeur ne peut pas figurer dans deux listes différentes, c'est-à-dire dans deux types énumérés
différents. Ainsi, la déclaration suivante n'est pas autorisée:

type appareils = (television, aspirateur, transistor,


ventilateur);
electronique = (resistance, condensateur, diode,
transistor);
En revanche, les possibilités offertes sont intéressantes. Comme pour tous les types scalaires
(sauf le type réel), les valeurs des types énumérés sont ordonnées et dénombrables, ce qui rend
possible l'utilisation des fonctions prédéfinies pred (prédécesseur), succ (successeur) et ord
(numéro d'ordre). Les valeurs d'un type énuméré sont ordonnées en fonction de l'ordre dans
lequel elles apparaissent lors de la déclaration, la première valeur d'un type porte le numéro
d'ordre 0. Voici quelques exemples, se référant aux déclarations de la figure 2.3, qui illustrent
l'emploi de ces nouvelles fonctions en relation avec les types entiers, booléen, caractère et
énuméré:
ord (6) vaut 6
ord ('A') vaut 65
succ ('b') vaut 'c'
pred (167) vaut 166

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-13


ord (mardi) vaut 1
pred (dimanche) vaut samedi
succ (lundi) vaut mardi

mais aussi:
ord (ord (mercredi)) vaut 2
pred (pommes) n'est pas correct
succ (Francais) n'est pas correct
pred (succ(poires)) vaut poires
succ (pred(poires)) vaut poires
pred (pred(dimanche)) vaut vendredi
pred (true) vaut false
succ (false) vaut true
ord (false) vaut 0

Lorsqu'un type énuméré compte 256 valeurs ou moins, ces dernières sont représentées par un
nombre de type byte. Dans le cas contraire, elles le sont par un nombre de type word. La fonction
pred appliquée au premier élément d'un type ordinal ou la fonction succ appliquée au dernier
élément d'un type ordinal ne fournissent pas un résultat correct.
Du fait qu'elles sont ordonnées, les valeurs d'un type énuméré peuvent être comparées à l'aide
des opérateurs relationnels. Les instructions sélectives et répétitives suivantes tirent profit de cette
possibilité:

if dessert >= amandes then


liste.items.add ('Fruits secs');

if aujourdhui < samedi then


liste.items.add ('Jour ouvrable');

while (langue = Pascal) or (langue = Francais) do ...

for fruit:=pommes to peches do


if dessert = fruit then
liste.items.add ('Ce n''est pas un fruit sec');

repeat
fruit := succ (fruit);
until fruit = noix;

Type intervalle

Nous avons vu qu'à chaque type de données est associé un ensemble de valeurs. Les variables
déclarées avec un type donné peuvent prendre uniquement des valeurs correspondant à ce type.
Mais il est fréquent d'utiliser seulement une portion de l'ensemble des valeurs possibles. Dans ce
cas, on pourrait restreindre cet ensemble de valeurs à l'intervalle qui nous intéresse. Le langage
Pascal permet cette restriction en faisant appel au "type" intervalle, qui n'est pas un type au sens
strict, mais un sous-ensemble de valeurs prises dans un type de base. Ce type de base peut être
un type scalaire quelconque, à l'exception d'un type réel. L'exemple qui suit illustre de quelle
manière l'étendue d'un type peut être restreinte à un intervalle:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-14


type lettre = 'A'..'Z';
entre_deux_guerres = 1919..1938;
minuscules = 'a'..'z';

var caractere : lettre;


date : entre_deux_guerres;
min : minuscules;
fig. 2.4

Le type lettre est un intervalle défini par rapport au type caractère. Donc, seuls les caractères
compris entre 'A' et 'Z' peuvent être affectés à la variable caractere. De même, la variable date
pourra prendre uniquement des valeurs entières comprises entre 1919 et 1938. Les bornes de
l'intervalle sont incluses dans l'ensemble des valeurs possibles.
Le type intervalle est essentiellement utilisé dans deux buts. D'abord pour améliorer la lisibilité et
la compréhension, mais également afin d'augmenter la fiabilité des programmes, car le Pascal
détecte si une variable reçoit une valeur hors de l'intervalle déclaré. Dans l'exemple de la figure
2.4, il est sous-entendu que la variable min ne contiendra que des lettres minuscules. Si, au cours
de l'exécution du programme, cette variable reçoit une autre valeur, une erreur sera signalée. Ce
genre de problème devrait inciter le programmeur à revoir son programme et éventuellement à
corriger une erreur de logique.
Le type énuméré sert souvent de type de base au type intervalle. L'exemple qui suit en est une
illustration:

type jours = (lundi, mardi, mercredi, jeudi, vendredi, samedi,


dimanche);
week_end = samedi..dimanche;

var aujourdhui : jours;


conge : week_end;
travail : lundi..vendredi;
L'utilisation du type intervalle entre dans le cadre de la discipline que le programmeur s'impose
pour faciliter une intervention ultérieure sur son programme. Cet effort supplémentaire est souvent
récompensé par la réalisation de programmes plus sûrs et plus lisibles.

Conversion de types scalaires

Les valeurs de type scalaire peuvent être converties en valeurs entières à l'aide de la fonction ord.
Le Pascal offre également la possibilité d'effectuer la conversion d'une valeur de type scalaire en
une valeur d'un autre type scalaire. Par exemple, considérons les déclarations suivantes:

type couleurs = (jaune, bleu, rouge, vert);


formes = (carre, triangle, cercle);
var teinte : couleurs;
figure : formes;
entier : integer;
Il est possible d'effectuer des conversions de types en spécifiant l'identificateur du type désiré suivi
d'un paramètre entre parenthèses. Ce paramètre doit être une valeur appartenant à un type
scalaire connu ou déclaré:

entier := integer (rouge); { = 2 }


figure := formes (bleu); { = triangle }
entier := integer ('7'); { = 55 }
teinte := couleurs (0); { = jaune }
figure := formes (0); { = carre }
Bien que cette conversion de types soit rarement utilisée, il convient de la signaler.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-15


D'une manière générale, en Pascal, il n'est pas permis de mélanger différents types de données.
Ce principe présente des exceptions lorsque deux types sont compatibles. C'est le cas, par
exemple, pour les différents types entiers, qui sont compatibles à condition que les valeurs soient
cohérentes par rapport à leur ensemble de définition. Les principes suivants régissent la
compatibilité des types:
• les règles de cohérence doivent être respectées lors du mélange de données de type
entier ou réel. Une expression mixte, comprenant des opérandes entiers et réels fournit un
résultat de type réel;
• le mélange de données de type numérique (entier ou réel) et de type caractère n'est pas
autorisé.

Structures séquentielles

Une structure séquentielle est une suite d'instructions qui s'exécutent les unes à la suite des
autres, en séquence:

begin
temperature := 28;
meteo.caption := 'Il fait chaud';
end;

Structures sélectives

Ces structures permettent d'effectuer des choix selon des critères (ou conditions) que le
programmeur a fixés. Ces instructions se comportent comme un aiguillage, à deux ou plusieurs
branches. Selon qu'un critère est satisfait ou non, l'exécution du programme se poursuivra dans
une "branche" ou dans une autre.

Instruction if
Considérons l'exemple suivant:

var temperature : integer;


...
temperature := StrToInt (Edit1.text);
if temperature > 20 then
Label1.Caption := 'Il fait chaud'
else
Label1.Caption := 'Il fait froid';
...
fig. 2.5

Selon la valeur indiquée par l'utilisateur et placée dans la variable temperature, le programme
affichera "Il fait chaud" ou bien "Il fait froid". On remarque également les trois mots réservés if,
then et else utilisés dans cette structure sélective qui, traduite en français, s'exprimerait par:

« si la température est supérieure à 20 alors afficher qu'il fait chaud


sinon afficher qu'il fait froid »

L'exemple qui suit utilise une structure sélective sous une forme quelque peu différente:

...
var revenu : real;
taxes : real;
...

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-16


taxes := revenu * 0.053;
if revenu > 4000.0 then
revenu := revenu - taxes;
...
fig. 2.6

Traduite en français, la structure sélective signifie:


« si le revenu est supérieur à 4000.0 alors on déduit 5,3 % du revenu »

Dans l'exemple de la figure 2.5, le choix s'effectue entre deux instructions, l'une des deux étant
forcément exécutée. Alors que dans le second exemple (figure 2.6) il s'agit d'exécuter ou de ne
pas exécuter une instruction, à savoir la déduction des taxes.
Une "instruction" peut être constituée par une seule instruction ou par un bloc d'instructions.
Illustrons cela par l'exemple suivant:

...
var revenu : real;
taxes : real;
...
if revenu > 4000.0 then
begin
revenu := revenu - (revenu * 0.053);
msg := 'Le revenu subit des déductions.');
end else
msg := 'Le revenu ne subit pas de déductions.');
...
Dans ce programme, deux instructions doivent être exécutées si le revenu est supérieur à 4000
francs. Il faut donc grouper ces deux instructions en un bloc délimité par begin et end. Ce concept
est général en Pascal:

A chaque endroit d'un programme où une instruction peut figurer, il est possible de la
remplacer par un bloc d'instructions.

Comme une structure sélective est elle-même une instruction, on peut emboîter plusieurs
structures if...then...else:

if x > 0 then positif := positif + 1


else if x < 0 then negatif := negatif + 1
else zero := zero + 1;

L'une des branches d'une instruction sélective peut donc contenir une autre instruction sélective.
Ces structures emboîtées contribuent, dans le cas de l'instruction if...then, à rendre un
programme moins lisible. L'instruction qui suit semble être ambiguë; à quel if se rapporte le
else?

if x > 0 then if y = 3 then z := y else z := x;

En fait, les langages de programmation n'admettent pas les ambiguïtés. Dans notre exemple,
l'ambiguïté est levée par une convention stipulant qu'un else se rapporte toujours au if
précédent qui est le plus proche. Il convient, dans tous les cas, de soigner la présentation d'un
programme; une indentation (décalage par rapport à la marge gauche) convenable augmente la
lisibilité du programme. L'exemple précédent peut également s'écrire:

if x > 0 then if y = 3 then z := y


else z := x;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-17


ou encore:

if x > 0 then
if y = 3 then
z := y
else
z := x;

Le Pascal permet de "court-circuiter" l'évaluation d'expressions booléennes contenant des


opérateurs and et/ou or. Pour mieux comprendre l'importance de la manière dont une telle
évaluation est effectuée, considérons l'instruction conditionnelle suivante:

if (b <> 0) and (a / b > 4) then ...

S'agissant d'une expression booléenne faisant intervenir un "et" logique, il suffit que l'un des deux
membres de l'expression prenne la valeur "faux" pour que l'expression entière soit "fausse". Dans
le cas où b possède la valeur 0, le Pascal n'évalue pas le second membre de l'expression, évitant
ainsi une erreur à l'exécution due à une division par zéro. Cette particularité peut s'avérer fort utile
dans certaines situations, et, dans tous les cas, elle augmente la vitesse d'exécution des
programmes.
Il est néanmoins possible d'éviter ce type de raccourci dans l'évaluation des expressions
booléennes grâce à une directive fournie au compilateur.

Instruction case
Considérons maintenant la partie d'un programme qui fait bouger un point sur l'écran. Supposons
que l'utilisateur appuie sur les lettres 'H' pour "haut", 'B' pour "bas", 'D' pour "droite", 'G' pour
"gauche". Cette partie de programme pourrait s'écrire de la manière suivante:

...
if key='H' then y := y - 1;
if key='B' then y := y + 1;
if key='D' then x := x + 1;
if key='G' then x := x - 1;
im.canvas.pixels[x, y] := clRed;
...

ou bien encore:

...
if key = 'H' then y := y - 1
else if key = 'B' then y := y + 1
else if key = 'D' then x := x + 1
else if key = 'G' then x := x – 1;
im.canvas.pixels[x, y] := clRed;
...

Dans ces exemples, les structures sélectives se ressemblent et paraissent un peu lourdes dans
leur notation. Le langage Pascal dispose d'une autre structure sélective permettant d'éviter dans
certains cas de telles situations. Cette nouvelle structure correspond à l'instruction case ... of, et
son utilisation permet de modifier l'exemple précédent en:

...
case key of
'H' : y := y - 1;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-18


'B' : y := y + 1;
'D' : x := x + 1;
'G' : x := x - 1;
end;
im.canvas.pixels[x, y] := clRed;
...

Cette nouvelle structure agit en fait à la manière d'un test à multiples branches. Pour chaque
branche l'égalité entre la valeur de ch (appel‚ sélecteur) et la constante correspondante est
vérifiée. Si effectivement la valeur de ch équivaut à une des constantes, l'instruction ou le bloc
d'instructions correspondant sont exécutés. Si la valeur du sélecteur ne correspond à aucune des
constantes indiquées l'exécution du programme se poursuit après la structure sélective.
Cette structure est commune à toutes les implémentations de Pascal. Delphi offre cependant une
variante parfois utile: une branche supplémentaire indiquée par un else permet l'exécution d'une
instruction ou d'un bloc d'instructions au cas où la valeur du sélecteur ne correspond à aucune des
constantes spécifiées. L'exemple qui suit illustre cette possibilité:

...
case key of
'H' : y := y - 1;
'B' : y := y + 1;
'D' : x := x + 1;
'G' : x := x - 1;
else
msg := 'Erreur';
end;
im.canvas.pixels[x, y] := clRed;
...

Remarquons qu'avant le mot réservé else d'une structure case on trouve un point virgule
Dans chaque branche d'une instruction case on peut indiquer plus d'une valeur, et même un
intervalle de valeurs. L'exemple qui suit illustre cette possibilité:

case a + b of
1 : a := b;
3..6 : b := a;
8, 9 : begin
a := 0;
b := 0;
end;
10..13, 15 : b := 0;
end;

Structures itératives

Introduction
Arrivés à ce point, nous ne sommes pas encore en mesure d'écrire un programme qui répète
certaines instructions. Il nous manque les structures itératives qui permettent d'effectuer ce que
l'on appelle communément des boucles. Dans le langage Pascal, on trouve trois types
d'instructions répétitives. Les deux premières se distinguent par le fait que la condition de sortie
est conséquente à l'évaluation d'une expression booléenne. La troisième est liée à l'évaluation
implicite d'un compteur de boucle.

Instruction while

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-19


Cette structure permet de répéter l'exécution d'une instruction ou d'un bloc d'instructions tant
qu'une expression est vérifiée:

...
while nombre < 10000 do
begin
Edit1.text := Edit1.text + '1';
nombre := nombre * 10;
end;
...
fig. 2.7

Dans cet exemple, tant que le nombre est inférieur à 10000, le chiffre 1 est concaténé.
Si le nombre vaut, par exemple, 20000 avant l'entrée dans la boucle, le programme n'effectue pas
les instructions contenues dans la structure while. Il est donc possible que les instructions
englobées par ce type de boucle ne soient jamais exécutées.

Instruction repeat
Cette instruction permet de répéter l'exécution d'une instruction ou d'un bloc d'instructions jusqu'à
ce qu'une condition soit vérifiée. En apparence, cette structure ressemble à la précédente. En
réalité, la différence est significative et d'importance. Reprenons l'exemple de la figure 2.7 et
voyons comment on pourrait l'écrire à l'aide d'une instruction repeat:

...
repeat
Edit1.text := Edit1.text + '1';
nombre := nombre * 10;
until nombre >= 10000;
...
fig. 2.8

Le fonctionnement des programmes illustrés sur les figures 2.7 et 2.8 est pratiquement identique.
La différence fondamentale concerne l'emplacement de la condition par rapport au contenu de la
boucle. Dans le second cas, même si le nombre est déjà supérieur à 10000, les instructions
contenues dans la structure repeat seront exécutées une fois. La condition qui détermine
l'éventuel arrêt de la répétition (nombre >= 10000) se trouvant à la fin de la boucle, l'entrée dans la
structure est obligatoire. De plus, dans l'exemple de la figure 2.8, l'expression booléenne
constituant la condition d'arrêt est la négation logique de l'expression booléenne de la figure 2.7.
En effet, dans le cas d'une boucle while, il s'agit d'une condition de continuation.

Instruction for
Cette troisième structure répétitive est utilisée lorsque le nombre d'itérations est connu. Il faut
spécifier l'identificateur d'une variable appelée indice dont la valeur est modifiée implicitement au
cours de l'exécution de la boucle, la première valeur prise par cette variable ainsi que la valeur
pour laquelle la répétition s'arrête. A chaque passage dans la boucle, l'indice prend la valeur
suivante ou précédente (selon si la boucle est ascendante ou descendante). Le programme qui
suit affiche les carrés des nombres entiers compris entre 1 et 10:

...
const max = 10;
var nombre : integer;
begin
for nombre := 1 to max do
liste.items.add (IntToStr (nombre * nombre));
end;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-20


Les résultats fournis par ce programme se présentent sous la forme d'un ListBox dont le contenu
est:

1
4
9
16
25
36
49
64
81
100

Le mot réservé to peut être remplacé par downto afin que la boucle soit parcourue dans l'ordre
décroissant de l'indice (dans l'exemple, la variable nombre). La boucle s'écrit alors:

for nombre := max downto 1 do ....

et les résultats seront:

100
81
..
Il n'est pas possible, en Pascal, d'indiquer explicitement un pas d'incrémentation de la boucle. En
indiquant to, l'indice prend la valeur suivante, alors qu'en utilisant downto il prend la valeur
précédente. Pour un pas différent, il convient d'utiliser une boucle while ou repeat.
Le Pascal ne permet pas non plus d'utiliser la boucle for avec un indice de type réel. Il faudra
dans ce cas aussi faire appel à une boucle while ou repeat. En revanche, l'indice d'une boucle for
peut être de type entier, énuméré, booléen ou caractère.
Lorsqu'une instruction for se trouve dans une procédure ou une fonction, la variable constituant
l'indice de la boucle doit obligatoirement être déclarée localement à la procédure ou à la fonction.

Procédure Halt

La procédure Halt provoque une fin anormale d'un programme et passe le contrôle au système
d'exploitation. Pour provoquer la fin normale d'une application il convient d'utiliser
Application.Terminate.
On peut facultativement passer un nombre entier comme paramètre à la procédure Halt, comme
dans l'exemple qui suit. Il s'agit d'un code de sortie transmis au système d'exploitation. Ce code
est laissé au choix du programmeur.

begin
if 1 = 1 then
if 2 = 2 then
if 3 = 3 then
Halt(1); { On quitte le programme }
Form1.caption := 'Ce code ne sera jamais exécuté';
end;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-21


Procédure Break

La procédure Break provoque l'interruption d'une boucle for, while ou repeat.


L'exemple qui suit montre une procédure qui teste si un nombre nb est premier. Elle comporte une
boucle for dans laquelle on détermine si le nombre nb est visible par les nombres compris entre 2
et nb - 1. Dès que l'un de ces nombres divise nb cela signifie que nb n'est pas premier. Il est donc
inutile de poursuivre l'exécution de la boucle, d'où l'utilisation de la procédure Break.

procedure TForm1.testClick(Sender: TObject);


var nb : integer; // nombre à tester
i : integer; // indice de boucle
premier : boolean;
begin
premier := true;
nb := strtoint (nombre.text);
for i := 2 to nb - 1 do begin
if nb mod i = 0 then begin
premier := false;
break;
end;
end;
if premier then
resultat.text := 'Nombre premier'
else
resultat.text := 'Nombre non premier';
end;

Procédure Continue

L'appel à la procédure Continue provoque le passage du contrôle de l'exécution à l'itération


suivante dans une instruction for, while ou repeat.

procedure traitement;
var i : integer;
begin
for i := 0 to MAX do begin
if t[i] = 0 then
continue; { on passe à l'itération suivante }
...
{ ici les instructions de la boucle si t[i] <> 0 }
...
end;
end;

Procédure Exit

La procédure Exit permet de quitter l'exécution de la procédure en cours. Si la procédure en cours


correspond au programme principal, Exit termine l'exécution du programme. L'utilisation de la
procédure Exit permet souvent de rendre le programme plus clair en évitant la lourdeur des
clauses else. L'utilisation de la procédure Exit dans le fragment de programme suivant permet,
entre autre de ne pas utiliser de else et ne nuit aucunement à la lisibilité du code, au contraire:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-22


procedure TForm1.AmisClick(Sender: TObject);
var nb : integer;
divnb1 : integer;
divnb2 : integer;
min, max : integer;
begin
if strtoint (inf.text) >= strtoint (sup.text) then begin
showmessage ('Limite supérieure > limite inférieure SVP');
exit;
end;
listbox1.clear;
min := strtoint (inf.text);
max := strtoint (sup.text);
for nb := min to max do begin
divnb1 := SommeDiviseurs (nb);
divnb2 := SommeDiviseurs (divnb1);
if (divnb2 = nb) then
listbox1.items.add (inttostr(nb) + ' et ' + inttostr(divnb1)
+ ' sont amis');
end;
end;

Instruction goto

Cette instruction correspond à un branchement inconditionnel et permet de rompre une séquence


en poursuivant l'exécution d'un programme à une autre instruction que la suivante. L'emplacement
de l'instruction de branchement est repéré par une étiquette, qui n'est autre qu'un identificateur ou
un numéro compris entre 0 et 9999. Une étiquette doit être déclarée après le mot réservé label
dans la partie réservée aux déclarations. Son domaine de définition correspond au bloc où elle est
déclarée; il n'est donc pas possible d'effectuer des branchements par l'instruction goto entre
procédures ou fonctions. Si une déclaration d'étiquette figure dans un programme, cette étiquette
doit impérativement être utilisée.
L'exemple qui suit montre comment s'utilise cette instruction:

procedure demo;
label ici;
...
begin
if Label1.left > 150 then
goto ici
else
Label1.caption := 'Fin';
...
ici:
...
end;
Ce programme est bien entendu construit de manière aberrante. Il suffit de l'écrire comme suit
pour que l'instruction goto n'ait plus de raison d'être:

procedure demo;
...
begin
if Label1.left <= 150 then begin
Label1.caption := 'Fin';
...
end;
...
end;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-23


L'emploi de cette instruction en Pascal peut presque toujours être évité, bien que dans de très
rares cas elle soit fort utile (par exemple en tant que sortie rapide d'une boucle complexe). Si l'on
se réfère au théorème de structures, l'utilisation de l'instruction goto n'est jamais nécessaire.

EXERCICES

Exercice 2.1:

Reproduire la fiche ci-dessus. L'utilisateur peut entrer deux nombres entiers, faire effectuer les
calculs et quitter le programme.
Améliorations:
• Refuser les divisions par 0
• Enlever les résultats lorsqu'ils ne sont plus justes
• Refuser les caractères illicites pour les 2 nombres

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-24


Exercice 2.2:

Reproduire la fiche ci-dessus.


L'utilisateur ne peut saisir que les quantités, les autres Edits sont en ReadOnly ou désactivés.
Le rabais dépend du montant total:
Si le total est inférieur à 1'000 Fr, le rabais est de 2 %
Si le total est compris entre 1'000 Fr et 5'000 Fr, le rabais est de 3.5 %
Si le total est compris entre 5'000 Fr et 10'000 Fr, le rabais est de 5 %
Si le total est supérieur à 10'000 Fr, le rabais est de 7 %

Exercice 2.3:
Ce programme doit simuler un chronomètre.

Le temps au départ est fixé par le programmeur. Tant que l'utilisateur n'a pas appuyé sur le
bouton Fin, le chronomètre avance.
Il faut utiliser 3 variables globales (heures, minutes, secondes).
La difficulté est de passer de 59 secondes à une minute supplémentaire et de 59 minutes à une
heure supplémentaire.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-25


Pour mesurer le temps qui passe, il faut utiliser un Timer (onglet Système)
Propriétés:
• Enabled utilisée pour activer (true) ou désactiver (false) le Timer
• Interval détermine l'intervalle de temps, exprimé en millisecondes, s'écoulant avant que
le composant timer génère un autre événement OnTimer.
• Name nom du composant

Evénement:
• OnTimer événement utilisé pour écrire un gestionnaire d'événement qui exécute des
actions à intervalles réguliers. La propriété Interval détermine la périodicité des
événements OnTimer. A chaque fois que l'intervalle spécifié s'écoule, l'événement
OnTimer a lieu.

Autre possibilité: refaire le programme en utilisant uniquement une variable représentant les
secondes. Les minutes et les heures seront obtenues en faisant appel à div et mod.

Exercice 2.4:
Ecrire un programme qui résout une équation de degré 2.
ax2 + bx + c = 0

L'algorithme est discuté en classe.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-26


Exercice 2.5:

Ce programme affiche, dans un ListBox, les nombres de 1 à 20, ou bien leur carré, leur racine
carrée, leur racine cubique, leur inverse.
Les seules propriétés à connaître concernant les ListBox sont:
• Clear efface le contenu du ListBox
• Items.Add ajoute une ligne après la dernière ligne du ListBox, l'argument doit être
une chaîne de caractères
Exemple:
Liste.Clear;
Liste.Items.Add ('Ceci est la dernière ligne');
Liste.Items.Add (IntToStr (nombre));

Utilisez l'aide de Delphi pour trouver comment extraire une racine cubique.

Amélioration:
Afficher chaque fois le nombre en regard de sa racine, de son inverse ou de son carré.

Exercice 2.6:
Intérêts composés et capital final C = C0 * (1 + T)N
Où C0 est le capital initial en francs
T est le taux d'intérêts en valeur décimale
N est le nombre d'années de placement

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-27


L'utilisateur doit indiquer le capital initial et le taux d'intérêts en % et le programme affiche ce que
devient ce capital, chaque année, s'il placé durant 20 ans.

Exercice 2.7:
Ecrire un programme qui approxime pi de 3 manières différentes:

1 1 1 1 π
+ +... → −
3. 5 7. 9 ( 4 k − 1)( 4 k + 1) 2 8

1 1 1 π2
1+ + +... + 2 →
4 9 k 6

1 1 1 π2
1+ + +... + →
9 25 ( 2 k + 1) 2 8

Affichez la valeur de la constante pi définie dans Delphi, celles obtenues par les 3 approximations,
ainsi que la différence entre chaque approximation et pi. L'utilisateur doit pouvoir choisir le nombre
de termes calculés pour approximer pi.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-28


Exercice 2.8:
Ecrire un programme qui se présente sous la forme suivante:

Le programme doit tenir compte des points suivants:


• un bouton voit sa largeur augmenter et diminuer continuellement
• un bouton marche/arrêt permet d'arrêter et de redémarrer le mouvement
• quand l'utilisateur clique sur le bouton qui bouge, l'indication du pourcentage au moment
du clic est reportée sous le bouton. De plus la valeur (entière) du pourcentage est ajoutée
dans une liste et la moyenne est calculée et affichée. La moyenne doit être entière.
• le bouton Fin termine le programme

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-29


Indications:
• il est conseillé d'utiliser un timer
• il ne faut pas stocker les valeurs autrement que dans la liste

Exercice 2.9:

Reproduire la fiche si-dessus.


L'utilisateur peut entrer des caractères.
A chaque changement de l'Edit contenant les caractères tapés par l'utilisateur, le programme
affiche, dans le second Edit, les code des caractères séparés par un espace. Tenir compte
également de la touche <BackSpace>.

Exercice 2.10:
Ecrire un programme simulant le fonctionnement d'une calculatrice simple (4 opérations).

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-30


Exercice 2.11:
Ecrire un programme qui affiche, dans un StringGrid, un carré magique de dimension n x n (où n
est impair et compris entre 3 et 15).
Dans un carré magique, la somme des nombres sur chaque ligne, chaque colonne et chaque
(grande) diagonale doit être la même
Une méthode consiste à placer le 1 au milieu de la première ligne. Pour placer le nombre suivant,
il faut se déplacer d'une case vers la droite et d'une case vers le haut. Lorsque l'on sort du carré, il
faut rentrer par le côté opposé. Si on tombe sur une case occupée, on choisit alors la case qui est
juste en dessous de la dernière case remplie.

Exercice 2.12:
Nombres premiers
Un nombre premier est un nombre entier divisible uniquement par 1 et par lui-même. Par exemple
1, 2, 3, 5, 7, …, 17, 19, …, 4549, 4561, 4567, …
Comment déterminer si un nombre est premier ?
Il faut essayer de le diviser successivement par les nombres compris entre 2 et sa racine carrée.
Si une des divisions est entière (n'a pas de reste), le nombre n'est pas premier. Si aucune des
divisions n'est entière, le nombre est premier.
Ecrivez l'algorithme qui permet de déterminer si un nombre est premier.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-31


Ecrivez un programme qui:
• Indique si un nombre donné par l'utilisateur est premier ou non
• Donne la liste des N premiers nombres premiers
• Donne la liste des nombres premiers compris entre X et Y
Voici l'aspect du programme:

Exercice 2.13:
Un petit peu de graphisme
Dans Delphi, on dessine sur la propriété canvas d'un composant. Nous allons utiliser un
composant de type TImage (onglet Suppléments). Le crayon, pen, permet de dessiner (par
défaut: couleur noire, épaisseur 1 pixel et style de trait continu).
Le système de coordonnées est "gradué" en pixels, l'origine (point (0, 0)) est située en haut à
gauche. La largeur de l'image est indiquée par Image.Width et sa hauteur par Image.Height (en
pixels).
Image.Canvas.MoveTo(X, Y: Integer);
Déplace le crayon au point (X,Y) sans laisser de trace. (Définit la position du crayon en (X, Y)).
Image.Canvas.LineTo(X, Y: Integer);
Dessine dans le canvas une ligne allant de la position en cours du crayon jusqu'au point de
coordonnées spécifiées par X et Y, puis définit la position du crayon en (X, Y).
La ligne est dessinée en utilisant les propriétés de Pen.
Image.Canvas.Color := clbleu;
Initialise à bleu la couleur du crayon. A partir de cette instruction, le crayon dessinera en bleu.
Ecrire un programme qui, lorsque l'on appuie sur un bouton Départ, dessine des rectangles
emboîtés (du plus grand au plus petit). L'utilisateur ne doit pas pouvoir changer la taille de la fiche.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-32


Mais lorsque le programmeur change la taille de Form1, il ne doit pas avoir besoin de retoucher
son code pour que le programme fonctionne correctement.
Amélioration 1: Lorsque tous les rectangles sont dessinés, les redessiner en gris en allant du plus
petit au plus grand.
Amélioration 2: Le programme dessine tout le temps les rectangles (du plus grand au plus petit au
plus grand…) jusqu'à ce que l'utilisateur appuie sur le bouton Fin.

Exercice 2.14:
Ce programme se compose de 2 Edits. Dans le premier, l'utilisateur peut taper des lettres qui sont
transformées en majuscules et affichées au fur et à mesure dans le deuxième Edit.

La fonction upcase retourne la majuscule correspondant à un caractère de l'alphabet. Pour les


lettres accentuées, il faut analyser tous les cas et utiliser pour cela un case of.
Pour le traitement du BackSpace (#8), utiliser la procédure delete et la fonction length concernant
les chaînes de caractères.
Servez-vous de l'aide de Delphi pour savoir comment utiliser les procédures et fonctions indiquées
ci-dessus.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-33


Exercice 2.15:
Ecrivez un programme qui fait avancer un trait (graphique) sur la fiche lorsque l'utilisateur appuie
sur les flèches de déplacement.
Considérez l'événement OnKeyDown. Le paramètre Key, de type Word, peut prendre les valeurs
suivantes qui nous intéressent:
• VK_RIGHT flèche vers la droite
• VK_DOWN flèche vers le bas
• VK_LEFT flèche vers la gauche
• VK_UP flèche vers le haut
• VK_HOME touche Home du pave numérique
• VK_PRIOR touche Pg Up du pave numérique
• VK_NEXT touche Pg Down du pave numérique
• VK_END touche End du pave numérique

Dans un premier temps, ne considérez que les flèches. Ensuite les touches du pavé numérique
pour avancer de biais.
Toujours dans un premier temps, le trait se bloque si l'on veut aller plus loin que le bord de la
fiche. Ensuite, si le trait sort d'un côté, il réapparaît de l'autre.
Voici l'aspect du programme:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 2-34


Chapitre 3 PROCEDURES ET FONCTIONS

A la fin de cette partie, vous serez capable de:

• structurer un programme en utilisant des fonctions et procédures;


• utiliser le passage de paramètres afin d'écrire des programmes ou modules plus
indépendants et réutilisables;
• comprendre la notion de récursivité

3.1 Introduction

Comme nous l'avons vu précédemment, un programme doit être clair et bien structuré afin que
son utilisation et sa maintenance posent un minimum de problèmes. Le besoin de structuration ne
semble pas toujours essentiel pour les petits programmes. Mais au fur et à mesure que la
complexité ou la taille d'un programme augmentent, une bonne structure n'est plus seulement un
besoin, mais une nécessité. Il convient donc d'acquérir de bonnes habitudes de programmation
dès le début, cet effort étant largement récompensé par la suite.
Le langage Pascal a été conçu pour que les programmes soient décomposés en modules
(procédures, fonctions ou unités) de taille raisonnable. Toutefois il faut éviter un découpage
artificiel du programme, au profit d'une décomposition logique. Chaque procédure ou fonction
correspond à une tâche élémentaire bien déterminée. La décomposition en modules permet de
sérier les différentes phases conduisant à la résolution d'un problème. Cette méthode offre entre
autres avantages celui d'une maintenance grandement facilitée, puisque la localisation d'un point
critique dans un programme est plus précise et rapide.
Les notions de procédure ou fonction s'apparentent à la notion plus classique de "sous-
programme", terme encore employé dans certains langages de programmation. En Pascal, les
procédures et les fonctions ont beaucoup de caractéristiques communes, et relativement peu de
différences. Dans les sections qui suivent nous nous attacherons surtout à l'étude des procédures.
Nous reviendrons dans la section 3.8 sur les caractéristiques qui différencient les fonctions des
procédures.
La mise en oeuvre de modules contribue également et largement à une bonne structuration et
modularisation des programmes. Nous reviendrons sur cette notion plus loin dans le cours.
Une procédure (ou une fonction) possède une structure analogue à celle d'un programme. En tant
qu'entité, une procédure apparaît dans un programme lors de deux phases distinctes. La première
constitue la déclaration, et la seconde l'invocation (ou appel).

3.2 Déclaration

La déclaration d'une procédure est constituée par une description complète comprenant: un en-
tête, une partie réservée aux déclarations locales et le corps proprement dit de la procédure. L'en-
tête comprend le mot réservé procedure, suivi du nom de la procédure et, facultativement, d'une
liste de paramètres. Les déclarations sont indiquées exactement de la même manière que pour le
programme. Le corps de la procédure est un bloc d'instructions, au même titre que le corps du
programme:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-1


procedure pied; Entête

const ...
Déclarations
type ...
var ...
begin Corps de la
... procédure
end;

Comme nous l'avons vu, en Delphi, l'essentiel des instructions d'un programme se trouve dans les
unités. Il faut ici distinguer deux situations dans lesquelles on est amené à écrire une procédure.
La première concerne l'écriture d'une procédure indépendante de la fiche courante. Dans ce cas,
la procédure n'a pas accès à la fiche et aux objets qu'elle contient. Il s'agit souvent de procédures
indépendantes, effectuant des opérations sans lien direct avec les objets de l'interface du
programme. Voici un exemple dans lequel le clic sur un bouton provoque l'émission d'un son:

Le code se présente de la manière suivante:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure Bip;
begin Code de la procédure
Messagebeep (0);
end;

procedure TForm1.Button1Click(Sender: TObject);


begin
Bip; Appel de la procédure
end;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-2


end.
La procédure Bip ne fait appel à aucun objet de l'interface utilisateur; Messagebeep (0) étant une
procédure standard de Windows. Le compilateur n'aurait cependant pas accepté l'instruction

Form1.Caption := 'exemple';
dans le corps de la procédure Bip.
La seconde situation dans laquelle on peut utiliser une procédure est celle où l'on permet à la
procédure d'accéder aux objets de la fiche. Prenons un exemple dans lequel un clic sur un bouton
provoque la mise au carré d'un nombre entier contenu dans un Edit:

Cet exemple bien qu'artificiel illustre néanmoins la situation:

unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Déclarations privées }
procedure AfficherCarre; Déclaration anticipée
public
{ Déclarations publiques }
end;

var Form1: TForm1;

implementation
{$R *.DFM}

procedure TForm1.AfficherCarre; Procédure


begin proprement dite
Edit1.text := inttostr (sqr(strtoint(Edit1.text)));
end;

procedure TForm1.Button1Click(Sender: TObject);


begin
AfficherCarre; Appel de la procédure
end;
end.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-3


On peut faire plusieurs constatations:
• il faut une déclaration anticipée (également appelée prototype) de la procédure. Cette
déclaration peut intervenir dans la partie private ou dans la partie public. Dans le premier
cas, la procédure AfficherCarre peut être appelée uniquement depuis l'unité unit1. Dans
le second cas, la procédure AfficherCarre pourrait également être appelée depuis une
autre unité faisant référence à unit1.
• Dans l'écriture proprement dite de la procédure, le nom de la procédure est préfixé par le
nom de la classe à qui elle appartient: Tform1.
• La procédure AfficherCarre peut accéder à la fiche Form1 et à tous les objets qu'elle
contient
Si plusieurs procédures doivent être définies, leurs déclarations se suivent:
...
procedure premiere;
begin
(* corps de la procédure "premiere" *)
end;
procedure deuxieme;
begin
(* corps de la procédure "deuxieme" *)
end;
fig. 3.1

3.3 Invocation (ou appel)

Comme nous venons de le voir, une fois définie, une procédure peut être invoquée en indiquant
son nom. Le nom tient lieu, en fait, d'instruction. En règle générale, une procédure ne peut être
invoquée qu'après avoir été déclarée.
Lors de l'invocation d'une procédure, l'exécution du programme se poursuit par l'exécution de la
première instruction du corps de la procédure. Au moment où l'exécution de la procédure est
terminée, le contrôle est rendu à la partie appelante du programme, c'est-à-dire à l'instruction qui
suit l'appel à la procédure.

3.4 Procédures emboîtées

Par analogie à la déclaration d'une procédure dans un programme ou une unité, il est possible de
déclarer une procédure à l'intérieur d'une autre procédure. L'exemple qui suit illustre
l'emboîtement des procédures premiere et deuxieme:

procedure autour;
var x,z : integer;

procedure premiere;
procedure deuxieme;
begin
(* corps de la procédure "deuxieme" *)
end;
begin
(* corps de la procédure "premiere" *)
end;

begin
(* corps de la procédure "autour" *)
end.
fig. 3.2

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-4


Les exemples des figures 3.1 et 3.2 diffèrent uniquement par l'emplacement où est déclarée la
procédure deuxieme. Dans le premier cas, les deux procédures sont déclarées au même niveau
et chacune d'entre elles peut être appelée depuis l'extérieur. Dans le second cas, seule la
procédure premiere peut être appelée depuis l'extérieur (par exemple depuis la procédure
autour); la procédure deuxieme étant déclarée à l'intérieur de la procédure premiere, elle ne peut
pas être appelée en dehors de la procédure premiere. Toutefois elle peut être appelée depuis le
corps de la procédure premiere. La procédure englobante agit comme un écran par rapport à la
procédure emboîtée. On dit que la "visibilité" de la procédure emboîtée deuxieme est masquée.
En Pascal, le processus d'emboîtement de procédures est une conséquence naturelle de
l'application de la méthode de raffinement graduel à la résolution d'un problème. Dans la pratique
il est rare de rencontrer des niveaux d'emboîtement supérieurs à quatre ou cinq. Dans la plupart
des programmes simples écrits en Delphi, il est même rare de rencontrer des procédures
emboîtées, étant donné que la programmation événementielle favorise la structure d'un
programme en de nombreuses procédures répondant précisément chacune à un événement.
L'emboîtement des procédures détermine donc des domaines de visibilité au niveau des
procédures, mais aussi au niveau des objets déclarés dans les procédures. Le domaine de
visibilité d'un objet est lié aux concepts de localité et de globalité. Une bonne compréhension et
maîtrise de ces nouvelles notions qui sont l'objet des deux prochaines sections, permettent une
meilleure approche de la programmation structurée.

3.5 Niveaux d'emboîtement

Nous avons vu que chaque procédure peut posséder ses propres objets, définis dans la partie
réservée aux déclarations. Dans Delphi, la quasi-totalité des instructions d'un programme étant
située dans les unités, nous allons prendre l'unité comme structure de référence afin de
comprendre les niveaux d'emboîtement des procédures et la notion de domaine de visibilité.

fig. 3.3

L'emboîtement des procédures crée une hiérarchie et définit des niveaux:

• L'unité (objet défini par le mot réservé unit) se trouve au niveau 1; on dira que l'unité est
un bloc de niveau 1;
• tout objet déclaré dans le bloc de niveau 1 sera global pour les blocs emboîtés;
• toute procédure déclarée dans le bloc de niveau 1 définit un bloc de niveau 2 et ainsi de
suite;
• tout objet défini au niveau 2 est local à ce niveau et global pour les blocs emboîtés.

Les concepts de localité et de globalité sont fondamentaux en Pascal. Un découpage tenant


compte de la localité et de la globalité des différents objets permet une cohérence de structure
optimale, et contribue à rendre le programme plus fiable.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-5


3.6 Règles concernant le domaine de visibilité

Nous appelons domaine de visibilité d'un objet, la portion de programme d'où cet objet peut être
référencé. Dans un programme Pascal, la visibilité d'un objet est souvent limitée à une procédure.
Les règles à connaître pour maîtriser le concept de domaine de visibilité sont simples, mais pas
évidentes de prime abord:

1. Le domaine de visibilité d'un objet est constitué par le bloc dans lequel il
est déclaré (où il est local) et les blocs subordonnés (pour lesquels il est
global), sous réserve du point 2.

2. Si un objet, défini dans un bloc A, est redéfini dans un bloc emboîté B,


alors le bloc B et tous les blocs emboîtés dans B sont exclus du domaine
de visibilité de l'objet déclaré dans A.

Afin de comprendre ces règles, examinons leur application à l'exemple suivant, dans lequel on
considère que le niveau global est représenté par la procédure autour:
...
procedure TForm1.autour;
var a, b, c : integer; { variables globales }
procedure englobe;
var b, d : integer; { variables locales à englobe }
procedure subord;
var a, e : integer; { variables locales à subord }
begin
a := b * c;
listbox1.items.add ('Dans subord, a=' + inttostr(a));
end;

begin
b := 3 * c;
subord;
end;
begin
a:=2; b:=4; c:=6;
englobe;
listbox1.items.add ('Dans la procédure autour a=' + inttostr(a)
+ ' b=' + inttostr(b) + ' c=' + inttostr(c));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
autour;
end;

Voici les résultats affichés par ce programme:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-6


L'affectation b:=3*c concerne la variable b locale à la procédure englobe, le b global étant
préservé. Dans la procédure subord, l'affectation a:=b*c concerne la variable a locale à la
procédure subord, la variable b de la procédure englobe et la variable c globale.
Les variables locales à une procédure ont une durée d'existence correspondant au temps
d'exécution de la procédure. Une procédure peut donc jouir d'une certaine indépendance, du
moins au niveau de ses objets locaux. Cette possibilité permet de préserver les objets globaux
d'éventuelles fausses manoeuvres, et ainsi de faciliter la recherche d'erreurs.

3.7 Transfert d'information avec les procédures

3.7.1 Introduction

Dans les exemples abordés jusqu'ici, l'échange d'information entre une procédure et son
environnement s'effectuait par l'intermédiaire des objets déclarés à un niveau global. Par exemple,
lors de l'appel à une procédure, celle-ci travaille avec des données contenues dans les variables
globales. A la fin de son exécution, le programme principal (ou la procédure appelante) reprend le
contrôle et peut utiliser les mêmes variables, dont le contenu aura éventuellement été modifié par
la procédure. Ce mode de fonctionnement est commun à d'autres langages de programmation tels
le BASIC ou le COBOL. Dans ces langages le domaine de visibilité des objets définis est le
programme entier. Tous les objets sont donc globaux.
Cette approche n'est pas forcément la meilleure, mais elle est facile à assimiler. En revanche, elle
implique un inconvénient allant à l'encontre des principes d'une programmation claire, sûre et
structurée: une protection insuffisante des variables, ou plutôt de leur contenu. En effet, une
variable peut être modifiée dans n'importe quelle partie d'un programme, et, de ce fait, la difficulté
de la maintenance d'un programme croît exponentiellement en fonction de sa taille.
Lorsque le domaine de visibilité d'un objet est restreint à une procédure, il est plus facile, en cas
de mauvais fonctionnement relatif à cet objet, d'intervenir uniquement dans la portion du
programme d'où cet objet est visible. La difficulté d'éliminer une erreur est alors moins dépendante
de la longueur du programme.
Donc, comme l'utilisation exclusive des variables globales n'est pas satisfaisante, nous allons
étudier une autre technique de communication entre procédures: le passage de paramètres.
Cette technique permet de rendre les procédures plus indépendantes, portables et souples, et est
implémentée dans presque tous les langages de programmation modernes (Modula-2, Portal,
ADA, etc.).

3.7.2 Passage de paramètres

Une procédure paramétrée se distingue par le fait que son nom est suivi d'une liste de paramètres
constituant, en quelque sorte, une boîte aux lettres:

procedure nom ( liste de paramètres );

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-7


Une liste de paramètres figure à la fois dans la déclaration et dans l'appel d'une procédure. Elle
consiste, lors de la déclaration, en une énumération d'identificateurs de variables suivis de leur
type, de manière analogue à la déclaration habituelle des variables. Des virgules séparent les
variables de même type et des points-virgules séparent les variables de types différents. Lors de
la déclaration d'une procédure, on parle de paramètres formels, et lors de l'invocation de la
procédure on parle de paramètres effectifs (ou réels). Ces variables particulières sont utilisées
pour les échanges d'information entre la procédure et son environnement. Chaque paramètre réel
est associé à un paramètre formel.

3.7.3 Nature des paramètres

Les paramètres d'une procédure peuvent appartenir à deux catégories distinctes. La première
concerne les paramètres utilisés par la procédure pour recevoir des informations de l'extérieur,
fournies lors de son invocation. Nous les appellerons paramètres d'entrée. La modification
éventuelle du contenu de ces paramètres dans la procédure ne peut être répercutée à l'extérieur
de celle-ci. Les paramètres d'entrée constituent donc des variables purement locales, dont la
valeur est reçue de l'extérieur. L'exemple qui suit illustre ce premier type de transfert d'information.

...
procedure TForm1.Ecrire (c: char);
var i : integer;
begin
for i := 1 to 10 do
Edit1.text := Edit1.text + c;
end;

procedure TForm1.Button1Click(Sender: TObject);


var car : char;
begin
car := '+';
Ecrire (car);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Ecrire ('x');
end;
...

Voici comment se présente le programme après avoir cliqué sur le bouton 1 puis sur le bouton 2:

Lors de la première invocation (clic sur le bouton 1), le caractère "+" contenu dans la variable car
est transmis à la procédure Ecrire. Cette dernière reçoit l'information via le paramètre formel c, et
l'utilise lors de son exécution pour ajouter le résultat dans Edit1:

++++++++++

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-8


Lors de la seconde invocation, une constante est directement transmise à la procédure qui affiche
le texte suivant dans Edit1:

xxxxxxxxxx

On constate dans cet exemple qu'un paramètre effectif peut être constitué par une variable ou par
une constante. En réalité, il est constitué par une expression, au sens complet du terme en
Pascal. Ceci comprend, en plus des variables et des constantes, des appels de fonctions, des
expressions mathématiques, logiques, etc.
La seconde catégorie concerne les paramètres utilisés par la procédure à la fois pour recevoir et
pour transmettre des informations. Nous les appellerons paramètres de sortie. Ce type de
paramètres intervient lorsque la procédure fournit des résultats à son environnement. Toute
modification, par la procédure, d'un paramètre de sortie est répercutée sur le paramètre réel
correspondant. Un paramètre de sortie constitue donc un canal de communication bidirectionnel
entre la procédure et l'extérieur. Dans l'exemple qui suit, a et b sont des paramètres d'entrée,
alors que somme est un paramètre de sortie.

Déclaration de la procédure:

procedure addition (a, b: integer; var somme: integer);


begin
somme := a + b;
end;
fig. 3.4

Appels à la procédure:

montant1 := 14;
montant2 := 7;
addition (montant1, montant2, total);
Edit1.text := inttostr(total);
addition (235, 418, resultat);
Edit2.text := inttostr(resultat);
fig. 3.5

Lors du premier appel, la procédure addition (figure 3.4) reçoit les valeurs de montant1 et de
montant2 par l'intermédiaire des paramètres a et b. Elle calcule leur somme et transmet le
résultat au programme principal par l'intermédiaire du paramètre effectif total. Après exécution,
Edit1 contiendra le nombre 21, et Edit2 le nombre 653. On constate qu'un paramètres de sortie
est précédé, lors de la déclaration, du mot réservé var.

3.7.4 Mécanisme de transfert

Le mécanisme de transfert faisant appel à des paramètres d'entrée est appelé transfert par
valeur. Dans le cas où des paramètres de sortie interviennent, on parle de transfert par référence
(ou par adresse) .
Lors de l'invocation d'une procédure avec paramètres, ces derniers doivent obéir aux règles de
concordance suivantes:

1. Le nombre de paramètres formels doit être égal au nombre de


paramètres effectifs.

2. L'ordre des paramètres spécifiés lors de l'appel à la procédure doit être


le même que celui spécifié dans la déclaration.

3. Le type d'un paramètre effectif doit être le même que celui du paramètre
formel correspondant.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-9


Remarques:

• les paramètres formels sont apparentés à des variables locales à la procédure;


• les paramètres formels transmis par référence sont précédés du mot réservé var, alors
que ceux transmis par valeur ne le sont pas;
• lors de l'invocation d'une procédure, à la place d'un paramètre réel transmis par valeur, on
peut indiquer une constante, une variable, ou une expression;
• lors de l'invocation d'une procédure, un paramètre réel transmis par référence ne peut être
qu'une variable.

Pour illustrer ces nouvelles notions, nous allons examiner trois exemples de programmes dont les
résultats à l'exécution sont strictement identiques; seul le mode de transfert de l'information
change. Le problème à résoudre par les trois programmes consiste à déterminer et afficher le plus
grand des deux nombres donnés.

Utilisation des variables globales

var Form1: TForm1;


x, y, max : real;
implementation
{$R *.DFM}
procedure TForm1.maximum;
begin
if x > y then
max := x
else
max := y;
edit1.text := floattostr(max);
end;

procedure TForm1.Button1Click(Sender: TObject);


begin
x := 12;
y := 23;
Maximum;
end;
Dans ce programme, la procédure utilise uniquement des variables globales, déclarées dans l'en-
tête de l'unité. Ceci présente un gros inconvénient, car la procédure maximum peut traiter
uniquement les variables x et y. Elle n'est donc pas assez générale.

Transfert de paramètres par valeur

var Form1: TForm1;


x, y, max : real;
implementation
{$R *.DFM}
procedure TForm1.maximum (a, b: real);
begin
if a > b then
max := a
else
max := b;
edit1.text := floattostr(max);
end;
procedure TForm1.Button1Click(Sender: TObject);

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-10


begin
x := 12;
y := 23;
Maximum (x, y);
end;
Dans ce programme, les paramètres formels a et b reçoivent lors de l'appel les valeurs contenues
dans les paramètres réels x et y.

Transfert de paramètres par valeur et par référence

var Form1: TForm1;


x, y, grand : real;
implementation
{$R *.DFM}
procedure TForm1.maximum (a, b: real; var max: real);
begin
if a > b then
max := a
else
max := b;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
x := 12;
y := 23;
Maximum (x, y, grand);
edit1.text := floattostr(grand);
end;

Dans ce dernier exemple, la procédure reçoit les deux valeurs à comparer par l'intermédiaire des
paramètres d'entrée a et b. Elle détermine ensuite la plus grande des deux valeurs (max) et la
transmet au programme appelant. Cette dernière phase fait appel au transfert par référence, max
étant un paramètre de sortie.
La procédure maximum décrite dans le dernier des trois exemples est la plus proche de l'esprit de
la programmation structurée. Dans cette optique, une procédure doit être indépendante et ses
échanges avec l'extérieur doivent passer, dans la mesure du possible, par un transfert de
paramètres. Lorsque ces conditions sont remplies la procédure est autonome, générale et plus
fiable. Il serait, par exemple, très facile de reprendre la procédure maximum du troisième exemple
dans un programme totalement différent. Son utilisation implique uniquement la connaissance de
ses paramètres et de leur signification.
Voici quelques avantages liés à l'utilisation des procédures:
- mise en pratique aisée des concepts de la programmation structurée et de la conception
descendante;
- rentabilisation du travail accompli. Lorsqu'un problème ou une partie de problème a déjà
été résolu, par soi-même ou par d'autres, il ne faut pas repartir de zéro. La tendance est
plutôt de constituer des bibliothèques de procédures et de fonctions, groupées, par
exemple, en unités. Cette méthode évite de récrire des routines semblables dans chaque
nouveau programme et est largement répandue dans le domaine de la programmation.
Plus un programmeur est expérimenté, plus sa bibliothèque de sous-programmes
(procédures, fonctions et unités) grandit, plus ses nouveaux programmes sont réalisés
rapidement;
- gain de place. Lorsque la même séquence d'instructions apparaît à plusieurs
emplacements d'un programme, il convient d'en faire une procédure. De plus, la
paramétrisation d'une procédure permet de traiter des problèmes analogues, mais pas
forcément identiques.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-11


- Enfin, lorsque les programmes font appel à des algorithmes récursifs, l'utilisation de
procédures ou de fonctions est indispensable.

3.7 Autres modes de passage de paramètres

Les passages de paramètres par valeur ou par référence sont les plus fréquemment utilisés.
Delphi propose d'autres possibilités concernant le passage de paramètres à une procédure.

Paramètres "constantes"

Lors d'un passage de paramètre par valeur, bien que qu'aucune information ne sort de la
procédure, celle-ci peut utiliser et modifier l'information quelle reçoit. Elle utilise alors le paramètre
formel comme une variable locale.
Dans le but d'éviter cette possibilité il est possible de faire appel à des paramètres "constantes"
dont voici un exemple d'utilisation:

procedure compare (const t1, t2: integer);


Dans la procédure compare les paramètres t1 et t2 ne peuvent pas être modifiés.

Paramètres de sortie

Un paramètre de sortie (précédé de out) est transmis par référence comme un paramètre précédé
de var. Toutefois, avec un paramètre de sortie, la valeur initiale de la variable référencée est
ignorée par la procédure à laquelle elle est transmise. Le paramètre out n'est utilisé qu'en sortie; il
indique simplement à la procédure où placer la valeur de sortie, sans spécifier la valeur d'entrée.
Dans l'exemple suivant:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-12


procedure TForm1.maximum (a, b: real; out max: real);
begin
if a > b then
max := a
else
max := b;
end;
il est clair que max est un paramètre de sortie. En effet, on ne connaît pas sa valeur lors de l'appel
de la procédure maximum.

Paramètres sans type

Seuls les paramètres précédés de var, out et const peuvent ne pas avoir de type (alors que les
paramètres passés par valeur doivent toujours avoir un type).
Dans le corps d'une procédure les paramètres sans type sont incompatibles avec tous les types.
De ce fait, ils doivent être utilisés uniquement en mode transtypage comme dans l'exemple qui
suit:
function Egal (var Src, Dest; Taille: Integer): Boolean;
type TOctets = array[0..MaxInt - 1] of Byte;
var N: Integer;
begin
N := 0;
while (N < Taille) and (TOctets(Dest)[N] = TOctets(Src)[N]) do
Inc(N);
Egal := N = Taille;
end;

3.8 Fonctions

Les notions abordées dans les sections précédentes, liées aux procédures, sont également
valables pour les fonctions. La structure et l'emploi de ces deux types d'objets est très semblable.
Voici toutefois quelles en sont les différences essentielles:
• le mot réservé function remplace le mot réservé procedure lors de la déclaration d'une
fonction;
• par analogie avec la notion mathématique, les paramètres (arguments) d'une fonction ne
devraient pas être modifiés. Toutefois le transfert de paramètres par référence à une
fonction est admis en Pascal;
• une fonction fournit un résultat de type scalaire. Ce type est spécifié, lors de la déclaration
de l'en-tête, par le signe ":" suivi de l'identificateur de type; cette indication est placée
après le nom de la fonction et la liste de paramètres éventuelle;
• dans le corps d'une fonction, on doit affecter au moins une fois une valeur à l'identificateur
de la fonction ou à la variable result;
• contrairement à l'appel d'une procédure qui, lui, est considéré comme une instruction,
l'appel à une fonction est considéré comme opérande d'une expression. Cet opérande,
ou l'expression qui le contient, peuvent être affectés à une variable ou bien imprimés.

La manière dont une fonction doit être déclarée est illustrée par l'exemple qui suit. La fonction tg
fournit la valeur de la tangente d'un angle exprimé en radians:

function tg (x: real): real;


begin
tg := sin (x) / cos (x);

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-13


end;

Dans un programme contenant cette déclaration, on pourrait, par exemple, écrire les instructions
suivantes:

z := tg (alpha);
ctg := 1 / tg (y);
Resultat.text := 'Tangente = ' + floattostr(tg(y));

Remarque

L'avantage d'affecter le résultat d'une fonction à la variable result plutôt qu'à l'identificateur de la
fonction est de pouvoir s'en servir sans risque de récursivité au sein de la fonction, comme dans
l'exemple suivant:

function tracer (x: real): real;


begin
result := 3*x-2;
if result < 0 then
result := abs (result)
end;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 3-14


Chapitre 4 CHAINES DE CARACTERES

A la fin de cette partie, vous serez capable de:

• Utiliser à bon escient les différents types de chaînes de caractères;


• Utiliser les différentes routines de traitement des chaînes de caractères;

4.1 Introduction

Depuis plusieurs années, les applications numériques ne sont plus le seul domaine de prédilection
de l'informatique. L'utilisation de logiciels devient toujours plus conviviale et naturelle. Le
traitement d'informations non-numériques s'avère donc nécessaire en programmation. Parmi les
types de données non-numériques nous avons déjà examiné les types booléen, caractère et
énuméré. Mais lorsqu'un programme traite des mots, des phrases, ou plus généralement du texte,
il faut pouvoir disposer d'un nouveau type de données, d'un emploi souple et efficace. Le type
chaîne de caractères (string) répond à ces critères. Il est absent des premières implémentations
de Pascal, y compris celle de N. Wirth. Mais actuellement tous les langages Pascal, dont Delphi,
autorisent ce type de données, et disposent de plusieurs procédures et fonctions standard qui lui
sont associées.

4.2 Type chaîne de caractères (string)

Une chaîne de caractères est une suite de caractères. Une variable de ce type est déclarée par le
mot string suivi de la longueur maximale de la chaîne de caractères, entre crochets. Lorsque le
mot string apparaît seul, une longueur maximale n'est pas déterminée:

var nom : string[25];


prenom : string[30];
adresse : string[50];
etudes : string;
fig. 4.1

A l'aide de ces déclarations, les affectations qui suivent sont possibles:

nom := 'Dupont';
adresse := '36, chemin d''enfer';
prenom := '';
etudes := 'Lettres';

Le contenu d'une chaîne de caractères doit être placé entre apostrophes. Si une apostrophe doit
figurer dans une chaîne de caractères, il faut la doubler. Il n'y a pas de restrictions concernant les
caractères qui peuvent figurer dans une chaîne de caractères. En revanche, il faut veiller à ne pas
dépasser le nombre de caractères maximum figurant dans la déclaration, faute de quoi l'excédent
est tronqué.

Analysons les caractéristiques de la variable nom après l'affectation qui suit:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-1


nom := 'Pam';

La longueur physique vaut 25 et correspond au nombre figurant entre crochets lors de la


déclaration (voir figure 4.1).
La longueur logique est le nombre de caractères effectivement occupés dans la variable nom.
Cette longueur vaut 3.
Les types chaîne de caractères et caractère ne sont pas entièrement compatibles. En particulier,
l'affectation d'un caractère à une chaîne de caractères est possible, la chaîne aura alors une
longueur de 1.

var adresse : string[10];


lettre : char;
...
lettre := 'G';
adresse := lettre;
...
En revanche, l'affectation d'une chaîne de caractères, même de longueur 0 ou 1, à une variable
de type caractère n'est pas autorisée. L'affectation suivante, relative à la variable lettre, n'est donc
pas admise:

var adresse : string[10];


lettre : char;
...
adresse := '4'; { initialisation }
lettre := adresse; { affectation incorrecte }
...
De plus, si une chaîne de caractères est affectée à une deuxième chaîne plus courte une
troncation est effectuée.

var mot1 : string[20];


mot2 : string[10];
...
mot1 := 'La prochaine fois';
mot2 := mot1; { mot2 contient la chaîne 'La prochai' }
...
Enfin, des expressions booléennes peuvent être construites à l'aide de chaînes de caractères et
d'opérateurs relationnels:

nom < 'Dupont'


prenom = 'Nestor'
adresse <> ''
Les comparaisons de chaînes de caractères se font d'après l'ordre lexicographique, en suivant le
code ASCII des caractères. Avec cette convention, la chaîne de caractères 'Dupont' est inférieure
à la chaîne de caractères 'dupont'. Il en est de même pour 'abc' et 'abcdef', ainsi que pour 'Z' et 'a'
! En effet, le code de 'Z' est 90, alors que celui de 'a' est 97.

4.3 Procédures et fonctions relatives aux chaînes de caractères

Dans cette section, nous allons examiner les procédures et fonctions standard les plus courantes
concernant les chaînes de caractères (d'autres procédures et fonctions seront abordées plus loin).
Elles sont données par ordre alphabétique.

Concat syntaxe: concat (st1, st2,..., stn)

Cette fonction permet de concaténer deux ou plusieurs chaînes de caractères. Des constantes ou
variables de type caractère peuvent également figurer dans la liste de paramètres de cette
fonction. Le nombre de paramètres est variable, mais il faut éviter que la longueur de la chaîne

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-2


résultante ne dépasse la longueur déclarée pour la variable à laquelle elle est affectée. Les
instructions qui suivent illustrent le fait de pouvoir concaténer indifféremment des caractères ou
des chaînes de caractères:

procedure TForm1.Button1Click(Sender: TObject);


var st1 : string[30];
resultat : string[60];
begin
st1 := 'Voici un passage ';
resultat := concat (st1, chr(13), 'à la ligne');
label1.caption := resultat;
end;
Dans cet exemple un clic sur le bouton affiche le contenu de la variable resultat dans un Label:

Le TURBO Pascal dispose d'une autre forme de concaténation. Elle utilise l'opérateur de
concaténation "+" et se présente de la manière suivante:

...
st1 := 'Voici un passage ';
resultat := st1 + chr(13) + 'à la ligne';
label1.caption := resultat;
...
Ces instructions produisent le même résultat que celles de l'exemple précédent. A noter que
chr(13) correspond au caractère "retour de chariot".

Copy syntaxe: copy (st, position, nbre)

Cette fonction retourne une chaîne de caractères extraite de la chaîne st; la position du premier
caractère à extraire est fournie par position, alors que nbre indique le nombre de caractères à
extraire. Les paramètres position et nbre doivent être de type entier.

st := 'Ceci est une chaîne très longue';


st2 := copy (st, 1, 19);
Edit1.text := st2;
L'exécution de ces instructions place la chaîne de caractères 'Ceci est une chaîne' dans Edit1.

Delete syntaxe: delete (st, position, nbre)

Cette procédure supprime un ou plusieurs caractères dans une chaîne de caractères, à partir
d'une position donnée; st est l'identificateur de la variable contenant la chaîne de caractères,
position est la position du premier caractère à supprimer et nbre est le nombre de caractères à
supprimer. Les paramètres position et nbre doivent être de type entier.

st := 'Ceci est un petit exemple';


delete (st, 13, 6);

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-3


Edit1.text := st;

L'exécution de ces instructions place la chaîne de caractères 'Ceci est un exemple' dans Edit1.

Insert syntaxe: insert (st1, st2, position)

Cette procédure insère la chaîne de caractères st1 dans la chaîne de caractères st2 à partir de la
position position (dans st2). Les paramètres st1 et st2 sont du type chaîne de caractères et
position est de type entier.

St1 := 'de';
St2 := 'Delphi Borland';
insert (st1, st2, 8);
Edit1.text := st2;

L'exécution de ces instructions place la chaîne de caractères 'Delphi de Borland' dans Edit1.

Length syntaxe: length (st)

Cette fonction fournit la longueur logique de la chaîne de caractères st, c'est-à-dire le nombre de
caractères qu'elle contient.

st := 'Cette phrase contient 26 lettres';


longueur := length(st);

L'exécution de ces instructions place le nombre 32 dans la variable longueur.

Pos syntaxe: pos (s_st, st)

Cette fonction permet de déterminer si la chaîne de caractères s_st est contenue dans la chaîne
st. Si c'est le cas, la fonction retourne la position où commence la chaîne s_st, à l'intérieur de la
chaîne st. Si la chaîne s_st est absente de la chaîne st, le résultat est 0.

st := 'un deux trois quatre cinq';


pos1 := pos ('trois', st);
pos2 := pos ('six', st);

Après l'exécution de ces instructions pos1 contient le nombre 9, alors que pos2 contient le
nombre 0.

Str syntaxe: str (valeur, st)

Cette procédure effectue la conversion de la valeur numérique contenue dans valeur en une
chaîne de caractères st. Le paramètre valeur est de type entier ou réel et peut être suivi d'un
paramètre d'écriture (dans notre exemple, on désire un nombre formaté sur 5 caractères au total,
dont 1 décimale).

nombre := 456.34;
str (nombre:5:1, st);
Edit1.text := st;
L'exécution de ces instructions place la chaîne de caractères '456.3' dans Edit1.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-4


Val syntaxe: val (st, variable, code)

Le but de cette procédure est de convertir la chaîne de caractères st en une variable numérique.
Pour que cette conversion soit effective, le contenu de la chaîne de caractères doit correspondre
aux règles d'écriture des nombres; de plus, aucun espace ne doit se trouver en première ou en
dernière position. Le paramètre variable peut être de type entier ou réel. Après l'appel à cette
procédure, si la conversion a pu être effectuée, la variable numérique code contient la valeur 0.
Dans le cas contraire, la variable code contient la position du premier caractère de la chaîne st
qui empêche la conversion, et le contenu de variable n'est pas défini.

st := '12.7';
val (st, nombre, code);
if code = 0 then
Edit1.text := floattostr(nombre) + ' ' + inttostr(code)
else
Edit1.text :=inttostr(code);
Le résultat de l'exécution de ces instructions place les nombres 12.7 et 0 dans Edit1. En revanche,
si st avait été initialisé à '12a.7' l'exécution des instructions aurait placé le nombre 3 dans Edit1,
correspondant à la position du caractère incorrect:

Remarque:

Il est possible d'accéder aux différents caractères d'une chaîne de caractères en indiquant, entre
crochets, la position du caractère désiré:

...
var st : string[10];
i : integer;
...
st := 'remarque';
for i := 1 to length (st) do
Label1.caption := Label1.caption + st[i] + chr(13);
...
Le résultat de l'exécution de ces instructions est l'affichage du mot "remarque" dans Label1,
verticalement, un caractère par ligne.

4.4 Quelques précisions concernant les chaînes de caractères

Le type string est un type générique. En fait, Delphi gère trois types de chaînes de caractères:

Type Longueur max. Mémoire nécessaire Utilisation


chaînes courtes,
ShortString 255 caractères 2 à 256 octets
compatibilité ascendante
31
AnsiString env. 2 caractères 4 octets à 2 Goctets caractères 8 bits (ANSI)
30
WideString env 2 caractères 4 octets à 2 Goctets caractères Unicode

Dans les versions actuelles de Delphi l'indication du type string correspond, par défaut, au type
AnsiString. Dans les cas où, pour des raisons de compatibilité avec d'anciennes versions de

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-5


Delphi ou de Pascal, on doit utiliser des chaînes courtes, il convient d'utiliser explicitement le type
ShortString dans les déclaration, comme dans:

var nom : ShortString;


Dans la plupart des cas il est préférable d'utiliser le type générique string pour traiter des chaînes
de caractères. Comme on peut le voir sur dans le tableau ci-dessus, une variable de type string
(AnsiString) peut occuper, en mémoire, de 4 octets à 2 Goctets. Comme 2 Goctets représente
une taille considérable, dans la pratique, la taille maximale est limitée par la quantité de mémoire
disponible.
Le type WideString est analogue au type string (AnsiString) à la différence que chaque
caractère de la chaîne est stocké sur deux octets (représentation Unicode) au lieu d'un. Pour une
utilisation courante et un jeu de caractères standard (occidental), il est inutile d'utiliser le type
WideString.

Remarques:
• L'apparition de nouveaux types de chaînes de caractères a été dictée par l'évolution des
systèmes d'exploitation avec le support des caractères Unicode, ainsi que par le besoin
de plus en plus fréquent d'utiliser de longues chaînes de caractères.
• Ces différents types peuvent sans autre être combinés dans les affectations et les
expressions. Le compilateur prend en charge les conversions nécessaires de manière
automatique et transparente.
• La gestion de la place mémoire occupée par les variables de type string s'effectue de
manière dynamique. De ce fait la déclaration suivante:

var nom : String;


ne signifie pas que la place mémoire réservée à la variable nom est de 2 Goctets. En fait,
la taille de la mémoire allouée à la variable nom varie en fonction du nombre de
caractères contenus dans cette variable.
• Nous n'aborderons pas dans ce chapitre la manière dont sont stockées et représentées
de manière interne les chaînes de caractères de type string (AnsiString).
• Quelques nuances, que nous n'aborderons pas ici, concernant la représentation interne
ainsi que le traitement par le compilateur distinguent les types AnsiString et WideString.
• La VCL (Visual Component Library) de Delphi ne fait pas appel aux chaînes de type
WideString.

Pchar et tableaux de caractères


Delphi supporte des chaînes de caractères appelées les chaînes à zéro terminal (AZT). Ces
chaînes sont largement utilisées par les langages de programmation C et C++, et par Windows lui-
même. Grâce au fait que Delphi supporte les chaînes à zéro terminal, ainsi que des fonctions de
gestion de ces chaînes (dans l'unité SysUtils), il est facile d'interfacer un programme Delphi avec
d'autres langages ou avec l'API Windows.
Une chaîne AZT est constituée d'une suite de caractères non null, suivis d'un caractère null (code
ASCII #0). Ces chaînes n'ont pas d'indicateur de longueur séparé; le premier caractère NULL
d'une chaîne AZT marque la fin de cette chaîne.
Une chaîne AZT se déclare de la manière suivante:

array[0..N] of Char
Un tableau de caractères à base zéro (dont le début commence à l'indice zéro) est compatible
avec le type PChar. Cela signifie que partout où un PChar est attendu, il est possible d'utiliser à la
place un tableau de caractères à base zéro.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-6


Lorsqu'un tableau de caractères à base zéro est utilisé à la place d'une valeur PChar, le
compilateur convertit le tableau de caractères en une constante pointeur dont la valeur correspond
à l'adresse du premier élément du tableau.

Chaînes longues et chaînes à zéro terminal


La mémoire allouée pour une chaîne longue est terminée par un caractère null (qui ne fait pas
partie de la chaîne, mais qui est stocké immédiatement après le dernier caractère de la chaîne).
Grâce à cette marque de fin de chaîne, il est possible de transtyper une variable de type
AnsiString en variable de type Pchar (pointeur vers une chaîne de caractères). Il suffit d'écrire
PChar(S), où S est une variable AnsiString. Pchar(S) fournit un pointeur vers le premier caractère
d'une chaîne longue.
Voici un exemple montrant cette possibilité. Si nous voulons afficher une boîte de message à
l'aide des instructions suivantes:

var libelle : string;


begin
libelle := 'Bonjour';
messagebox (0, libelle, 'Message', mb_ok);
end;
une erreur se produit et le compilateur signale: "Types incompatibles 'String' et 'Pchar'". Il suffit
alors d'effectuer la modification suivante pour que tout rentre dans l'ordre:

var libelle : string;


begin
libelle := 'Bonjour';
messagebox (0, Pchar(libelle), 'Message', mb_ok);
end;
Cet exemple montre en fait comment passer des chaînes longues à une fonction qui attend des
paramètres chaîne à zéro terminal. Ce type de transtypage est très courant et même essentiel
lorsque l'on doit appeler des fonctions de l'API de Windows qui, rappelons-le, sont entièrement
écrites en langage C.

4.5 Routines de traitement des chaînes de caractères

Vous trouverez dans les tableaux qui suivent une description succincte des principales fonctions
et procédures proposées par Delphi pour la gestion des chaînes de caractères. Toutes ces
routines sont directement disponibles dans Delphi. Rappelons que l'on peut se procurer des
librairies de routines en tout genre permettant de compléter l'assortiment proposé par Delphi.
Routines de traitement de chaînes de type string (chaînes Pascal)

Fonction Description
AdjustLineBreaks Transforme les ruptures de lignes dans une chaîne en séquences CR/LF.
AnsiCompareStr Comparaison, en tenant compte des majuscules/minuscules, de deux
chaînes.
AnsiCompareText Comparaison, sans tenir compte des majuscules/minuscules, de deux
chaînes.
AnsiLowerCase Convertit des caractères en minuscules.
AnsiUpperCase Convertit des caractères en majuscules.
CompareStr Comparaison, en tenant compte des majuscules/minuscules, de deux
chaînes.
CompareText Comparaison, sans tenir compte des majuscules/minuscules, de deux

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-7


chaînes.
Concat Concatène une suite de chaînes.
Copy Renvoie une sous-chaîne d'une chaîne.
Delete Efface une sous-chaîne d'une chaîne.
DisposeStr Libère une chaîne du tas.
FmtLoadStr Charge une chaîne dans la ressource table de chaînes d'un programme.
Insert Insère une sous-chaîne dans une chaîne.
IntToHex Convertit un entier en hexadécimal.
IntToStr Convertit un entier en chaîne.
IsValidIdent Renvoie True si la chaîne spécifiée est un identificateur valide.
Length Renvoie la longueur dynamique de la chaîne.
LoadStr Charge la ressource chaîne depuis le fichier exécutable de l'application.
LowerCase Met en minuscules la chaîne spécifiée.
NewStr Alloue une nouvelle chaîne dans le tas.
Pos Recherche une sous-chaîne dans une chaîne.
Str Convertit une valeur numérique en chaîne.
StrToInt Convertit une chaîne en entier.
StrToIntDef Convertit une chaîne en entier ou à une valeur par défaut.
Trim Supprime les espaces de début et de fin et les caractères de contrôle
d'une chaîne donnée.
TrimLeft Supprime les espaces de début et les caractères de contrôle d'une chaîne
donnée
TrimRight Supprime les espaces de fin et les caractères de contrôle d'une chaîne
donnée.
UpperCase Met en majuscules la chaîne spécifiée.
Val Convertit une valeur chaîne en sa représentation numérique.

Routines de traitement de chaîne AZT

Fonction Description
StrAlloc Alloue une zone tampon d'une taille donnée sur le tas.
StrBufSize Renvoie la taille d'une zone tampon de caractère allouée en utilisant StrAlloc ou
StrNew.
StrCat Concatène deux chaînes.
StrComp Compare deux chaînes.
StrCopy Copie une chaîne.
StrDispose Dispose une zone tampon caractère allouée en utilisant StrAlloc ou StrNew.
StrECopy Copie une chaîne et renvoie un pointeur à la fin de la chaîne.
StrEnd Renvoie un pointeur à la fin d'une chaîne.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-8


StrFmt Formate une ou plusieurs valeurs dans une chaîne.
StrIComp Compare deux chaînes sans tenir compte des majuscules/minuscules.
StrLCat Concatène deux chaînes avec une longueur maximum donnée de la chaîne
résultante.
StrLComp Compare deux chaînes pour une longueur maximum donnée.
StrLCopy Copie une chaîne jusqu'à une longueur maximum donnée.
StrLen Renvoie la longueur d'une chaîne.
StrLFmt Formate une ou plusieurs valeurs dans une chaîne avec une longueur maximum
donnée.
StrLIComp Compare deux chaînes pour une longueur maximum donnée sans tenir compte
des majuscules/minuscules.
StrLower Convertit une chaîne en minscules.
StrMove Déplace un bloc de caractères d'une chaîne sur l'autre.
StrNew Alloue une chaîne sur le tas.
StrPCopy Copie une chaîne Pascal vers une chaîne à zéro terminal.
StrPLCopy Copie une chaîne Pascal vers une chaîne AZT avec une longueur maximum
donnée.
StrPos Renvoie un pointeur sur la première occurrence d'une sous-chaîne donnée dans
une chaîne.
StrRScan Renvoie un pointeur sur la dernière occurrence d'un caractère donné dans une
chaîne.
StrScan Renvoie un pointeur sur la première occurrence d'un caractère donné dans une
chaîne.
StrUpper Convertit une chaîne en majuscules.

Exercice 4.1

Ecrire un programme dans lequel l'utilisateur peut taper un texte dans un Edit. Lorsque l'utilisateur
clique sur le bouton "Encadrer" le programme doit afficher la chaîne de caractères dans un Label,
encadrée par des astérisques. Il convient de prendre un Label assez haut pour contenir au moins
trois lignes. Pour une bonne présentation du résultat, il convient également de choisir une police
de caractères non proportionnelle pour le Label.
Voici l'aspect du programme:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-9


Exercice 4.2

Ecrire un programme qui agit sur une phrase en remplaçant toutes les séquences de deux ou
plusieurs espaces par un seul espace, et affiche la phrase obtenue. Afin de mieux visualiser les
espaces, il est préférable de choisir une police de caractères non proportionnelle.
Voici l'aspect du programme:

Exercice 4.3

Ecrire un programme qui affiche dans un Listbox les mots d'une phrase introduite par l'utilisateur.
Chaque ligne du Listbox doit contenir un mot de la phrase. On considère que les mots sont
séparés par un ou plusieurs espaces.
L'instruction qui permet d'ajouter une ligne dans un Listbox est:
Listbox.Items.Add (mot); // mot est la chaîne à ajouter
Voici l'aspect du programme:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-10


Exercice 4.4

Ecrire un programme qui calcule la fréquence d'apparition des voyelles dans un texte que
l'utilisateur fournit. Ce programme affiche ensuite un histogramme sous la forme suivante:

Le texte est placé dans un Memo (en fait dans Memo.text). L'histogramme est constitué de six
Labels ayant une couleur se distinguant du fond gris de la fenêtre et dont la largeur varie en
fonction de la fréquence d'apparition des voyelles.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-11


Améliorer ce programme en ajoutant les fonctionnalités suivantes:
• afficher le nombre d'apparition de chaque voyelle
• effectuer des tests pour éviter que les barres de couleur ne sortent de la fenêtre
• au cas où une barre de couleur atteint le bord droit de la fenêtre, effectuer une
renormalisation qui réduit proportionnellement la largeur de toutes les barres.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-12


Chapitre 5 TYPES STRUCTURES

A la fin de cette partie, vous serez capable de:

• Utiliser à bon escient les types structurés ensemble, tableau, enregistrement;


• Utiliser les composants Listbox, Memo et StringGrid dans des applications
Delphi;

5.1 Introduction

Les types de données que nous avons rencontrés jusqu'ici étaient des types scalaires (ou non
structurés), c'est-à-dire élémentaires. Parmi ces types nous avons distingué les types prédéfinis
de ceux que l'utilisateur peut lui-même définir. Dans ce chapitre, nous allons étudier les types
structurés qui constituent une des richesses de la représentation des données en Pascal. Un type
est structuré s'il est composé de plus d'un élément. C'est le cas des types suivants:
• ensembles
• tableaux
• enregistrements
• fichiers
Les fichiers font l'objet d'une étude séparée dans la suite de ce cours, étant donné leur importance
et les notions qui leur sont attachées. Nous avons réservé une place en fin de chapitre aux
constantes déclarées avec type et aux variables absolues.

5.2 Type ensemble (set)

En Pascal, les ensembles sont mis en oeuvre avec la même signification qu'ils possèdent en
mathématique. On y retrouve d'ailleurs les mêmes opérations. Pour mieux comprendre l'utilité du
type ensemble, considérons le problème suivant. Comment savoir si un caractère tapé dans un
Edit par l'utilisateur est une voyelle majuscule ? Une structure sélective permet de résoudre
facilement ce problème. Elle doit être placée dans l'événement OnKeyPress de l'Edit.:

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);


begin
if (Key = 'A') or (Key = 'E') or (Key = 'I') or (Key = 'O')
or (Key = 'U') or (Key = 'Y') then
Label1.caption := 'C''est une voyelle';
end;

Ce genre de problème, consistant à déterminer si le contenu d'une variable appartient à un


ensemble de valeurs données, se rencontre souvent en programmation. Voici comment il est
possible de résoudre ce problème à l'aide d'un ensemble:

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);


begin
if Key in ['A','E','I','O','U','Y'] then
Label1.caption := 'C''est une voyelle';
end;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-1


Ici, l'ensemble des voyelles est spécifié de manière explicite par une liste de constantes placée
entre crochets, appelée constructeur d'ensemble. Cette forme d'écriture abrégée est bien plus
élégante, plus claire et plus concise qu'une expression booléenne complexe. L'ensemble des
voyelles peut, dans cet exemple, être considéré comme une constante de type ensemble.
Un ensemble contient toujours des éléments du même type de base, scalaire (prédéfini ou
énuméré) ou intervalle, à l'exception du type réel. Le type ensemble se déclare de la manière
suivante:

SYNTAXE

type type_ensemble = set of type_de_base;

Une variable de type type_ensemble peut contenir des éléments de type type_de_base,
qui doit être un type scalaire ou intervalle, à l'exception du type réel.

Le nombre maximum d'éléments d'un ensemble est limité à 256. De plus, deux ensembles sont
égaux s'ils contiennent les mêmes éléments, indépendamment de leur l'ordre.
La définition d'une variable de type ensemble comporte la spécification du type de base des
éléments de cet ensemble. Seule une affectation permet de placer des valeurs dans une variable
de ce type. L'erreur consistant à croire que la déclaration d'une telle variable suffit à lui attribuer
des éléments se rencontre fréquemment.
L'affectation d'éléments à un ensemble s'effectue à l'aide d'un constructeur d'ensemble qui n'est
autre qu'une liste d'éléments du type de base, délimitée par des crochets. La notation d'intervalle
est possible lorsque plusieurs éléments sont consécutifs. Le fragment de programme qui suit
montre quelques exemples de déclaration et d'utilisation de variables de type ensemble:

type alphabet = set of 'A'..'Z';


couleurs = (rouge, vert, bleu, jaune, blanc);
teinte = set of couleurs;
bizarre = set of 0..255;

var lettre, voyelle : alphabet;


c : char;
lumiere : teinte;
clair : set of jaune..blanc;
magique : bizarre;

...
begin
lettre := ['A'..'Z'];
voyelle := ['A','E','I','O','U','Y'];
lumiere := [blanc];
clair := [jaune, blanc];
magique := [0..8,12,19,22..38,99];
...
if c in voyelle then Edit1.Text := 'Voyelle';
end;
...
Le langage Pascal dispose d'opérateurs agissant sur les ensembles, de manière analogue aux
opérateurs de la théorie des ensembles en mathématique. On trouve, d'un côté, les opérateurs
ensemblistes proprement dits (union, intersection et différence) et, de l'autre, des opérateurs
relationnels (appartenance, égalité, altérité, inclusion). Comme pour les nombres, ces opérateurs
ont un degré de priorité et, en cas de même degré de priorité, l'évaluation des expressions se fait
de gauche à droite. Mais voyons comment s'expriment ces opérateurs en Pascal. Les variables
utilisées sont déclarées comme suit:

var nombres, resultat, a, b : set of 0..10;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-2


reponse : char;
daccord : boolean;

intersection *
nombres := [1..10];
resultat := nombres * [2,4..6]; // resultat aura la valeur [2,4,5,6]
a := [1,2,4];
b := [3,5];
if a * b = [] then // [ ] représente l'ensemble vide.
Label1.Caption := 'a et b sont disjoints';

union +

resultat := a + b + [6,7,8,9]; // resultat aura la valeur


// [1,2,3,4,5,6,7,8,9]

différence -

resultat := nombres - [1,3..7]; // resultat aura la valeur [2,8,9]

égalité =
if (nombres = [1..10]) then
resultat := nombres - [5..10];

altérité <>
if (nombres - [5..10]) <> [] then
Label1.Caption := 'Correct';

inclusion <=
if nombres <= [0,2,4,6,8] then
Label1.Caption := 'pair';
if (a <= b) and (a <> b) then
Label1.Caption := 'a inclus mais non égal à b';

appartenance in
daccord := reponse in ['o','O'];
Il est important de ne pas confondre la notion d'ensemble avec la notion d'intervalle ou de type
énuméré. Si, par exemple, on désire écrire:

caractere := ['A'..'L'];
caractere peut, par exemple, être déclaré comme:

var caractere : set of 'A'..'L';


et non comme:

var caractere : 'A'..'L';

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-3


5.3 Type tableau (array)

Les tableaux, ou variables indicées, sont nés du besoin de pouvoir traiter plusieurs variables du
même type, groupées sous le même identificateur et différentiables par la valeur d'un indice. Pour
mieux saisir la nécessité de disposer de tableaux, essayons de résoudre le problème qui consiste
à calculer la moyenne de dix nombres. Une solution serait d'écrire le programme suivant:

var a1, a2, a3, a4, a5, a6, a7, a8, a9, a10 : real;
a_moyen : real;

...
procedure TForm1.Button1Click(Sender: TObject);
{on suppose que l'on a affecté un contenu aux variables a1 à a10}
begin
a_moyen := (a1+a2+a3+a4+a5+a6+a7+a8+a9+a10) / 10;
Label1.Caption := FloatToStr (a_moyen);
end;

Il serait évidemment aberrant de calculer la moyenne de 10'000 nombres avec une telle méthode.
Les variables a1 à a10 contiennent des données de même nature, il serait donc plus judicieux de
disposer d'une variable unique, mais structurée en plusieurs composantes pouvant recevoir
chacune une donnée de même type. Cette idée est précisément reprise dans la plupart des
langages de programmation qui disposent du type tableau. Voici ce que devient le programme de
calcul de moyenne avec l'utilisation d'une variable de type tableau:

const MAX = 10;


type tableau = array [1..MAX] of real;
var a : tableau;
i : 1..MAX;
somme : real;

begin
somme := 0;
for i := 1 to MAX do
somme := somme + a[i];
Label1.Caption := FloatToStr (somme / MAX);
end.

La distinction des valeurs contenues dans le tableau a se fait par la valeur de l'indice i, indiquant
le numéro de l'élément considéré. En effet, pour référencer un élément d'un tableau, il faut
spécifier l'identificateur de la variable suivi, entre crochets, de l'indice. De plus, notre programme
est devenu beaucoup plus général, puisqu'il suffit de changer la valeur de la constante MAX pour
qu'il calcule la moyenne de 100, 1000 ou 10'000 nombres. Cette forme de programme permet
notamment l'utilisation aisée des structures itératives.

SYNTAXE

type type_tableau = array [intervalle] of type_element;

Type_tableau décrit une collection d'éléments de même nature et de même type,


type_element. Intervalle doit être un intervalle de type simple: entier, booléen, caractère
ou énuméré, mais pas réel. L'indice du tableau prend ses valeurs dans intervalle. Le type
type_element peut être un type simple ou structuré, comme par exemple un tableau.

La taille d'une variable de type tableau est déterminée par la taille de ses éléments et par leur
nombre.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-4


On utilise généralement un tableau lorsque le nombre d'éléments est connu à l'avance. Une
déclaration telle que array [1..10] of integer interdit les valeurs de l'indice non comprises dans
l'intervalle 1..10.
Nous avons vu qu'il n'y a pas de restriction sur le type des éléments d'un tableau. Ainsi on peut
définir des tableaux de tableaux, appelés aussi tableaux multidimensionnels.
Comme premier exemple, considérons une grille de mots croisés de 30 lignes et 40 colonnes. Les
déclarations suivantes montrent comment transposer la nature d'une grille de mots croisés dans
une structure de données informatique équivalente. Chaque case de la grille est représentée par
un élément d'un tableau bidimensionnel:

type ligne = array [1..40] of char;


tableau = array [1..30] of ligne;
var grille : tableau;
rangee : ligne;
Ces déclarations peuvent également s'écrire de la manière suivante:

const largeur = 40;


hauteur = 30;
type horizontal = 1..largeur;
vertical = 1..hauteur;
ligne = array [horizontal] of char;
tableau = array [vertical] of ligne;
var grille : tableau;
rangee : ligne;

On peut remarquer dans ces déclarations l'interdépendance des différents objets. La constante
largeur figure dans la déclaration de horizontal, le type horizontal dans celle de ligne, le type
ligne dans celle de tableau, enfin le type tableau dans celle de la variable grille. Les justifications
de cette façon de procéder sont nombreuses et, parmi elles, celle de donner un nom à chaque
objet. Cela permet de multiples références, comme par exemple, déclarer plusieurs variables de
même type. Une autre justification est illustrée par la facilité d'adaptation du programme qui en
découle. Supposons que l'on veuille satisfaire à une nouvelle requête ou situation, par exemple
adapter ce programme pour une nouvelle grille comportant 20 lignes et 35 colonnes. Il suffit alors
de modifier les constantes largeur et hauteur, puis de recompiler le programme. Les programmes
ainsi paramétrés sont plus généraux.
Mais revenons aux déclarations, et en particulier aux deux suivantes:
ligne = array [horizontal] of char;
tableau = array [vertical] of ligne;
nous aurions pu les contracter de cette manière:

tableau = array [vertical] of array [horizontal] of char;


ou ainsi:

tableau = array [vertical, horizontal] of char;


Les trois formes sont équivalentes. Les instructions permettant de remplir la première ligne de la
variable grille avec des astérisques seraient:

for i := 1 to largeur do
grille [1, i] := '*';

Mettre la grille de mots croisés à blanc reviendrait à écrire:


for j := 1 to hauteur do
for i := 1 to largeur do
grille [j, i] := ' ';

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-5


Une fois encore, on constate que l'emploi de structures itératives convient tout à fait au traitement
des données représentées sous forme de tableaux.
Voyons maintenant un exemple de programme utilisant un tableau dont l'indice est de type
caractère. Ce petit programme détermine la fréquence d'apparition de chaque lettre minuscule
dans un texte tapé par l'utilisateur:

Pour ne pas compliquer l'exemple nous avons utilisé l'événement OnKeyPress du Memo dans
lequel l'utilisateur tape le texte. A chaque frappe, s'il s'agit d'une lettre minuscule, le tableau
contenant le nombre d'apparitions est mis à jour et son contenu est affiché dans le Listbox de
droite. Voici le code complet de ce programme:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
ListBox1: TListBox;
Memo1: TMemo;
Label1: TLabel;
procedure FormActivate(Sender: TObject);
procedure Memo1KeyPress(Sender: TObject; var Key: Char);
private
{ Déclarations privées }
public
{ Déclarations publiques }
table : array ['a'..'z'] of integer;
end;

var Form1: TForm1;

implementation

{$R *.DFM}

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-6


procedure TForm1.FormActivate(Sender: TObject);
{ initialisation du tableau }
var lettre : char;
begin
for lettre := 'a' to 'z' do
table[lettre] := 0;
end;

procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char);


var lettre : char;
begin
if key in ['a'..'z'] then
table[key] := table[key] + 1;
listbox1.clear;
for lettre := 'a' to 'z' do
listbox1.items.add (lettre + ' ' + inttostr(table[lettre]));
end;

end.
L'intérêt de ce programme est que chaque élément du tableau n'est pas repéré par un nombre,
mais par un caractère. Le contenu d'un élément est un nombre entier correspondant à la
fréquence d'apparition du caractère désignant ce même élément. La variable table possède donc
26 éléments, et il convient d'initialiser à zéro chacun des 26 éléments au début du programme. La
condition if lettre in ['a'..'z'] ... permet de s'assurer que le caractère lu (indice du tableau) est bien
dans l'intervalle 'a'..'z'.
Dans le prochain exemple, l'indice du tableau est de type énuméré. Seules les déclarations et une
portion du programme sont présentées. Un indice de type énuméré peut contribuer à rendre les
programmes plus lisibles:

type rayons = (sport, menage, habillement, alimentation);


gain = array [rayons] of real;
var benefice : gain;
rayon : rayons;
total : real;
...
(* calcul du bénéfice réalisé dans tous les rayons du magasin *)
total := 0;
for rayon := sport to alimentation do
total := total + benefice[rayon];
Edit1.Text := 'Bénéfice réalisé: ', total:10:2, ' francs.';
...
Les opérations agissant sur les tableaux mettent en jeu les éléments pris individuellement et non
le tableau de manière globale. Pour illustrer cette particularité, considérons les déclarations qui
suivent:

type lignes = array [1..10] of integer;


var a, b, c : lignes;
il n'est pas permis d'écrire:
a := b + c;
Une addition composante par composante doit être effectuée, par exemple, à l'aide de la
procédure suivante:

procedure somme (x, y: lignes; var z: lignes);


var i : integer;
begin
for i := 1 to max do
z[i] := x[i] + y[i];
end;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-7


L'affectation fait office d'exception. En effet, le transfert du contenu d'un tableau dans un autre est
possible de manière globale, pour autant que les deux soient du même type. Dans ce cas,
l'instruction:
b := a;
est acceptée et est équivalente à:
for i := 1 to 10 do
b[i] := a[i];
Parfois, il est nécessaire de paramétrer une procédure avec des éléments d'un tableau. Dans ce
cas, il est préférable de passer tout le tableau comme paramètre, ce qui ne pose aucun problème,
mais amène une légère contrainte. Le type des paramètres spécifiés dans l'en-tête d'une
procédure doit être un identificateur. Il n'est donc pas possible de spécifier une déclaration de ce
genre:
procedure tri (var liste: array[1..10] of real);
On la remplacera donc par:
type tableau = array [1..10] of real;
...
procedure tri (var liste: tableau);
L'en-tête de la procédure gagne en lisibilité, la contrainte n'est pas trop pesante.
Pour terminer, nous allons nous intéresser à un programme consistant à effectuer un tri, dans
l'ordre croissant, des valeurs contenues dans un tableau de 10 éléments. Le programme dispose
d'un bouton qui génère 10 nombres entiers au hasard. Il les place dans un tableau et les affiche
dans un Listbox. Puis la procédure tri effectue un tri par échange. Enfin, le contenu du tableau trié
est affiché dans le second Listbox.
La méthode de tri par échange consiste à prendre chaque élément du tableau et à le comparer
avec tous ses suivants. Chaque fois que les deux éléments comparés ne figurent pas dans le
tableau en ordre croissant, ils sont permutés. Cette méthode n'est de loin pas la plus efficace,
mais elle est facile à comprendre et à mettre en oeuvre. Voici comment se présente notre
exemple:

Le code de ce programme se présente de la manière suivante:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-8


unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

const MAX = 10;


type tableau = array [1..MAX] of integer;
type
TForm1 = class(TForm)
ListBox1: TListBox;
ListBox2: TListBox;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
table : tableau;
end;
var Form1: TForm1;

implementation
{$R *.DFM}

procedure tri (var liste: tableau);


var i, j : integer;
temp : integer;
begin
for i := 1 to MAX - 1 do
for j := i + 1 to MAX do
if liste[i] > liste[j] then begin
temp := liste[i];
liste[i] := liste[j];
liste[j] := temp;
end;
end;

procedure TForm1.Button1Click(Sender: TObject);


var i : integer;
begin
listbox1.clear; // on vide le listbox
for i := 1 to MAX do begin
table[i] := random (100);
listbox1.items.add (inttostr(table[i]));
end;
end;

procedure TForm1.Button2Click(Sender: TObject);


var i : integer;
begin
tri (table); // tri proprement dit
listbox2.clear;
for i := 1 to MAX do
listbox2.items.add (inttostr(table[i]));
end;

end.
fig. 5.1

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-9


5.4 Utilisation de composants intégrant des tableaux

5.4.1 Introduction
Les tableaux apparaissent, implicitement ou explicitement, très fréquemment dans Delphi. Que ce
soit en tant que propriétés de certains types de données, ou bien lorsque l'on utilise certains
composants de la VCL (Visual Component Library). Dans cette section nous allons nous attarder
essentiellement sur trois composants: les Listbox, les Memo et les StringGrid..

5.4.2 Listbox
Un Listbox, appelé zone de liste en français, est un ensemble ordonné de chaînes de caractères
représenté sous la forme suivante:

En fonction du nombre d'éléments, un Listbox peut disposer d'une barre de défilement. Chaque
élément d'un Listbox se trouve sur une et une seule ligne. De plus, il n'est pas possible d'écrire
directement dans un Listbox. L'utilisateur peut ajouter, supprimer ou sélectionner un ou plusieurs
éléments de la liste. Une zone de liste dispose de plusieurs propriétés et méthodes dont nous
allons décrire ci-dessous les plus importantes:

Items property Items: TStrings;

Items contient l'ensemble des chaînes de caractères de la liste.


Le type Tstrings représente une liste de chaînes de caractères.

Exemple: Liste1.Items.Add ('Licorne');

Items possède lui-même des propriétés et des méthodes (voir plus loin).
Dans l'exemple ci-dessus, Add est une méthode de Items permettant
d'ajouter une chaîne de caractères dans la liste existante Items. Dans la
liste ci-contre, 'Vache', 'Cheval', … sont les chaînes de caractères
contenues dans Items:

Itemindex property ItemIndex: Integer;

La propriété ItemIndex indique le numéro d'ordre de l'élément sélectionné dans la liste.


ItemIndex est utilisé aussi bien pour sélectionner un élément de la liste, que pour connaître le
numéro de l'élément sélectionné au moment du fonctionnement du programme.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-10


Pour sélectionner un élément de la liste, il faut attribuer à la propriété ItemIndex la valeur désirée,
correspondant au numéro d'ordre de l'élément dans la liste. La valeur ItemIndex pour le premier
élément est 0. Si aucun élément n'est sélectionné, cette valeur est de -1.

Exemple: Liste2.Items.Add (liste1.Items[Liste1.ItemIndex]);

Dans la liste ci-contre, la chaîne 'Chat' est l'élément numéro 3 de la liste.

Liste1.Itemindex vaut 3, et donc,


Liste1.Items[Liste1.Itemindex] vaut 'Chat'.

Lorsque la propriété MultiSelect (voir ci-dessous) est vraie, la valeur ItemIndex représente le
numéro de l'élément qui a le "focus" parmi les éléments sélectionnés. Si la propriété MultiSelect
est vraie la valeur par défaut de ItemIndex est 0.

MultiSelect property MultiSelect: Boolean;


Lorsque cette propriété est vraie, il est possible de sélectionner plusieurs éléments de la liste en
même temps.
Petit rappel: pour sélectionner plusieurs éléments d'une liste, il faut cliquer avec la souris sur
l'élément ou les éléments choisis et appuyer en même temps les touches shift ou contrôle du
clavier.

SelCount property SelCount: Integer;


La propriété SelCount permet de connaître le nombre d'éléments sélectionnés dans la liste, si la
propriété MultiSelect est vraie.
Cette propriété est consultable, mais n'est pas modifiable. Lorsque la propriété MultiSelect et
fausse, la propriété SelCount vaut toujours -1.

Exemple: Res.Text := 'Il y a ' + IntToStr(Liste1.SelCount) + ' éléments sélectionnés.';

Dans la liste suivante, Selcount vaut 4.

Selected property Selected[Index: Integer]: Boolean;


La propriété Selected permet de déterminer ou de connaître quels éléments de la liste sont
sélectionnés. Il s'agit en fait d'un tableau de valeurs booléennes.
Dans la liste précédente, Liste1.Selected[1] vaut true, alors que Liste1.Selected[4] vaut false.
Cette propriété est très utile lorsque, par exemple, un traitement doit être appliqué uniquement aux
éléments sélectionnés d'une liste.

Exemple: if Liste1.Selected[4] then…

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-11


Sorted property Sorted: Boolean;
La propriété Sorted indique si la liste doit être automatiquement triée alphabétiquement.

Exemple: Liste1.Sorted := true;

Clear procedure Clear;


Cette méthode permet de vider toute la liste en une seule fois.

ItemAtPos function ItemAtPos(Pos: TPoint; Existing: Boolean): Integer;


Cette méthode permet de connaître le numéro de l'élément situé à une coordonnée spécifiée.
Ceci peut être utile pour retrouver un élément de la liste qui se trouve sous le curseur de la souris,
par exemple.
Si Pos se trouve après le dernier élément de la liste, la valeur du paramètre Existing détermine la
valeur retournée par ItemAtPos:
• Si Existing est true, ItemAtPos retourne –1
• Si Existing est false, ItemAtPos retourne l'indice du dernier élément de la liste, plus 1.

Propriétés et méthodes de Items

Count property Count: Integer;


La propriété Count indique le nombre d'éléments actuellement contenus dans une liste.

Exemple: vide := (Liste1.Items.Count = 0);

Dans la liste suivante, Count vaut 5:

Strings property Strings[Index: Integer]: string;


Cette propriété permet de spécifier une chaîne de caractères d'une liste grâce à son indice.
Les deux lignes de l'exemple suivant sont équivalentes:

Exemple: Liste1.Items.Strings[0] := 'Ceci est la 1ère chaîne';


Liste1.Items [0] := 'Ceci est la 1ère chaîne';

Add function Add(const S: string): Integer; virtual;


Cette méthode permet d'ajouter un élément à la fin d'une liste
Add retourne l'indice du nouvel élément placé dans la liste.

Exemple: Num := Liste1.Items.Add ('Ligne ajoutée à la liste');


Liste1.Items.Add ('Ligne aussi ajoutée à la liste');

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-12


AddStrings procedure AddStrings(Strings: TStrings); virtual;
Cette méthode permet d'ajouter une liste de chaîne de caractères dans la liste courante.
Dans l'exemple suivant, tous les éléments de la liste Liste2 sont ajoutés en une seule fois dans la
liste Liste1:

Exemple: Liste1.Items.AddStrings (Liste2.Items)

Append procedure Append(const S: string);


Cette méthode est équivalente à la méthode Add, sauf qu'elle ne retourne pas l'indice du nouvel
élément ajouté.

Exemple: Liste1.Items.Append ('Ligne ajoutée à la liste');

Delete procedure Delete(Index: Integer); virtual; abstract;


Cette méthode permet de supprimer une chaîne de caractères de la liste courante.

Exemple: Liste1.Items.Delete (2);

Voici le résultat:

Exchange procedure Exchange(Index1, Index2: Integer); virtual;


Cette méthode permet de permuter deux éléments d'une liste en indiquant leurs indices respectifs.

Exemple: Liste1.Items.Exchange (0, 3);

Voici le résultat

Insert procedure Insert(Index: Integer; const S: string); virtual; abstract;


Cette méthode permet d'insérer un élément à une position donnée de la liste courante.

Exemple: Liste1.Items.Insert (3, 'Coq');

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-13


LoadFromFile procedure LoadFromFile(const FileName: string); virtual;
Cette méthode permet de placer dans la liste courante les lignes du fichier texte passé comme
paramètre.
L'exemple suivant permet d'afficher dans la liste Liste1 le contenu du fichier c:\config.sys.

Exemple: Liste1.Items.LoadFromFile ('c:\config.sys');

Move procedure Move(CurIndex, NewIndex: Integer); virtual;


Cette méthode permet de déplacer un élément dans la liste courante.

Exemple: Liste1.Items.Move (0,3);

Voici le résultat:

SaveToFile procedure SaveToFile(const FileName: string); virtual;


Cette méthode permet d'enregistrer les éléments de la liste courante dans le fichier texte passé
comme paramètre.

Exemple: Liste1.Items.SaveToFile ('c:\config.sys');

5.4.3 Memo
Un Memo est un composant permettant la saisie de texte multiligne. Il possède des similitudes
avec le Listbox:
• La propriété Lines du Memo est équivalente et du même type que la propriété Items du
Listbox. Les deux sont du type Tstrings; donc, toutes les propriétés et méthodes décrites
ci-dessus pour les Listbox sont également valables pour les Memo
• L'aspect est le même que celui d'un Listbox.

Les principales différences sont:


• Un Memo est essentiellement utilisé pour saisir du texte généralement de taille assez
importante. De ce point de vue, on peut dire qu'il remplit le même rôle qu'un Edit, avec
l'aspect multiligne en plus
• L'utilisateur peut manipuler librement le texte: regrouper, ajouter, supprimer des lignes
• Le contenu d'un Memo peut être référencé et géré ligne par ligne, en faisant appel à la
propriété (tableau) Lines, par exemple:
Memo.Lines.Add ('Dernière ligne');
Mais il peut aussi être traité comme un tout, en faisant appel à sa propriété Text, de la
même manière qu'un Edit. La propriété Text n'est pas accessible depuis l'inspecteur
d'objet, mais on peut la gérer depuis le code d'un programme.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-14


Pour illustrer le fonctionnement d'un Memo observons l'exemple suivant:

Le code du premier bouton est le suivant:

procedure TForm1.Ligne3Click(Sender: TObject);


begin
Edit1.text := Memo1.Lines[3];
end;
Ici on accède au contenu du Memo ligne par ligne. En l'occurrence on place la ligne 3 (attention!
La première ligne porte le numéro zéro) dans le texte de l'Edit.

Le code du deuxième bouton est le suivant:

procedure TForm1.AjouterClick(Sender: TObject);


begin
Memo1.Lines.Add (Edit2.Text);
end;
Le contenu de l'Edit est ajouté en tant que nouvelle ligne dans le Memo. On aurait pu écrire:

procedure TForm1.AjouterClick(Sender: TObject);


begin
Memo1.Text := Memo1.text + Edit2.text;
end;
Dans ce cas le texte de l'Edit est simplement ajouté au texte du Memo, sans insérer de saut de
ligne.

Enfin, le code du troisième bouton est le suivant:

procedure TForm1.SupprimerClick(Sender: TObject);


begin
Memo1.lines.Delete (1);
end;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-15


5.4.4 StringGrid
Un StringGrid est une grille constituée de cellules, un peu à la manière d'un tableur. Chaque
cellule peut contenir une chaîne de caractères, d'où le nom de ce composant.
Les propriétés et méthodes des StringGrid sont nombreuses. Vous trouverez ci-dessous une
description des principales. Pour avoir des informations concernant les autres vous pouvez faire
appel à l'aide de Delphi.

Cells property Cells[ACol, ARow: Integer]: string;


On utilise la propriété Cells pour accéder aux cellules de la grille, en fait aux chaînes de
caractères qu'elles contiennent.
ACol indique la coordonnée de la colonne et ARow celle de la ligne. La première cellule en haut
et à gauche a pour coordonnées (0,0).

Exemple: StringGrid1.Cells[2,3] := 'Capital initial';


Voici un exemple montrant comment sont numérotées les cellules d'une grille:

fig. 5.2

Cols property Cols[Index: Integer]: TStrings;


On utilise la propriété Cols pour accéder aux chaînes de caractères d'une colonne entière.
Index indique la coordonnée de la colonne.

Exemple: Listbox1.items := StringGrig1.Cols[3];

Rows property Rows[Index: Integer]: TStrings;


On utilise la propriété Rows pour accéder aux chaînes de caractères d'une ligne entière.
Index indique la coordonnée de la ligne.

Exemple: StringGrid1.Rows[2] := StringGrid1.Rows[3];


Dans cette exemple, le contenu de la colonne 3 est copié dans la colonne 2.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-16


Col property Col: Longint;
La propriété Col indique l'indice de la colonne contenant la cellule sélectionnée, autrement dit la
cellule courante.

Row property Row: Longint;


La propriété Row indique l'indice de la ligne contenant la cellule sélectionnée, autrement dit la
cellule courante.

Exemple: procedure TForm1.StringGrid1Click(Sender: TObject);


begin
Label1.Caption := 'Le curseur se trouve dans la colonne ' +
IntToStr(StringGrid1.Col + 1) + ', en ligne ' +
IntToStr(StringGrid1.Row + 1);
end;

ColCount property ColCount: Longint;


La propriété ColCount indique le nombre de colonnes de la grille.

RowCount property ColCount: Longint;


La propriété RowCount indique le nombre de lignes de la grille.

Exemple: for i := 0 to StringGrid1.ColCount - 1 do


for j := 0 to StringGrid1.RowCount - 1 do
StringGrid1.Cells[i, j] := '(' + inttostr(i) + ',' + inttostr(j) + ')';
Cet exemple a été utilisé pour remplir la grille de la figure 5.2.

DefaultColWidth property DefaultColWidth: Integer;


La propriété DefaultColWidth indique, en pixels, la largeur de toutes les colonnes de la grille
n'ayant pas été explicitement redimensionnées.

DefaultRowHeight property DefaultRowHeight: Integer;


La propriété DefaultRowHeight indique, en pixels, la hauteur de toutes les lignes de la grille
n'ayant pas été explicitement redimensionnées.

ColWidths property ColWidths[Index: Longint]: Integer;


La propriété ColWidths indique la largeur, exprimée en pixels, de chaque colonne de la grille.
Index permet de spécifier la colonne.

Exemple: StringGrid1.ColWidths[2] := 50;


Cet exemple fixe la largeur de la troisième colonne à 50 pixels.

RowHeights property RowHeights[Index: Longint]: Integer;


La propriété RowHeights indique la hauteur, exprimée en pixels, de chaque ligne de la grille.
Index permet de spécifier la ligne.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-17


FixedCols property FixedCols: Integer;
La propriété FixedCols indique le nombre de colonnes à gauche de la grille qui ne défilent pas.
Comme on peut le voir sur la figure 5.2, par défaut, les cellules correspondantes sont de couleur
grise.

FixedRows property FixedCols: Integer;


La propriété FixedRows indique le nombre de lignes en haut de la grille qui ne défilent pas.
Comme on peut le voir sur la figure 5.2, par défaut, les cellules correspondantes sont de couleur
grise.

CellRect function CellRect(ACol, ARow: Longint): TRect;


La fonction CellRect renvoie les coordonnées écran d'une cellule de la grille.
ACol et ARow indiquent les coordonnées de la cellule. Le résultat est fourni sous forme d'un
rectangle, correspondant à l'emplacement de la cellule dans la grille.

MouseToCell procedure MouseToCell(X, Y: Integer; var ACol, ARow: Longint);


La procédure MouseToCell renvoie les indices de colonne et de ligne de la cellule occupant la
position de coordonnées écran (X,Y).
X et Y représentent des coordonnées écran, par exemple celles de la souris; ACol et ARow sont
les coordonnées de la cellule, exprimées en termes de numéro de ligne et de colonne.

Afin d'illustrer ce qui précède, nous allons examiner un exemple d'application faisant intervenir un
StringGrid.

Pour afficher les coordonnées de chaque cellule dans la grille nous avons utilisé les instructions
suivantes, déclenchées par l'événement OnActivate de la fiche:

procedure TForm1.FormActivate(Sender: TObject);


var i, j : integer;
begin
for i := 0 to SG.ColCount - 1 do

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-18


for j := 0 to SG.RowCount - 1 do
SG.Cells[i, j] := '(' + inttostr(i) + ',' + inttostr(j) + ')';
end;
Le bouton "Cols[1]" exécute l'instruction suivante:

listbox1.items := SG.Cols[1];
Il est important de comprendre que SG.Cols[1] ne représente pas une chaîne de caractères, mais
la liste des chaînes de caractères de la colonne 1. Cette liste est de type Tstrings, tout comme
d'ailleurs la propriété Items d'un Listbox, d'où la possibilité d'affectation de l'un à l'autre. Voici ce
qui apparaît lorsque l'on clique sur ce bouton:

Le bouton "Rows[1]" effectue une opération équivalente, mais concerne la ligne 1. Voici le résultat:

Puisque Cols[1] représente la liste des chaînes de caractères de la colonne 1, comment obtenir le
contenu d'une cellule particulière de la colonne 1? Simplement en indiquant le numéro de la ligne
désirée sous forme d'un indice de tableau:

Edit1.Text := SG.Cols[1][3];
Voici le résultat de l'exécution de cette instruction:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-19


Le bouton " Rows[2] := Rows[3]" affecte à la ligne 2 le contenu des cellules de la ligne 3. Le code
est le suivant:

SG.Rows[2] := SG.Rows[3];

Enfin, pour illustrer l'utilisation de la procédure MouseToCell nous avons fait appel à l'événement
OnMouseMove de la grille:

procedure TForm1.SGMouseMove(Sender: TObject; Shift: TShiftState;


X, Y: Integer);
var col, ligne : integer;
begin
SG.MouseToCell (x, y, col, ligne);
label1.caption := 'La souris se trouve sur la cellule (' +
inttostr(col) + ',' + inttostr(ligne) + ')';
end;
Rappelons que cette procédure événementielle est appelée à chaque mouvement de la souris.
Voici le résultat obtenu:

Une des plus grandes limitations de ce composant est que ses diverses propriétés concernent
toutes les cellules. Il n'existe pas de propriété permettant de changer la police de caractères ou la
couleur de fond d'une cellule particulière.
Toutefois ce composant peut être géré avec plus de souplesse en faisant appel à l'événement
OnDrawCell. Cet événement est généré autant de fois qu'il y a de cellules dans la grille. Pour
chaque cellule, à chaque fois que la grille doit être rafraîchie ou redessinée, Delphi nous fournit,
entre autres, les paramètres ACol et ARow contenant le numéro de la colonne et de la ligne de la
cellule concernée, ainsi que le paramètre Rect représentant le rectangle sous-jacent à la cellule:

procedure TForm1.SGDrawCell(Sender: TObject; ACol, ARow: Integer;


Rect: TRect; State: TGridDrawState);

C'est précisément en réponse à cet événement que le programmeur peut appliquer un


comportement différentié aux cellules de la grille.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-20


Pour terminer, voici un exemple de programme faisant appel à un StringGrid. Ce programme
ouvre un fichier graphique de type BMP, l'affiche en taille réelle (en haut à droite de la fenêtre) et
le transpose dans un StringGrid. Chaque pixel de l'image correspond à une cellule du StringGrid.
L'utilisateur peut, entre autres, dessiner sur le StringGrid avec la couleur qu'il choisit à l'aide de
trois curseurs (R, G et B). Le dessin sur le StringGrid est automatiquement répercuté sur l'image
en taille réelle. Il peut aussi capturer une couleur de l'image et l'utiliser pour dessiner. Le
programme ne permet pas de sauvegarder une image, mais l'utilisateur peut recharger l'image
d'origine.

Le programme complet est étonnamment compact: il est constitué d'une trentaine de lignes de
code écrites par le programmeur. Le voici au complet:
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, Grids, StdCtrls;

type
TForm1 = class(TForm)
SG: TStringGrid;
IM: TImage;
Recharger: TButton;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-21


rouge: TScrollBar;
vert: TScrollBar;
bleu: TScrollBar;
ex: TPanel;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
charger: TButton;
OD: TOpenDialog;
Label4: TLabel;
Label5: TLabel;
procedure SGDrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect;
State: TGridDrawState);
procedure rougeChange(Sender: TObject);
procedure SGMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormActivate(Sender: TObject);
procedure SGMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure SGMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure RechargerClick(Sender: TObject);
procedure chargerClick(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
dessin : boolean;
nom : string;
end;

var Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.SGDrawCell(Sender: TObject; ACol, ARow: Integer;


Rect: TRect; State: TGridDrawState);
begin
SG.Canvas.Brush.color := im.canvas.pixels[Acol, Arow];
SG.canvas.fillrect (rect);
end;

procedure TForm1.rougeChange(Sender: TObject);


begin
ex.Color := rgb(rouge.position, vert.position, bleu.position);
end;

procedure TForm1.SGMouseDown(Sender: TObject; Button:


TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if ssleft in shift then begin
im.canvas.pixels[SG.Col, SG.row] := ex.color;
dessin := true;
end;
if ssright in shift then begin
rouge.position := GetRvalue (SG.canvas.pixels[x,y]);
vert.position := GetGvalue (SG.canvas.pixels[x,y]);
bleu.position := GetBvalue (SG.canvas.pixels[x,y]);
end;
end;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-22


procedure TForm1.FormActivate(Sender: TObject);
begin
dessin := false;
nom := '';
end;

procedure TForm1.SGMouseUp(Sender: TObject; Button: TMouseButton;


Shift: TShiftState; X, Y: Integer);
begin
dessin := false;
end;

procedure TForm1.SGMouseMove(Sender: TObject; Shift: TShiftState;


X, Y: Integer);
var r, c : integer;
begin
if dessin then begin
SG.MouseToCell (x, y, c, r);
im.canvas.pixels[c, r] := ex.color;
end;
end;

procedure TForm1. RechargerClick(Sender: TObject);


begin
if nom <> '' then begin
im.Picture.LoadFromFile (nom);
SG.refresh;
end;
end;

procedure TForm1.chargerClick(Sender: TObject);


begin
if OD.execute then begin
im.picture.loadfromfile (OD.filename);
nom := OD.FileName;
SG.refresh;
end;
end;

end.
Le principe de fonctionnement est assez simple pour être expliqué en détail. Voici tout d'abord les
noms des divers composants et leur signification:
Rouge, vert et bleu les trois barres de défilement du choix de couleur
SG le StringGrid
im l'image
charger bouton pour le chargement de l'image
recharger bouton pour le rechargement de l'image
ex surface rectangulaire montrant la couleur actuelle
OD dialogue d'ouverture de fichier
La variable dessin indique si l'utilisateur est en train de dessiner. La variable nom contient le nom
du fichier image; le bouton recharger utilise ce nom pour recharger l'image.
L'événement OnDrawCell est utilisé pour remplir la cellule courante avec la couleur du pixel
correspondant sur l'image en taille réelle:

SG.Canvas.Brush.color := im.canvas.pixels[Acol, Arow];


SG.canvas.fillrect (rect);

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-23


L'événement OnChange des trois barres de défilement contient l'instruction qui met à jour la
couleur du panel:

ex.Color := rgb(rouge.position, vert.position, bleu.position);


L'événement OnMouseDown du StringGrid gère les situations suivantes. Lorsque l'utilisateur
clique sur le bouton gauche de la souris, la couleur spécifiée par les barres de défilement est
placée dans la cellule correspondante et dessin prend la valeur true, indiquant que l'utilisateur est
en train de dessiner. S'il s'agit du bouton droit, on met à jour la couleur en positionnant les barres
de défilement:
if ssleft in shift then begin
im.canvas.pixels[SG.Col, SG.row] := ex.color;
dessin := true;
end;
if ssright in shift then begin
rouge.position := GetRvalue (SG.canvas.pixels[x,y]);
vert.position := GetGvalue (SG.canvas.pixels[x,y]);
bleu.position := GetBvalue (SG.canvas.pixels[x,y]);
end;
Lorsque le bouton de la souris est relâché, on change simplement la valeur de dessin.
Lorsque l'utilisateur déplace la souris en appuyant sur le bouton gauche, le programme convertit,
à l'aide de la méthode MouseToCell, les coordonnées de la souris (x, y) en coordonnées de
cellule (c, r). L'image en taille réelle est également mise à jour:
if dessin then begin
SG.MouseToCell (x, y, c, r);
im.canvas.pixels[c, r] := ex.color;
end;
Il ne reste plus que le code associé aux deux boutons, qui est d'une simplicité évidente.

5.5 Type enregistrement (record)

La plupart des langages de programmation de haut niveau incluent les tableaux. En revanche, les
enregistrements (record) sont une structure que l'on rencontre plus rarement. Ils permettent une
meilleure organisation et une gestion plus rationnelle des informations, et sont étroitement liés à
une autre structure fondamentale que sont les fichiers (voir plus loin).
Un enregistrement, comme un tableau, regroupe plusieurs informations. Alors que dans un
tableau tous les éléments doivent être du même type, dans un enregistrement les différentes
composantes (ou champs) peuvent être de nature ou de type différents. Enfin, on repère une
composante d'un enregistrement par le nom de la variable de type enregistrement et le nom du
champ auquel on veut accéder.

SYNTAXE

type enreg_type = record


liste_id1 : type1;
liste_id2 : type2;
...
liste_idn : typen;
end;

La déclaration d'un enregistrement comprend le nom (enreg_type), puis entre les mots
réservés record et end, une liste d'identificateurs (les différents champs) suivis du type
correspondant; type1 ... typen peuvent être des types quelconques.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-24


Pour mieux entrevoir les facilités associées au type enregistrement, envisageons une structure de
données qui pourrait figurer dans un programme de gestion de loyers. Les renseignements
désirés concernant les locataires seraient:

nom
prénom
sexe
loyer
charges
numéro d'appartement
La structure d'enregistrement se prête particulièrement bien à la représentation de ces
informations:

type str20 = string[20];


locataire = record
nom, prenom : str20;
sexe : (masculin, feminin);
loyer, charges : real;
no_appart : integer;
end;

var personne : locataire;


loyer_total : real;
fig. 5.3

L'accès à un champ d'un enregistrement s'effectue en indiquant le nom de l'enregistrement et le


nom du champ, séparés par un point (notation pointée). Par exemple, l'instruction d'entrée
permettant la saisie du nom d'un locataire s'écrit:

personne.nom := Edit1.Text;
De même, le loyer total se calcule à l'aide de l'instruction:

loyer_total := personne.loyer + personne.charges;


Dans certains passages de programmes où la manipulation des champs d'un enregistrement est
fréquente, la notation pointée peut devenir lourde. Le langage Pascal a prévu un moyen de la
remplacer, grâce à l'instruction with ... do. Les instructions:

personne.nom := Edit1.Text;
loyer_total := personne.loyer + personne.charges;
peuvent être transformées en:

with personne do begin


nom := Edit1.Text;
loyer_total := loyer + charges;
end;
ce qui allège considérablement l'écriture des programmes. A l'intérieur d'une instruction with, on
utilise donc uniquement les noms des champs à manipuler. D'apparence très pratique, cette
instruction est à utiliser avec parcimonie, étant donné la confusion qu'elle peut parfois provoquer
au niveau des identificateurs. Lorsque plusieurs variables de type enregistrement ayant des
champs de même nom se trouvent dans une structure with ... do, il peut y avoir ambiguïté
apparente. En fait, le Pascal se réfère toujours à la variable dont le nom d'enregistrement est le
plus proche. Considérons l'exemple suivant:
...
type pdg = record
nom : string[20];
age : integer;
societe : string[20];
end;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-25


emp = record
nom : string[20];
age : integer;
revenu : real;
end;
var employe : emp;
chef : pdg;
deductions : real;
...

with employe, chef do begin


nom := Edit1.Text;
revenu := revenu - deductions;
Edit2.Text := societe;
end;
...
nom et societe se rapportent à la variable chef, alors que revenu concerne la variable employe.
Comme dans le cas des tableaux, les opérateurs arithmétiques ou logiques doivent se rapporter
aux champs d'un enregistrement et non à l'enregistrement en tant qu'entité. L'exception en est
l'affectation. Il est donc possible après la déclaration:

var personne, gens : locataire;


d'écrire l'instruction:

personne := gens;
Les deux variables doivent bien entendu posséder exactement la même structure, c'est-à-dire être
de même type.
Il est assez fréquent, en programmation, de faire appel à des paramètres de type enregistrement,
car un enregistrement constitue un groupe logique d'informations.
Après avoir étudié les tableaux et les enregistrements, il semble tout naturel de combiner ces deux
types de données. Une forme qui se rencontre souvent est le tableau d'enregistrements. Dans
beaucoup de cas, les objets traités par un programme ont une structure d'enregistrement, et ces
objets eux-mêmes sont en grand nombre. Pour que l'exemple de la figure 5.3 soit un peu plus
réaliste, il convient de transformer notre locataire unique en un ensemble de locataires:

const maxloc = 20;


type habitants = array [1..maxloc] of locataire;
var habitant : habitants;
Un indice est utilisé pour repérer les différents locataires. Ainsi une augmentation de loyer du
troisième locataire s'écrira comme suit:

habitant[3].loyer := habitant[3].loyer + 50.0;


On comprend peut-être mieux à présent l'importance d'avoir à disposition plusieurs types de
structures de données. Il est ainsi beaucoup plus facile de construire des programmes ayant un
rapport direct et naturel avec l'information à traiter. Une des phases les plus importantes, si ce
n'est la plus importante, dans la résolution d'un problème au moyen de la programmation est
l'analyse. C'est au cours de cette phase que s'effectue, entre autres, le choix des structures de
données les plus adéquates. Une bonne analyse facilite grandement la phase de codification.
Nous allons examiner une structure de données plus complexe, construite à l'aide de types de
données étudiés au préalable:

const maxempl = 100;


type etat_civil = (marie, celibataire);
date = record
jour : 1..31;
mois : 1..12;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-26


annee : 1900..1999;
end;
adresse = record
rue : string[20];
no : integer;
code_postal : string[6];
ville : string[12];
end;
departements = (personnel, travaux, machines, maintenance);
employe = record
nom : string[15];
prenom : string[15];
age : integer;
etat : etat_civil;
maison : adresse;
salaire : real;
date_entree : date;
end;
division = array [1..maxempl] of employe;
var entreprise : array [departements] of division;
travailleur : employe;
section : division;

Dans le cadre de cette structure, le nom de l'employé représenté par la variable travailleur est
donné par:

travailleur.nom
Le salaire du deuxième employé de la section s'exprime par:

section[2].salaire
Le jour d'entrée dans l'entreprise du troisième employé de la division du personnel est repéré par:

entreprise[personnel][3].date_entree.jour
Ces formes d'écriture, bien que claires, peuvent devenir lourdes. En réalité, les informations sont
le plus souvent destinées à être conservées dans un fichier qui n'est rien d'autre qu'une suite
d'enregistrements. Nous verrons plus loin que pour utiliser un fichier de manière optimale il vaut
mieux éviter une structure d'enregistrement trop complexe. On préfère souvent utiliser deux ou
trois fichiers de structure simple plutôt qu'un seul fichier trop dense et enchevêtré.
Dans la plupart des implémentations de Pascal, une souplesse supplémentaire a été introduite au
niveau des enregistrements: les variantes. Les enregistrements avec variantes possèdent un ou
plusieurs champs variables en contenu et en structure. L'exemple qui suit en présente une
utilisation:

type forme = (triangle, carre, cercle);


teinte = (rouge, bleu, jaune);
figure = record
couleur : teinte;
case aspect : forme of
triangle : (base, hauteur : real);
carre : (cote : real);
cercle : (rayon : real);
end;

var dessin1, dessin2 : figure;


Les variantes sont spécifiées dans un enregistrement à l'aide d'une structure semblable à celle de
l'instruction case ... of. La partie fixe d'un enregistrement doit toujours figurer au début de la
déclaration alors que les variantes viennent à la fin. Si, pour une variante donnée, il n'est pas
prévu de champ, on l'indiquera par ( ). Le champ qui sélectionne les différentes variantes fait

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-27


également partie de l'enregistrement. Enfin, la place mémoire occupée par une variable de type
figure correspond à la somme de la taille des champs fixes et de la taille de la plus grande des
variantes.

dessin1.couleur := bleu;
dessin1.aspect := triangle;
dessin1.base := 10;
dessin1.hauteur := 5;
dessin2.couleur := rouge;
dessin2.aspect := cercle;
dessin2.rayon := 4;
Il revient au programmeur d'éviter d'écrire, par exemple:

dessin2.base
sans avoir testé la valeur de la variable aspect.

5.6 Compatibilité de types

En Pascal, deux types peuvent être identiques ou seulement compatibles. Certaines situations
exigent la première propriété, alors que d'autres se satisfont de la seconde. De plus, la
compatibilité peut être considérée au niveau d'une affectation, d'un passage de paramètres ou
d'une expression. Le non-respect de certaines règles de compatibilité peut conduire à des erreurs
lors de l'exécution d'un programme. C'est la raison pour laquelle il est bon d'avoir cette notion de
compatibilité à l'esprit. En cas de doute il est conseillé de se reporter à l'aide de Delphi.

5.7 Constantes avec type

Nous avons vu, dans le chapitre 2, la différence existant entre une variable et une constante. En
Pascal standard, les constantes peuvent être de type entier, réel, caractère, booléen ou chaîne de
caractères. Delphi permet, en plus, d'utiliser des constantes avec type, qu'il faut envisager plutôt
comme des variables initialisées. Ces constantes peuvent être de type scalaire ou structuré.

SYNTAXE

const identificateur : type = valeur;

Pour les constantes avec type non structuré, chaîne de caractères ou ensemble.

const identificateur : type = (valeurs);

Pour les constantes avec type structuré (tableau, enregistrement ou composite), sauf
ensemble.

Les exemples qui suivent constituent des déclarations de constantes typées non structurées:

const max_lignes : integer = 24;


rabais : real = 0.05;
demo : boolean = true;
message : string[5] = 'Merci';
arret : char = ^C;
code : byte = 2;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-28


Une constante typée, contrairement à une constante habituelle, peut apparaître comme paramètre
transmis par référence d'une procédure ou d'une fonction. De plus, sa valeur peut également être
modifiée par affectation. En revanche, elle ne peut pas intervenir dans la déclaration d'autres
constantes ou types. Pour illustrer cette dernière caractéristique, voici une déclaration non
autorisée:

const max : integer = 10;


type table = array [1..max] of integer;
Une constante de type ensemble contenant les lettres minuscules se déclare de la manière
suivante:

const minuscules : set of char = ['a'..'z'];


Une constante avec type peut également appartenir à l'un des types structurés tableau ou
enregistrement. L'intérêt est alors une initialisation compacte, possible au moment de la
déclaration. Examinons, à l'aide d'exemples, ces deux sortes de constantes typées structurées.
L'initialisation d'un tableau contenant le nombre de jours que compte chaque mois s'écrit:

const jour_par_mois : array [1..12] of integer =


(31,28,31,30,31,30,31,31,30,31,30,31);
Pour des tableaux multidimensionnels, on procède de la manière suivante:

const grille : array [1..4,1..3] of integer =


((1,4,3),(3,1,4),(10,11,10),(1,0,0));
Les constantes de type enregistrement doivent être précédées de la déclaration de
l'enregistrement auquel elles se réfèrent:

type simple = record


nom : string[20];
age : integer;
end;
const individu : simple = (nom : 'Dupont'; age : 56);

5.8 Variables absolues

Généralement, l'adresse mémoire à laquelle le compilateur place les variables d'un programme ne
peut pas être choisie ou contrôlée par le programmeur. Toutefois, Delphi permet d'indiquer
explicitement dans la déclaration l'adresse mémoire où doit être placée une variable. L'adresse est
indiquée sous forme d'une adresse absolue ou sous forme de référence à un autre objet du
programme. Ainsi la déclaration suivante permet de connaître le numéro du mode vidéo en cours,
celui-ci étant stocké à l'adresse mémoire $0040.

var CrtMode: Byte absolute $0040;

Cette déclaration "superpose" l'emplacement de la variable CrtMode à la position mémoire $0040.


Toutefois avec les nouveaux systèmes d'exploitation, une protection de la machine, et de la
mémoire en particulier est mise en place. Dans notre cas, lors de l'exécution du programme
utilisant la variable CrtMode, le système affiche un message d'erreur concernant une violation
d'accès mémoire.
Voyons maintenant une autre utilisation des variables absolues sous la forme du petit exemple
suivant:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-29


Au fur et à mesure que l'utilisateur introduit des caractères dans la zone de texte, le programme
affiche le nombre de caractères contenus dans cette zone de texte. Pour cela nous avons
simplement utilisé un Timer qui, toutes les 100 millisecondes, grâce à une variable absolue, extrait
et affiche le nombre de caractères de la zone de texte. Voici le code exécuté par le timer:

procedure TForm1.Timer1Timer(Sender: TObject);


var Str: shortstring;
StrL: Byte absolute Str;
begin
Str := Edit1.text;
Label2.caption := inttostr (StrL) + ' caractères';
end;
En effet, il faut se souvenir que les variables de type ShortString contiennent dans leur premier
octet (octet 0) la longueur effective de la chaîne de caractères. Comme on peut le constater le
contenu de la variable StrL change uniquement par le fait qu'elle est superposée, en mémoire, à
la chaîne de caractères Str.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-30


EXERCICES

Exercice 5.1:

Ecrire un programme qui comporte un Edit et un ListBox. L'utilisateur tape un texte dans l'Edit.
Lorsqu'il tape <Return>, le texte est ajouté dans le ListBox et l'Edit est vidé.

Exercice 5.2:

Reprendre l'exercice 1 et ajouter un bouton qui vide complètement le ListBox, ainsi qu'un bouton
qui supprime du ListBox la ligne sélectionnée (en surbrillance).

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-31


Exercice 5.3:

Partir de l'exercice précédent et ajouter une case à cocher indiquant si le contenu du ListBox doit
être trié ou non. De plus, trouver un moyen pour qu'après avoir cliqué sur un des deux boutons ou
sur la case à cocher le "focus" (ici le curseur) se trouve sur l'Edit.

Exercice 5.4:

Ecrire un programme effectuant des tris selon 3 méthodes: tri par bulle, tri par comptage, tri par
échange. Afin de visualiser les 250 nombres (compris entre 0 et 999) tirés au hasard en cliquant
sur le bouton "Remplir", ainsi que les résultats obtenus avec les trois méthodes nous utilisons des
ListBox.

Méthode de tri par bulle:

• On compare deux à deux les éléments adjacents en remontant la liste (le tableau); si
l'ordre n'est pas le bon, les deux éléments comparés sont permutés
• On redescend dans la liste si le plus petit des éléments permutés dans la phase
précédente a encore des éléments plus grands que lui. La descente s'arrête dès qu'il a
trouvé sa place ou si l'on se trouve au début de la liste
• On reprend la montée de la liste là où on l'avait laissée

Exemple: 6 7 3 8 5 1 montée
3 7 permutation
3 6 7 descente
7 8 5 1 montée
5 8 permutation
5 7 8 descente
3 5 6 7 8 1 descente, montée
1 8 permutation
1 7 8 descente
1 6 7 8 descente
1 5 6 7 8 descente
1 3 5 6 7 8 descente

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-32


Méthode de tri par comptage:

Pour trier un tableau A[1..n] on détermine le rang de chaque élément en comptant le nombre des
éléments qui lui sont inférieurs.
Dans ce but, on associe à chaque A[i] un compteur. On compare successivement un élément du
tableau à tous ses prédécesseurs; on répète l'opération pour tous les éléments et on obtient l'état
du tableau en analysant le contenu des compteurs. Si le compteur C[i] indique la valeur k, la place
de l'élément A[i] est la k-ième position du tableau trié. Voici la suite des opérations à effectuer:

• Initialiser les compteur à 1


• On compare A[1] et A[2]:
o Si A[1] < A[2] alors on incrémente C[2] sinon on incrémente C[1]
• On compare ensuite A[3] avec les éléments A[1] et A[2]. En fonction des résultats des
comparaisons, on incrémentera les compteurs correspondants, comme dans le point
précédent. Lorsque tous les éléments ont été traités, les compteurs indiquent le rang de
l'élément correspondant.

Méthode de tri par échange:

Cette méthode consiste à comparer chaque élément du tableau (en partant de l'indice le plus bas)
avec les éléments suivants. Chaque fois que l'ordre n'est pas le bon, les deux éléments sont
permutés. Il s'agit de l'une des méthodes de tri les moins efficaces.

Exercice 5.5:

Ecrire un programme comportant deux ListBox. Celui de droite est vide au départ, alors que celui
de gauche doit contenir les nombres de 1 à 8 en toutes lettres (trouver comment faire cela). Deux
boutons permettent de copier l'élément sélectionné dans le ListBox de gauche ou de droite. Les
boutons sont activés uniquement si la copie est possible.

Exercice 5.6:

Modifier le programme pour que les boutons déplacent et non plus copient les éléments de
gauche à droite et vice versa. De plus, ajouter l'indication du nombre d'éléments sous chaque
ListBox (indication visible dès le lancement du programme).

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-33


Exercice 5.7:

Ajouter au programme deux boutons permettant de supprimer l'élément sélectionné dans un


ListBox (après avoir cliqué sur ces boutons, l'affichage du nombre d'éléments doit être correct).

De plus, après avoir cliqué sur un des quatre boutons, le programme doit (s'il reste des éléments
dans la liste) forcer la sélection à être sur la 1ère ligne de la liste.

Exercice 5.8:

Reprendre l'exercice précédent et ajouter la possibilité de faire une multi-sélection des lignes des
deux ListBox (sélection de plusieurs lignes). Le programme doit effectuer toutes les opérations
comme précédemment, mais sur toutes les lignes sélectionnées.

Exercice 5.9:

Ecrire un programme qui comporte un StringGrid. Le StringGrid doit avoir une dimension de 8 x 8
et aucune colonne grisée. En cliquant sur un bouton, le programme doit remplir la première et la
dernière colonne avec la lettre 'X'.

Exercice 5.10:

Modifier le programme de l'exercice précédent pour qu'il remplisse aussi la première et la dernière
ligne avec la lettre 'X', ainsi que l'intérieur du carré délimité par les 'X' avec la lettre 'O'.

Exercice 5.11:

Modifier le programme de l'exercice précédent pour qu'il place des '1' dans les cases des deux
diagonales.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-34


Exercice 5.12:

Modifier le programme de l'exercice précédent pour qu'il place des '8' dans les cases restées
vides, c'est-à-dire celles qui se trouvent entre les côtés et les diagonales (quatre zones
triangulaires).

Exercice 5.13:

Reprendre la base de l'exercice 9 et faire en sorte qu'en cliquant sur le bouton le programme
inscrive, dans chaque case, la somme de son numéro de ligne et de colonne. Par exemple, la
case (2,3) contiendra 5.

Exercice 5.14:

Reprendre l'exercice précédent. Le programme doit laisser choisir la taille de la grille par
l'utilisateur (entre 2 x 2 et 8 x 8) à l'aide d'un "TrackBar". A n'importe quelle position du TrackBar
l'utilisateur peut cliquer sur le bouton.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-35


Exercice 5.15:

Faire un programme basé sur un StringGrid de 8 x 8. On aimerait, en utilisant l'événement


"OnDrawCell", que les cellules (1,4) et (5,5) soient rouges (les autres restent blanches) dès le
lancement du programme.

Exercice 5.16:

Modifier l'exercice précédent pour qu'une case sur deux soit rouge.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-36


Exercice 5.17

Ecrire un programme se comportant comme suit:


• Au départ les nombres indiqué sur l'image doivent se trouver dans la grille
• Un TrackBar permet de choisir le nombre de colonnes (entre 3 et 8)
• Un "ColorGrid" permet de choisir la couleur avec laquelle les cases vides seront remplies
(pour rafraîchir le StringGrid: StringGrid1.Refresh).

Exercice 5.18:

Ecrire un programme utilisant un StringGrid de 10 x 10. La couleur des lignes alterne entre jaune
et "acqua". Les cases sélectionnées doivent apparaître avec un trait rouge sur leur diagonale:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-37


Exercice 5.19:

Ecrire un programme qui comporte un StringGrid de 12 x 12 et un ListBox.


Placez quelques mots dans le ListBox. L'utilisateur doit pouvoir:
• Cliquer sur une case de la grille
• Choisir un mot du ListBox

Ensuite le programme place le mot, à partir de la case choisie, de manière que le mot entre dans
la grille. Si la case choisie est en bas à droite, le mot partira probablement vers le haut ou vers la
gauche. Indication: ne pas tenir compte des directions en diagonale.

Exercice 5.20:

Ecrire un programme utilisant un StringGrid et simulant le déplacement d'une tour ou d'un fou sur
un échiquier. L'utilisateur choisit la pièce (tour ou fou), puis choisit une case. Le programme met
des 'X' ou des 'O' dans toutes les cases qui peuvent être atteintes par cette pièce.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-38


Exercice 5.21:

Ecrire un programme qui transforme les nombres arabes en nombres romains. Le nombre arabe
doit être limité à 4 chiffres. Pour mémoire voici les correspondances entre nombres romains et
arabes: I (1), V (5), X (10), L (50), C (100), D (500) et M (1000).

Exercice 5.22:

Ecrire un programme qui transforme les nombres romains en nombres arabes. Le programme
n'effectue aucun contrôle de validité du nombre romain spécifié par l'utilisateur. Celui-ci ne doit
pas pouvoir entrer d'autres caractères que ceux correspondant aux nombres romains. De plus, au
fur et à mesure que les caractères sont tapés, le nombre romain est affiché dans sa forme arabe.

Exercice 5.23:

Améliorer le programme de l'exercice précédent en vérifiant la syntaxe du nombre romain spécifié


par l'utilisateur, notamment en ce qui concerne les points suivants:

• Les séquences suivantes ne doivent pas être admises dans un nombre romain: DD,
CCCC, LL, XXXX, VV, IIII
• Dans un nombre romain il peut y avoir deux lettres qui sont dans l'ordre croissant (par
exemple XC dans MXCI), mais pas trois (par exemple IXC dans MIXC). Il ne peut pas y
avoir non plus plus de deux lettres numériquement inférieures à la lettre suivante (par
exemple 1109 s'écrit MCIX et non MIXC)

Comme la vérification de "syntaxe" ne peut se faire en cours de frappe, prévoir un bouton pour la
transformation

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-39


Exercice 5.24:

Ecrire un programme qui, partant d'un chiffre de départ donné (ci-dessous le chiffre 1), calcule et
affiche la suite que l'on peut voir dans l'exemple. Pour obtenir une nouvelle ligne, on se base sur
la ligne précédente énumérant son contenu:

• Ligne 2: dans la ligne précédente il y a 1 '1'


• Ligne 3: dans la ligne précédente il y a 2 '1'
• Ligne 4: dans la ligne précédente il y a 1 '2' et 1 '1'
• Ligne 5: dans la ligne précédente il y a 1 '1, 1 '2' et 2 '1''
• etc

Comme vous pouvez le constater aucun chiffre plus grand que 3 n'apparaît. Essayez de
comprendre pourquoi et de l'expliquer. Cette propriété est-elle valable en partant d'un autre chiffre
que 1 ?

Exercice 5.25:

Ecrire un programme de codage rudimentaire basé sur le principe du XOR. Une clé, choisie par
l'utilisateur, permet de coder et de décoder un texte. Pour cela il faut appliquer l'opérateur xor
entre le premier caractère du texte à coder et le premier caractère de la clé et ainsi de suite pour
les autres caractères. Lorsque l'on a épuisé les caractères de la clé, on se repositionne sur son
premier caractère. Le même principe, appliqué au texte codé, permet de retrouver le texte en clair
(d'origine).

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-40


Exercice 5.26:

Ecrire un programme qui génère le triangle de Pascal sous la forme suivante:

Comme on peut le voir chaque nombre (sauf les 1 des bords) est la somme des deux nombres qui
se situent au-dessus de lui.

Exercice 5.27:

Ecrire un programme de changement de base permettant de passer de la base 10 à une base


quelconque comprise entre 2 et 36. Pour pouvez laisser entrer par l'utilisateur un nombre de 18
chiffres au maximum et utiliser le type entier int64 afin de travailler avec de plus grands nombres.
Une liste déroulante permet de choisir la base d'arrivée.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-41


Exercice 5.28:

Ecrire un programme de simulation de traversée d'une route à plusieurs voies. L'utilisateur peut
choisir:
• le nombre de voies de la route à traverser: entre 1 et 6
• la fréquence d'apparition d'une voiture, c'est-à-dire la probabilité qu'il y ait une voiture au
moment de traverser
• le nombre de traversées à simuler

Le principe est le suivant:

• on suppose qu'il faut 1 seconde pour traverser une voie


• à chaque tentative de traversée, si une voiture se présente, il faut attendre 1 seconde
supplémentaire

En mémorisant, dans un tableau, le temps nécessaire à chaque traversée, on peut savoir combien
il y a eu de traversées qui ont duré n secondes. Le programme doit ensuite représenter

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-42


graphiquement les résultats. Horizontalement est représenté le temps (on voit sur l'exemple que,
pour une route à 3 voies, on met au minimum 3 secondes pour traverser) et verticalement le
nombre de traversées. Etant donné que le nombre de traversées peut varier considérablement,
vous devrez trouver un facteur correctif permettant d'avoir dans la plupart des cas un graphique
entièrement visible. Le nombre de traversées admis doit être compris entre 100 et 9999999.

Exercice 5.29:

Ecrire un programme permettant de générer des phrases aléatoires, formant un discours. Chaque
phrase est construite à partir de quatre fragments, pris dans quatre Listbox correspondants. Le
premier contient des débuts de phrases, le dernier des fins de phrases et les deux autres des
parties intermédiaires. Chaque Listbox charge son contenu d'un fichier (LISTE1.TXT, LISTE2.TXT,
LISTE3.TXT, ou LISTE4.TXT) au moment du démarrage du programme. Sous chaque Listbox
figure un Edit dans lequel l'utilisateur peut taper des fragments de phrases qui vont s'ajouter au
Listbox correspondant quand il tape sur <Return>. Pour supprimer un fragment de phrase d'un
Listbox il suffit de faire un double-clic sur la ligne désirée. Il est possible à tout moment de
sauvegarder le contenu des quatre Listbox à l'aide d'une option du menu Fichier. Enfin, l'utilisateur
peut choisir le nombre de phrases à générer (le programme insère aléatoirement des sauts de
ligne pour constituer des paragraphes) et peut imprimer le discours à l'aide du bouton "Imprimer".

Exercice 5.30:

Ecrire un programme permettant de résoudre les jeux du type mot secret ou mot caché. Une grille
(dans notre cas de 15 x 15) contient des lettres. On dispose également d'une liste de mots.
Chacun des mots apparaît dans la grille et peut être écrit dans les quatre directions
(horizontalement, verticalement et selon les deux diagonales) et dans les deux sens (par exemple

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-43


de haut en bas ou de bas en haut). Une fois que tous les mots de la liste ont été tracés dans la
grille, il est possible de former un mot avec les lettres restantes. Un régulateur de vitesse
d'exécution permet de voir évoluer plus ou moins vite la résolution du jeu.

Vous disposez de deux fichiers de type texte (GR1.GR2 et GR1.MO2), le premier contient les
lettres de la grille, le second la liste de mots.

Dans l'exemple présenté ci-dessous, chaque mot trouvé est mis en minuscules et sur fond clair
dans la grille.

Diverses stratégies sont possibles, mais il est conseillé de partir des mots de la liste et de les
trouver l'un après l'autre.

Exercice 5.31

Cet exercice consiste à simuler des mesures de tension électrique. Il comporte trois étapes à
effectuer dans l'ordre.

1ère étape:
Générer un fichier (MESURES.TXT) de 150 nombres réels compris entre 215 et 225; ces nombres
sont aléatoirement distribués entre ces deux limites.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-44


2ème étape:
Le bouton Calcul permet de compter combien il y a de mesures dans chaque intervalle de 1 [Volt].
Les résultats sont placés dans un Listbox.

3ème étape:
Le bouton Graphique permet de représenter sommairement de manière graphique les résultats
calculés à l'étape précédente

Vous pouvez améliorer ce programme en indiquant des valeurs et des unités sur les axes du
graphique.

Exercice 5.32

Cet exercice consiste à simuler une course en ligne droite et en couloir entre 8 concurrents. Les
coureurs sont symbolisés par 8 figures rondes (composants de type Tshape). En cliquant sur le
bouton "Départ" on lance la course. L'avancement des coureurs est contrôlé par le tirage de
nombres aléatoires entre 1 et 8. Le nombre tiré permet de faire avancer le coureur du couloir
correspondant.

Le programme fait appel à un Timer pour séquencer la course.

Lorsqu'un coureur touche la ligne d'arrivée, le jeu s'arrête. Le bouton "Nouv. Course" replace les
coureurs à leur point de départ, prêts pour une nouvelle course.
Voici comment se présente ce programme:

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-45


Exercice 5.33

Modifiez le programme de l'exercice précédent comme suit:

• l'utilisateur peut parier sur un coureur en choisissant un no. de couloir. Le coureur choisi
prend la couleur bleu turquoise
• lorsque le premier coureur arrive un message indique si l'utilisateur a gagné ou perdu.
• Le coureur qui gagne se met à clignoter un fois la ligne d'arrivée atteinte.
• L'utilisateur peut régler la vitesse des coureurs

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-46


Exercice 5.34

Modifiez le programme précédent en remplaçant l'utilisation d'un Timer par une boucle dont
l'exécution est déclenchée par le bouton "Départ"

Exercice 5.35

Le jeu de la vie, inventé par J. Conway en 1970, consiste à simuler une population de bactéries.
Sur un quadrillage (considéré comme étant toroïdal) chaque case est soit vivante soit morte. L'état
initial est choisi par l'utilisateur. On considère que chaque cellule a 8 voisins. Pour déterminer
l'étape (ou génération) suivante on doit satisfaire à certaines règles:

• une cellule vivante meurt par isolement si elle a 0 ou 1 voisin vivant


• une cellule vivante meurt par étouffement si elle a plus de 3 voisins vivants
• dans les autres cas, la cellule reste vivante
• une cellule morte (case vide) qui a exactement 3 voisins vivants devient vivante

Le programme utilise un StringGrid sur lequel l'utilisateur peut cliquer avec le bouton gauche (pour
noircir) ou droit (pour blanchir) une case. Les boutons "Départ" et "Arrêt" lancent et arrêtent le jeu.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 5-47


Chapitre 6 FICHIERS

A la fin de cette partie, vous serez capable de:

• Utiliser à bon escient les différents types de chaînes de caractères;


• Utiliser les différentes routines de traitement des chaînes de caractères;

6.1 Différentes sortes de fichiers

Dans les chapitres précédents nous avons introduit des types de données divers, mais dont la
particularité était de ne pas trop dépendre du support physique, si ce n'est pour la précision des
nombres ou pour la place mémoire nécessaire aux variables que l'on peut déclarer avec de tels
types. Afin de pallier la limite imposée par la taille mémoire centrale, mais aussi et surtout de
permettre le stockage des informations produites ou modifiées par le programme, le moyen le plus
adéquat est l'utilisation des fichiers.
Un fichier est une collection d'informations, enregistrées sur un support physique de mémoire
secondaire (mémoire de masse). Il est défini par un identificateur et des attributs tels que le type,
les conditions et les méthodes d'accès. Les fichiers sont, de plus, gérés par le système
d'exploitation et accessibles par l'intermédiaire de celui-ci.
Delphi dispose de plusieurs possibilités d'enregistrement de données dans des fichiers:
• Les fichiers dans leur forme traditionnelle
• Les fichiers liés et supportés par la VCL, offerts par différentes classes (Tstream.
Tcomponent,…) ou supportés par des méthodes de plusieurs composants
• Les fichiers inhérents à l'utilisation de bases de données
Dans ce chapitre nous nous attacherons essentiellement à l'étude des fichiers de la première
catégorie. Parmi ceux-ci nous nous arrêterons sur:
• Les fichiers de type texte (ou fichiers ASCII)
• Les fichiers à accès direct (ou fichiers typés)
• Les fichiers non typés
Avant d'examiner les caractéristiques de ces différents types de fichiers, voyons ce qu'ils ont en
commun. La notion de fichier recouvre deux objets distincts: un identificateur (nom interne) défini
dans un programme et le fichier physique géré par le système d'exploitation (associé à un nom
externe). Afin qu'un programme puisse gérer un fichier, ces deux noms doivent être mis en
relation par la procédure AssignFile (NomInterne, NomExterne). Lorsque l'utilisation du
fichier est terminée, il doit être fermé à l'aide de la procédure CloseFile (NomInterne). Entre
ces deux opérations, diverses procédures et fonctions prédéfinies relatives aux fichiers peuvent
être utilisées.
La gestion de fichiers, autrement dit la gestion des informations qu'ils contiennent, est un domaine
important de la programmation. Toutefois, Delphi met à disposition du programmeur des outils
permettant parfois de masquer l'utilisation explicite des fichiers.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-1


6.2 Fichiers de type texte

Ces fichiers, appel‚s aussi "fichiers ASCII", sont constitués par une suite de caractères groupés en
lignes, comme dans un texte. Chaque ligne est terminée par la séquence de caractères CR et LF
(Carriage Return et Line Feed), correspondant à un passage au début de la ligne suivante.
L'accès à ce type de fichiers peut s'effectuer uniquement de manière séquentielle, c'est-à-dire un
élément après l'autre, en partant du premier. Leur contenu peut être visualisé en les chargeant
dans un éditeur de texte, par exemple le blocnote (Notepad) de Windows.
Nous allons passer en revue et illustrer quelques-unes unes des instructions relatives aux fichiers
de type texte.

Déclaration de fichier

Le mot réservé TextFile est utilisé pour déclarer des fichiers de type texte:

var fichier_texte : TextFile;

Assignation de fichier

La procédure AssignFile permet de relier la variable constituant le nom interne du fichier au fichier
proprement dit, spécifié par le chemin permettant de le retrouvé sur un disque:

AssignFile (fichier_texte, 'C:\AUTOEXEC.BAT');

Ouverture de fichier

Après avoir été assigné, un fichier doit encore être ouvert. Cette opération est réalisée par l'une
des deux procédures prédéfinies:

rewrite (fichier_texte);
reset (fichier_texte);

La procédure rewrite ouvre un nouveau fichier en vue d'une opération d'écriture .Si un fichier du
même nom existe, il sera détruit et son contenu perdu. La procédure reset ouvre un fichier
existant en vue d'une opération de lecture ou d'écriture. Lors de l'appel à cette procédure, une
erreur d'entrée-sortie est signalée si le fichier spécifié est absent.

Ecriture d'informations

Les deux procédures permettant d'enregistrer des données dans un fichier sont write et
writeln. Le premier paramètre qui leur est transmis est l'identificateur du fichier:

write (fichier_texte, 'Voic un début de ligne ');


writeln (fichier_texte, ' puis on saute à la ligne suivante.');

Lecture d'informations

De manière analogue, les procédures read et readln sont utilisées pour la lecture des données
placées dans un fichier de type texte. L'exemple qui suit, illustre la lecture du contenu d'un fichier
et son affichage; ligne par ligne dans un composant de type Memo. De plus, le nom du fichier ainsi
que le nombre de lignes sont également affichés.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-2


Voici comment se présente le fiche de ce projet. Elle comporte:
• Un Label permettant l'affichage du nom du fichier
• Un Memo dans lequel le contenu du fichier est placé
• Un Label indiquant le nombre de lignes lues
• Un bouton qui déclenche le choix du fichier et l'affichage de son contenu
• Un OpenDialog (dialogue d'ouverture de fichier) permettant de choisir le fichier à lire

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-3


Voici enfin comment est écrit le programme:

unit uftxt1;

interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls,


Forms,
Dialogs, StdCtrls;

type
TFtexte = class(TForm)
Memo: TMemo;
nomf: TLabel;
OD: TOpenDialog;
lignes: TLabel;
Lire: TButton;
procedure LireClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var Ftexte: TFtexte;

implementation

{$R *.DFM}

procedure TFtexte.LireClick(Sender: TObject);


var f : TextFile; { fichier texte }
ligne : string; { ligne lue }
nbl : integer; { compteur du nombre de lignes }
begin
nbl := 0;
if OD.execute then begin
nomf.caption := OD.FileName;
AssignFile (f, OD.FileName);
Reset (f);
while not eof (f) do begin
readln (f, ligne);
Memo.Lines.Add (ligne);
inc (nbl);
end;
CloseFile (f);
lignes.caption := inttostr (nbl);
end;
end;

end.
Le cœur du programme est constitué par les lignes suivantes:

AssignFile (f, OD.FileName);


Reset (f);
while not eof (f) do begin
readln (f, ligne);
Memo.Lines.Add (ligne);
inc (nbl);
end;
CloseFile (f);

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-4


Fin de ligne

La fonction Eoln retourne une valeur booléenne indiquant si la fin d'une ligne (End Of Line) est
atteinte. Dans le fragment de programme suivant, la lecture d'un fichier texte s'effectue caractère
par caractère à l'aide de la procédure read et, à chaque détection de la fin d'une ligne, un bip est
émis:

AssignFile (f, OD.FileName);


Reset (f);
while not eof (f) do begin
read (f, caractere); { on lit un caractère }
if eoln (f) then
MessageBeep (0); { bip standard }
end;
CloseFile (f);

Fin de fichier

La fonction Eof retourne une valeur booléenne indiquant si la fin du fichier (End Of File) est
atteinte.

Fermeture de fichier

Il est fortement conseillé de fermer tous les fichiers ouverts, à l'aide de la procédure CloseFile,
avant la fin de l'exécution d'un programme:

CloseFile (fichier_texte);

6.3 Fichiers à accès direct

Ces fichiers sont structurés en éléments ou enregistrements (record) dont le type peut être
quelconque. Les éléments d'un fichier à accès direct se suivent logiquement, mais pas forcément
physiquement; ils ont tous la même taille. Le terme accès direct signifie qu'il est possible
d'accéder directement à n'importe quel élément, pour autant que l'on spécifie son rang. Voici un
exemple de déclarations liées au traitement d'un fichier à accès direct:

type element = record


nom : string[12];
age : integer;
end;
var personne : element;
f : file of element;

Le schéma suivant illustre l'organisation logique des premiers enregistrements du fichier f:

rang 0 1 2 3
Durant Mercier César Alex
24 44 37 67

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-5


La flèche symbolise le pointeur de fichier.
Chaque enregistrement d'un fichier à accès direct peut être accédé en spécifiant son rang. Un
pointeur de fichier mémorise le rang de l'enregistrement concerné par la prochaine opération de
lecture ou d'écriture. Lorsqu'un fichier est ouvert, son pointeur indique l'enregistrement de rang
zéro, c'est-à-dire le premier enregistrement. Après chaque opération de lecture ou d'écriture, le
pointeur de fichier est incrémenté du nombre d'enregistrements lus ou écrits. Un enregistrement
est l'unité minimale de transfert d'information entre un fichier et un programme.
Le contenu des fichiers à accès direct est stocké sous forme binaire compactée et n'est donc pas
directement affichable à l'écran. Il est également important de savoir que l'on peut accéder de
manière séquentielle à un fichier à accès direct.
Très souvent les éléments d'un fichier à accès direct sont de type enregistrement.

Déclaration du type de fichier

La déclaration d'un fichier à accès direct est effectuée à l'aide des mots réservés file of:

type client = record


code : string[10];
nom : string[40];
solde : real;
premiere_facture : integer;
derniere_facture : integer;
end;

var fichier_client : file of client;


un_client : client;
ch : char;

En plus d'une variable de type fichier, il faut définir une variable dont le type est celui des éléments
de ce fichier. Cette dernière (un_client) est utilisée comme paramètre par les procédures de
lecture et d'écriture. Elle tient lieu de variable intermédiaire, ou de mémoire tampon.

Assignation de fichier

La procédure AssignFile remplit le même rôle que pour les fichiers texte:

assign (fichier_client, 'CLIENT.DAT');

Ouverture de fichier

Cette opération est effectuée par l'une des procédures reset ou rewrite, comme pour les
fichiers de type texte:

rewrite (fichier_client); { le fichier peut exister }


reset (fichier_stock); { le fichier doit exister }

L'emploi de la procédure reset nécessite l'existence préalable du fichier. En revanche l'utilisation


de la procédure rewrite crée et ouvre inconditionnellement un fichier. Si un fichier portant le
même nom existait déjà, son contenu serait perdu. Il convient donc d'être prudent lors de la
création d'un fichier à l'aide de cette procédure.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-6


Positionnement à l'intérieur d'un fichier

A chaque fichier utilisé dans un programme Delphi est associé un pointeur permettant au système
et à l'utilisateur de connaître l'emplacement du prochain élément accessible. La procédure seek
(nom_de_fichier, no) permet de positionner le pointeur associé au fichier nom_de_fichier
sur l'enregistrement numéro no, en vue d'une opération de lecture ou d'écriture. Il faut se rappeler
que le premier enregistrement d'un fichier porte le numéro zéro.

Instructions de lecture et d'écriture

La procédure de lecture est read (nom_de_fichier, liste_elements) et celle d'écriture


est write (nom_de_fichier, liste_elements), où liste_elements est une liste de
variables du même type que les éléments du fichier. Il est important de savoir qu'à chaque écriture
et à chaque lecture le pointeur associé au fichier est automatiquement incrémenté de manière à
être positionné sur l'enregistrement suivant. Partant de ce principe, la modification du contenu de
l'enregistrement numéro 5 d'un fichier implique, par exemple, les opérations suivantes:

seek (fichier_client, 5);


read (fichier_client, un_client);
... { modification des données contenues dans un_client }
seek (fichier_client, 5);
write (fichier_client, un_client);

Enregistrement courant et taille d'un fichier

La fonction FilePos (nom_de_fichier) fournit le rang de l'enregistrement courant. La


fonction FileSize (nom_de_fichier) donne la taille du fichier, exprimée en nombre
d'éléments. Le fragment de programme suivant permet de déterminer si un fichier est vide:

if filesize (f) = 0 then


writeln ('Fichier vide');

Changement de nom et effacement d'un fichier

Ces opérations sont effectuées, respectivement, par les procédures Rename et Erase. La
procédure Erase permet d'éliminer un fichier du répertoire dans lequel il figure:

rename (fichier_client, 'CLIENT.BAK');


erase (fichier_client);

Fin de fichier

La fonction booléenne Eof (nom_de_fichier) indique si la fin du fichier nom_de_fichier est


atteinte, comme pour les fichiers texte

Fermeture de fichier

La fermeture d'un fichier à accès direct s'effectue de la même manière que pour un fichier de type
texte:

CloseFile (fichier_client);

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-7


1er exemple

Afin d'illustrer l'utilisation d'un fichier à accès direct, examinons ce premier programme permettant
d'enregistrer des données (nom et âge de diverses personnes) dans un fichier.
La fenêtre de l'application se présente sous la forme suivante:

Le principe de fonctionnement est le suivant: chaque clic sur le bouton Enregistrer écrit un nouvel
enregistrement dans le fichier de données. La première fois (et seulement la première) que
l'utilisateur clique sur ce bouton, une boîte de dialogue permettant de choisir le nom du fichier
apparaît à l'écran.
Voici maintenant le code du programme:

unit udirect1;

interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls,


Forms,
Dialogs, StdCtrls;

type
TPersonne = record
nom : string[20];
age : integer;
end;

TFichier = file of TPersonne;

TFtype1 = class(TForm)
Nom: TEdit;
Label1: TLabel;
age: TEdit;
Label2: TLabel;
enregistrer: TButton;
SD: TSaveDialog;
procedure enregistrerClick(Sender: TObject);
procedure FormActivate(Sender: TObject);

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-8


procedure FormClose(Sender: TObject; var Action:
TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
f : TFichier;
pers : TPersonne;
nomf : string; { nom du fichier }
end;

var Ftype1: TFtype1;

implementation

{$R *.DFM}

procedure TFtype1.enregistrerClick(Sender: TObject);


begin
if nomf = '' then { le fichier n'est pas ouvert }
if SD.execute then begin
nomf := SD.filename;
caption := 'Fichiers typés [' + nomf + ']';
assignfile (f, nomf);
rewrite (f);
end;
if nomf <> '' then begin
pers.nom := Nom.text; { on enregistre les données }
pers.age := strtoint (Age.text);
write (f, pers);
Nom.text := '';
Age.text := '0';
end;
end;

procedure TFtype1.FormActivate(Sender: TObject);


begin
nomf := '';
end;

procedure TFtype1.FormClose(Sender: TObject; var Action:


TCloseAction);
begin
if nomf <> '' then
closefile (f);
end;

end.
En inspectant ce programme on peut noter les points suivants:
• La manière de déclarer les types TPersonne et Tfichier
• L'emplacement où sont déclarées les variables f, pers et nomf
• La variable nomf (contenant le nom du fichier) permet de savoir si le fichier a déjà été
ouvert (nomf<>'')
• Le nom du fichier ouvert est placé dans la barre de titre de l'application
• SD est le nom du composant OpenDialog
• Parmi les propriétés du composant OpenDialog on a spécifié
"ofOverwritePrompt=true", c'est-à-dire que le programme met en garde

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-9


l'utilisateur lorsque le nom de fichier choisi existe déjà. L'utilisateur peut décider de
l'écraser ou de changer le nom choisi.

Comme on peut le constater, ce programme ne fait qu'enregistrer des données, les unes à la suite
des autres.

2ème exemple

Le deuxième exemple illustre le déplacement parmi les enregistrements d'un fichier à accès direct
ayant la même structure que celui de l'exemple précédent. On pourra dont lire les fichiers créés
par ce dernier.
Tant que l'utilisateur n'a pas cliqué sur le bouton Lire, les boutons de déplacement sont
désactivés. Le bouton Lire permet justement de choisir le fichier de données à consulter.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-10


Voici comment se présente cette fenêtre après le choix du nom du fichier de données:

L'utilisateur peut alors se déplacer dans le fichier à l'aide des boutons; le numéro de
l'enregistrement courant est affiché dans un but de vérification.
Voici le code de ce programme :

unit udirect1;

interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls,


Forms,
Dialogs, StdCtrls, ExtCtrls, DBCtrls;

type
TPersonne = record
nom : string[20];
age : integer;
end;

TFichier = file of TPersonne;

TFtype1 = class(TForm)
Nom: TEdit;
Label1: TLabel;
age: TEdit;
Label2: TLabel;
Lire: TButton;
OD: TOpenDialog;
premier: TButton;
Precedent: TButton;
Suivant: TButton;
Dernier: TButton;
rec: TLabel;
procedure LireClick(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action:
TCloseAction);
procedure premierClick(Sender: TObject);
procedure SuivantClick(Sender: TObject);
procedure DernierClick(Sender: TObject);
procedure PrecedentClick(Sender: TObject);

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-11


private
{ Private declarations }
public
{ Public declarations }
f : TFichier;
pers : TPersonne;
nomf : string; { nom du fichier }
position : integer;
procedure Afficher;
end;

var Ftype1: TFtype1;

implementation

{$R *.DFM}

procedure TFtype1.Afficher;
begin
Nom.text := pers.nom;
Age.Text := inttostr (pers.age);
Rec.caption := 'Enregistrement no. ' + inttostr (position);
end;

procedure TFtype1.LireClick(Sender: TObject);


begin
if nomf <> '' then begin
closefile (f);
nomf := '';
end;
if OD.execute then begin
nomf := OD.filename;
caption := 'Fichiers typés 2 [' + nomf + ']';
assignfile (f, nomf);
reset (f);
position := 0;
read (f, pers);
Afficher;
premier.enabled := true;
dernier.enabled := true;
suivant.enabled := true;
precedent.enabled := true;
end else begin
premier.enabled := false;
dernier.enabled := false;
suivant.enabled := false;
precedent.enabled := false;
end;
end;

procedure TFtype1.FormActivate(Sender: TObject);


begin
nomf := '';
end;

procedure TFtype1.FormClose(Sender: TObject; var Action:


TCloseAction);
begin
if nomf <> '' then
closefile (f);
end;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-12


procedure TFtype1.premierClick(Sender: TObject);
begin
seek (f, 0);
read (f, pers);
position := 0;
Afficher;
end;

procedure TFtype1.SuivantClick(Sender: TObject);


begin
if not eof (f) then begin
position := filepos (f);
read (f, pers);
Afficher;
end;
end;

procedure TFtype1.DernierClick(Sender: TObject);


begin
seek (f, filesize(f)-1);
position := filepos (f);
read (f, pers);
Afficher;
end;

procedure TFtype1.PrecedentClick(Sender: TObject);


begin
if filepos(f) > 1 then begin
seek (f, filepos(f)-2);
position := filepos (f);
read (f, pers);
Afficher;
end;
end;

end.
En inspectant ce deuxième programme on peut noter les points suivants:
• La variable position permet de garder en mémoire la position du pointeur de fichier
(numéro de l'enregistrement)
• L'emplacement et la manière de déclarer la procédure Afficher
• La variable nomf (contenant le nom du fichier) permet de savoir si le fichier a déjà été
ouvert (nomf<>'')
• La manière dont sont implémentées les quatre opérations de déplacement dans le
fichier
• Les sécurités mises en place pour éviter quelques désagréables surprises. Par
exemple, ne pas fermer le fichier s'il n'a pas encore été ouvert, ou encore ne pas
laisser le programme essayer de se positionner avant le premier enregistrement du
fichier
• Après chaque déplacement la procédure Afficher est appelée afin de visualiser les
données lues,

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-13


3ème exemple

Pour terminer nous allons ajouter une fonctionnalité au programme précédent: pouvoir modifier le
contenu d'un enregistrement.

Comme on peut le constater, il ressemble beaucoup au programme précédent, sauf le code


permettant de modifier un enregistrement, c'est-à-dire la procédure TFtype1.ModifierClick.

unit udirect1;

interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls,


Forms,
Dialogs, StdCtrls, ExtCtrls, DBCtrls;

type
TPersonne = record
nom : string[20];
age : integer;
end;

TFichier = file of TPersonne;

TFtype1 = class(TForm)
Nom: TEdit;
Label1: TLabel;
age: TEdit;
Label2: TLabel;
Lire: TButton;
OD: TOpenDialog;
premier: TButton;
Precedent: TButton;
Suivant: TButton;
Dernier: TButton;
rec: TLabel;
Modifier: TButton;
Label3: TLabel;
procedure LireClick(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action:
TCloseAction);
procedure premierClick(Sender: TObject);

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-14


procedure SuivantClick(Sender: TObject);
procedure DernierClick(Sender: TObject);
procedure PrecedentClick(Sender: TObject);
procedure ModifierClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
f : TFichier;
pers : TPersonne;
nomf : string; { nom du fichier }
position : integer;
procedure Afficher;
end;

var Ftype1: TFtype1;

implementation

{$R *.DFM}

procedure TFtype1.Afficher;
begin
Nom.text := pers.nom;
Age.Text := inttostr (pers.age);
Rec.caption := 'Enregistrement no. ' + inttostr (position);
end;

procedure TFtype1.LireClick(Sender: TObject);


var ODres : boolean; { variable auxiliaire }
begin
if nomf <> '' then begin
closefile (f);
nomf := '';
end;
ODres := OD.execute;
if ODres then begin
nomf := OD.filename;
caption := 'Fichiers typés 3 [' + nomf + ']';
assignfile (f, nomf);
reset (f);
position := 0;
read (f, pers);
Afficher;
end;
premier.enabled := ODres;
dernier.enabled := ODres;
suivant.enabled := ODres;
precedent.enabled := ODres;
modifier.Enabled := ODres;
end;

procedure TFtype1.FormActivate(Sender: TObject);


begin
nomf := '';
end;

procedure TFtype1.FormClose(Sender: TObject; var Action:


TCloseAction);
begin
if nomf <> '' then

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-15


closefile (f);
end;

procedure TFtype1.premierClick(Sender: TObject);


begin
seek (f, 0);
read (f, pers);
position := 0;
Afficher;
end;

procedure TFtype1.SuivantClick(Sender: TObject);


begin
if not eof (f) then begin
position := filepos (f);
read (f, pers);
Afficher;
end;
end;

procedure TFtype1.DernierClick(Sender: TObject);


begin
seek (f, filesize(f)-1);
position := filepos (f);
read (f, pers);
Afficher;
end;

procedure TFtype1.PrecedentClick(Sender: TObject);


begin
if filepos(f) > 1 then begin
seek (f, filepos(f)-2);
position := filepos (f);
read (f, pers);
Afficher;
end;
end;

procedure TFtype1.ModifierClick(Sender: TObject);


begin
pers.nom := Nom.text; { on stocke les nouvelles données }
pers.age := strtoint (Age.text);
seek (f, position); { on se replace à la position
courante }
write (f, pers); { on écrit les données }
seek (f, position); { on se replace à la position
courante }
end;

end.
Nous pourrions multiplier les exemples, de gestions de stock en facturations, mais cela ne paraît
pas souhaitable étant donné que Delphi excelle également dans la gestion des bases de données.
Dès que la complexité d'une application gérant des fichiers augmente, il est plus aisé de la
développer en faisant appel aux outils spécifiques aux bases de données

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-16


6.4 Fichiers non typés

Les fichiers non typés se déclarent à l'aide du mot réservé file:

var fichier: file; { et non: file of... }

Une variable de type file permet d'accéder à un fichier quelconque, sans connaître sa structure ni
son contenu. Les opérations de lecture et d'écriture sont effectuées par les procédures
BlockRead et BlockWrite. Les opérations d'assignation et de fermeture sont les mêmes que
pour les fichiers à accès direct ou de type texte.
Voici un exemple permettant de copier un fichier, quel que soit son type. L'utilisateur choisit un
nom de fichier source, un nom de fichier destination, puis lance la copie.
Voici comment se présente la fiche en version "construction":

Et en version "exécution":

Le code de cette application est le suivant:

unit UfnonType;
interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls,


Forms, Dialogs,
StdCtrls;

type
TFnonType = class(TForm)
OD: TOpenDialog;
SD: TSaveDialog;
source: TEdit;
Label1: TLabel;

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-17


dest: TEdit;
Label2: TLabel;
ChoixSource: TButton;
ChoixDest: TButton;
copie: TButton;
procedure ChoixSourceClick(Sender: TObject);
procedure ChoixDestClick(Sender: TObject);
procedure copieClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var FnonType: TFnonType;

implementation

{$R *.DFM}

procedure TFnonType.ChoixSourceClick(Sender: TObject);


begin
if OD.execute then
source.text := OD.filename;
end;

procedure TFnonType.ChoixDestClick(Sender: TObject);


begin
if SD.execute then
dest.text := SD.FileName;
end;

procedure TFnonType.copieClick(Sender: TObject);


var src, desti: file; { les 2 fichiers }
Nblu, Nbecrit: Integer; { nbre de bytes lus et écrits }
Buf: array[1..2048] of Char; { buffer de transfert }
begin
if not FileExists (source.text) or not FileExists (dest.text)
then
begin
Messagebeep (0);
exit;
end;
AssignFile (src, source.text);
Reset (src, 1); { taille d'un record = 1 }
AssignFile (desti, dest.text);
Rewrite (desti, 1); { taille d'un record = 1 }
repeat
BlockRead (src, Buf, SizeOf (Buf), Nblu);
BlockWrite (desti, Buf, Nblu, Nbecrit);
until (Nblu = 0) or (Nbecrit <> Nblu);
CloseFile (src);
CloseFile (desti);
end;

end.

En regardant ce programme on peut noter les points suivants:


• La manière dont la boucle repeat…until est écrite, ainsi que l'utilisation de
BlockRead et BlockWrite

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-18


• Avant de commencer la copie, plutôt que faire des tests compliqués du contenu des
zones de texte concernant les noms des fichiers, on fait appel à la fonction bien
pratique FileExists

6.5 Détection des erreurs d'entrée-sortie

Normalement, lorsqu'une erreur d'entrée-sortie survient, l'exécution du programme est


interrompue. Il est possible d'éviter cette interruption intempestive et de gérer depuis le
programme l'erreur qui en est la cause. Pour cela, il faut, avant une opération d'entrée-sortie,
désactiver la détection des erreurs à l'aide de la directive de compilation {$I-} et, après l'opération,
invoquer la fonction IoResult afin de connaître l'erreur éventuelle. Cette fonction fournit un
nombre entier indiquant un code d'erreur lié à la dernière opération d'entrée-sortie. Si ce code vaut
zéro, l'opération s'est déroulée sans problème. A l'aide de la directive de compilation {$I+}, le
compilateur reprend en charge la détection des erreurs d'entrée-sortie.
Le fragment de programme qui suit montre comment vérifier l'existence d'un fichier dont le nom
est fourni par l'utilisateur:

...
begin
repeat
SaisieNomFichier;
assignfile (fichier, nom_fichier);
{$I-} { traitement des erreurs désactivé }
reset (fichier);
io := ioresult; { io contient le code d'erreur }
{$I+} { traitement des erreurs réactivé }
if io <> 0 then
Res.text := 'ERREUR : ' + inttostr (io);
until io = 0; { pas d'erreur, le fichier existe }
end;

Après invocation de la fonction IoResult, celle-ci retourne la valeur zéro jusqu'à la prochaine
opération d'entrée-sortie; c'est pourquoi il faut affecter son résultat à une variable en vue du
traitement de l'erreur par le programme. Dans une suite d'instructions comprises entre {$I-} et
{$I+}, la fonction IoResult doit systématiquement être appelée après chaque opération d'entrée-
sortie, afin d'éviter des résultats imprévisibles.
Voici un autre exemple utilisant les directives de compilation {$I-} et {$I+}. Il s'agit de l'exemple de
la section 6.4, modifié afin d'incorporer ces directives:

procedure TFnonType.copieClick(Sender: TObject);


var src, desti: file; { les 2 fichiers }
Nblu, Nbecrit: Integer; { nbre de bytes lus et écrits }
Buf: array[1..2048] of Char; { buffer de transfert }
begin
// On utilise pas de test d'existence de fichiers
// afin de mettre en évidence l'utilisation de la directive {$I-}
//---------------------------------------------------------------
// if not FileExists (source.text) or not FileExists (dest.text)
// then begin
// Messagebeep (0);
// exit;
// end;
//---------------------------------------------------------------
AssignFile (src, source.text);
{$I-}
Reset (src, 1); { taille d'un record = 1 }
{$I+}
if ioresult > 0 then begin

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-19


messagedlg ('Erreur d''ouverture du fichier source', mtError,
[mbOK],0);
exit;
end;
AssignFile (desti, dest.text);
{$I-}
Rewrite (desti, 1); { taille d'un record = 1 }
{$I+}
if ioresult > 0 then begin
messagedlg ('Erreur d''ouverture du fichier destination',
mtError, [mbOK],0);
CloseFile (src);
exit;
end;
repeat
BlockRead (src, Buf, SizeOf (Buf), Nblu);
BlockWrite (desti, Buf, Nblu, Nbecrit);
until (Nblu = 0) or (Nbecrit <> Nblu);
CloseFile (src);
CloseFile (desti);
end;

Avec Delphi, il est toutefois préférable de traiter les erreur d'accès aux fichiers à l'aide des
exceptions (voir plus loin). De plus, l'utilisation de dialogues standard d'ouverture et de
sauvegarde de fichiers permet généralement d'éviter les erreurs.

Delphi (J-C Armici janvier 2003 www.unvrai.com) page 4-20

Vous aimerez peut-être aussi