IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Guide Pascal et Delphi

Date de publication : 20/10/07

Par Frédéric Beaulieu
 

Cette page recense toute l'aide disponible sur les mini-projets. Pour chaque mini-projet, il est proposé l'accès à des indications, à la solution guidée pas à pas ainsi qu'au téléchargement du projet constitué par cette solution.

Si vous constatez une ou plusieurs erreurs dans les solutions proposées, ou si vous souhaitez que je corrige votre ou vos essais, contactez-moi en joignant le mini-projet dans son état actuel compressé dans un fichier ZIP. Je ne m'engage à aucun délai, mais j'essaierai dans la mesure de mes possibilités de répondre aux demandes.

            

I. Mini-projet n°1 : Nombre secret
I.A. Rappel de l'énoncé
I.B. Indications
I.B.1. Indications générales
I.B.2. Indications avancées
I.C. Solution pas à pas
I.C.1. Création du projet
I.C.2. Création de l'interface
I.C.3. Fonctionnement du bouton « Nouveau nombre secret »
I.C.4. Fonctionnement du bouton « Proposer un nombre »
I.D. Téléchargement
II. Mini-projet n°2 : Première Liste
II.A. Rappel de l'énoncé
II.B. Indications
II.C. Solution pas à pas
II.D. Téléchargement
III. Mini-projet n°3 : Equations du second degré
III.A. Enoncé
IV. Mini-projet n°5 : Tableaux Associatifs
IV.A. Enoncé
IV.B. Etapes de progression
IV.C. Solution pas à pas
IV.D. Téléchargement


I. Mini-projet n°1 : Nombre secret


I.A. Rappel de l'énoncé

  • L'interface est, pour cette fois, imposée. Elle doit être similaire à ceci (seuls les textes sont à respecter) :

  • Le bouton « Nouveau nombre secret » fixe un nombre secret entier entre 0 et 100. Ce nombre est choisi par l'ordinateur, et n'est ni demandé ni montré à l'utilisateur.
  • Le bouton « Proposer un nombre » demande un nombre entier à l'utilisateur. Si le nombre entier n'est pas compris entre 0 et 100, l'utilisateur en est informé. Sinon, la position par rapport au nombre secret est indiquée :
    « Le nombre secret est inférieur/supérieur à (le nombre proposé par l'utilisateur) »
  • Lorsque l'utilisateur trouve le nombre secret, un message l'en informe : « bravo, le nombre secret était (indiquer le nombre secret) », puis le nombre secret est changé et l'utilisateur est informé de ce changement et invité à rejouer : « Nombre secret changé, vous pouvez rejouer ».
Ce mini-projet étant le premier, il est possible que vous ayez du mal à le réaliser. Dans ce cas, n'hésitez pas dans ce cas à consulter les indications, puis le guide pas à pas, ou en dernier recours à télécharger le mini-projet terminé (auquel vous avez accès même si vous avez la version téléchargée du site).

Rappels et indications indispensables :

  • Pour pouvoir réagir au clic sur un bouton, il faut faire un double-clic dessus dans Delphi, puis écrire les instructions dans la procédure qui est créée à cet effet (Ecrire la procédure et la déclarer sans passer par cette étape du double-clic ne suffit en aucun cas).

I.B. Indications


I.B.1. Indications générales

  • Commencez par créer l'interface de votre projet : il faudra dimensionner correctement la fiche principale, y placer deux boutons, puis les dimensionner à leur tour. N'oubliez pas de changer les textes affichés.
  • Le nombre secret devra être mis en mémoire. Pour cela, une variable sera nécessaire. Le nombre secret étant un nombre entier entre 0 et 100, le type de cette variable peut être 'byte' ou 'integer' par exemple (on préférera alors 'integer'). La variable ne pourra pas être locale à une procédure car serait dans ce cas effacée à la fin de l'exécution de la procédure, mais devra être au contraire globale : accessible dans toute l'unité principale, ou plutôt dans son implémentation.
  • Un clic sur le bouton « Nouveau nombre secret » changera la valeur de cette variable. On utilisera les fonctions 'Trunc' et 'Random'.
  • Un clic sur le bouton « Proposer un nombre » effectuera les tâches suivantes dans l'ordre :

    • Demande d'un nombre sous forme de texte
    • Conversion de ce texte en nombre
    • Suivant trois cas : inférieur, supérieur ou égal, la réaction sera différente : si le nombre proposé est différent, on dit s'il est inférieur ou supérieur au nombre secret, sinon, on félicite l'utilisateur, puis on fixe un nouveau nombre comme avec l'autre bouton, et on en informe l'utilisateur.

I.B.2. Indications avancées

  • Pour obtenir un entier entre 0 et 100, il faut écrire :
    
    Trunc(Random(101))
    
    Le résultat devra être affecté à la variable destinée à recevoir le nombre secret.

  • Pour demander un nombre à l'utilisateur, deux variables sont nécessaires. Une, de type chaîne, est d'abord initialisée, puis utilisée dans un appel à 'InputQuery'. La deuxième permettra de stocker le résultat de la conversion de la première par 'IntToStr'. Tout cela s'effectue en trois instructions.
  • Pour gérer les trois cas (inférieur, supérieur, égal), voici le squelette du bloc 'if' à utiliser :
    
    if ... < ... then
      ...
    else if ... > ... then
      ...
    else
    begin
      ...
    end;
    

I.C. Solution pas à pas

Cette solution guidée pas à pas vous permettra de construire le projet entier. Rappelez-vous que la version de Delphi employée ici peut différer de la votre, mais que cela n'a normalement que des conséquences mineures.


I.C.1. Création du projet

  1. Sous Delphi, créez un nouveau projet à l'aide de la commande 'Nouveau Projet' ou 'Nouvelle Application' du menu 'Fichier'.
  2. Enregistrez le projet par la commande 'Enregistrer Tout' du menu Fichier. Choisissez de préférence un répertoire créé pour cette occasion. Un nom pour le projet (fichier .dpr) vous sera demandé, entrez « NombreSecret ». Un nom d'unité (.pas) sera également demandé pour stocker l'unique fiche et son unité associée : nommez-la « Principale » (et pensez à toujours donner à vos unités des noms significatifs qui soient également des identificateurs)

I.C.2. Création de l'interface

  • Affichez la fiche principale : au besoin, sélectionnez, dans la liste des unités, l'unité 'Principale' et appuyez sur F12 pour passer à sa fiche associée.
  • Une fois la fiche visible, trouvez, dans la barre des composants, le bouton 'Button'. Le fait de cliquer sur ce bouton puis sur la fiche place un 'Button' (un bouton, donc), sur la fiche à l'endroit où vous cliquez. Placez ainsi deux boutons sur la fiche.
  • Le moment est venu de placer tout cela : il va falloir utiliser soit la souris, soit l'inspecteur d'objets. Si vous voulez utiliser la souris, rappelez-vous que les actions que vons effectuez agissent sur le composant sélectionné (la fiche et chaque bouton, je le rappelle, sont des composants), il vous faudra donc par exemple sélectionner un bouton (en cliquant une seule fois dessus) avant de changer sa position ou ses dimensions à l'aide de la souris.
  • En ce qui concerne l'inspecteur d'objets, affichez-le au besoin à l'aide de la touche F11. Le tableau suivant donne les propriétés à changer ainsi que les nouvelles valeurs à donner à ces propriétés ainsi qu'une brève explication (Vous devez savoir utiliser l'inspecteur d'objets au moins de manière rudimentaire si vous avez suivi le cours normal du guide).

Fiche :

Caption Le Nombre Secret Permet de choisir le texte de titre de la fenêtre
Height 148 Donne la hauteur de la fiche en pixels
Width 193 Donne la largeur de la fiche en pixels

Bouton « Nouveau nombre secret »

Caption Nouveau nombre secret Fixe le texte qui apparait sur le bouton
Height 25 Hauteur du bouton
Left 24 Décalage par rapport au bord gauche de la fiche
Top 24 Décalage par rapport au haut de la fiche
Width 137 Largeur du bouton

Bouton « Proposer un nombre »

Caption Proposer un nombre Fixe le texte qui apparait sur le bouton
Height 25 Hauteur du bouton
Left 24 Décalage par rapport au bord gauche de la fiche
Top 72 Décalage par rapport au haut de la fiche
Width 137 Largeur du bouton

I.C.3. Fonctionnement du bouton « Nouveau nombre secret »

Maintenant que l'interface est terminée, concentrons-nous sur le code Pascal.

  1. Un clic sur le premier bouton doit déclencer une action. Pour cela, le seul moyen que vous connaissiez consiste à double-cliquer sur le bouton sous Delphi, puis à écrire des instructions dans la procédure qui est alors générée (ceci est expliqué en détail dans un chapitre du guide). Double-cliquez donc sur le bouton « Nouveau nombre secret ». Une procédure est générée, prète à recevoir nos instructions.
  2. Le principe ici est de fixer une nouvelle valeur, donc de changer la valeur d'une variable. Cette variable, nous pourrions la déclarer dans la procédure qui vient d'être générée, mais alors elle serait non seulement inaccessible depuis d'autres procédures, mais aussi "oubliée" lorsque la procédure se termine (elle n'existerait que pendant l'exécution de la procédure). Il nous faut donc déclarer cette variable dans un endroit plus accessible. Parmi les possibilités, l'interface ou l'implémentation de l'unité 'Principale', ou le fichier-projet. L'endroit le plus indiqué est au début de l'implémentation de l'unité 'Principale' car alors la variable sera accessible à toute procédure qui suivra. En outre, le fichier-projet est à exclure dans la grande majorité des cas, et l'interface est inadaptée car la variable n'a pas à être accessible depuis d'autres unités. Déclarez donc une variable 'NbSecret' de type 'Integer' au début de l'implémentation de l'unité (c.f. code source à la fin de cette solution).
  3. Quant à la procédure proprement dite, il va suffir de générer un nombre aléatoire entre 0 et 100, opération que l'on fait en deux temps (mais en une seule instruction) : on génère un nombre aléatoire entre 0 (inclus) et 101 (exclus), puis ce nombre à virgule est tronqué pour donner un entier entre 0 et 100. Enfin, le résultat de ces deux opérations est affecté à 'NbSecret'. Voici l'instruction à inscrire :
    
     NbSecret := Trunc(Random(101));
    

I.C.4. Fonctionnement du bouton « Proposer un nombre »

Maintenant, il ne nous reste plus qu'à faire fonctionner le deuxième bouton, c'est un peu plus compliqué.

  • Créez la procédure de réponse au clic sur ce bouton, comme pour le précédent.
  • On va devoir ici dans un premier temps demander un nombre à l'utilisateur. Il est impossible de lui demander directement un entier à ce stade du guide, nous devrons donc passer par une chaîne de caractères et une conversion. Il va donc nous falloir deux variables : une de type chaîne qui stockera la réponse de l'utilisateur, et une autre de type entier pour stocker la réponse convertie (ici encore on supposera l'utilisateur assez gentil pour ne donner que des nombres). Voici la déclaration des deux variables locales à la procédure :
    
    var
      NbTest: Integer;
      Reponse: string;
    
  • Le questionnement de l'utilisateur passera comme d'habitude par un appel à 'InputQuery'. Il faudra, comme à chaque fois, lui fournir une variable dont la valeur sera utilisée comme réponse suggèrée. La variable 'Reponse' servira à cela et sera initialisée avant utilisation (il faut toujours initialiser les variables avant utilisation). Une instruction convertira ensuite la réponse en nombre.
    Voici les trois premières instructions de la procédure, qui effectue les tâches décrites ci-dessus :
    
     Reponse := '0';
    InputQuery('Proposez un nombre', 'Valeur :', Reponse);
    NbTest := StrToInt(Reponse);
    
  • Plusieurs cas sont alors à envisager : soit l'utilisateur à entré un nombre inférieur ou supérieur au nombre secret, et on l'en informe, soit il a trouvé le nombre secret et davantage de choses devront être faites. Voici la structure du bloc 'if' qui examinera les cas :
    
    if NbTest < NbSecret then
      ShowMessage('Le nombre secret est supérieur à ' + IntToStr(NbTest))
    else if NbTest > NbSecret then
      ShowMessage('Le nombre secret est inférieur à ' + IntToStr(NbTest))
    else
    begin
      ...
    end;
    
    Vous noterez que dans les deux premiers cas, une simple instruction suffit et rappelle à l'utilisateur sa proposition.

  • Le dernier cas nécessite un bloc d'instructions. En effet, il va falloir féliciter l'utilisateur, fixer un nouveau nombre secret, et en avertir l'utilisateur. Voici les trois instructions à inscrire dans ce bloc (vous remarquerez que la deuxième est identique à celle utilisée par l'autre bouton) :
    
     ShowMessage('bravo, le nombre secret était ' + IntToStr(NbSecret));
    NbSecret := Trunc(Random(101));
    ShowMessage('Nombre secret changé, vous pouvez rejouer');
    
Le projet est maintenant complet, vous pouvez le compiler et le tester à loisir. Voici le code source complet de l'implémentation de l'unité 'Principale' :

implementation

{$R *.DFM}

var
  NbSecret: Integer;

procedure TForm1.Button1Click(Sender: TObject);
begin
  NbSecret := Trunc(Random(101));
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  NbTest: Integer;
  Reponse: string;
begin
  Reponse := '0';
  InputQuery('Proposez un nombre', 'Valeur :', Reponse);
  NbTest := StrToInt(Reponse);
  if NbTest < NbSecret then
    ShowMessage('Le nombre secret est supérieur à ' + IntToStr(NbTest))
  else if NbTest > NbSecret then
    ShowMessage('Le nombre secret est inférieur à ' + IntToStr(NbTest))
  else
    begin
      ShowMessage('bravo, le nombre secret était ' + IntToStr(NbSecret));
      NbSecret := Trunc(Random(101));
      ShowMessage('Nombre secret changé, vous pouvez rejouer');
    end;
end;

end.
Enfin, en guise de conclusion, voici une liste de choses qui pourraient être améliorées dans ce projet. Je ne suis pas assez bête pour dire que cette liste est complète, mais elle donne quelques idées qui feront l'objet de recherches par les plus courageux.

  1. Si l'utilisateur entre un nombre qui n'est pas entre 0 et 100, rien ne lui est reproché, ce serait mieux.
  2. La limite 0 est peu contestable, par contre, la valeur 100 pourrait être fixée par l'utilisateur au moyen d'une question posée par un troisième bouton (il faudrait dans ce cas recalculer un nouveau nombre secret).
  3. L'instruction qui initialise le nombre secret est répètée deux fois. Dans ce genre de cas, il est préférable d'en faire une procédure ou une fonction séparée, qui sera appelée en lieu et place de l'instruction.
  4. L'utilisateur pourrait avoir un nombre limité d'essais, et pourrait aussi avoir un score.

I.D. Téléchargement

Code source du projet : 01_nombre_secret.zip


II. Mini-projet n°2 : Première Liste


II.A. Rappel de l'énoncé

L'objectif de ce mini-projet est de vous faire créer une application exploitant les notions que vous venez de découvrir, à savoir :

  • Gestion de composants, propriétés et méthodes.
  • Utilisation d'une zone d'édition, d'une liste.
Le but de ce projet est, à partir de l'interface que voici, composée d'une liste, d'une zone de saisie et de trois boutons, de permettre les actions mentionnées ci-dessous :

  • Ajout de texte dans la liste : pour cela, on entre le texte dans la zone d'édition puis on clique sur le bouton. Le texte est alors ajouté à la liste et la zone de saisie est vidée (le texte y est effacé).
  • Effacement de la liste : en cliquant sur le bouton adéquat, le contenu de la liste sera effacé.
  • Fermeture de l'application, en cliquant sur 'Quitter'.

II.B. Indications

  • Chaque bouton nécessite une procédure de réponse au clic : une instruction est suffisante pour chacun des 2 boutons 'Effacer la liste' et 'Quitter'.
  • Pour l'ajout, vous devez utilisez la méthode 'Add' de la propriété 'Items' de la liste, en transmettant soit une variable chaîne (ce qui allonge la procédure mais est plus simple au début), ou directement transmettre le texte de la zone d'édition.
  • Pour effacer le contenu de la zone de saisie, il faut assigner la chaîne vide à la propriété 'Text' de la zone d'édition.

II.C. Solution pas à pas

Cette solution ne rentrera pas dans les détails de la création de l'interface, domaine qui doit maintenant vous être familier et qui ne doit pas poser de problème particulier. Voici les propriétés importantes à fixer pour les composants :

Composant Propriété Valeur
Fiche Name : fmPrinc
Fiche Caption : Première Liste
Liste Name : lbArchives
Zone d'édition Name : edPhrase
Zone d'édition Text : (Vide)
Bouton Ajout Name : btAjout
Bouton Ajout Caption : Ajout
Bouton Effacer Name : btEffacer
Bouton Effacer Caption : Effacer la liste
Bouton Quitter Name : btQuitter
Bouton Quitter Caption : Quitter

Dans la procédure de réponse au clic sur le bouton 'Quitter', on doit faire appel à la méthode 'Close' de la fiche, soit l'unique instruction suivante :

Close;
Dans la procédure de réponse au clic sur le bouton 'Effacer la liste', on doit faire appel à la méthode 'Clear' de la propriété 'Items' du composant 'lbArchives'. Voici donc l'unique instruction à inscrire :

lbArchives.Items.Clear;
Dans la procédure de réponse au clic sur le bouton 'Ajouter', vous devez tout d'abord récupérer la valeur du texte de la zone d'édition, soit 'edPhrase.Text'. Cette valeur sera ensuite transmise en tant que paramètre à la méthode 'Add' de 'Items' de 'lbArchives'. Ensuite, il suffit d'affecter la chaîne vide à 'edPhrase.Text'. Voici une première version, longue, de cette procédure :

procedure TfmPrinc.btAjoutClick(Sender: TObject);
var
  Temp: string;
begin
  Temp := edPhrase.Text;
  lbArchives.Items.Add(Temp);
  edPhrase.Text := '';
end;
et voici la procédure un peu plus courte qui fait la même chose :

procedure TfmPrinc.btAjoutClick(Sender: TObject);
begin
  lbArchives.Items.Add(edPhrase.Text);
  edPhrase.Text := '';
end;

II.D. Téléchargement

Code source du projet : 02_premiere_liste.zip


III. Mini-projet n°3 : Equations du second degré


III.A. Enoncé

Le sujet, loin d'effrayer les matheux, est : Calcul des racines d'un trinôme du second degré. Que les non matheux se rassurent, je vais poser plus clairement le problème et faire tous les rappels mathématiques nécessaires.

Le but de l'exercice est de donner les valeurs de X vérifiant l'équation du second degré :

      a × X2 + b × X + c = 0

où a, b et c sont des constantes réelles, a non nul.

Nous nous placerons au niveau de la classe de première. Je rappelle que l'existence de solutions dépend d'une valeur Delta calculée à partir de a, b et c. Voici la valeur de Delta :

      Delta = b2 - 4 × a × c

Trois cas sont alors possibles suivant le signe de Delta :

  • Si Delta est strictement négatif, il n'y a pas de solution réelle à cette équation.
  • Si Delta est nul, l'équation admet une solution réelle unique dont la valeur est égale à :

          -b / (2 × a)
  • Si enfin Delta est positif, l'équation admet deux solutions réelles distinctes dont les valeurs sont :

Si vous avez suivi la progression normale du guide, vous savez effectuer chacune des opérations nécessaire au calcul de ces solutions (je rappelle cependant que la fonction sqrt permet de calculer une racine carrée).

C'est à vous de déterminer quelles variables et quels types vous allez utiliser. Je ne vous donnerai pas l'interface, mais seulement une idée de ce qu'elle devrait être : elle montrera en haut de la seule fenêtre du logiciel un polynôme avec 3 zones d'édition pour remplir les valeurs des coéfficients a, b et c. Vous pourrez supposer que l'utilisateur donnera des valeurs réelles correctes, pour cette fois. En dessous, un mémo affichera les résultats des calculs lancés par un bouton : valeur de Delta, existence de solutions et valeurs éventuelles de ces solutions. Un autre bouton effacera le contenu des zones d'édition ainsi que du mémo, et un dernier permettra de quitter le logiciel.

Vous veillerez à certains détails, comme à vérifier que a est bien non nul, sans quoi vos calculs risquent de ne pas fonctionner. Pensez également à initialiser vos variables, et à vider le mémo avant d'y écrire du texte. Vous êtes libre quant aux noms donnés à vos composants et quant aux textes affichés. Veillez cependant à créer une interface agréable que vous apprécieriez vous-même.

En option (pour ceux qui connaissent uniquement) : L'équation admet des solutions non réelles si Delta est négatif. Donnez les solutions dans ce cas.


IV. Mini-projet n°5 : Tableaux Associatifs


IV.A. Enoncé

Certains langages de programmation comme PHP proposent un type de données particulièrement intéressant qui n'a aucun équivalent en Pascal Objet. Ce genre de tableau utilise en fait n'importe quel indice pour contenir n'importe quelle donnée. Ainsi, on peut trouver dans un tableau associatif une chaîne indexée par un entier et un enregistrement indexé par une chaîne. La taille de ce type de tableau est en outre variable, et dépend des éléments ajoutés et retirés du tableau associatif.

Ce mini-projet consiste à réaliser une classe permettant l'utilisation d'un tableau associatif simple indexé uniquement par des chaînes de caractères et dont chaque élément est également une chaîne de caractères. Pour ce qui est du stockage d'un nombre quelconque d'éléments, vous avez le droit d'utiliser la classe "TList" qui permet de sauvegarder un nombre variable de pointeurs. L'accès aux éléments du tableau associatif doit se faire via une propriété tableau par défaut, pour un maximum de simplicité. Ainsi, les instructions suivantes, où "TA" est un tableau associatif, se doivent de fonctionner :

TA['prénom'] := 'Jacques';
TA['nom'] := 'Dupont';
TA['age'] := '53';
ShowMessage(TA['prénom'] + ' ' + TA['nom'] + ' a ' + TA['age'] + ' ans.');

IV.B. Etapes de progression

  1. Comme dans tout projet donnant lieu à la création de classes, commencez par réfléchir : quels sont vos objectifs, vos contraintes en termes de programmation, en termes de connaissances ? Quelle est votre marge de manoeuvre ?
  2. Créez un projet vierge et une unité sans fiche. Dans cette dernière, déclarez la classe "TableauAsssociatif" avec deux sections privées et publiques.
  3. Documentez-vous un peu sur TList : regardez l'aide de Delphi sur "TList", sur son constructeur (Create), son destructeur (Destroy), les méthodes "add", "remove" et "pack".
  4. Déclarez un champ (privé) de classe TList. Surchargez le constructeur et le destructeur pour créer et détruire cet objet à la volée.
  5. Un élément sera stocké sous la forme d'un pointeur vers un enregistrement. Déclarez donc un enregistrement et le type pointeur correspondant. L'enregistrement doit contenir deux chaînes : "cle" et "valeur".
  6. Commencez par coder les méthodes les plus simples, celles ne nécessitant pas trop de recherche. Ainsi, il est impératif de pouvoir connaître la taille d'un tableau associatif (son nombre d'éléments). Créez donc une propriété "Count" en lecture seule. Utilisez en lecture une méthode faisant appel à la méthode de même nom de l'instance de "TList".
  7. Les méthodes suivantes sont plus complexes, puisqu'à une clé ne peut correspondre qu'une seule valeur. Il va nous falloir un moyen de savoir si une clé donnée existe déjà dans le tableau. Créez pour cela une méthode privée acceptant une chaîne en paramètre ("cle") et retournant un pointeur vers un élément du type enregistrement créé auparavant. Cette méthode recherchera dans la liste un élément comportant comme clé la chaîne transmise. Si cet élément est trouvé, il est retourné comme résultat. Dans le cas contraire, la méthode retournera nil.
  8. Créez maintenant une méthode d'ajout d'élément à deux paramètres de type chaîne ("cle" et "valeur"). Cette méthode recherche d'abord dans la liste fElems si la clé existe déjà (utilisez la méthode créée juste avant). Dans l'affirmative, sa valeur associée est remplacée par la "valeur" transmise. Dans la négative, un nouvel élément est créé et placé dans la liste interne "fElems".
  9. Le programmeur doit avoir un moyen simple d'accèder à la valeur associée à une clé. Créez donc une méthode obtenirValeur qui à partir d'un paramètre "cle" de type chaîne fourni un résultat de type chaîne. Si la clé existe, la valeur est retournée, sinon on prend comme convention la chaîne vide.
  10. A l'aide des deux méthodes d'ajout (AjoutElement) et de recherche de valeur (obtenirValeur), vous pouvez maintenant déclarer une propriété tableau de type chaîne indexée par une chaîne (l'aviez-vous vue venir ? Si oui bravo, si non remarquez l'ordre dans lequel nous avons procédé : les deux méthodes puis la propriété). Les deux accesseurs sont évidemment les deux méthodes citées précédemment.
  11. Il manque encore la possibilité de supprimer une entrée en spécifiant sa clé. Créez donc une méthode publique "retirerElement" prenant un paramètre chaîne et tentant de retirer l'élément comportant cette chaîne comme clé. Aucune erreur ne sera signalée en cas d'absence de l'élément dans le tableau. Vous pouvez utiliser la méthode "extract" de la classe "TList" pour vous aider.
  12. Il manque une dernière chose pour que la classe soit acceptable : la libération de la mémoire associée aux pointeurs non retirés du tableau lors de sa destruction. En effet, cette mémoire n'est pour l'instant libérée nulle part. Le meilleur endroit pour réaliser cela est lors de la destruction, donc lors de l'appel au destructeur "Destroy". Complètez donc le destructeur surchargé en y incorporant la libération de mémoire.
  13. La toute dernière étape consiste à vérifier que l'extrait de code fourni fonctionne.

IV.C. Solution pas à pas

La classe que nous créons ici va servir de "boite noire" permettant de créer un tableau associatif restreint. L'intérêt de créer une classe est qu'on pourra en déclarer autant d'instance que nous voulons. Ces instances seront indépendantes les unes des autres. Le fait d'avoir des éléments publics et privée ainsi qu'une propriété tableau par défaut donne à la fois accès uniquement aux membres importants de la classe et facilite leur accès.

Cette solution pas à pas reprend les intitulés précédent en les complétant autant que possible pour donner tout le cheminement qui amène à la résolution complète du mini-projet :

  1. Comme dans tout projet donnant lieu à la création de classes, commencez par réfléchir : quels sont vos objectifs, vos contraintes en termes de programmation, en termes de connaissances ? Quelle est votre marge de manoeuvre ?

    L'objectif (mais pas forcément sa réalisation, ce qui n'a rien à voir) est ici très simple : créer une classe implémentant des fonctionnalités. Les contraintes de programmation sont nombreuses : programmer sous Delphi, en utilisant la programmation objet, utiliser une propriété tableau par défaut, utiliser la classe "TList" (sachez lire entre les lignes : ce qui est "possible" est la plupart du temps conseillé et ici un impératif. Vous devrez sans cela réaliser vous-même un TAD liste permettant de stocker les éléments du tableau). Les contraintes en termes de connaissances sont ici le nerf de la guerre car c'est justement pour acquérir des connaissances que vous réalisez ce mini-projet. Vous devrez donc porter toute votre attention sur l'utilisation scrupuleuse des notions concernant la programmation objet. L'utilisation de la classe "TList" est un passage presque incontournable, mais pas l'essentiel : gardez en tête que l'utilisation des objets a été vue dans un chapitre antérieur.
  2. Créez un projet vierge et une unité sans fiche. Dans cette dernière, déclarez la classe "TableauAsssociatif" avec deux sections privées et publiques.

    Il s'agit ici de créer la base de travail sous Delphi. Le projet vous servira à tester directement la classe à créer, et l'unité vierge permet d'écrire du code indépendant de toute fiche, ce qui est impératif puisque nous devons créer une classe et non une application. Voici le code source de l'unité destinée à contenir la classe terminée :
    
    unit tabassoc;
    
    interface
    
    type
      TableauAssociatif = class
      private
    
      public
    
      end;
    
    implementation
    
    end.
    
  3. Documentez-vous un peu sur TList : regardez l'aide de Delphi sur "TList", sur son constructeur (Create), son destructeur (Destroy), les méthodes "add", "remove" et "pack".

    Si ca n'a pas été fait auparavant (et c'est une erreur fréquente de débutant que de se lancer dans l'aventure sans emmener sa bible, à savoir la documentation de Delphi), il est impératif de regarder comment fonctionne la classe "TList". Elle manipuile des pointeurs génériques (Pointer), ce qui va nous donner l'occasion de quelques transtypages. On ajoute un élément via la méthode "Add", on en retire via "Remove". Il est précisé dans l'aide de Delphi que ces deux méthodes ne gèrent pas la méthode associée aux pointeurs : ce sera à vous de le faire.
  4. Déclarez un champ (privé) de classe TList. Surchargez le constructeur et le destructeur pour créer et détruire cet objet à la volée.

    Comme le tableau associatif va stocker ses éléments dans un objet de classe TList, et qu'on ne peut pas utiliser de variable globale ou locale (où pourrions-nous les placer ???), il reste le choix idéal du champ. Ainsi, chaque objet de classe "TableauAssociatif" aura sa propre instance de la classe "TList" dans laquelle les éléments du tableau seront stockés. Voici la déclaration des trois nouveaux membres de "TableauAssociatif" :
    
    TableauAssociatif = class
      private
        fElems: TList;
      public
        constructor Create; virtual;
        destructor Destroy; override;
      end;
    
    et voici le code source du constructeur et du destructeur. Notez l'emploi de "Free" :
    
    constructor TableauAssociatif.Create;
    begin
      fElems := TList.Create;
    end;
    
    destructor TableauAssociatif.Destroy;
    begin
      fElems.Free;
      inherited;
    end;
    
  5. Un élément sera stocké sous la forme d'un pointeur vers un enregistrement. Déclarez donc un enregistrement et le type pointeur correspondant. L'enregistrement doit contenir deux chaînes : "cle" et "valeur".

    Rien de compliqué ici. Le type enregistrement et son pointeur associé vont être les éléments stockés dans l'instance de "TList". C'est une chose à faire avant de commencer à coder les différentes méthodes d'ajout, de retrait, de recherche... Placez simplement les deux déclarations de type avant la déclaration de la classe :
    
    type
      TElementTableauAssociatif = record
        Cle: string;
        Valeur: string;
      end;
      PElementTableauAssociatif = ^TElementTableauAssociatif;
    
  6. Commencez par coder les méthodes les plus simples, celles ne nécessitant pas trop de recherche. Ainsi, il est impératif de pouvoir connaître la taille d'un tableau associatif (son nombre d'éléments). Créez donc une propriété "Count" en lecture seule. Utilisez en lecture une méthode faisant appel à la méthode de même nom de l'instance de "TList".

    Il s'agit ici d'une mise en bouche. Voici la déclaration de cette propriété très simple :
    
    property Count: integer
          read getCount;
    
    déclarez la méthode "getCount" comme suit :
    
        ....
        fElems: TList;
        function getCount: integer;
      public
      ...
    
    Voici le code source de cette méthode :
    
    function TableauAssociatif.getCount: integer;
    begin
      Result := fElems.Count;
    end;
    
  7. Les méthodes suivantes sont plus complexes, puisqu'à une clé ne peut correspondre qu'une seule valeur. Il va nous falloir un moyen de savoir si une clé donnée existe déjà dans le tableau. Créez pour cela une méthode privée acceptant une chaîne en paramètre ("cle") et retournant un pointeur vers un élément du type enregistrement créé auparavant. Cette méthode recherchera dans la liste un élément comportant comme clé la chaîne transmise. Si cet élément est trouvé, il est retourné comme résultat. Dans le cas contraire, la méthode retournera nil.

    Cette méthode va nous rendre beaucoup de services puisque c'est elle qui permettra d'atteindre des éléments déjà placés dans le tableau (mais en interne uniquement : le programmeur utilisant la classe terminée ne devra pas avoir connaissance de cette méthode). Il s'agit ici d'un bête parcours de la liste, avec à chaque fois un transtypage de pointeur puis une comparaison de chaîne. Voici le code source ; étudiez-le à fond :
    
    function TableauAssociatif.rechercheElem(Cle: string): PElementTableauAssociatif;
    var
      indx: integer;
    begin
      indx := 0;
      Result := nil;
      { recherche jusqu'à ce que Result soit modifié (clé trouvée) ou qu'il n'y ait plus aucun
        élément à trouver. Remarquez le choix du while bien préférable à un for. }
      while (indx < fElems.Count) and (Result = nil) do
        begin
          { remarquez que Items est une propriété tableau par défaut et que l'on
            pourrait écrire fElems[indx] à la place de fElems.Items[indx].
            Remarquez le transtypae et la comparaison dans la foulée... }
          if PElementTableauAssociatif(fElems.Items[indx])^.Cle = Cle then
            // ici, on exploite le coté "par défaut" de Items
            Result := fElems[indx];
          inc(indx);
        end;
    end;
    
    N'oubliez pas de déclarer cette méthode dans la section privée de la classe.

  8. Créez maintenant une méthode d'ajout d'élément à deux paramètres de type chaîne ("cle" et "valeur"). Cette méthode recherche d'abord dans la liste fElems si la clé existe déjà (utilisez la méthode créée juste avant). Dans l'affirmative, sa valeur associée est remplacée par la "valeur" transmise. Dans la négative, un nouvel élément est créé et placé dans la liste interne "fElems".

    On rentre ici dans le vif du sujet. Voici la déclaration de la méthode, qui doit être privée (nous verrons plus tard pourquoi) :
    
        ...
        function rechercheElem(Cle: string): PElementTableauAssociatif;
        procedure AjoutElement(Cle: string; Valeur: string);
      public
      ...
    
    La méthode marche en deux temps : le premier recherche une entrée correspondante à la clé. Si cette entrée existe, la valeur est simplement mise à jour dans un second temps. Sinon, on doit réaliser un ajout complet. Voici le code source :
    
    procedure TableauAssociatif.AjoutElement(Cle, Valeur: string);
    var
      Elem: PElementTableauAssociatif;
    begin
      // recherche d'une entrée comportant la clé transmise
      Elem := rechercheElem(Cle);
      // si l'entrée existe
      if Elem <> nil then
        { mise à jour de la valeur (l'entrée sera toujours référencée dans la liste,
          il n'y a donc rien de plus à faire au niveau du pointeur puisque ce n'est
          pas lui qui change mais une des valeurs pointées. }
        Elem^.Valeur := Valeur
      else
        begin
          { Création d'un nouvel élément }
          new(Elem);
          Elem^.Cle := Cle;
          Elem^.Valeur := Valeur;
          { ajout d'une entrée dans la liste des pointeurs. Notez le transtypage implicite
            de Elem en type Pointer. Notez également qu'on ne DOIT PAS appeler Dispose
            sur Elem ici : ce sera fait lorsque l'élément sera plus tard retiré. }
          fElems.Add(Elem);
        end;
    end;
    
  9. Le programmeur doit avoir un moyen simple d'accèder à la valeur associée à une clé. Créez donc une méthode obtenirValeur qui à partir d'un paramètre "cle" de type chaîne fourni un résultat de type chaîne. Si la clé existe, la valeur est retournée, sinon on prend comme convention la chaîne vide.

    Cette méthode est très simple : elle fait appel à la recherche d'un élément avec la clé transmise. Si l'élément est trouvé, sa partie valeur est retournée, sinon, c'est une chaîne vide. Voici le code source :
    
    function TableauAssociatif.obtenirValeur(Cle: string): string;
    var
      Elem: PElementTableauAssociatif;
    begin
      // recherche d'une entrée comportant la clé transmise
      Elem := rechercheElem(Cle);
      if Elem <> nil then
        Result := Elem^.Valeur
      else
        Result := '';
    end;
    
    N'oubliez pas encore une fois de déclarer cette méthode dans la section privée de la classe.

  10. A l'aide des deux méthodes d'ajout (AjoutElement) et de recherche de valeur (obtenirValeur), vous pouvez maintenant déclarer une propriété tableau de type chaîne indexée par une chaîne (l'aviez-vous vue venir ? Si oui bravo, si non remarquez l'ordre dans lequel nous avons procédé : les deux méthodes puis la propriété). Les deux accesseurs sont évidemment les deux méthodes citées précédemment.

    Nous avons créé auparavant exactement ce qu'il faut pour faire fonctionner une propriété tableau de type chaîne indexée par une chaîne. En effet, nous avons, pour la lecture, une fonction à un seul paramètre chaîne qui retourne une chaîne, et pour l'écriture une procédure à deux paramètres de type chaîne. Reste à deviner la fonction de la propriété : c'est tout simplement une propriété "Valeur" (som nom est sans importance) auquel on accède en donnant une chaîne servant de clé entre crochets. Le nom est bien sans importance puisque la propriété va être déclarée "par défaut". Voici la déclaration proprement dite (il n'y a besoin d'aucun code source supplémentaire !) :
    
        ...
        property Count: integer
          read getCount;
        property Valeur[cle: string]: string
          read obtenirValeur
          write AjoutElement; default;
      end;
      ...
    
  11. Il manque encore la possibilité de supprimer une entrée en spécifiant sa clé. Créez donc une méthode publique "retirerElement" prenant un paramètre chaîne et tentant de retirer l'élément comportant cette chaîne comme clé. Aucune erreur ne sera signalée en cas d'absence de l'élément dans le tableau. Vous pouvez utiliser la méthode "extract" de la classe "TList" pour vous aider.

    Pouvoir ajouter des éléments de manière transparente est bien joli, mais il peut être intéressant d'en retirer de manière explicite cette fois. Cette méthode le fera, sans toutefois râler si la clé transmise n'est pas présente dans le tableau. La méthode extract de TList permet d'extraire sans douleur un pointeur d'une liste, il faut donc connaître la valeur du pointeur avant l'appel de cette méthode, et libèrer la mémoire associée à ce pointeur après l'appel, car l'élement doit être supprimé. Voici le code source :
    
    procedure TableauAssociatif.RetirerElement(Cle: string);
    var
      Elem: PElementTableauAssociatif;
    begin
      // recherche d'une entrée comportant la clé transmise
      Elem := rechercheElem(Cle);
      if Elem <> nil then
        begin
          fElems.Extract(Elem);
          Dispose(Elem);
        end;
    end;
    
  12. Il manque une dernière chose pour que la classe soit acceptable : la libération de la mémoire associée aux pointeurs non retirés du tableau lors de sa destruction. En effet, cette mémoire n'est pour l'instant libérée nulle part. Le meilleur endroit pour réaliser cela est lors de la destruction, donc lors de l'appel au destructeur "Destroy". Complètez donc le destructeur surchargé en y incorporant la libération de mémoire.

    La libération en elle-même se fait facilement puisqu'il suffit d'un parcours de la liste et d'un "Dispose" sur chaque pointeur transtypé au préalable (appeler dispose sur un pointeur de type Pointer n'a aucun sens).
    
    destructor TableauAssociatif.Destroy;
    var
      indx: integer;
    begin
      for indx := 0 to fElems.Count - 1 do
        Dispose(PElementTableauAssociatif(fElems[indx]));
      fElems.Free;
      inherited;
    end;
    
  13. La toute dernière étape consiste à vérifier que l'extrait de code fourni fonctionne.

    Pour cela, placez un simple bouton sur l'unique fiche de l'application. Utilisez le code ci-dessous, qui reprend le code fourni, pour complèter la méthode de réponse au clic sur le bouton :
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      TA: TableauAssociatif;
    begin
      TA := TableauAssociatif.Create;
      TA['prénom'] := 'Jacques';
      TA['nom'] := 'Dupont';
      TA['age'] := '53';
      ShowMessage(TA['prénom'] + ' ' + TA['nom'] + ' a ' + TA['age'] + ' ans.');
      TA.Destroy;
    end;
    
Le résultat obtenu lors d'un clic confirme le bon fonctionnement du tableau :

Libre à vous de créer un exemple plus complet, ou d'utiliser cette classe dans vos projets. Ce mini-projet est maintenant terminé.


IV.D. Téléchargement

Code source du projet : 05_tableaux_associatifs.zip



            

Valid XHTML 1.1!Valid CSS!

Copyright © 2000 Frédéric Beaulieu. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.