TP : La POO en pratique avec ZString
Formation
En Ligne
Description
-
Typologie
Formation
-
Méthodologie
En ligne
Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.
Les Avis
Le programme
Vous avez dû vous en rendre compte en lisant le cours de C++ : la programmation orientée objet n'est pas simple à comprendre. Il faut un temps avant d'arriver à imaginer que l'on manipule des "objets". Les objets sont des sortes de boîtes qui contiennent un ensemble de variables et de fonctions qui modifient ces variables.
On peut voir la POO de 2 côtés :
Le côté utilisateur : cela correspond à utiliser les classes en créant des objets. C'est là que la POO se révèle simple et agréable.
Le côté créateur : cela correspond à créer les classes. C'est le plus délicat car il faut bien réfléchir avant de se lancer à coder.
Nous avons déjà vu la POO côté utilisateur avec l'exemple de la classe string fournie avec la bibliothèque standard du C++.
Ce que je vous propose dans ce TP, c'est de voir maintenant le côté créateur en pratique. Nous allons recréer la classe string.
Vous vous demandez peut-être : pourquoi refaire la classe string si elle existe déjà ? Tout simplement parce que c'est un très bon exercice et que ça va beaucoup vous faire progresser.
Comme je sais que la POO n'est pas simple à appréhender quand on débute, je ne vous laisserai pas vous débrouiller tous seuls dans ce TP. Au contraire, je vais vous aider tout au long de la création de notre classe.
Ce qui compte, c'est de lire, comprendre, et essayer de programmer. Si vous n'arrivez pas à programmer comme il faut du premier coup, ce n'est pas grave.
Si vous vous dites "Jamais je n'aurais pu deviner qu'il fallait faire comme ça", ce n'est pas grave non plus. C'est le métier qui rentre. Faites l'effort de comprendre comment j'ai fait, et ce sera déjà très bien :)
Notre objectif est de recréer la classe string de la bibliothèque standard du C++.
C'est une classe qui gère les chaînes de caractères. Nous allons donc beaucoup manipuler les chaînes de caractères dans ce chapitre.
Il est important que vous soyez au point vis à vis des chaînes de caractères. Si vous ne vous souvenez plus qu'une chaîne de caractères se termine par un \0 de fin de chaîne par exemple, je vous invite à relire le chapitre sur les chaînes de caractères vu dans le cours de C.
C'est important, j'insiste. Prenez le temps de revoir ce chapitre si vous en avez besoin.
Vous avez déjà appris à utiliser la classe "string" dans un chapitre précédent de ce cours. Vous avez vu à quel point c'était simple : c'est la classe qui gère tout pour nous. Plus besoin de créer un tableau de la bonne taille, c'est la classe qui s'en occupe. Si la taille de la chaîne change, le tableau de caractères est automatiquement réalloué par la classe.
string maChaine = "Bonjour"; // Crée un tableau de caractères de 8 cases (\0 compris) maChaine = "Bonjour Mateo"; // Change automatiquement la taille du tableau : 14 casesUn objet string n'est au final rien d'autre qu'un objet qui contient un tableau de char (pour stocker la chaîne de caractères). La particularité c'est que c'est la classe qui gère la taille de ce tableau, l'utilisateur n'a pas à s'en soucier.
Quand vous modifiez le contenu de la chaîne, le tableau de char que l'objet maChaine contient est réalloué pour s'adapter à la nouvelle taille. Schématiquement il se passe donc ceci :
D'autre part, on bénéficie des outils puissants du C++ comme la surcharge des opérateurs. Cela nous permet d'écrire des choses intuitives comme :
string message = "Bonjour"; string maChaine = message + " Mateo"; // Vaudra "Bonjour Mateo"C'est ce genre de choses que je veux que l'on arrive à refaire. On va y aller méthodiquement en commençant par écrire les constructeurs, le destructeur, puis on rajoutera des fonctionnalités à la classe en créant de nouvelles méthodes (comme une méthode pour connaître la longueur de la chaîne actuelle). On verra enfin la surcharge des opérateurs en dernier.
Quelques préparatifsBon assez bavardé, on a du pain sur la planche pour arriver à faire tout ça.
Choisir un nomIl va falloir commencer par donner un nom à notre classe qui imite "string". On ne peut pas l'appeler "string" puisqu'il existe déjà une classe de ce nom dans la bibliothèque standard.
Je vous propose de l'appeler ZString, pour "Zéro String" ;)
Par convention, la plupart des programmeurs mettent au moins la première lettre du nom de leurs classes en majuscules. C'est ce que je fais ici. Bon j'ai mis aussi la seconde lettre pour faire joli, j'avoue.
La classe "string" de la bibliothèque standard est un mauvais exemple à ne pas suivre :p
Pour faire ce TP, vous allez devoir créer un nouveau projet. Utilisez l'IDE que vous voulez, moi pour ma part vous savez que j'utilise Code::Blocks ;)
Demandez à créer un nouveau projet console C++.
Ce projet sera constitué de 3 fichiers que vous pouvez déjà créer :
main.cpp : ce fichier contiendra uniquement la fonction main. Dans la fonction main, nous créerons des objets basés sur notre classe ZString pour tester son fonctionnement. C'est le côté utilisateur.
ZString.h : ce fichier contiendra la définition de notre classe ZString avec la liste de ses attributs et les prototypes de ses méthodes. C'est une partie du côté créateur.
ZString.cpp : ce fichier contiendra l'implémentation des méthodes de la classe ZString, c'est-à-dire le "code" à l'intérieur des méthodes. C'est l'autre partie du côté créateur.
Faites attention aux noms des fichiers et en particulier aux majuscules et minuscules. Les fichiers ZString.h et ZString.cpp commencent par 2 lettres majuscules, si vous écrivez "zstring" ou encore "Zstring" ça ne marchera pas et vous aurez des problèmes.
Le code de base de chaque fichierNous allons écrire un peu de code dans chacun de ces fichiers. Juste le strict minimum pour pouvoir commencer.
main.cppCe fichier va contenir la fonction main, ainsi que les includes de iostream (pour faire des cout) et de ZString.h (pour pouvoir utiliser la classe ZString !).
#include <iostream> #include "ZString.h" using namespace std; int main() { ZString chaine; // Crée un objet de type ZString (appel du constructeur par défaut) return 0; }Comme vous pouvez le voir, le main se contentera dans un premier temps de créer un objet de type ZString appelé "chaine". Les objets commencent par une lettre minuscule par convention.
Ce code provoquera l'appel du constructeur par défaut de la classe ZString. Le constructeur est la méthode qui est appelée à chaque fois que l'on crée un nouvel objet, et là on parle de constructeur par défaut car on n'envoie aucun paramètre.
Le main est court mais on le complètera par la suite pour tester notre classe au fur et à mesure qu'on lui rajoutera des possibilités.
ZString.hCe fichier contiendra la définition de la classe ZString. Il fait aussi un include de iostream pour nos besoins futurs (nous aurons besoin de faire des cout dans la classe les premiers temps, ne serait-ce que pour débugger notre classe).
#ifndef DEF_ZSTRING #define DEF_ZSTRING #include <iostream> class ZString { public: private: }; #endifVous noterez que je n'ai pas oublié de faire un #ifndef pour vérifier que le header n'a pas été inclus plusieurs fois. C'est une technique de protection que nous avons vue dans le cours de C et que je vous recommande d'utiliser dans chacun de vos headers.
La classe ZString est pour l'instant vide. Je l'ai séparée en deux : la partie publique et la partie privée.
La partie publique est accessible de l'extérieur de la classe (par l'utilisateur) et la partie privée n'est accessible qu'à l'intérieur de la classe elle-même.
Je vous rappelle que la règle d'or est que tous les attributs d'une classe doivent être privés. C'est le principe d'encapsulation.
Les méthodes, elles, peuvent être soit publiques soit privées selon les cas (elles sont souvent publiques, mais il arrive qu'on ait besoin de créer des méthodes privées).
Ce fichier doit contenir l'implémentation des méthodes de la classe. Pour l'instant, nous n'avons écrit aucune méthode, mais nous allons au moins faire un include de ZString.h, c'est le strict minimum ^^
#include "ZString.h"C'est tout !
De quels attributs notre classe a-t-elle besoin ?Comme vous le savez, une classe est constituée d'attributs et de méthodes.
Les attributs sont des variables. Les méthodes interagissent sur ces variables.
De quels attributs notre classe ZString doit-elle être constituée, vous en avez pas une petite idée hmm ?
Réfléchissez, le but de notre classe est de gérer de manière intelligente une chaîne de caractères. Or, vous savez qu'une chaîne de caractères se présente en mémoire sous la forme d'un tableau de char, terminé par un \0 qui signifie "fin de chaîne" (j'espère que vous savez tout ça, sinon il est grand temps d'aller relire le chapitre sur les chaînes de caractères !).
Nous aurons donc besoin au moins d'un tableau de char en attribut.
En plus de cela, il me semble nécessaire de mettre la taille de la chaîne de caractères (un int) en attribut aussi. Vous me direz : on peut toujours la recalculer (il suffit de compter le nombre de caractères jusqu'à l'\0), mais je pense que c'est une bonne idée de garder la taille de la chaîne en mémoire pour éviter d'avoir à la recalculer à chaque fois.
Nous allons donc modifier notre ZString.h pour y ajouter ces 2 attributs :
#ifndef DEF_ZSTRING #define DEF_ZSTRING #include <iostream> class ZString { public: private: char *m_chaine; // Tableau de caractères (contiendra la chaîne) int m_longueur; // Longueur de la chaîne }; #endifNos attributs commencent toujours par le préfixe "m_". C'est une bonne habitude de programmation que je vous ai enseignée dans les chapitres précédents ;)
Cela nous permettra par la suite de savoir si on est en train de manipuler un attribut de la classe ou une simple variable "locale" à une méthode.
Hé ! Tu avais dit qu'il fallait créer un tableau de char pour gérer la chaîne ! Or là je ne vois qu'un pointeur de char, pourquoi as-tu fait ça ?
J'attendais une question de ce genre :D
Je vais vous répondre par une autre question : quelle taille vous donneriez à ce tableau de char vu que vous ne connaissez pas la taille de la chaîne à stocker ?
Vous pourriez certes me dire "Bah il suffit de créer un très grand tableau de char, par exemple m_chaine[10000]".
Mais ce serait mauvais. Non, ce serait même carrément nul :
Rien ne vous dit que personne ne dépassera jamais les 10 000 caractères.
Ca fait beaucoup de mémoire inutilisée pour rien.
Notre but est justement d'allouer un tableau en mémoire qui fasse pile la taille nécessaire.
Donc comme on ne sait pas la taille que fera le tableau dans la suite du programme, on crée juste un pointeur sur char. C'est nous qui allouerons la taille nécessaire par la suite, dans le constructeur (c'est son rôle, initialiser les attributs).
D'ailleurs en parlant de constructeur, je crois qu'il est temps de s'en occuper maintenant que nous nous sommes mis d'accord sur les attributs que la classe allait manipuler :)
Constructeurs et destructeurNous allons commencer par écrire les méthodes les plus importantes d'une classe : les constructeurs et le destructeur.
J'ai bien dit LES constructeurs, car on peut surcharger le constructeur (en faire plusieurs versions), et LE destructeur, car celui-ci ne peut pas être surchargé.
Je vous propose de créer 3 constructeurs et le destructeur pour commencer :
Le constructeur par défaut (celui qui ne prend pas de paramètre). Si l'utilisateur se sert de ce constructeur, la chaîne sera vide : "".
Un autre constructeur (une surcharge) qui prendra en paramètre une chaîne de caractères pour initialiser la ZString avec une chaîne. La ZString contiendra donc dès le départ la chaîne qu'on lui aura envoyée.
Ce constructeur recevra en paramètre un tableau de char (un char *) correspondant à la chaîne envoyée par l'utilisateur pour initialiser la ZString.Le constructeur de copie : quelle que soit la classe qu'on écrit, il est toujours conseillé d'écrire le constructeur de copie car il est souvent nécessaire. C'est un constructeur qui prend une référence vers un objet du même type (un const ZString &).
Le destructeur pour supprimer le tableau de char m_chaine avant que l'objet ne soit lui-même supprimé. Cela permet d'éviter les fuites de mémoire.
On créera d'autres constructeurs par la suite, mais pour l'instant nous commençons simplement :)
Commençons par ajouter les prototypes de nos méthodes dans ZString.h :
#ifndef DEF_ZSTRING #define DEF_ZSTRING #include <iostream> class ZString { public: ZString(); // Constructeur par défaut (crée une chaîne vide "") ZString(const char *chaine); // Constructeur surchargé (crée la chaîne envoyée) ZString(const ZString &chaine); // Constructeur de copie ~ZString(); // Destructeur (détruit le tableau de char pour libérer la mémoire) private: char *m_chaine; int m_longueur; }; #endifBien, voilà qui est fait.
Il faut maintenant implémenter ces méthodes, rendez-vous dans le fichier ZString.cpp.
On commence par implémenter le constructeur par défaut. Je vous rappelle que le but d'un constructeur est d'initialiser les attributs de la classe. La question est : quelle valeur on va leur mettre ? o_O
Comme on travaille sur le constructeur par défaut, vous pouvez voir que celui-ci ne prend pas de paramètre. C'est le constructeur qui est appelé lorsqu'on crée un nouvel objet de type ZString sans préciser de paramètre.
C'est précisément ce que l'on a fait dans le main.cpp que je vous ai donné plus haut :
ZString chaine; // Appel du constructeur par défaut (aucun paramètre envoyé)Que doit contenir la chaîne lorsqu'on n'envoie rien ?
Bah... rien :p
Si l'utilisateur n'envoie aucun texte, nous n'allons rien mettre dans l'attribut m_chaine. Il est donc inutile d'allouer un tableau de char (y'a rien à stocker !).
Ce qu'on va...
TP : La POO en pratique avec ZString
