Chapitre cinq : l`héritage
Transcription
Chapitre cinq : l`héritage
Chapitre 5 L’héritage Une classe peut être dérivée d’une autre (cette dernière étant alors dite classe de base de la classe dérivée), ou même de plusieurs, en ce sens qu’on ajoute des attributs supplémentaires et/ou des méthodes supplémentaires. Plutôt que de redéfinir entièrement une telle classe, on peut indiquer qu’elle hérite des attributs et des méthodes de la première et ne porter toute son attention qu’aux éléments vraiment nouveaux. C’est la technique de l’héritage, que nous allons étudier dans ce chapitre. On parle d’héritage simple lorsque la classe hérite d’une seule classe et d’héritage multiple lorsqu’elle hérite de plusieurs classes. 39 40 CHAPITRE 5. L’HÉRITAGE 5.1 Héritage simple Commençons par un exemple d’héritage simple. Le problème.- Considérons le problème suivant de programmation. On tient à jour dans un établissement scolaire un répertoire des personnes le fréquentant. On distinguera traditionnellement les élèves, les enseignants et le personnel non enseignant, catégories pour lesquelles les renseignements conservés sont très différents. On considèrera une superclasse (c’est-à-dire une classe dérivée d’aucune autre classe) personne avec les types de renseignements communs à chacune des trois classes dérivées de cette classe eleve, enseignant et personnel (sous-entendu non enseignant). Pour simplifier, nous ne nous occuperons ici que des classes personne et eleve. Les seuls renseignements (et donc attributs) concernant une personne seront son nom (nom) et son numéro de téléphone (tel), nécessaires pour la contacter rapidement en cas de besoin. Les méthodes associées seront l’initialisation (init()) et l’affichage (affiche()) de ces renseignements. Le seul renseignement supplémentaire que nous prendrons en compte ici pour un élève sera sa promotion (promo). Nous aurons besoin de méthodes pour initialiser et afficher ce renseignement. 5.1.1 Définition des classes dérivées Introduction.- Nous allons voir comment définir une classe dérivée d’une autre classe. Commençons cependant par revoir comment nous aurions fait sans notion d’héritage. Définition des classes sans héritage.- Bien entendu nous pouvons faire face au problème choisi à titre d’exemple sans utiliser la notion d’héritage de la façon suivante : class personne { char nomM[30]; char tel[20]; public: void init(char *name, char *phone); void affiche(void); }; class eleve { char nom[30]; char tel[20]; char promo[10]; public: void init(char *name, char *phone); void init_p(char *classe); void affiche(void); void affiche_p(void); }; en laissant le soin au lecteur, pour l’instant, de définir les fonctions membres. Mise en place avec héritage.- La déclaration des premiers attributs et la définition des méthodes communes à chacune des trois classes étant les mêmes, on peut espérer gagner du code (et donc 5.1. HÉRITAGE SIMPLE 41 de la place) et ne s’intéresser qu’aux choses importantes en utilisant la notion d’héritage. On a alors les déclarations suivantes. class personne { protected: char nom[30]; char tel[20]; public: void init(char *name, char *phone); void affiche(void); }; class eleve: public personne { protected: char promo[10]; public: void init_p(void); void affiche_p(void); }; Mises en place.- Lorsqu’on veut qu’une classe B soit dérivée d’une classe A, on l’indique dans l’en-tête de la définition de la classe B de la façon suivante : class B : public A la chaı̂ne de caractères ‘ : public’ indiquant que les membres publics (et protégés) de la classe A sont aussi des membres de la classe B. Membres publics, privés et protégés.- On aura remarqué que l’héritage nous invite à considérer un nouveau genre de membres : les membres protégés. Pour une classe elle-même un membre protégé se comporte de la même façon qu’un membre privé. C’est lors de l’héritage que se fait la différence : – un membre public de la classe de base est un membre public de la classe dérivée ; – un membre protégé de la classe de base est un membre protégé de la classe dérivée ; – un membre privé de la classe de base n’est pas un membre de la classe dérivée ou, plus exactement on ne peut pas y avoir accès dans les fonctions de la classe dérivée. 5.1.2 Utilisation Écrivons un programme complet montrant l’utilisation de la classe dérivée. // herit_s.cpp #include <iostream.h> #include <string.h> class personne { protected: 42 CHAPITRE 5. L’HÉRITAGE char nom[30]; char tel[20]; public: void init(char *name, char *phone); void affiche(void); }; class eleve: public personne { protected: char promo[10]; public: void init_p(char *classe); void affiche_p(void); }; void personne::init(char *name, char *phone) { strcpy(nom, name); strcpy(tel, phone); } void personne::affiche(void) { cout << nom << " : " << tel; } void eleve::init_p(char *classe) { strcpy(promo, classe); } void eleve::affiche_p(void) { cout << promo; } void main(void) { personne p; eleve e; p.init("Pierre", "01 23"); e.init("Paul", "04 52"); e.init_p("FI-2"); cout << "\n"; p.affiche(); cout << "\n"; e.affiche(); cout << " : "; e.affiche_p(); cout << "\n"; } 5.2. HÉRITAGE MULTIPLE 5.2 43 Héritage multiple Avec l’héritage simple, une classe peut hériter d’une autre classe et, éventuellement par transitivité, d’autres classes. Mais une classe ne peut pas hériter de deux classes (ou plus). Ceci peut se faire, par contre, grâce à l’héritage multiple. 5.2.1 Un exemple Considérons, comme d’habitude, un petit exemple simple. Le problème.- Supposons que nous voulions manipuler des points colorés, caractérisés par leurs coordonnées dans le plan et une couleur. Considérons deux classes de base point et couleur à partir desquelles nous dérivons une classe pointcol. Les méthodes seront réduites à l’essentiel, à savoir une fonction d’initialisation et une fonction d’affichage pour chacune des classes. Pour simplifier, les couleurs seront des entiers compris entre 0 et 15. Un programme.- Ceci conduit au programme suivant. // herit_m.cpp #include <iostream.h> class point { protected: float x, y; public: void init(float, float); void affiche(void); }; class couleur { protected: short n; public: void init(short); void affiche(void); }; class pointcol: public point, public couleur { public: void init(float, float, short); void affiche(void); }; void point::init(float abs, float ord) { 44 CHAPITRE 5. L’HÉRITAGE x = abs; y = ord; } void point::affiche(void) { cout << ’(’ << x << ’,’ << y << ’)’; } void couleur::init(short col) { n = col; } void couleur::affiche(void) { cout << n; } void pointcol::init(float abs, float ord, short col) { point::init(abs, ord); couleur::init(col); } void pointcol::affiche(void) { point::affiche(); cout << " de couleur "; couleur::affiche(); } void main(void) { pointcol p; p.init(2.0, 3.0, 4); cout << "\n"; p.point::affiche(); cout << "\n"; p.affiche(); cout << "\n"; } Remarques.- 1o ) Dans notre exemple très simple, on n’ajoute pas d’attributs supplémentaires. Ceci n’est évidemment pas toujours le cas. - 2o ) Par risque de confusion, nous sommes obligés, lors de la définition de la méthode pointcol::init(), de préciser point::init() et couleur::init() en utilisant l’opérateur de portée. On n’est pas obligé de l’utiliser lorsqu’il n’y a pas ambiguı̈té. 5.2. HÉRITAGE MULTIPLE 5.2.2 45 Commentaires Mise ne place.- Les classes de base sont définies comme d’habitude, sans oublier de remplacer certains hh private ii par hh protected ii. La classe dérivée indique les classes de base qu’elle grâce au suffixe de l’en-tête : : public A, public B, public C si elle hérite des classes A, B et C. Résolution.- Dans notre exemple il y a trois méthodes init(), une par classe déclarée. On indique facilement à quelle classe chacune d’entre elles se rapporte grâce à l’opérateur de résolution de portée hh :: ii. Cette façon de fairenous permet de définir la fonction init() de la classe pointcol. L’objet p est déclaré comme appartenant à la classe pointcol. L’appel d’une méthode, par exemple affiche(), fait donc référence à pointcol::affiche() sans avoir à le spécifier, c’est-àdire à utiliser explicitement l’opérateur de résolution de portée. Cependant cet objet p appartient aussi à la classe point, puisque pointcol est une sousclasse de la classe point. On peut donc utiliser la fonction affiche() de cette classe pour p mais il faut alors spécifier explicitement p.point::affiche() pour lever toute ambiguı̈té. Ceci ne serait pas nécessaire pour une fonction dont le nom serait propre à une des classes de base. 46 CHAPITRE 5. L’HÉRITAGE