Jeux Libres
       
           

» Les Tutoriels » Apprenez à programmer en C++ ! » L'héritage

Note : Vous vous apprêtez à lire un tutoriel de M@teo21 et Nanoc initialement publié à cette adresse sous la licence Creative Commons BY-NC-SA 2.0.

L'héritage


Nous allons maintenant découvrir une des notions les plus importantes de la POO : l'héritage.
Qu'on se rassure, il n'y aura pas de mort. ;-)

L'héritage est un concept très important qui représente une part non négligeable de l'intérêt de la programmation orientée objet. Bref, cela ne rigole pas. Ce n'est pas le moment de s'endormir au fond, je vous ai à l'il !

Dans ce chapitre nous allons réutiliser notre exemple de la classe Personnage, que nous simplifierons beaucoup pour nous concentrer uniquement sur ce qui est important. En clair, nous ne garderons que le strict minimum, histoire d'avoir un exemple simple mais que vous connaissez déjà.

Allez, bon courage : cette notion n'est pas bien dure à comprendre, elle est juste très riche.





Chapitre précédent     Sommaire     Chapitre suivant


Exemple d'héritage simple


Vous devez vous dire que le terme « Héritage » est étrange dans le langage de la programmation. Mais vous allez le voir, il n'en est rien.
Alors c'est quoi l'héritage ? C'est une technique qui permet de créer une classe à partir d'une autre classe. Elle lui sert de modèle, de base de départ. Cela permet d'éviter d'avoir à réécrire un même code source plusieurs fois.

Comment reconnaître un héritage ?



C'est la question à se poser. Certains ont tellement été traumatisés par l'héritage qu'ils en voient partout, d'autres au contraire (surtout les débutants) se demandent à chaque fois s'il y a un héritage à faire ou pas. Pourtant ce n'est pas « mystique », il est très facile de savoir s'il y a une relation d'héritage entre deux classes.

Comment ? En suivant cette règle très simple : Il y a héritage quand on peut dire : « A est un B ».

Pas de panique, ce ne sont pas des maths.
Et afin de vous persuader, je vais prendre un exemple très simple : on peut dire « Un guerrier est un personnage » ou encore « Un magicien est un personnage ». On peut donc définir un héritage : « la classe Guerrier hérite de Personnage », « la classe Magicien hérite de Personnage ».

Pour être sûr que vous compreniez bien, voici quelques exemples supplémentaires et corrects d'héritage :

  • une voiture est un véhicule (Voiture hérite de Vehicule) ;

  • un bus est un véhicule (Bus hérite de Vehicule) ;

  • un moineau est un oiseau (Moineau hérite d'Oiseau) ;

  • un corbeau est un oiseau (Corbeau hérite d'Oiseau) ;

  • un chirurgien est un docteur (Chirurgien hérite de Docteur) ;

  • un diplodocus est un dinosaure (Diplodocus hérite de Dinosaure) ;

  • etc.

En revanche, vous ne pouvez pas dire « Un dinosaure est un diplodocus », ou encore « Un bus est un oiseau ». Donc, dans ces cas là, on ne peut pas faire d'héritage ou, plus exactement, cela n'aurait aucun sens

J'insiste mais il est très important de respecter cette règle. Vous risquez de vous retrouver confrontés à de gros problèmes de logique dans vos codes si vous ne le faites pas.


Avant de voir comment réaliser un héritage en C++, il faut que je pose l'exemple sur lequel on va travailler.

Notre exemple : la classe Personnage



Petit rappel : cette classe représente un personnage d'un jeu vidéo de type RPG (jeu de rôle). Il n'est pas nécessaire de savoir jouer ou d'avoir joué à un RPG pour suivre mon exemple. J'ai simplement choisi celui-là car il est plus ludique que la plupart des exemples barbants que les profs d'informatique aiment utiliser (Voiture, Bibliotheque, Universite, PompeAEssence ).

Nous allons un peu simplifier notre classe Personnage. Voici ce sur quoi je vous propose de partir :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef DEF_PERSONNAGE
#define DEF_PERSONNAGE
 
#include <iostream>
#include <string>
 
class Personnage
{
   public:
       Personnage();
       void recevoirDegats(int degats);
       void coupDePoing(Personnage &cible) const;
 
   private:
       int m_vie;
       std::string m_nom;
};
 
#endif

Notre Personnage a un nom et une quantité de vie.
On n'a mis qu'un seul constructeur, celui par défaut. Il permet d'initialiser le Personnage avec un nom et lui donne 100 points de vie.
Le Personnage peut recevoir des dégâts, via la méthode recevoirDegats() et en distribuer, via la méthode coupDePoing().

À titre informatif, voici l'implémentation des méthodes dans Personnage.cpp :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "Personnage.h"
 
using namespace std;
 
Personnage::Personnage() : m_vie(100), m_nom("Jack")
{
 
}
 
void Personnage::recevoirDegats(int degats)
{
   m_vie -= degats;
}
 
void Personnage::coupDePoing(Personnage &cible) const
{
   cible.recevoirDegats(10);
}

Rien d'extraordinaire pour le moment.

La classe Guerrier hérite de la classe Personnage



Intéressons-nous maintenant à l'héritage : l'idée est de créer une nouvelle classe qui soit une sous-classe de Personnage. On dit que cette classe hérite de Personnage.

Pour cet exemple, je vais créer une classe Guerrier qui hérite de Personnage. La définition de la classe, dans Guerrier.h, ressemble à ceci :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef DEF_GUERRIER
#define DEF_GUERRIER
 
#include <iostream>
#include <string>
#include "Personnage.h"
//Ne pas oublier d'inclure Personnage.h pour pouvoir en hriter !
 
class Guerrier : public Personnage
//Signifie : crer une classe Guerrier qui hrite de la classe Personnage
{
 
};
 
#endif

Grâce à ce qu'on vient de faire, la classe Guerrier contiendra de base tous les attributs et toutes les méthodes de la classe Personnage.
Dans un tel cas, la classe Personnage est appelée la classe « Mère » et la classe Guerrier la classe « Fille ».

Mais quel intérêt de créer une nouvelle classe si c'est pour qu'elle contienne les mêmes attributs et les mêmes méthodes ?


Attendez, justement ! Le truc, c'est qu'on peut rajouter des attributs et des méthodes spéciales dans la classe Guerrier. Par exemple, on pourrait rajouter une méthode qui ne concerne que les guerriers, du genre frapperCommeUnSourdAvecUnMarteau() (bon ok, c'est un nom de méthode un peu long, je l'avoue, mais l'idée est là).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef DEF_GUERRIER
#define DEF_GUERRIER
 
#include <iostream>
#include <string>
#include "Personnage.h"
 
class Guerrier : public Personnage
{
   public:
       void frapperCommeUnSourdAvecUnMarteau() const;
       //Mthode qui ne concerne que les guerriers
};
 
#endif

Schématiquement, on représente la situation comme à la figure suivante.


Le schéma se lit de bas en haut, c'est-à-dire que « Guerrier hérite de Personnage ».

Guerrier est la classe fille, Personnage est la classe mère. On dit que Guerrier est une « spécialisation » de la classe Personnage. Elle possède toutes les caractéristiques d'un Personnage (de la vie, un nom, elle peut recevoir des dégâts) mais elle possède en plus des caractéristiques propres au Guerrier comme frapperCommeUnSourdAvecUnMarteau().

Retenez bien que, lorsqu'on fait un héritage, on hérite des méthodes et des attributs.
Je n'ai pas représenté les attributs sur le schéma ci-dessus pour éviter de le surcharger mais la vie et le nom du Personnage sont bel et bien hérités, ce qui fait qu'un Guerrier possède aussi de la vie et un nom !


Vous commencez à comprendre le principe ? En C++, quand on a deux classes qui sont liées par la relation « est un », on utilise l'héritage pour mettre en évidence ce lien. Un Guerrier « est un » Personnage amélioré qui possède une méthode supplémentaire.

Ce concept n'a l'air de rien comme cela mais croyez-moi, cela fait la différence ! Vous n'allez pas tarder à voir tout ce que cela a de puissant lorsque vous pratiquerez, plus loin dans le cours.

La classe Magicien hérite aussi de Personnage



Tant qu'il n'y a qu'un seul héritage, l'intérêt semble encore limité. Mais multiplions un peu les héritages et les spécialisations et nous allons vite voir tout l'intérêt de la chose.

Par exemple, si on créait une classe Magicien qui hérite elle aussi de Personnage ? Après tout, un Magicien est un Personnage, donc il peut récupérer les mêmes propriétés de base : de la vie, un nom, donner un coup de poing, etc.
La différence, c'est que le Magicien peut aussi envoyer des sorts magiques, par exemple bouleDeFeu() et bouleDeGlace(). Pour utiliser sa magie, il a une réserve de magie qu'on appelle « Mana » (cela fait un attribut à rajouter). Quand mana tombe à zéro, il ne peut plus lancer de sort.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef DEF_MAGICIEN
#define DEF_MAGICIEN
 
#include <iostream>
#include <string>
#include "Personnage.h"
 
class Magicien : public Personnage
{
   public:
       void bouleDeFeu() const;
       void bouleDeGlace() const;
 
   private:
       int m_mana;
};
 
#endif

Je ne vous donne pas l'implémentation des méthodes (le .cpp) ici, je veux juste que vous compreniez et reteniez le principe (figure suivante).


Notez que, sur le schéma, je n'ai représenté que les méthodes des classes mais les attributs (vie, nom ) sont eux aussi hérités !

Et le plus beau, c'est qu'on peut faire une classe qui hérite d'une classe qui hérite d'une autre classe !
Imaginons qu'il y ait deux types de magiciens : les magiciens blancs, qui sont des gentils qui envoient des sorts de guérison, et les magiciens noirs qui sont des méchants qui utilisent leurs sorts pour tuer des gens (super exemple, j'en suis fier). Figure suivante.


Et cela pourrait continuer longtemps comme cela. Vous verrez dans la prochaine partie sur la bibliothèque C++ Qt qu'il y a souvent cinq ou six héritages qui sont faits à la suite. C'est vous dire si c'est utilisé !


La dérivation de type


Imaginons le code suivant :
1
2
3
4
5
Personnage monPersonnage;
Guerrier monGuerrier;
 
monPersonnage.coupDePoing(monGuerrier);
monGuerrier.coupDePoing(monPersonnage);

Si vous compilez, cela fonctionne. Mais si vous êtes attentifs, vous devriez vous demander pourquoi cela a fonctionné, parce que normalement cela n'aurait pas dû ! Non, vous ne voyez pas ?

Allez, un petit effort. Voici le prototype de coupDePoing (il est le même dans la classe Personnage et dans la classe Guerrier, rappelez-vous) :
1
void coupDePoing(Personnage &cible) const;

Quand on fait monGuerrier.coupDePoing(monPersonnage);, on envoie bien en paramètre un Personnage.
Mais quand on fait monPersonnage.coupDePoing(monGuerrier);, cela marche aussi et le compilateur ne hurle pas à la mort alors que, selon toute logique, il le devrait ! En effet, la méthode coupDePoing attend un Personnage et on lui envoie un Guerrier. Pourquoi diable cela fonctionne-t-il ?

Eh bien c'est justement une propriété très intéressante de l'héritage en C++ que vous venez de découvrir là : on peut substituer un objet de la classe fille à un pointeur ou une référence vers un objet de la classe mère. Ce qui veut dire, dans une autre langue que le chinois, qu'on peut faire cela :
1
2
3
4
Personnage *monPersonnage(0);
Guerrier *monGuerrier = new Guerrier();
 
monPersonnage = monGuerrier; // Mais mais a marche !?

Les deux premières lignes n'ont rien d'extraordinaire : on crée un pointeur Personnage mis à 0 et un pointeur Guerrier qu'on initialise avec l'adresse d'un nouvel objet de type Guerrier.
Par contre, la dernière ligne est assez surprenante. Normalement, on ne devrait pas pouvoir donner à un pointeur de type Personnage un pointeur de type Guerrier. C'est comme mélanger les torchons et les serviettes, cela ne se fait pas.

Alors oui, en temps normal le compilateur n'accepte pas d'échanger des pointeurs (ou des références) de types différents. Mais Personnage et Guerrier ne sont pas n'importe quels types : Guerrier hérite de Personnage. Et la règle à connaître, c'est justement qu'on peut affecter un élément enfant à un élément parent ! En fait c'est logique puisque Guerrier est un Personnage.

Par contre, l'inverse est faux ! On ne peut pas faire monGuerrier = monPersonnage;.
Cela plante et c'est strictement interdit. Attention au sens de l'affectation, donc.


Cela nous permet donc de placer un élément dans un pointeur (ou une référence) de type plus général.
C'est très pratique dans notre cas lorsqu'on passe une cible en paramètre :
1
void coupDePoing(Personnage &cible) const;

Notre méthode coupDePoing est capable de faire mal à n'importe quel Personnage ! Qu'il soit Guerrier, Magicien, MagicienBlanc, MagicienNoir ou autre, c'est un Personnage après tout, donc on peut lui donner un coupDePoing.

Je reconnais que c'est un peu choquant au début mais on se rend compte qu'en réalité, c'est très bien fait. Cela fonctionne, puisque la méthode coupDePoing se contente d'appeler des méthodes de la classe Personnage (recevoirDegats) et que ces méthodes se trouvent forcément dans toutes les classes filles (Guerrier, Magicien).

Si vous ne comprenez pas, relisez-moi et vous devriez saisir pourquoi cela fonctionne.

Eh bien non, moi je ne comprends pas ! Je ne vois pas pourquoi cela marche si on fait objetMere = objetFille;. Là on affecte la fille à la mère or la fille possède des attributs que la mère n'a pas. Cela devrait coincer ! L'inverse ne serait-il pas plus logique ?


Je vous rassure, j'ai mis des mois avant d'arriver à comprendre ce qui se passait vraiment (comment cela, vous n'êtes pas rassurés ?).

Votre erreur est de croire qu'on affecte la fille à la mère or ce n'est pas le cas : on substitue un pointeur (ou une référence). Ce n'est pas du tout pareil. Les objets restent comme ils sont dans la mémoire, on ne fait que diriger le pointeur vers la partie de la fille qui a été héritée. La classe fille est constituée de deux morceaux : les attributs et méthodes héritées de la mère d'une part, et les attributs et méthodes qui lui sont propres d'autre part. En faisant objetMere = objetFille;, on dirige le pointeur objetMere vers les attributs et méthodes hérités uniquement (figure suivante).


Je peux difficilement pousser l'explication plus loin, j'espère que vous allez comprendre. Sinon, pas de panique, j'ai survécu plusieurs mois en programmation C++ sans bien comprendre ce qui se passait et je n'en suis pas mort
(mais c'est mieux si vous comprenez !).

En tout cas, sachez que c'est une technique très utilisée, on s'en sert vraiment souvent en C++ ! Vous découvrirez cela par la pratique, dans la prochaine partie de ce cours, en utilisant Qt.


Héritage et constructeurs


Vous avez peut-être remarqué que je n'ai pas encore parlé des constructeurs dans les classes filles (Guerrier, Magicien ). C'est justement le moment de s'y intéresser.

On sait que Personnage a un constructeur (par défaut) défini comme ceci dans le .h :
1
Personnage();

et son implémentation dans le .cpp :
1
2
3
4
Personnage::Personnage() : m_vie(100), m_nom("Jack")
{
 
}

Comme vous le savez, lorsqu'on crée un objet de type Personnage, le constructeur est appelé avant toute chose.

Mais maintenant, que se passe-t-il lorsqu'on crée par exemple un Magicien qui hérite de Personnage ? Le Magicien a le droit d'avoir un constructeur lui aussi ! Est-ce que cela ne risque pas d'interférer avec le constructeur de Personnage ? Il faut pourtant appeler le constructeur de Personnage si on veut que la vie et le nom soient initialisés !

En fait, les choses se déroule dans l'ordre suivant :

  1. Vous demandez à créer un objet de type Magicien ;

  2. Le compilateur appelle d'abord le constructeur de la classe mère (Personnage) ;

  3. Puis, le compilateur appelle le constructeur de la classe fille (Magicien).

En clair, c'est d'abord le constructeur du « parent » qui est appelé, puis celui du fils, et éventuellement celui du petit-fils (s'il y a un héritage d'héritage, comme c'est le cas avec MagicienBlanc).

Appeler le constructeur de la classe mère



Pour appeler le constructeur de Personnage en premier, il faut y faire appel depuis le constructeur de Magicien. C'est dans un cas comme cela qu'il est indispensable de se servir de la liste d'initialisation (vous savez, tout ce qui suit le symbole deux-points dans l'implémentation).
1
2
3
4
Magicien::Magicien() : Personnage(), m_mana(100)
{
 
}

Le premier élément de la liste d'initialisation indique de faire appel en premier lieu au constructeur de la classe parente Personnage. Puis on réalise les initialisations propres au Magicien (comme l'initialisation du mana à 100).

Lorsqu'on crée un objet de type Magicien, le compilateur appelle le constructeur par défaut de la classe mère (celui qui ne prend pas de paramètre).


Transmission de paramètres



Le gros avantage de cette technique est que l'on peut « transmettre » les paramètres du constructeur de Magicien au constructeur de Personnage. Par exemple, si le constructeur de Personnage prend un nom en paramètre, il faut que le Magicien accepte lui aussi ce paramètre et le fasse passer au constructeur de Personnage :
1
2
3
4
Magicien::Magicien(string nom) : Personnage(nom), m_mana(100)
{
 
}

Bien entendu, si on veut que cela marche, il faut aussi surcharger le constructeur de Personnage pour qu'il accepte un paramètre string !
1
2
3
4
Personnage::Personnage(string nom) : m_vie(100), m_nom(nom)
{
 
}

Et voilà comment on fait « remonter » des paramètres d'un constructeur à un autre pour s'assurer que l'objet se crée correctement.

Schéma résumé



Pour bien mémoriser ce qui se passe, rien de tel qu'un schéma résumant tout ceci, n'est-ce pas (figure suivante) ?


Il faut bien entendu le lire dans l'ordre pour en comprendre le fonctionnement. On commence par demander à créer un Magicien. « Oh mais c'est un objet » se dit le compilateur, « il faut que j'appelle son constructeur ».
Or, le constructeur du Magicien indique qu'il faut d'abord appeler le constructeur de la classe parente Personnage. Le compilateur va donc voir la classe parente et exécute son code. Il revient ensuite au constructeur du Magicien et exécute son code.

Une fois que tout cela est fait, notre objet merlin devient utilisable et on peut enfin faire subir les pires sévices à notre cible.


La portée protected


Il me serait vraiment impossible de vous parler d'héritage sans vous parler de la portée protected.

Actuellement, les portées (ou droits d'accès) que vous connaissez déjà sont :

   public : les éléments qui suivent sont accessibles depuis l'extérieur de la classe ;

   private : les éléments qui suivent ne sont pas accessibles depuis l'extérieur de la classe.

Je vous ai en particulier donné la règle fondamentale du C++, l'encapsulation, qui veut que l'on empêche systématiquement au monde extérieur d'accéder aux attributs de nos classes.

La portée protected est un autre type de droit d'accès que je classerais entre public (le plus permissif) et private (le plus restrictif). Il n'a de sens que pour les classes qui se font hériter (les classes mères) mais on peut l'utiliser sur toutes les classes, même quand il n'y a pas d'héritage.

Voici sa signification : les éléments qui suivent protected ne sont pas accessibles depuis l'extérieur de la classe, sauf si c'est une classe fille.

Cela veut dire, par exemple, que si l'on met des éléments en protected dans la classe Personnage, on y aura accès dans les classes filles Guerrier et Magicien. Avec la portée private, on n'aurait pas pu y accéder !

En pratique, je donne personnellement toujours la portée protected aux attributs de mes classes. Le résultat est comparable à private (donc cela respecte l'encapsulation) sauf qu'au cas où j'hérite un jour de cette classe, j'aurai aussi directement accès aux attributs.
Cela est souvent nécessaire, voire indispensable, sinon on doit utiliser des tonnes d'accesseurs (méthodes getVie(), getMana(), etc.) et cela rend le code bien plus lourd.

1
2
3
4
5
6
7
8
9
10
11
class Personnage
{
  public:
      Personnage();
      Personnage(std::string nom);
      void recevoirDegats(int degats);
      void coupDePoing(Personnage &cible) const;
  protected: //Priv, mais accessible aux lments enfants (Guerrier, Magicien)
     int m_vie;
     std::string m_nom;
};

On peut alors directement manipuler la vie et le nom dans tous les éléments enfants de Personnage, comme Guerrier et Magicien !


Le masquage


Terminons ce chapitre avec une notion qui nous servira dans la suite : le masquage.

Une fonction de la classe mère



Il serait intéressant pour notre petit RPG que nos personnages aient le moyen de se présenter. Comme c'est une action que devraient pouvoir réaliser tous les personnages, quels que soient leur rôle, la fonction sePresenter() va dans la classe Personnage.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Personnage
{
   public:
       Personnage();
       Personnage(std::string nom);
       void recevoirDegats(int degats);
       void coupDePoing(Personnage& cible) const;
       
       void sePresenter() const;
 
   protected:
       int m_vie;
       std::string m_nom;
};

Remarquez le const qui indique que le personnage ne sera pas modifié quand il se présentera. Vous en avez maintenant l'habitude, mais j'aime bien vous rafraîchir la mémoire.


Et dans le fichier .cpp :
1
2
3
4
5
void Personnage::sePresenter() const
{
   cout << "Bonjour, je m'appelle " << m_nom << "." << endl;
   cout << "J'ai encore " << m_vie << " points de vie." << endl;
}

On peut donc écrire un main() comme celui-ci :
1
2
3
4
5
6
int main()
{
  Personnage marcel("Marcel");
  marcel.sePresenter();
  return 0;
}

Ce qui nous donne évidemment le résultat suivant :
Bonjour, je m'appelle Marcel.
J'ai encore 100 points de vie.

La fonction est héritée dans les classes filles



Vous le savez déjà, un Guerrier est un Personnage et, par conséquent, il peut également se présenter.
1
2
3
4
5
int main(){
  Guerrier lancelot("Lancelot du Lac");
  lancelot.sePresenter();
  return 0;
}

Avec pour résultat :
Bonjour, je m'appelle Lancelot du Lac.
J'ai encore 100 points de vie.

Jusque là, rien de bien particulier ni de difficile.

Le masquage



Imaginons maintenant que les guerriers aient une manière différente de se présenter. Ils doivent en plus préciser qu'ils sont guerriers. Nous allons donc écrire une version différente de la fonction sePresenter(), spécialement pour eux :
1
2
3
4
5
6
void Guerrier::sePresenter() const
{
   cout << "Bonjour, je m'appelle " << m_nom << "." << endl;
   cout << "J'ai encore " << m_vie << " points de vie." << endl;
   cout << "Je suis un Guerrier redoutable." << endl;
}

Mais il y aura deux fonctions avec le même nom et les mêmes arguments dans la classe ! C'est interdit !


Vous avez tort et raison. Deux fonctions ne peuvent avoir la même signature (nom et type des arguments). Mais, dans le cadre des classes, c'est différent. La fonction de la classe Guerrier remplace celle héritée de la classe Personnage.

Si l'on exécute le même main() qu'avant, on obtient cette fois le résultat souhaité.
Bonjour, je m'appelle Lancelot du Lac.
J'ai encore 100 points de vie.
Je suis un Guerrier redoutable.

Quand on écrit une fonction qui a le même nom que celle héritée de la classe mère, on parle de masquage. La fonction héritée de Personnage est masquée, cachée.

Pour masquer une fonction, il suffit qu'elle ait le même nom qu'une autre fonction héritée. Le nombre et le type des arguments ne joue aucun rôle.


C'est bien pratique cela ! Quand on fait un héritage, la classe fille reçoit automatiquement toutes les méthodes de la classe mère. Si une de ces méthodes ne nous plaît pas, on la réécrit dans la classe fille et le compilateur saura quelle version appeler. Si c'est un Guerrier, il utilise la « version Guerrier » de sePresenter() et si c'est un Personnage ou un Magicien, il utilise la version de base (figure suivante).


Gardez bien ce schéma en mémoire, il nous sera utile au prochain chapitre.

Économiser du code



Ce qu'on a écrit est bien mais on peut faire encore mieux. Si l'on regarde, la fonction sePresenter() de la classe Guerrier a deux lignes identiques à ce qu'il y a dans la même fonction de la classe Personnage. On pourrait donc économiser des lignes de code en appelant la fonction masquée.

Économiser des lignes de code est souvent une bonne attitude à adopter, le code est ainsi plus facile à maintenir. Et souvenez-vous, être fainéant est une qualité importante pour un programmeur.


On aimerait donc écrire quelque chose du genre :
1
2
3
4
5
6
7
8
void Guerrier::sePresenter() const
{
   appel_a_la_fonction_masquee();
   //Cela afficherait les informations de base
   
   cout << "Je suis un Guerrier redoutable." << endl;
   //Et ensuite les informations spcifiques
}

Il faudrait donc un moyen d'appeler la fonction de la classe mère.

Le démasquage



On aimerait appeler la fonction dont le nom complet est : Personnage::sePresenter(). Essayons donc :
1
2
3
4
5
void Guerrier::sePresenter() const
{
  Personnage::sePresenter();
   cout << "Je suis un Guerrier redoutable." << endl;
}

Et c'est magique, cela donne exactement ce que l'on espérait.
Bonjour, je m'appelle Lancelot du Lac.
J'ai encore 100 points de vie.
Je suis un Guerrier redoutable.

On parle dans ce cas de démasquage, puisqu'on a pu utiliser une fonction qui était masquée.

On a utilisé ici l'opérateur :: appelé opérateur de résolution de portée. Il sert à déterminer quelle fonction (ou variable) utiliser quand il y a ambiguïté ou si il y a plusieurs possibilités.




En résumé



  • L'héritage permet de spécialiser une classe.

  • Lorsqu'une classe hérite d'une autre classe, elle récupère toutes ses propriétés et ses méthodes.

  • Faire un héritage a du sens si on peut dire que l'objet A « est un » objet B. Par exemple, une Voiture « est un » Vehicule.

  • La classe de base est appelée classe mère et la classe qui en hérite est appelée classe fille.

  • Les constructeurs sont appelés dans un ordre bien précis : classe mère, puis classe fille.

  • En plus de public et private, il existe une portée protected. Elle est équivalente à private mais elle est un peu plus ouverte : les classes filles peuvent elles aussi accéder aux éléments.

  • Si une méthode a le même nom dans la classe fille et la classe mère, c'est la méthode la plus spécialisée, celle de la classe fille, qui est appelée.



Chapitre précédent     Sommaire     Chapitre suivant



Distribué et adapté par David
Consulté 2376 fois



Hébergeur du site : David
Version PHP : 5.4.45-0+deb7u2
Uptime : 300 jours 2 heures 59 minutes
Espace libre : 1519 Mo
Dernière sauvegarde : 19/09/2019
Taille de la sauvegarde : 1115 Mo


5507989 pages ont été consultées sur le site !
Dont 4486 pages pendant les 24 dernières heures.

Page générée en 1.401 secondes


Nos sites préférés
- Création d'un jeu de plateforme de A à Z avec SDL
- Zelda ROTH : Jeux amateurs sur le thème de Zelda
- Zeste de Savoir : la connaissance pour tous et sans pépins
- YunoHost : s'héberger soi-même en toute simplicité
- Site de Fvirtman : recueil de projets et de codes en C et C++
- Par ici la sortie : le site des idées de sorties


  © 2005-2019 linor.fr - Toute reproduction totale ou partielle du contenu de ce site est strictement interdite.