Cours Delphi
Cours Delphi
Cours 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.
fig. 1.2
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:
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.
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.
fig 1.9
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.
fig 1.11
fig 1.12
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.
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.
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.
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);
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:
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
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 Unit2;
Partie {$R *.DFM}
implémentation
procedure TForm1.Button1Click(Sender: TObject);
begin
form2.show;
end;
end.
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
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
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.
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:
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.
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.
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.
Il existe plusieurs type permettant de stocker des valeurs entière. Voici leurs caractéristiques:
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 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:
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
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
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)));
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.
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.
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);
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:
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:
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:
...
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:
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é:
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:
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:
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:
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:
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:
L'exemple qui suit utilise une structure sélective sous une forme quelque peu différente:
...
var revenu : real;
taxes : real;
...
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:
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?
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;
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;
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
...
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;
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:
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;
Procédure Continue
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
Instruction goto
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;
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
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.
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
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
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.
Exercice 2.9:
Exercice 2.10:
Ecrire un programme simulant le fonctionnement d'une calculatrice simple (4 opérations).
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.
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.
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.
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:
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:
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:
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;
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:
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;
implementation
{$R *.DFM}
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.
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
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'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.
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.
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;
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.).
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:
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;
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:
++++++++++
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:
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.
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:
3. Le type d'un paramètre effectif doit être le même que celui du paramètre
formel correspondant.
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.
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.
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:
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:
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:
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:
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.
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:
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é.
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.
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
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".
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.
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.
L'exécution de ces instructions place la chaîne de caractères 'Ceci est un exemple' dans Edit1.
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.
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.
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.
Après l'exécution de ces instructions pos1 contient le nombre 9, alors que pos2 contient le
nombre 0.
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.
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.
Le type string est un type générique. En fait, Delphi gère trois types de chaînes de caractères:
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
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:
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.
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
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.
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:
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:
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.
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.
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.:
SYNTAXE
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:
...
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:
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 +
différence -
é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:
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:
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
La taille d'une variable de type tableau est déterminée par la taille de ses éléments et par leur
nombre.
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:
for i := 1 to largeur do
grille [1, i] := '*';
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;
implementation
{$R *.DFM}
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:
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
implementation
{$R *.DFM}
end.
fig. 5.1
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 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:
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.
Voici le résultat:
Voici le résultat
Voici le résultat:
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.
fig. 5.2
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:
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:
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:
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:
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;
implementation
{$R *.DFM}
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:
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
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.
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:
personne.nom := Edit1.Text;
De même, le loyer total se calcule à l'aide de l'instruction:
personne.nom := Edit1.Text;
loyer_total := personne.loyer + personne.charges;
peuvent être transformées en:
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:
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:
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.
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.
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
Pour les constantes avec type non structuré, chaîne de caractères ou ensemble.
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:
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.
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).
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.
• 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
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:
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).
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.
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.
Exercice 5.16:
Modifier l'exercice précédent pour qu'une case sur deux soit rouge.
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:
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.
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:
• 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
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:
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).
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 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
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
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
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.
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.
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:
• 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
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:
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.
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.
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:
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:
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:
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.
unit uftxt1;
interface
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;
implementation
{$R *.DFM}
end.
Le cœur du programme est constitué par les lignes suivantes:
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:
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);
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:
rang 0 1 2 3
Durant Mercier César Alex
24 44 37 67
La déclaration d'un fichier à accès direct est effectuée à l'aide des mots réservés file of:
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:
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:
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.
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:
Fin de fichier
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);
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
type
TPersonne = record
nom : string[20];
age : integer;
end;
TFtype1 = class(TForm)
Nom: TEdit;
Label1: TLabel;
age: TEdit;
Label2: TLabel;
enregistrer: TButton;
SD: TSaveDialog;
procedure enregistrerClick(Sender: TObject);
procedure FormActivate(Sender: TObject);
implementation
{$R *.DFM}
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
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.
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
type
TPersonne = record
nom : string[20];
age : integer;
end;
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);
implementation
{$R *.DFM}
procedure TFtype1.Afficher;
begin
Nom.text := pers.nom;
Age.Text := inttostr (pers.age);
Rec.caption := 'Enregistrement no. ' + inttostr (position);
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,
Pour terminer nous allons ajouter une fonctionnalité au programme précédent: pouvoir modifier le
contenu d'un enregistrement.
unit udirect1;
interface
type
TPersonne = record
nom : string[20];
age : integer;
end;
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);
implementation
{$R *.DFM}
procedure TFtype1.Afficher;
begin
Nom.text := pers.nom;
Age.Text := inttostr (pers.age);
Rec.caption := 'Enregistrement no. ' + inttostr (position);
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
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":
unit UfnonType;
interface
type
TFnonType = class(TForm)
OD: TOpenDialog;
SD: TSaveDialog;
source: TEdit;
Label1: TLabel;
implementation
{$R *.DFM}
end.
...
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:
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.