Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zéro > Les tutoriels > Officiels > Programmation > Apprenez à programmer en C++ ! > [Théorie] La Programmation Orientée Objet > Les classes (Partie 2/2) > Lecture du tutoriel

Les classes (Partie 2/2)

Avatar
Auteur : M@teo21
Note : 19 / 20 (18 votes)
Visualisations : 124 355


Plus d'informations Plus d'informations
Allez, hop hop hop, on enchaîne ! Pas question de s'endormir, on est en plein dans la POO là ^^

Dans le chapitre précédent, nous avons appris à créer une classe basique, à rendre le code modulaire en POO, et surtout nous avons découvert le principe d'encapsulation (suuuper important l'encapsulation, c'est la base de tout je le rappelle).

Dans cette seconde partie du chapitre, nous allons découvrir comment initialiser nos attributs à l'aide d'un constructeur, un élément indispensable à toute classe qui se respecte. Puisqu'on parlera de constructeur, on parlera aussi de destructeur, ça va de paire vous verrez.
Nous complèterons notre classe Personnage et nous l'associerons avec une nouvelle classe Arme que nous allons créer. Nous découvrirons alors tout le pouvoir qu'il y a de combiner des classes entre elles, et vous devriez normalement commencer à imaginer pas mal de possibilités à partir de là ;)
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Constructeur et destructeur

Reprenons. Nous avons maintenant 3 fichiers :


Pour l'instant, nous avons défini et implémenté pas mal de méthodes. Je voudrais vous parler ici de 2 méthodes particulières que l'on retrouve dans la plupart des classes : le constructeur et le destructeur.


Voyons voir plus en détail comment fonctionnent ces méthodes un peu particulières...


Le constructeur



Comme son nom l'indique, c'est une méthode qui sert à construire l'objet. Dès qu'on crée un objet, le constructeur est automatiquement appelé s'il existe.

Par exemple, lorsqu'on fait dans notre main :

Code : C++
1
Personnage david, goliath;


S'il existe, le constructeur de l'objet david est appelé, et de même pour le constructeur de l'objet goliath.
Mais... comme nous n'avons pas encore défini de constructeur dans la classe Personnage, rien de particulier ne s'est passé. Le constructeur n'est pas obligatoire, mais on a presque toujours besoin d'en créer un, vous allez vite comprendre pourquoi.


Le rôle du constructeur



Si le constructeur est appelé lors de la création de l'objet, ce n'est pas pour faire joli. En fait, le rôle principal du constructeur est d'initialiser les attributs.
En effet, souvenez-vous : nos attributs sont déclarés dans Personnage.h, mais pas initialisés !

Revoici Personnage.h :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <string>
 
class Personnage
{
    public:
 
    void recevoirDegats(int nbDegats);
    void attaquer(Personnage &cible);
    void boirePotionDeVie(int quantitePotion);
    void changerArme(std::string nomNouvelleArme, int degatsNouvelleArme);
    bool estVivant();
 
 
    private:
 
    int m_vie;
    int m_mana;
    std::string m_nomArme;
    int m_degatsArme;
};


Nos attributs m_vie, m_mana, et m_degatsArmes ne sont pas initialisés ! Pourquoi ? Parce qu'on n'a pas le droit d'initialiser les attributs ici. C'est justement dans le constructeur qu'il faut le faire.

En fait, le constructeur est indispensable pour initialiser les attributs qui ne sont pas des objets (type classique : int, double, char...). En effet, ceux-ci ont une valeur inconnue en mémoire (ça peut être 0 comme -3451).
En revanche, les attributs qui sont des objets, comme c'est le cas de m_nomArme ici qui est un string, sont automatiquement initialisés par le langage C++ avec une valeur par défaut.


Créer un constructeur



Le constructeur est une méthode, mais une méthode un peu particulière.
En effet, pour créer un constructeur, il y a 2 règles à respecter :



Si on déclare son prototype dans Personnage.h, ça donne ça :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <string>
 
class Personnage
{
    public:
 
    Personnage(); // Constructeur
    void recevoirDegats(int nbDegats);
    void attaquer(Personnage &cible);
    void boirePotionDeVie(int quantitePotion);
    void changerArme(std::string nomNouvelleArme, int degatsNouvelleArme);
    bool estVivant();
 
 
    private:
 
    int m_vie;
    int m_mana;
    std::string m_nomArme;
    int m_degatsArme;
};


Le constructeur se voit du premier coup d'oeil : déjà parce qu'il n'a aucun type de retour (pas de void ni rien), et ensuite parce qu'il a le même nom que la classe ^^

Et si on en profitait pour implémenter ce constructeur dans Personnage.cpp maintenant ? :)
Voici à quoi pourrait ressembler son implémentation :

Code : C++
1
2
3
4
5
6
7
Personnage::Personnage()
{
    m_vie = 100;
    m_mana = 100;
    m_nomArme = "Epée rouillée";
    m_degatsArme = 10;
}


Vous noterez une fois de plus qu'il n'y a pas de type de retour, pas même void (très important, c'est une erreur que l'on fait souvent ;) ).
J'ai choisi de mettre la vie et la mana à 100, le maximum, ce qui est logique. J'ai mis par défaut une arme appelée "Epée rouillée" qui fait 10 de dégâts à chaque coup.

Et voilà ! Notre classe Personnage a un constructeur qui initialise les attributs, elle est désormais pleinement utilisable :)
Maintenant, à chaque fois que l'on crée un objet de type Personnage, celui-ci est initialisé à 100 points de vie et de mana, avec l'arme "Epée rouillée". Nos deux compères david et goliath commencent donc à égalité lorsqu'ils sont créés dans le main :

Code : C++
1
Personnage david, goliath; // Les constructeurs de david et goliath sont appelés.


Autre façon d'initialiser avec un constructeur : la liste d'initialisation



Le C++ permet d'initialiser les attributs de la classe d'une autre manière (un peu déroutante) appelée liste d'initialisation.
Reprenons le constructeur qu'on vient de créer :

Code : C++
1
2
3
4
5
6
7
Personnage::Personnage()
{
    m_vie = 100;
    m_mana = 100;
    m_nomArme = "Epée rouillée";
    m_degatsArme = 10;
}


Le code que vous allez voir ci-dessous produit le même effet :

Code : C++
1
2
3
4
Personnage::Personnage() : m_vie(100), m_mana(100), m_nomArme("Epée rouillée"), m_degatsArme(10)
{
    // Rien à mettre dans le corps du constructeur, tout a déjà été fait !
}


La nouveauté, c'est qu'on rajoute un symbole deux-points (:) suivi de la liste des attributs que l'on veut initialiser avec la valeur entre parenthèses. Avec ce code, on initialise la vie à 100, la mana à 100, l'attribut m_nomArme à "Epée rouillée", etc.

Cette technique est un peu surprenante, surtout que du coup on n'a plus rien à mettre dans le corps du constructeur entre les accolades, vu que tout a déjà été fait avant ! Elle a toutefois l'avantage d'être "plus propre" et se révèlera pratique dans la suite du chapitre.
On va donc utiliser autant que possible les listes d'initialisation avec les constructeurs, c'est une bonne habitude à prendre.

Le prototype du constructeur (dans le .h) ne change pas. Toute la partie après les deux-points n'apparaît pas dans le prototype.



Surcharger le constructeur



Vous savez qu'en C++ on a le droit de surcharger les fonctions, donc de surcharger les méthodes. Et comme le constructeur est une méthode, on a le droit de le surcharger lui aussi.
Pourquoi je vous en parle ? Ce n'est pas par hasard : en fait, le constructeur est une méthode que l'on a tendance à beaucoup surcharger. Cela permet de créer un objet de plusieurs façons différentes.

Pour l'instant, on a créé un constructeur sans paramètres :

Code : C++
1
Personnage();


On appelle ça : le constructeur par défaut (il fallait bien lui donner un nom le pauvre :p ).


Supposons que l'on souhaite créer un personnage qui ait dès le départ une meilleure arme... comment diable faire ?
C'est là que la surcharge devient utile. On va créer un 2ème constructeur qui prendra en paramètre le nom de l'arme et ses dégâts.

Dans Personnage.h, on va donc rajouter ce prototype :

Code : C++
1
Personnage(std::string nomArme, int degatsArme);


Le préfixe std:: est obligatoire ici comme je vous l'ai dit plus tôt car on n'utilise pas la directive using namespace std; dans le .h (cf chapitre précédent).


L'implémentation dans Personnage.cpp sera la suivante :

Code : C++
1
2
3
4
Personnage::Personnage(string nomArme, int degatsArme) : m_vie(100), m_mana(100), m_nomArme(nomArme), m_degatsArme(degatsArme)
{
 
}


Vous noterez ici tout l'intérêt de mettre le préfixe m_ au début des attributs : comme ça on peut faire la différence dans notre code entre m_nomArme, qui est un attribut, et nomArme, qui est le paramètre envoyé au constructeur.
Ce qu'on fait ici, c'est juste placer dans l'attribut de l'objet le nom de l'arme envoyé en paramètre. On recopie juste la valeur. C'est tout bête, mais il faut le faire, sinon l'objet ne se "souviendra pas" du nom de l'arme qu'il possède.

La vie et la mana, eux, sont toujours fixés à 100 (il faut bien les initialiser), mais l'arme, elle, peut maintenant être indiquée par l'utilisateur lorsqu'il crée l'objet.

Quel utilisateur ? o_O


Souvenez-vous, l'utilisateur c'est celui qui crée et utilise les objets. Le concepteur c'est celui qui crée les classes.
Dans notre cas, la création des objets est faite dans le main. Pour le moment, la création de nos objets ressemble à ça :

Code : C++
1
Personnage david, goliath;


Comme on n'a spécifié aucun paramètre, c'est le constructeur par défaut (celui sans paramètres) qui sera appelé.
Maintenant supposons que l'on veuille donner dès le départ une meilleure arme à Goliath (c'est lui le plus fort après tout :-° ). On va indiquer entre parenthèses le nom et la puissance de cette arme :

Code : C++
1
Personnage david, goliath("Epée aiguisée", 20);


Goliath est équipé de l'épée aiguisée dès sa création. David est équipé de l'arme par défaut, l'épée rouillée.
Comme on n'a spécifié aucun paramètre lors de la création de david, c'est le constructeur par défaut qui sera appelé pour lui. Pour goliath, comme on a spécifié des paramètres, c'est le constructeur correspondant à la signature (string, int) qui sera appelé.

Si vous avez oublié ce qu'est une signature de fonction (ou de méthode, c'est pareil), je vous invite très fortement à relire ce passage du cours, que vous avez normalement dû lire quelques chapitres plus tôt :-°


Exercice : on aurait aussi pu permettre à l'utilisateur de modifier la vie et la mana de départ, mais je ne l'ai pas fait ici. Ce n'est pas compliqué, vous pouvez le faire pour vous entraîner. Ca vous fera un troisième constructeur surchargé ;)


Le destructeur



Le destructeur est une méthode appelée lorsque l'objet est supprimé de la mémoire. Son principal rôle est de désallouer la mémoire (via des delete) qui a été allouée dynamiquement.

Dans le cas de notre classe Personnage, on n'a fait aucune allocation dynamique (il n'y a aucun new). Le destructeur est donc inutile. Cependant, vous en aurez certainement besoin un jour où l'autre, car on est souvent amené à faire des allocations dynamiques.
Tenez, l'objet string par exemple, vous croyez qu'il fonctionne comment ? Il a un destructeur qui lui permet, juste avant la destruction de l'objet, de supprimer le tableau de char qu'il a alloué dynamiquement en mémoire. Il fait donc un delete sur le tableau de char, ce qui permet de garder une mémoire propre et d'éviter les fameuses "fuites de mémoire" :-°


Créer un destructeur



Bien que ce soit inutile dans notre cas (je n'ai pas mis d'allocations dynamiques pour ne pas trop compliquer de suite :p ), je vais vous montrer comment on crée un destructeur. Voici les règles à suivre :



Dans Personnage.h, le prototype du destructeur sera donc :

Code : C++
1
~Personnage();


Dans Personnage.cpp, l'implémentation sera :

Code : C++
1
2
3
4
5
6
7
8
Personnage::~Personnage()
{
    /* Rien à mettre ici car on ne fait pas d'allocation dynamique
    dans la classe Personnage. Le destructeur est donc inutile mais
    je le mets pour montrer à quoi ça ressemble ^^
    En temps normal, un destructeur fait souvent des delete et quelques
    autres vérifications si nécessaire avant la destruction de l'objet */
}


Bon vous l'aurez compris, mon destructeur ne fait rien. C'était même pas le peine de le créer (il n'est pas obligatoire après tout).
Cela vous montre néanmoins la procédure à suivre. Soyez rassurés, nous ferons des allocations dynamiques plus tôt que vous ne le pensez (je sais je suis diabolique :diable: ), et nous aurons alors grand besoin du destructeur pour désallouer la mémoire !

Associer des classes entre elles

La programmation orientée objet devient vraiment intéressante et puissante lorsqu'on se met à combiner plusieurs objets entre eux. Pour l'instant, nous n'avons créé qu'une seule classe : Personnage.
Or en pratique, un programme objet est un programme constitué d'une multitude d'objets différents !

Il n'y a pas de secret, c'est en pratiquant que l'on apprend petit à petit à penser objet.
Ce que nous allons voir par la suite ne sera pas nouveau : vous allez réutiliser tout ce que vous savez déjà sur la création de classes, de manière à améliorer notre petit RPG et à vous entraîner encore plus à manipuler des objets :)


La classe Arme



Ce que je vous propose dans un premier temps, c'est de créer une nouvelle classe Arme. Plutôt que de mettre les informations de l'arme (m_nomArme, m_degatsArme) directement dans le Personnage, nous allons l'équiper d'un objet de type Arme. Le découpage de notre programme sera alors un peu plus dans la logique d'un programme orienté objet.

Souvenez-vous ce que je vous ai dit au début : il y a 100 façons différentes de concevoir un même programme en POO. Tout est dans l'organisation des classes entre elles, comment elles communiquent, etc.
Ce que nous avons fait jusqu'ici était pas mal, mais je veux vous montrer ici qu'on peut faire autrement, un peu plus dans l'esprit objet, donc... mieux ;)


Qui dit nouvelle classe dit 2 nouveaux fichiers :


On n'est pas obligé de procéder ainsi. On pourrait tout mettre dans un seul fichier. On pourrait même mettre plusieurs classes par fichier, rien ne l'interdit en C++. Cependant, pour des raisons d'organisation, je vous recommande de faire comme moi.


Arme.h



Voici ce que je propose de mettre dans Arme.h :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#ifndef DEF_ARME
#define DEF_ARME
 
class Arme
{
    public:
 
    Arme();
    Arme(std::string nom, int degats);
    void changer(std::string nom, int degats);
    void afficher();
 
    private:
 
    std::string m_nom;
    int m_degats;
};
 
#endif


Mis à part les includes qu'il ne faut pas oublier, le reste de la classe est très simple.

On met le nom de l'arme et ses dégâts dans des attributs, et comme ce sont des attributs, on vérifie qu'ils soient bien privés (encapsulation). Vous remarquerez qu'au lieu de m_nomArme et m_degatsArme, j'ai choisi de nommer mes attributs m_nom et m_degats tout simplement. C'est plus logique en effet : vu qu'on est déjà dans l'Arme, ce n'est pas la peine de repréciser dans les attributs qu'il s'agit de l'arme, on le sait déjà, on est dedans ;)

Ensuite, on ajoute un ou deux constructeurs, une méthode pour changer d'arme à tout moment, et une autre allez, soyons fous :p , pour afficher le contenu de l'arme.

Reste à implémenter toutes ces méthodes dans Arme.cpp. Pfeuh, fastoche ! :D

Arme.cpp



Entraînez-vous à écrire Arme.cpp, c'est tout bête, les méthodes font maxi 2 lignes, bref c'est à la portée de tout le monde ^^

Voici mon Arme.cpp pour comparer :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <string>
#include "Arme.h"
 
using namespace std;
 
Arme::Arme() : m_nom("Epée rouillée"), m_degats(10)
{
 
}
 
Arme::Arme(string nom, int degats) : m_nom(nom), m_degats(degats)
{
 
}
 
void Arme::changer(string nom, int degats)
{
    m_nom = nom;
    m_degats = degats;
}
 
void Arme::afficher()
{
    cout << "Arme : " << m_nom << " (Dégâts : " << m_degats << ")" << endl;
}


Bon là je n'ai rien à ajouter vraiment, c'est beaucoup trop simple ;)
N'oubliez quand même pas d'inclure "Arme.h" si vous voulez que ça marche ^^

Et ensuite ?



Bon, notre classe Arme est créée, c'est bon pour ça. Mais maintenant, il va falloir adapter la classe Personnage pour qu'elle utilise non pas m_nomArme et m_degatsArme, mais un objet de type Arme.
Et là... c'est là que ça se complique :D


Adapter la classe Personnage pour utiliser une Arme



La classe Personnage va subir quelques modifications pour utiliser la classe Arme. Restez attentifs, car utiliser un objet DANS un objet, c'est un peu particulier.


Personnage.h



Zou, direction le .h. On commence par virer nos 2 attributs m_nomArme et m_degatsArme qui ne servent plus à rien.

Les méthodes n'ont pas besoin d'être changées. En fait, il ne vaut mieux pas les changer. Pourquoi ? Parce que les méthodes sont déjà potentiellement utilisées par quelqu'un (par exemple dans notre main). Si on les renomme ou si on en supprime, notre programme ne fonctionnera plus.

Ce n'est peut-être pas grave pour un si petit programme, mais dans le cas d'un gros programme si on supprime une méthode, c'est la cata assurée dans le reste du programme. Et je vous parle même pas de ceux qui écrivent des librairies C++ : si d'une version à l'autre des méthodes disparaissent, tous les programmes qui utilisent la librairie ne fonctionneront plus ! :waw:

Je vais peut-être vous surprendre en vous disant ça, mais c'est là tout l'intérêt de la programmation orientée objet, et plus particulièrement de l'encapsulation. On peut changer nos attributs comme on veut, vu qu'ils ne sont pas accessibles de l'extérieur, on ne prend pas le risque que quelqu'un les utilise déjà dans le programme.
En revanche, pour les méthodes, faites plus attention. Vous pouvez ajouter de nouvelles méthodes, modifier l'implémentation des méthodes existantes, mais PAS en supprimer ou en renommer, sinon l'utilisateur risque d'avoir des problèmes.


Cette petite réflexion sur l'encapsulation étant faite (vous en comprendrez tout le sens avec la pratique ;) ), il va falloir ajouter un objet de type Arme à notre Personnage.

Il faut penser à ajouter un include de "Arme.h" si on veut pouvoir utiliser un objet de type Arme.


Voici mon nouveau Personnage.h :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#ifndef DEF_PERSONNAGE
#define DEF_PERSONNAGE
 
#include "Arme.h" // Ne PAS oublier d'inclure Arme.h pour en avoir la définition
 
class Personnage
{
    public:
 
    Personnage();
    Personnage(std::string nomArme, int degatsArme);
    ~Personnage();
    void recevoirDegats(int nbDegats);
    void attaquer(Personnage &cible);
    void boirePotionDeVie(int quantitePotion);
    void changerArme(std::string nomNouvelleArme, int degatsNouvelleArme);
    bool estVivant();
 
 
    private:
 
    int m_vie;
    int m_mana;
    Arme m_arme; // Notre arme est "contenue" dans le Personnage
};
 
#endif



Personnage.cpp



Nous n'avons besoin de changer que les méthodes qui utilisent l'arme pour les adapter.
On commence par les constructeurs :

Code : C++
1
2
3
4
5
6
7
8
9
Personnage::Personnage() : m_vie(100), m_mana(100)
{
 
}
 
Personnage::Personnage(string nomArme, int degatsArme) : m_vie(100), m_mana(100), m_arme(nomArme, degatsArme)
{
 
}


Notre objet m_arme est ici initialisé avec les valeurs reçues en paramètre par Personnage (nomArme, degatsArme). C'est là que la liste d'initialisation devient utile. En effet, on n'aurait pas pu initialiser m_arme sans une liste d'initialisation !

Peut-être ne voyez-vous pas bien pourquoi. Conseil perso : ne vous prenez pas la tête à essayer de comprendre le pourquoi du comment ici, et contentez-vous de toujours utiliser les listes d'initialisation avec vos constructeurs, ça vous évitera bien des problèmes.


Revenons au code.
Dans le premier constructeur, c'est le constructeur par défaut de la classe Arme qui est appelé, tandis que dans le second c'est celui ayant la signature (string, int) qui est appelé.



La méthode recevoirDegats n'a pas besoin de changer.
En revanche, la méthode attaquer est délicate. En effet, on ne peut pas faire :

Code : C++
1
2
3
4
void Personnage::attaquer(Personnage &cible)
{
    cible.recevoirDegats(m_arme.m_degats);
}


Pourquoi est-ce interdit ? Parce que m_degats est un attribut, et que comme tout bon attribut qui se respecte, il est privé ! Diantre... On est en train d'utiliser la classe Arme au sein de la classe Personnage, et comme on est utilisateurs, on ne peut pas accéder aux éléments privés o_O

(La POO, ça peut parfois donner mal à la tête j'avais oublié de vous prévenir :p )

Bon, comment résoudre le problème ? Il n'y a pas 36 solutions. Ca va peut-être vous surprendre, mais on doit créer une méthode pour récupérer la valeur de cet attribut. Cette méthode est appelée accesseur et commence généralement par le préfixe get (récupérer, en anglais). Dans notre cas, notre méthode s'appellerait getDegats.

On conseille généralement de rajouter le mot-clé const aux accesseurs.

Une méthode... constante ? Qu'est-ce que ça signifie ? o_O

Une méthode constante est une méthode qui ne peut pas modifier les attributs de la classe. Cela garantit que la méthode ne fait que "lire" les attributs et qu'elle ne modifie donc pas l'objet. C'est une bonne habitude de programmation de créer des accesseurs const, bien que là encore ça ne soit pas obligatoire.

Voici à quoi ressemble la méthode, avec le mot-clé const :

Code : C++
1
2
3
4
int Arme::getDegats() const
{
    return m_degats;
}


Oubliez pas de mettre à jour Arme.h avec le prototype aussi, qui sera le suivant :

Code : C++
1
int getDegats() const;


Voilà, c'est con comme bonjour, ça peut paraître lourd, et pourtant c'est une sécurité nécessaire. On est parfois obligé de créer une méthode qui fait juste un return pour accéder indirectement à un attribut.

De même, on crée parfois des accesseurs permettant de modifier des attributs. Ces accesseurs sont généralement précédés du préfixe set (mettre, en anglais).
Vous avez peut-être l'impression qu'on viole la règle d'encapsulation ? Eh bien non. Car la méthode nous permet de faire des tests pour vérifier qu'on ne met pas n'importe quoi dans l'attribut, donc ça reste une façon sécurisée de modifier un attribut.


Vous pouvez maintenant retourner dans Personnage.cpp et écrire :

Code : C++
1
2
3
4
void Personnage::attaquer(Personnage &cible)
{
    cible.recevoirDegats(m_arme.getDegats());
}


getDegats renvoie le nombre de dégâts, qu'on envoie à la méthode recevoirDegats de la cible. Pfiou ! ^^


Le reste des méthodes n'a pas besoin de changer, à part changerArme de la classe Personnage :

Code : C++
1
2
3
4
void Personnage::changerArme(string nomNouvelleArme, int degatsNouvelleArme)
{
    m_arme.changer(nomNouvelleArme, degatsNouvelleArme);
}


On appelle la méthode changer de m_arme.
Le Personnage répercute donc la demande de changement d'arme à la méthode changer de son objet m_arme.


Comme vous pouvez le voir, on peut faire communiquer des objets entre eux, à condition d'être bien organisé et de se demander à chaque instant "est-ce que j'ai le droit d'accéder à cet élément ou pas ?".
N'hésitez pas à créer des accesseurs si besoin est, même si ça peut paraître lourd c'est la bonne méthode. En aucun cas vous ne devez mettre un attribut public pour simplifier un problème. Vous perdriez tous les avantages et la sécurité de la POO (et vous n'auriez aucun intérêt à continuer le C++ dans ce cas :p ).

Action !

Nos personnages combattent dans le main, mais... on ne voit rien de ce qui se passe. Il serait bien d'afficher l'état de chacun des personnages pour savoir où ils en sont.

Je vous propose de créer une méthode afficherEtat dans Personnage. Cette méthode sera chargée de faire des cout pour afficher dans la console la vie, la mana et l'arme du personnage.


Prototype et include



On va rajouter le prototype, tout bête, dans le .h :

Code : C++
1
void afficherEtat();


Implémentation



Implémentons ensuite la méthode. C'est simple, on a juste des cout à faire. Grâce aux attributs, on peut indiquer toutes les infos sur le personnage :

Code : C++
1
2
3
4
5
6
void Personnage::afficherEtat()
{
    cout << "Vie : " << m_vie << endl;
    cout << "Mana : " << m_mana << endl;
    m_arme.afficher();
}


Comme vous pouvez le voir, les informations sur l'arme sont demandées à l'objet m_arme via sa méthode afficher(). Encore une fois, les objets communiquent entre eux pour récupérer les informations dont ils ont besoin.


Appel de afficherEtat dans le main



Bien, tout ça c'est bien beau, mais tant qu'on n'appelle pas la méthode, elle ne sert à rien :p
Je vous propose donc de compléter le main et de rajouter à la fin les appels de méthode :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
int main()
{
    // Création des personnages
    Personnage david, goliath("Epée aiguisée", 20);
 
    // Au combat !
    goliath.attaquer(david);
    david.boirePotionDeVie(20);
    goliath.attaquer(david);
    david.attaquer(goliath);
    goliath.changerArme("Double hache tranchante vénéneuse de la mort", 40);
    goliath.attaquer(david);
 
    // Temps mort ! Voyons voir la vie de chacun...
    cout << "David" << endl;
    david.afficherEtat();
    cout << endl << "Goliath" << endl;
    goliath.afficherEtat();
 
    return 0;
}


On peut enfin exécuter le programme et voir quelque chose dans la console :D

Code : Console
David
Vie : 40
Mana : 100
Arme : Epée rouillée (Degats : 10)
 
Goliath
Vie : 90
Mana : 100
Arme : Double hache tranchante vénéneuse de la mort (Degats : 40)


Si vous êtes sous Windows, vous aurez probablement un bug avec les accents dans la console. Ignorez-le, ne vous en préoccupez pas, ce qui nous intéresse c'est le fonctionnement de la POO ici. Et puis de toute manière, dans la prochaine partie du cours on travaillera avec de vraies fenêtres, donc la console c'est temporaire pour nous :-°



Pour que vous puissiez vous faire une bonne idée du projet dans son ensemble, je vous propose de télécharger un fichier zip contenant :


... bref, c'est-à-dire tout le projet tel qu'il est sur mon ordinateur à l'heure actuelle.



Je vous invite à faire des tests pour vous entraîner. Par exemple :


Prenez cet exercice très au sérieux, ceci est peut-être la base de votre futur MMORPG révolutionnaire !

Précision utile : la phrase ci-dessus était une boutade :-°
Ce cours ne vous apprendra pas à créer un MMORPG, vu le travail phénoménal que cela représente. Mieux vaut commencer par se concentrer sur de plus petits projets réalistes, et notre RPG en est un. Ce qui est intéressant ici, c'est de voir comment est conçu un jeu orienté objet (comme c'est le cas de la plupart des jeux aujourd'hui). Si vous avez bien compris le principe, vous devriez commencer à voir des objets dans tous les jeux que vous connaissez ! Par exemple, un bâtiment dans Age of Empires est un objet qui a un niveau de vie, un nom, il peut produire des unités (via une méthode), etc.

Si vous commencez à voir des objets partout, c'est bon signe ! C'est ce que l'on appelle "penser objet" ;)

Méga schéma résumé

Croyez-moi si vous le voulez, mais je vous demande même pas vraiment d'être capable de programmer tout ce qu'on vient de voir en C++. Je veux que vous reteniez le principe, le concept, comment tout cela est agencé.
Et pour retenir, rien de tel qu'un méga schéma bien mastoc, non ? Ouvrez grand vos yeux, je veux que vous soyez capable de le reproduire les yeux fermés la tête en bas avec du poil à gratter dans le dos !

Image utilisateur

Q.C.M.

Quel est le rôle du constructeur ?
Qu'est-ce qu'un constructeur par défaut ?
Qu'est-ce qu'un accesseur ?
Une méthode qui commence par un tilde ~ est un...
Peut-on surcharger un constructeur ?

Statistiques de réponses au QCM


Si vous avez dû retenir une bonne chose de ce second chapitre, c'est cet échange, cette communication constante entre les objets. Et encore ! On n'avait ici que 2 classes, Personnage et Arme. Je vous laisse imaginer dans un vrai projet ce que ça donne ;)
L'intérêt de la POO est là : une organisation précise, chaque objet fait ce qu'il a à faire et délègue certaines parties de son travail à d'autres objets (ici, Personnage déléguait la gestion de l'arme à un objet de type Arme).

On ne peut pas dire "Je fais de la POO" du jour au lendemain, c'est clair. C'est un travail qui demande de l'organisation, de la méthode. Il faut toujours bien réfléchir avant de se lancer dans un projet, si simple soit-il.
Mais réfléchir un peu avant de programmer, est-ce un mal ? Je ne crois pas ;)

Concentrez-vous sur le fichier zip que je vous ai donné et essayez de vous familiariser avec, en faisant par exemple les améliorations proposées. Il ne faut surtout pas que vous soyez perdus.


Dans le chapitre suivant, nous allons aller un peu plus dans le détail des classes en introduisant... les pointeurs ! :D
Les pointeurs en POO méritent en effet à eux seuls au moins un chapitre entier.
Chapitre précédent Sommaire Chapitre suivant
Retour en haut Retour en haut


Créé : le 18/09/2007 à 17:13:58
Modifié : le 23/10/2008 à 14:17:50
Avancement : 100%
Licence : Copie non autorisée

45 commentaires

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | RSS tutoriels | RSS news
Édité par Simple IT SARL : Nous contacter | Notre blog | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 184 Zéros connectés | Requêtes SQL 8 requêtes | Temps de génération de la page : Total (SQL) 0.0699s (0.0554s)