Big Tuto : Apprenez le C++

Chapitre 25 : L'héritage (1)

 

Tutoriel présenté par : Robert Gillard (Gondulzac)
Date de publication : 2 janvier 2017
Date de révision : -

 

Retrouvez les projets complets de ce chapitre :

  

 

 

  Préliminaires

 

   Après quelques chapitres relatant d'une découverte plus approfondie des conteneurs séquentiels, nous reprenons avec celui-ci, l'étude des classes de C++, notamment en ce qui concerne l'héritage et  la dérivation de classes. wink

   La dérivation des classes va nous permettre d'introduire les notions d'Héritage simple, ainsi que d'Héritage multiple avec le Polymorphisme et l'Héritage virtuel.

   Que ces termes ne vous effrayent pas, notre idée est de vous amener petit-à-petit à maîtriser la notion de dérivation de classes par une approche ludique, qui vous permettra tout au long de cette étude, de pouvoir réaliser l'ébauche de la construction d'un petit jeu se déroulant en tour par tour. angel

   Et nous nous lançons sans plus attendre dans la découverte de l'Héritage simple. smiley



   1.0 – L'Héritage simple

      1.1 - Généralités

 

   Qu'es-ce que l'Héritage ? frown En tout cas, en ce qui nous concerne, rassurez-vous, il ne s'agit pas d'une affaire de ''gros sous''... laugh

   Considérons quelques personnages divers pouvant faire partie de notre petite expérience. Sur notre champ de bataille nous pouvons retrouver des guerriers, des mages, des prêtres, et tout un tas d'autres individus dont le point commun est d'être tous des personnages (Notez que si un personnage est de type ''humain'', nous pourrions approfondir le concept en proposant un type ''non humain'', comprenant des elfes, des gobelins et des orcs, mais nous pourrons aussi admettre que ce sont des personnages). wink

   Si un guerrier est un personnage, un mage ou un prêtre le sont tout autant. Considérons maintenant la création d'une classe ''Personnage'' ; comme toute autre classe que nous avons vue jusqu'à présent, celle-ci aura ses propres constructeurs, ses propres variables et ses propres méthodes membres.

   Puisqu'un guerrier est un Personnage, nous pouvons créer une classe Guerrier et dire que la classe Guerrier ''hérite'' ou ''dérive'' de la classe Personnage, tout simplement. Plus généralement, pour qu'une classe B hérite d'une classe A, il faut que nous puissions dire: ''B est un A'' (un guerrier est un personnage, par exemple). En d'autres termes nous dirons que la Classe Personnage est la classe de base (classe mère) et que la classe Guerrier est la classe fille et qu'une classe fille hérite (dérive) de la classe mère. La classe Guerrier est donc une classe dérivée de la classe Personnage.

   Vous devez savoir que l'héritage ne se limite pas à une classe fille dérivant d'une classe de base.

   Nous verrons en effet qu'un guerrier peut-être soit un homme d'armes, soit un archer ou un cavalier et que d'autre part un mage peut être un mage de guerre ou un archimage. Dans ces cas, les hommes d'armes, les archers, et les cavaliers hériteront de guerrier, de même le mage de guerre et l'archimage hériteront de mage.

   En poussant plus loin la dérivation, nous verrons avec l'héritage multiple que nous pourrons aussi avoir une classe qui dérive de plusieurs autres, par exemple un archer de cavalerie qui dérive de cavalier mais également d'archer.

   Nous allons tout d'abord schématiser le plus simple de ces concepts par un petit dessin. wink

 

 

 

   Dans ce schéma, la classe Personnage est la classe mère ou classe de base. Les classes Guerrier, Mage et Prêtre sont les classes filles ou classes dérivées. Toutes trois héritent de la classe mère Personnage.

   D'une manière générale, la syntaxe de déclaration d'une classe dérivée sera :

class classeDerivee : typeAcces classeDeBase

   soit pour nos classes Personnage et Guerrier, par exemple :

class Guerrier : public Personnage

 

   La classe dérivée est suivie de deux points et du nom de la classe de base précédée du type d'accès. En général, le type d'accès d'une classe de base sera toujours ''public'' mais pourra dans certains cas être déclaré ''private''. Dans nos exemples à suivre, nous utiliserons le type d'accès ''public''.

   Nous allons commencer par présenter, dans un exemple, les déclarations dans un seul fichier, de deux des quatre classes représentées dans notre schéma. Ce programme permettra d'appréhender la manière d'organiser les déclarations d'une classe de base et d'une classe dérivée. cheeky

 

 

   Exemple 147HeritageSimple_0

 

//Héritage simple
//Exemple de déclaration d'une classe de base et d'une classe dérivée
#include <iostream>
#include <conio.h>
 
using namespace std;
using uint = unsigned int;
 
//Déclaration de la classe Personnage
class Personnage
{
public:
 
//Constructeur et destructeur
Personnage();
~Personnage();
 
//Fonctions membres
void recevoirDegats(uint degats);
void frapperAvecLesPoings(Personnage &autre) const;
void avancer();
void reculer();
void crier();
 
protected:
uint qte_Vie;
std::string _nom;
 
};
 
 
//Déclaration de la classe Guerrier
class Guerrier : public Personnage
{
public:
 
//Constructeur et destructeur
Guerrier();
~Guerrier();
 
//Fonction membre
void coup_d_Epee(Personnage &autre) const;
 
 
protected:
 
};
 
 
int main(void)
{
cout << endl;
cout << "Programme d'exemple de declaration d'une classe de base" << endl;
cout << "et d'une classe derivee." << endl;
 
_getch();
return 0;
} 

 

       Voyons de plus près les déclarations de nos deux classes. Dans la classe Personnage nous déclarons un constructeur par défaut et un destructeur. Ce constructeur par défaut sans paramètres initialisera un simple personnage sur lequel nous n'avons encore aucune information. crying

   Suivent plusieurs fonctions qui pourraient être appliquées à un objet Personnage. On pourrait en ajouter d'autres, mais on le fera plus tard quand cela s'avérera nécessaire ; pour l'instant ces fonctions sont suffisantes. wink

   La fonction recevoirDegats() prend une variable de type unsigned int en paramètre, il s'agit des dégâts que pourrait recevoir un objet Personnage, c'est-à-dire du nombre égal de points de vie qui lui seront retirés à l'appel de cette fonction. wink

   La méthode frapperAvecLesPoings() prend en paramètre, quant-à-elle une référence vers un autre personnage. Et pour en terminer avec les déclarations de fonctions de la classe Personnage, suivent quelques autres fonctions auxquelles nous trouverons un peu plus tard un certain intérêt. cheeky

   Vous voyez ensuite que nous avons changé le type d'accès, celui-ci est en effet passé de privé à protégé (protected). Depuis le début de notre étude des classes, nous avons utilisé le type private comme type de protection de nos variables membres mais il se fait que les membres privés des classes de base ne sont pas accessibles par les classes dérivées. Le C++ permet néanmoins un autre type afin de garantir cet accès, il s'agit du type protectedangel

   Classe Guerrier : Notre guerrier étant un homme d'armes, nous déclarons une méthode dénommée coup_d_Epee(), qui prend également une référence vers un autre personnage.

   Mais allez-vous me dire et les autres fonctions !? frown Un guerrier ne peut-il pas également frapper avec ses poings, avancer, reculer ou crier ? surprise Et bien vous venez de résumer tout l'intérêt de l'héritage. La classe Guerrier hérite de la classe Personnage avec le type d'accès public, donc toutes les fonctions et variables membres déclarées dans la classe Personnage sont également accessibles dans la classe Guerrier en plus de la fonction coup_d_Epee(). Mais attention, l'héritage ne se fait que dans le sens de la classe mère vers la classe fille donc la fonction coup_d_Epee(), ni aucune autre nouvelle fonction éventuellement créée dans la classe Guerrier, ne pourrait en aucun cas être utilisée par un objet de notre classe Personnage cheeky

   Ceci étant acquis, nous pouvons maintenant créer un personnage et un guerrier. cool

 

 

      1.2 – Manipulation d'objets de classes de base et de classes dérivées

 

   Projet 148HeritageSimple_1

      Fichier : Personnage.h  

 

//Projet 148HeritageSimple_1
//Héritage simple - utilisation d'objets dérivés(1)
 
//fichier Personnage.h
#ifndef DEF_PERSONNAGE
#define DEF_PERSONNAGE
 
#include <iostream>
using uint = unsigned int;
 
//Déclaration de la classe Personnage
class Personnage
{
public:
 
//Constructeur et destructeur
Personnage() {};
~Personnage() {};
 
//Fonctions
void recevoirDegats(uint degats) {}
void frapperAvecLesPoings(Personnage &autre) const
{
autre.recevoirDegats(2);
}
void avancer() { std::cout << "Le personnage avance d'un pas" << std::endl; }
void reculer() { std::cout << "Le personnage recule d'un pas" << std::endl; }
void crier() const { std::cout << "Whooo" << std::endl; }
 
 
protected:
 
uint _qte_Vie;
std::string _nom;
 
};
 
#endif  

 

   Allez, on avance pas-à-pas. Il n'y a rien de compliqué dans le fichier Personnage.h.

   Nous n'avons pas encore touché au constructeur mais nous implémentons la fonction frapperAvecLesPoings() qui appelle la fonction recevoirDegats(), cette dernière prenant deux unités de vie en paramètre.

   Dans les fonctions avancer() et reculer(), nous n'avons fait qu'afficher le déplacement du personnage qui avance / recule d'un pas.

   Pour terminer, la fonction crier() affiche quant-à-elle le cri du personnage. cheeky

 

   Fichier : Guerrier.h  

 

//Projet 148HeritageSimple_1
//Héritage simple - utilisation d'objets dérivés
 
//fichier Guerrier.h
 
#ifndef DEF_GUERRIER
#define DEF_GUERRIER
 
#include <iostream>
using uint = unsigned int;
 
//Déclaration de la classe Guerrier
class Guerrier : public Personnage
{
public:
 
//Constructeur et destructeur
Guerrier() {};
~Guerrier() {};
 
//Autres fonctions
void coup_d_Epee(Personnage &autre) const
{
autre.recevoirDegats(10);
}
 
protected:
 
};
 
#endif 

 

   Voyons le fichier Guerrier.h maintenant. Nous définissons la fonction coup_d_Epee() qui appelle la fonction recevoirDegats(), cette dernière prenant dix unités de vie en paramètre.

   La partie protected de la classe est restée vide pour l'instant mais nous aurons bientôt besoin de variables spécifiques à un guerrier. wink

   Bien, la classe Guerrier hérite de la classe Personnage avec le type d'accès public. Nous n'avons donc pas besoin de réécrire les fonctions et variables membres de la classe Personnage dans la classe Guerrier, celle-ci peut y avoir accès à partir d'un objet de type Guerrier. Dans notre projet, un guerrier est donc un objet qui possède toutes les caractéristiques d'un personnage en plus que de posséder ses propres fonctions et variables. Notre guerrier est donc un personnage amélioré.

   Et nous allons constater ceci dans notre fichier principal.

 

   Fichier : HeritageSimple_1.cpp 

 

//Projet 148HeritageSimple_1
//Héritage simple - utilisation d'objets dérivés
//fichier HeritageSimple_1.cpp
 
#include <iostream>
#include "Personnage.h"
#include "Guerrier.h"
 
using namespace std;
 
 
int main()
{
cout << endl;
 
//Création d'un objet Personnage et Guerrier
Personnage unPersonnage;
Guerrier unGuerrier;
 
cout << "Un personnage crie : ";
unPersonnage.crier();
 
cout << "Un guerrier crie : ";
unGuerrier.crier();
 
cout << endl;
cout << "Un personnage donne un coup de poing a un guerrier..." << endl;
unPersonnage.frapperAvecLesPoings(unGuerrier);
 
cout << "Le guerrier frappe le personnage avec son epee..." << endl;
unGuerrier.coup_d_Epee(unPersonnage);
 
cin.get();
return 0;
} 

 

     Dans la fonction main() nous commençons par créer un objet de type Personnage et un objet de type Guerrier.

    Nous faisons crier un personnage avec la méthode crier() appartenant à sa classe et nous faisons pousser un cri identique à un guerrier à l'aide de la même méthode. La classe Guerrier ayant hérité des méthodes de la classe mère Personnage, il n'y a donc aucune raison pour que l'objet unGuerrier ne puisse utiliser cette fonction.

  On applique ensuite la méthode frapperAvecLesPoints() sur un objet de type Personnage. Le prototype de la méthode utilisée étant :

void frapperAvecLesPoings(Personnage &autre) const

 

   La méthode appartient à la classe Personnage et une référence à la classe Personnage est passée à la fonction mais puisque Guerrier hérite de Personnage, nous pouvons aussi passer un guerrier en référence.

   Par la suite, on applique la méthode coup_d_Epee() sur un objet de type Guerrier. Ici aussi, un objet de type Personnage est passé en référence d'une méthode appartenant à la classe Guerrier :

void coup_d_Epee(Personnage &autre) const;

 

   Ce qui affiche dans la console :

 

 

 

   Et il n'y a rien d'autre à dire sur cet exemple, j'espère que vous avez bien assimilé la notion d'héritage, on peut donc passer à la suite. wink

   Allez, on peaufine un peu notre projet. Nous allons modifier notre constructeur de la classe Personnage, ajouter quelques méthodes membres d'accès publique et dériver une classe Shaman (qui est une sorte de prêtre orc ou gobelin) et lui attribuer le pouvoir de soigner.

 

   Projet 149HeritageSimple_2

       Fichier : Personnage.h  

 

//Projet 149HeritageSimple_2
//Héritage simple - utilisation d'objets dérivés(2)
//fichier Personnage.h
#ifndef DEF_PERSONNAGE
#define DEF_PERSONNAGE
 
//#include <iostream>
#include <string>
 
using uint = unsigned int;
 
//Déclaration de la classe Personnage
class Personnage
{
public:
 
//Constructeur et destructeur
Personnage() : _race("ELFE"), _nom("Elwyn"), _qte_Vie(100) {};
~Personnage() {};
 
//Méthodes membres d'accès publique
std::string get_Name() const { return _nom; }
uint get_Vie() const { return _qte_Vie; }
std::string get_Race() const { return _race; }
 
//Fonctions
void recevoirDegats(uint degats) { _qte_Vie -= degats;}
void frapperAvecLesPoings(Personnage &autre) const
{
autre.recevoirDegats(2);
}
void avancer() { std::cout << "Le personnage avance d'un pas" << std::endl; }
void reculer() { std::cout << "Le personnage recule d'un pas" << std::endl; }
void crier() const { std::cout << "Whooo" << std::endl; }
 
 
protected:
 
uint _qte_Vie;
std::string _nom;
std::string _race;
 
}; 

 

   On modifie notre constructeur. Celui-ci va initialiser la race, le nom et la quantité de vie de notre personnage.

   Nous ajoutons ensuite deux méthodes membres d'accès publique, celles-ci nous permettront justement de récupérer le nom et la quantité de vie de notre personnage.

   Pour terminer, on ajoute les variables _nom et _race dans la partie protégée de notre classe. wink

 

   Fichier : Shaman.h

 

//Projet 149HeritageSimple_2
//Héritage simple - utilisation d'objets dérivés (2)
//fichier Shaman.h
#ifndef DEF_SHAMAN
#define DEF_SHAMAN
 
using uint = unsigned int;
 
//Déclaration de la classe Shaman
class Shaman : public Personnage
{
public:
 
//Constructeur et destructeur
Shaman() : _titre("Guerisseur Orc") {};
~Shaman() {}
 
//méthodes membres d'accès publique
std::string get_Titre() const { return _titre; }
 
//Autres fonctions
void recevoirSoins(uint soins) {}
void soigner(Shaman &autre) const
{
autre.recevoirSoins(20);
}
 
 
protected:
 
std::string _titre;
 
};
 
#endif 

 

    Nous créons ici une nouvelle classe qui hérite de Personnage, la classe Shaman. Le constructeur initialise le titre de notre shaman qui est ''Guerisseur Orc''.

   Une méthode d'accès publique get_Titre() retournera le titre de notre prêtre-guérisseur. Le shaman peut soigner d'autres objets de sa classe, c'est pourquoi nous passons une référence sur un objet shaman en référence. En fait, il pourrait soigner tout autre Orc de sa tribu mais nous verrons ceci un peu plus tard. wink


   Fichier : HeritageSimple_2.cpp 

 

//Projet 149HeritageSimple_2
//Héritage simple - utilisation d'objets dérivés (2)
//fichier HeritageSimple_2.cpp
 
#include <iostream>
#include <string>
//#include <conio.h>
#include "Personnage.h"
#include "Shaman.h"
 
using namespace std;
 
 
int main()
{
cout << endl;
 
//Création d'un objet Personnage
Personnage unPersonnage;
 
cout << "Creation de l'objet unPersonnage" << endl;
cout << "Ce personnage se nomme " << unPersonnage.get_Name() << endl;
cout << "Il est de race " << unPersonnage.get_Race() << endl;
cout << "Il a " << unPersonnage.get_Vie() << " points de vie" << endl;
 
cout << endl;
 
//Création d'un objet Shaman
Shaman unShaman;
 
cout << "Creation de l'objet unShaman" << endl;
cout << "Ce shaman est un " << unShaman.get_Titre() << endl;
cout << "Il a " << unPersonnage.get_Vie() << " points de vie" << endl;
 
cout << "Le shaman sait se servir de ses poings. Il frappe un objet Personnage" << endl;
unShaman.frapperAvecLesPoings(unPersonnage);
cout << "Le personnage a recu deux points de degats" << endl;
 
cin.get();
//_getch();
return 0;
} 

 

      Dans la fonction main() nous créons un objet Personnage et un objet Shaman. Les méthodes membres d'accès publique nous permettent de récupérer les nom, race et points de vie de notre objet Personnage.

   La méthode membre d'accès publique de la classe Shaman nous permet de récupérer le titre de l'objet unShaman.

   D'autre part, l'objet unShaman a accès à la fonction get_Vie() ainsi qu'à la fonction frapperAvecLesPoings() de la classe Personnage (dérivation d'accès publique). Il ne se gêne donc pas pour se servir de cette dernière... cheeky

   Et ceci nous donne comme résultat dans la console :

 

 

 

   Allez, un petit exercice ! cheeky

   Exercice 1 :

   Ecrivez un programme qui affichera, à l'aide de méthodes membres d'accès publique, la race (ORC), le nom (Zorbar), le titre (Chef shaman) d'un objet unShaman. Vous ferez dériver la classe Shaman d'une classe Personnage et faites en sorte que le shaman frappe le personnage d'un coup d'épée (Oui je sais, c'est pas bien mais on se vengera plus tard... laugh). Pour terminer, affichez le nombre de points de vie du personnage (en supposant qu'il en perde 10) à l'aide d'une méthode membre d'accès publique. Votre personnage devra être de race ELFE.

 

   Bien, penchons-nous maintenant sur le petit schéma suivant si vous le voulez bien. wink

 

     

 

   Nous avons deux classes dérivées (Guerrier et Pretre) qui héritent toutes deux d'une classe de base (Personnage) et une classe Archer qui hérite de la classe Guerrier. Bien que la classe Pretre ne soit pas en relation directe avec la classe Guerrier ni la classe Archer, nous voudrions quand-même qu'un prêtre puisse soigner (à l'aide d'un parchemin par exemple wink) un guerrier et un archer blessés.

   Voyons cela dans l'exemple suivant :

 

   Projet 150HeritageSimple_3

      Fichier : Personnage.h 

 

//Projet 150HeritageSimple_3
//Héritage simple - utilisation d'objets dérivés(3)
//fichier Personnage.h
#ifndef DEF_PERSONNAGE
#define DEF_PERSONNAGE
 
#include <string>
 
using uint = unsigned int;
 
//Déclaration de la classe Personnage
class Personnage
{
public:
 
//Constructeur et destructeur
Personnage() : _qte_Vie(100) {};
 
~Personnage() {};
 
//Fonctions
void recevoirSoins(int soins)
{
_qte_Vie += soins;
 
if (_qte_Vie > 100)
_qte_Vie = 100;
}
 
uint get_Vie() const { return _qte_Vie; }
void recevoirDegats(uint degats) { _qte_Vie -= degats; }
 
void frapperAvecLesPoings(Personnage &autre) const
{
autre.recevoirDegats(2);
}
 
 
protected:
 
uint _qte_Vie;
 
};
 
#endif 

 

   Dans la classe Personnage, nous initialisons la quantité de vie maximale dans le constructeur et nous définissons les fonctions recevoirSoins(), get_Vie(), recevoir_Degats() et frapperAvecLesPoings(). Pour l'instant c'est tout ce qu'un personnage peut faire. indecision


   Fichier : Guerrier.h 

 

//Projet 150HeritageSimple_3
//Héritage simple - utilisation d'objets dérivés(3)
//fichier Guerrier.h
#ifndef DEF_GUERRIER
#define DEF_GUERRIER
 
#include "Personnage.h"
 
using uint = unsigned int;
 
//Déclaration de la classe Guerrier – Hérite de Personnage
class Guerrier : public Personnage
{
public:
 
//Constructeur et destructeur
Guerrier() {};
~Guerrier() {}
 
//Autre fonction
void coup_d_Epee(Guerrier &autre) const
{
autre.recevoirDegats(10);
}
 
protected:
 
};
#endif 

 

   Notre classe Guerrier hérite de Personnage, elle a donc accès aux fonctions get_Vie(), et recevoirDegats(). Notre constructeur est un simple constructeur par défaut.

   Un guerrier pouvant se battre, nous initialisons la fonction coup_d_Epee().


   Fichier : Pretre.h 

 

//Projet 150HeritageSimple_3
//Héritage simple - utilisation d'objets dérivés (3)
//fichier Pretre.h
#ifndef DEF_PRETRE
#define DEF_PRETRE
 
using uint = unsigned int;
 
//Déclaration de la classe Pretre – Hérite de Personnage
class Pretre : public Personnage
{
public:
 
//Constructeur et destructeur
Pretre() {};
~Pretre() {}
 
//Autres fonctions
void recevoirSoins(int soins)
{
_qte_Vie += soins;
 
if (_qte_Vie > 100)
_qte_Vie = 100;
}
 
void soigner(Guerrier &autre) const
{
autre.recevoirSoins(20);
}
 
 
protected:
 
};
 
#endif 

 

     La classe Pretre hérite de la classe Personnage et possède également un constructeur par défaut. Le prêtre peut soigner des guerriers et des objets dérivant de la classe Guerrier mais peut aussi avoir besoin de recevoir des soins en cas de blessures. Nous initialisons ici les méthodes recevoirSoins() et soigner().


   Fichier : Archer.h 

 

//Projet 150HeritageSimple_3
//Héritage simple - utilisation d'objets dérivés(3)
 
//fichier Archer.h
#ifndef DEF_ARCHER
#define DEF_ARCHER
 
using uint = unsigned int;
 
//Déclaration de la classe Archer – Hérite de Guerrier
class Archer : public Guerrier
{
 
public:
 
//Constructeur et destructeur
Archer() {};
~Archer() {}
 
//Autre fonction
void tir_a_l_Arc(Guerrier &autre) const
{
autre.recevoirDegats(7);
}
 
protected:
 
};
 
#endif 

 

     La classe Archer hérite de Guerrier, il n'y a donc pas besoin de réécrire la fonction recevoirSoins() dans cette classe. wink

   Nous définissons par contre la fonction tir_a_l_Arc(), l'arc étant l'arme de prédilection d'un archer. cheeky


   Fichier : HeritageSimple_3.cpp 

 

//Projet 150HeritageSimple_3
//Héritage simple - utilisation d'objets dérivés(3)
//fichier HeritageSimple_3.cpp
 
#include <iostream>
#include <string>
#include "Personnage.h"
#include "Guerrier.h"
#include "Pretre.h"
#include "Archer.h"
 
using namespace std;
 
int main()
{
 
//Création des objet unPersonnage et unGuerrier
Personnage unPersonnage;
Guerrier unGuerrier;
Pretre unPretre;
Archer unArcher;
 
cout << endl;
cout << "Les points de vie d'un guerrier sont de " << unGuerrier.get_Vie() << endl;
cout << "Un personnage donne dix coups de poings au guerrier (- 10 x 2 pdV)" << endl;
 
//Le personnage donne dix coups de poings au guerrier
for (int i(0); i != 10; ++i)
{
 
unPersonnage.frapperAvecLesPoings(unGuerrier);
 
}
 
cout << "Les points de vie du guerrier sont maintenant de ";
cout << unGuerrier.get_Vie() << endl << endl;
 
 
//Le prêtre soigne le guerrier
cout << "Un pretre soigne le guerrier avec un parchemin de guerison (+ 20 pdV)" << endl;
unPretre.soigner(unGuerrier);
cout << "Le guerrier a desormais ";
cout << unGuerrier.get_Vie() << " points de vie" << endl << endl;
 
//L'archer décoche une flèche guerrier
cout << "L'archer decoche une fleche au guerrier (- 7 pdV)" << endl;
unArcher.tir_a_l_Arc(unGuerrier);
cout << "Les points de vie du guerrier sont maintenant de ";
cout << unGuerrier.get_Vie() << endl << endl;
 
//Le guerrier frappe l'archer de son glaive
cout << "Le guerrier frappe l'archer de son glaive (- 10 pdV)" << endl;
unGuerrier.coup_d_Epee(unArcher);
cout << "Les points de vie de l'archer sont maintenant de ";
cout << unArcher.get_Vie() << endl << endl;
 
//Le prêtre soigne les deux antagonistes
cout << "Finalement le pretre soigne les deux antagonistes (+ 20 pdV)" << endl;
unPretre.soigner(unGuerrier);
cout << "Le guerrier a desormais ";
cout << unGuerrier.get_Vie() << " points de vie (Max = 100)" << endl;
unPretre.soigner(unArcher);
cout << "L'archer a desormais ";
cout << unArcher.get_Vie() << " points de vie (Max = 100)" << endl; 
 
 
}

 

     Vu que nous avons implémenté la fonction recevoirSoins() dans la classe Personnage, tous les objets héritant de Personnage pourront recevoir des soins (ici, unGuerrier). De même, unArcher étant un objet dérivé de la classe Guerrier, on peut lui appliquer la fonction recevoirSoins() puisque la classe Guerrier hérite de la classe Personnage.

   Ce qui affiche dans la console : 

 

 

 

 

   1.3 – Appel de constructeurs / destructeurs dans les classes de base et classes dérivées

 

   Dans une classe de base, le constructeur est appelé à la création d'un objet. Reprenons l'exemple 149HeritageSimple_2 où aucun paramètre n'est passé au constructeur de la classe de base (Personnage) ni au constructeur de la classe dérivée (Shaman). Nous allons réécrire ce projet en simplifiant les classes ainsi que la fonction main() mais en y introduisant un signalement d'appel des constructeurs et destructeurs lors de la création et de la destruction d'un objet.

   Voyons cela dans l'exemple suivant :

 

   Projet 151HeritageSimple_4

       Fichier : Personnage.h  

 

//Projet 150HeritageSimple_4
//Héritage simple - Appel de constructeurs et destructeurs
//fichier Personnage.h
#ifndef DEF_PERSONNAGE
#define DEF_PERSONNAGE
 
#include <string>
 
using uint = unsigned int;
 
 
//Déclaration de la classe Personne
class Personnage
{
public:
 
//Constructeur et destructeur
Personnage() : _race("ELFE"), _nom("Elwyn")
{
std::cout << "Constructeur de Personnage..." << std::endl;
};
 
 
~Personnage() { std::cout << "Destructeur de Personnage" << std::endl; };
 
//Methodes membres d'accès publique
std::string get_Name() const { return _nom; }
std::string get_Race() const { return _race; }
 
 
 
protected:
 
std::string _race;
std::string _nom;
 
};
 
#endif 

 

    Dans la classe Personnage on signale l'appel au constructeur et au destructeur à l'aide de la méthode ''cout'' du flux ostream.

   On implémente ensuite deux méthodes membres d'accès publique get_Name() et get_Race() qui retourneront le nom et la race d'un personnage. wink

 

      Fichier : Shaman.h  

 

//Projet 150HeritageSimple_4
//Héritage simple - Appel de constructeurs et destructeurs
//fichier Shaman.h
#ifndef DEF_SHAMAN
#define DEF_SHAMAN
 
using uint = unsigned int;
 
//Déclaration de la classe Shaman
class Shaman : public Personnage
{
public:
 
//Constructeur et destructeur
Shaman() : _race("ORC"), _titre("Guerisseur Orc")
{
std::cout << "Constructeur de Shaman..." << std::endl;
};
 
~Shaman() { std::cout << "Destructeur de Shaman" << std::endl; };
 
//méthode d'accès publique
std::string get_Race() const { return _race; }
std::string get_Titre() const { return _titre; }
 
 
 
protected:
 
std::string _race;
std::string _titre;
 
 
};
 
#endif 

 

   Dans la classe Shaman on signale l'appel au constructeur et au destructeur à l'aide de la méthode ''cout'' du flux ostream.

   On implémente ensuite deux méthodes membres d'accès publique get_Race() et get_Titre() qui retourneront la race et le titre d'un shaman.


      Fichier : HeritageSimple_4.cpp  

 

//Projet 150HeritageSimple_4
//Héritage simple - Appel de constructeurs et destructeurs
//fichier HeritageSimple_2.cpp
 
#include <iostream>
#include <string>
#include "Personnage.h"
#include "Shaman.h"
 
using namespace std;
 
 
int main()
{
 
cout << endl;
//Création d'un objet Personnage
Personnage unPersonnage;
 
cout << "Ce personnage se nomme " << unPersonnage.get_Name() << endl;
cout << "Il est de race " << unPersonnage.get_Race() << endl;
cout << endl;
 
//Création d'un objet Shaman
Shaman unShaman;
 
cout << "Ce shaman est un " << unShaman.get_Race() << endl;
cout << "Son titre est : " << unShaman.get_Titre() << endl;
 
cin.get();
return 0;
 
} 

 

    Voyons d'abord le résultat de notre programme dans la console :

 

 

 

   Le constructeur de Personnage est appelé à la création de l'objet unPersonnage. Dans la classe dérivée, lors de la création de l'objet unShaman, le constructeur de Personnage est appelé avant le constructeur de unShaman. Ceci est logique car les objets créés par la classe dérivée doivent avoir accès aux membres et méthodes (variables) de la classe de base. Retenez donc que, lors de la création d'un objet d'une classe dérivée, le constructeur de la classe de base de cette classe sera toujours appelé avant le constructeur de la classe dérivée. wink

   Nous pouvons maintenant passer à un petit challenge qui fera appel à ce que vous avez vu jusqu'ici dans ce chapitre ainsi que dans le chapitre 17. cool

 

 

   Exercice 2 (Un petit challenge)



   En vous basant sur le projet 106Class_13 du chapitre 17 qui utilise une fonction amie comme ''pont' entre deux classes, écrivez un programme qui fera dériver une classe ''Archer'' et une classe ''Guerrier'' d'une classe ''Personnage''. Vous ferez également dériver la classe ''Guerrier_du_Nord'' de la classe ''Guerrier'' et la classe ''Archer_des_Plaines'' de la classe ''Archer''.

   Dans la fonction main() vous créerez un objet Guerrier et un objet Archer et vous les ferez combattre chacun pendant un tour (l'archer subira -10 pdV et le guerrier -7pdV).

   Par la suite, les fils des deux combattants décideront de faire la paix et échangeront à cet effet leur arme secondaire. surprise

   Dans le fichier ''Pont'', n'implémentez que l'affichage des armes principales et secondaires au départ et l'affichage des armes secondaires après échange. Utilisez une variable temporaire pour effectuer l'échange des armes à l'aide de méthodes membres d'accès publique, ceci afin de ne plus écrire explicitement le nom des armes échangées comme nous l'avions fait dans le projet 106Class_13 du chapitre 17wink

 

 

   Corrigés des exercices du chapitre 24

 

      Exercice 1 (un petit challenge)

 

   Pour cet exercice, on demandait d'écrire un programme qui devait contenir les fonctions Recherche_mot() et Recherche_char().

   Les recherches identiques à celles de l'exemple du projet 141ContSequentiels_10 se porteront sur la chaîne ''Le grand roi Nabuchodonosor, roi de Babylone''.

   Dans les fonctions à écrire vous utiliserez cette fois la fonction STL find() de la classe string. Les positions du mot 'roi' et des caractères 'o' seront placés dans une référence vers deux conteneurs séquentiels autres que vector, deux conteneurs deque par exemple. Le contenu de ceux-ci sera affiché dans la fonction main().

 

   Le code :

 

//Projet Chap24Exercice_1
//Implémentation d'une fonction de recherche de caractères
//et d'une fonction de recherche de sous-chaîne
//Utilisation de conteneurs deque.
#include <iostream>
#include <conio.h>
#include <string>
#include <deque>
 
using namespace std;
using uint = unsigned int;
 
 
// Déclaration des fonctions Recherche_char() et Recherche_mot()
size_t Recherche_char(const string &chaine, const char c, size_t &nb_occurrences, deque<uint> &);
size_t Recherche_mot(const string &chaine, const string &mot, size_t &nb_occurrences, deque<uint> &);
 
 
int main()
{
// Création de deux conteneurs deque unsigned int
deque<uint> uneDeque;
deque<uint> autreDeque;
 
 
//La chaine sur laquelle se porte la recherche
const string chaine{ "Le grand roi Nabuchodonosor, roi de Babylone" };
cout << endl;
cout << "On initialise la chaine :" << endl;
cout << chaine << endl << endl;
 
//Le caractère à rechercher
const char c = 'o';
 
//Le nb d'occurrences de 'o' dans la chaîne
size_t compteur(0);
 
 
Recherche_char(chaine, c, compteur, uneDeque);
 
//**ON PARCOURT LA CHAINE A L'AIDE D'ITERATEURS**
for (auto iter = begin(uneDeque); iter != end(uneDeque); ++iter)
cout << "Un caractere " << c << " se trouve en position " << *iter << endl;
 
cout << "Le caractere " << c << " a ete trouve " << compteur << " fois." << endl << endl;
 
//Recherche de la sous-chaîne 'roi'
const string mot = "roi";
 
//Le nb d'occurrences de 'roi' dans la chaîne
compteur = 0;
 
Recherche_mot(chaine, mot, compteur, autreDeque);
 
//**ON PARCOURT LA CHAINE A L'AIDE DE LA BOUCLE FOR**
//**SIMPLIFIEE DE LA STL
for (auto i : autreDeque)
cout << "La sous-chaine " << mot << " se trouve en position " << i << " dans la chaine" << endl;
 
cout << "La sous-chaine " << mot << " a ete trouvee " << compteur << " fois." << endl;
 
_getch();
return 0;
 
}
 
 
size_t Recherche_char(const string &chaine, const char caract, size_t &nb_occurrences, deque<uint> &myDeque)
{
 
size_t debut = 0;
auto position = chaine.find(caract, debut);
 
while (position != string::npos)
{
nb_occurrences++;
 
//On ajoute la position dans le conteneur myDeque
myDeque.push_back(position);
debut = position + 1;
position = chaine.find(caract, debut);
}
 
return 0;
}
 
 
 
size_t Recherche_mot(const string &chaine, const string &mot, size_t &nb_occurrences, deque<uint> &monAutreDeque)
{
 
size_t debut = 0;
auto position = chaine.find(mot, debut);
 
while (position != string::npos)
{
nb_occurrences++;
 
//On ajoute la position dans le conteneur myAutreDeque
monAutreDeque.push_back(position);
 
debut = position + 1;
position = chaine.find(mot, debut);
}
 
return 0;
} 

 

    Au début de notre programme, nous déclarons une fonction recherche_char() et une fonction recherche_mot().

   Les paramètres passés à la fonction recherche_char() sont une référence vers une chaîne constante, une constante de type char, une référence de type size_t vers le nombre d'occurrences et
une référence vers un conteneur deque de type size_t. La valeur de retour de la fonction est de type size_t.

   Les paramètres passés à la fonction recherche_mot() sont une référence vers une chaîne constante, une seconde référence vers une chaîne constante, une référence de type size_t vers le nombre d'occurrences et une référence vers un conteneur deque de type size_t. La valeur de retour de la fonction est de type size_t.

   Voyons pour commencer l'implémentation de la fonction recherche_char(). wink

   Nous initialisons premièrement à 0 une variable ''debut'' de type size_t. Cette variable, passée comme second paramètre à la fonction find(), indique simplement le numéro d'index de début sur lequel doit s'effectuer la recherche. Et comme cette recherche s'effectue tout naturellement en position 0, cet index est égal à 0.

   La variable position de type auto retourne quant-à-elle la position dans la chaîne où le caractère 'caract' a été trouvé par la fonction find().

   La variable ''nb_occurrences'' nous donne le nombre de fois que le caractère 'caract' a été trouvé.

   A chacune des occurrences, la position du caractère 'o' est placée dans le conteneur deque 'myDeque' et la recherche s'achève dès que la valeur prise par la variable position est égale à string::npos (voir chapitre 24, § 1.2 – Opérations de recherche sur les chaînes)

   En ce qui concerne l'implémentation de la fonction recherche_mot(), le mode de raisonnement est similaire à celui de la fonction recherche_char(). Il suffit de se rappeler que c'est un mot (donc un type chaîne) que l'on recherche ici, et non un caractère. wink

   Dans la fonction main(), nous initialisons deux conteneurs vides de type deque ainsi que la chaîne sur laquelle se portera la recherche. On appelle ensuite la fonction recherche_char() qui stockera dans le premier conteneur les positions du caractère 'o' dans la chaîne et on lit les valeurs contenues dans le conteneur à l'aide d'un itérateur positionné sur le début de celui-ci.

   La fonction recherche_mot() est, quant-à-elle, appelée pour stocker dans le second conteneur, la position du premier caractère de la sous-chaîne recherchée. On lit ensuite les valeurs contenues dans le conteneur à l'aide de la boucle for simplifiée de la STL.

   Et le résultat dans la console est identique à celui du projet 141ContSequentiels_10 du chapitre 24wink

 


   Exercice 2 (Un second challenge)

 

   Pour cet exercice on demandait :

   Ecrivez un programme en reprenant les 9 objets de type ''Arme'' de la fonction main() du projet 138ContSequentiels_7 du chapitre 23. Après avoir créé les 9 objets en fonction du prix croissant, vous créerez une priority_queue dans laquelle les objets seront stockés dans un ordre quelconque.

   Pour terminer, vous afficherez tous les éléments de la file selon une priorité désirée du plus cher vers le moins cher.

   Vous utiliserez le foncteur de comparaison de l'exemple 146ContAdaptatifs_4 et vous devrez ajouter 2 fonctions au programme car ce sont des objets que vous allez afficher cette fois, pas des chaînes.

 

   Le code :

 

//Projet Chap24Exercice_2
//Organiser une priorité d'objets dans une priority_queue
#include <iostream>
#include <queue>
#include <string>
#include <conio.h>
 
using namespace std;
using uint = unsigned int;
 
// Type des données stockées dans la file
struct Arme
{
//Constructeur
Arme(int priority, string nom, string type, uint degats, uint etat, double poids,
 
double prix) : _priority(priority), _nom(nom), _type(type), _degats(degats), _etat(etat), _poids(poids), _prix(prix) {}
 
//Variables membres
string _nom; // Nom
int _priority; // Priorité
string _type; // Type
uint _degats; // Dégats
uint _etat; // Etat
double _poids; // Poids
double _prix; // Prix
 
};
 
 
//Fonction operator<<()
std::ostream &operator<<(std::ostream &out, Arme &rhs)
{
//out << rhs._priority; //Pas nécessaire d'afficher le no d'ordre dans la file
out << rhs._nom;
out << rhs._type;
out << rhs._etat;
out << rhs._degats;
out << rhs._poids;
out << rhs._prix;
return out;
}
 
 
//Fonction operator<()
 
bool operator<(const Arme &lhs, const Arme &rhs)
{
return (lhs._prix < rhs._prix);
}
 
 
// Foncteur de comparaison selon les priorités
class Comparaison
{
public:
 
bool operator() (const Arme &arme1, const Arme &arme2)
{
return arme1._priority < arme2._priority;
}
};
 
 
 
int main(void)
{
 
//Création de 9 objets de type "Arme" en fonction du prix croissant
Arme arme_1(1, "Coutelas en fer peu solide", "Lame courte une main", 1, 100, 0.5, 75.0);
Arme arme_2(2, "Epee courte en fer", "Lame courte une main", 2, 300, 2.5, 140.0);
Arme arme_3(3, "Dague furtive de l'Assassin", "Lame courte une main", 3, 300, 0.75, 150.0);
Arme arme_4(4, "Arc de misere", "Arc court leger", 3, 600, 1.5, 155.0);
Arme arme_5(5, "Epee longue d'acier du heros Rabidja", "Lame longue une main", 4, 600, 3.5, 175.0);
Arme arme_6(6, "Arc de la fureur de Myin", "Arc long composite", 5, 900, 2.5, 220);
Arme arme_7(7, "Claymore d'acier", "Lame longue deux mains", 5, 1100, 5.5, 280.0);
Arme arme_8(8, "Lance cruelle perceuse d'ames", "Lance une main", 7, 1500, 4.5, 410.0);
Arme arme_9(9, "Terrible hache de plaie", "Hache lourde deux mains", 7, 1600, 6.0, 450.0);
 
//création d'une priority_queue à partir des objets
priority_queue<Arme, vector<Arme>, Comparaison> uneArme;
 
//On remplit les objets uneArme dans un ordre quelconque
uneArme.push(arme_3);
uneArme.push(arme_6);
uneArme.push(arme_5);
uneArme.push(arme_2);
uneArme.push(arme_1);
uneArme.push(arme_9);
uneArme.push(arme_8);
uneArme.push(arme_7);
uneArme.push(arme_4);
 
 
// On récupère les éléments de la file
std::cout << endl;
 
while (!uneArme.empty())
{
std::cout << uneArme.top() << endl;
uneArme.pop();
}
 
_getch();
return 0;
 
} 

 

   Au début de notre programme nous créons une structure de type Arme. Le constructeur de Arme est initialisé au sein de la structure et prend les éléments suivants en paramètres : priorité, nom, type, dégâts, état, poids et prix.

   Nous devons afficher les objets créés donc nous avons besoin de surcharger l'opérateur ''<< ''.

   A cet effet nous définissons la fonction operator<<() qui affichera tous les éléments contenus dans chaque objet.

   Les objets devant être comparés deux par deux, nous devons également surcharger l'opérateur ''<'', ce que nous faisons en implémentant la fonction operator<() retournant un booléen. Etant donné que nous avons demandé l'affichage final du plus cher vers le moins cher, c'est bien l'opérateur ''<'' qui doit être surchargé, en effet, l'objet de plus petit prix doit être entré en premier dans la file. wink

   Remarquez qu'il nous suffit de retourner une seule valeur (et n'importe laquelle) de la fonction operator<(). Nous avons choisi le prix mais nous aurions pu prendre n'importe quel autre paramètre passé au constructeur.

   A la suite, nous définissons notre foncteur de comparaison (voir chapitre 24 – Projet 146ContAdaptatifs_4), qui redéfinit l'opérateur ''( )'' et retourne le plus petit des deux éléments comparés.

   Dans la fonction main() nous créons nos 9 objets en fonction du prix croissant et nous créons une priority_queue à partir de ces objets en n'oubliant pas d'y inclure le foncteur de comparaison:  

 

priority_queue<Arme, vector<Arme>, Comparaison> uneArme; 

 

   On remplit ensuite les objets uneArme dans l'adaptateur et ce, dans un ordre quelconque pour ensuite les récupérer (std::cout << uneArme.top()) déjà triés dans l'ordre que nous avons choisi
au départ (prix décroissants).

   Ce qui affiche dans la console :

 

 

   Et dans le dernier champ de chaque objet, nous voyons que nos armes sont bien affichées de la plus chère à la moins chère.

 

   Terrible hache de plaie                               450
   Lance cruelle perceuse d'âmes                 410
   Claymore d'acier                                       280
   Arc de la fureur de Myin                            220
   Epee longue d'acier du héros Rabidja      175
   Arc de misère                                            155
   Dague furtive de l'Assassin                      150
   Epee courte en fer                                    140
   Coutelas en fer peu solide                         75

 

 

   Voilà, c'est tout en ce qui concerne ce chapitre. cool

   Les corrigés des exercices de ce chapitre seront présentés à la fin du chapitre 26.

      @ bientôt pour le chapitre 26 – L'héritage (2).                                                                     

            Gondulzak.  angel
 
 
 

Connexion

CoalaWeb Traffic

Today90
Yesterday282
This week884
This month3177
Total1742384

19/04/24