vendredi 11 janvier 2013

C++, l'objet

POO en C++

Incontournable, et contourné depuis longtemps, je m'en vais me taper cet énorme chapitre sur l'objet.
J'ai en fait déjà un peu étudié l'objet, donc je ne suis pas hyper surprise de ce que j'y trouve.

la POO, c'est ce qui permet de manipuler des "objets" sans savoir ce qu'il y a dedans, comment ça fonctionne en interne. C'est donc génial ^^

On va donc étudier pour commencer le premier objet qu'on manipule en c++, l'objet string

C'est pas pour rien qu'on fait un include string au début d'un fichier c++, c'est que le type string n'existe pas, c'est jamais qu'une chaîne de caractères gérés en tableau... Et à ce sujet, le tuto que je suis mentionne l'existence du manuel d'utilisation, mais n'en dit pas plus. Moi, je suis allée fouiner, et voilà ce qu'on trouve :
http://www.cplusplus.com/reference/string/string/
la liste des classes accessibles sur l'objet string, et si elles sont publiques. (pas trouvé une seule privée, d'ailleurs... Je n'ai pas encore saisi ça, ça viendra, je pense.)
Donc résumons, il y a une tonne de méthodes dispos sur l'objet string, comme size, begin, et j'en passe.
Intéressant de voir que mes visites à l'ulb n'ont pas servi à rien du tout ^^

A noter : un objet se déclare normalement en majuscule, OR string, non. J'ai essayé, et même si on souhaite le faire, a march pach.
C'est comme ça, mais les autres objets, on les déclarera en maj quand même.

Attributs et Méthodes


Bon, contrairement aux fonctions où on a des variables, etc. dans un objet, on parle d'attributs pour une valeur, et de méthode pour une fonction, en gros.
Rien de nouveau encore sous le soleil.

Pour utiliser une méthode, on utilise la syntaxe objet.methode()
Normalement, on doit pouvoir accéder aux attributs, mais l'intérêt de la POO est de n'accéder qu'aux méthodes, pas aux attributs.

Exemple d'utilisation de méthode sur string, size.

-> maChaine.size(); renvoie la taille de la chaîne. On n'a pas besoin d'envoyer de paramètre, ça renvoie la taille, c'est tout.
On a aussi maChaine.erase(), qui a un équivalent : maChaine = "";

Modéliser une classe


Avant de créer un objet, on créé une classe.
Qu'est-ce que c'est ? C'est le plan de l'objet.
Qu'est-ce que je veux comme objet, j'en dessine un plan rudimentaire en disant ce que je mets dedans.

Quand on créé un objet, on dit qu'on une instance une classe.






Ca me plaît beaucoup, on va travailler sur un RPG. Tout ne sera pas développé dans le cours, me faudra le finir moi-même, youpi :D

Dans une classe, il y a :

  1. Des attributs (variables). On parle aussi de variables membres
  2. des fonctions, appelées méthodes.
On va modéliser ici un Personnage. (attention, les objets commencent par une majuscule !)
UML : C'est un langage spécial conçu pour concevoir les classes proprement, je crois bien !

Il n'y a pas de règle imposée pour savoir si on commence par les attributs ou les méthodes. Logiquement, peut-être, on définit avant les attributs, PUIS les méthodes...

N'hésitons pas à sortir un crayon et une feuille de papier pour bien concevoir les choses !!

On conçoit un personnage de RPG, genre diablo. Dans tout RPG, un personnage a de la vie, du mana (taux de magie), une arme, et un degré de dégâts engendrés par l'arme.
class Personnage 
{
int m_vie;
int m_mana;
string m_nomArme;
int m_degatsArme;
}
Quand j'ai écrit ça, j'ai décrit ce qu'il y avait comme attributs dans cet objet.
=> Une grande partie des programmeurs utilise comme convention de nommer les attributs en commençant m_. Ca permet de dire "variable membre" quoi...

Les méthodes

Elles sont les actions que le Personnage peut effectuer, ou qu'on peut lui faire.
Par exemple, notre personnage peut recevoir des dégats, peut en causer (plus ou moins selon degatsArme) remettre sa vie et son mana au max, changer d'arme, être vivant ou mort. (true si vivant, false si mort)
:
class Personnage
{
void recevoirDegats(int nbDegats)
{

}

void attaquer(Personnage &cible)
{

}
void restaurerVie(int quantitePotion)
{

[...]};

!!!!!!!!!! On ferme une classe d'objet avec ;
class Truc
{
};
Eh oui...

A partir de maintenant, je continue sur mon fichier code::blocks, je peux le mettre sur le serveur ici après... mais sinon c'est trop compliqué.

L'encapsulation

Argh, mais qu'est-ce que c'est que cet engin-là encore ?

Déjà, il faut bien différencier le fait de CRÉER des classes (objets), et le fait d'utiliser des objets...

Bon, on lance le script plus haut (plus complet hein)et oh, surprise, ça renvoie une erreur. Soit disant, les attributs sont privés, on ne peut donc pas les modifier.
C'est tout l'intérêt de les déclarer en public. C'est, je crois ce qu'on appelle l'encapsulation.

En, fait, il y a plus que deux façons, public ou private, mais on verra ça plus tard.
CE QU'IL FAUT RETENIR :
On créé un objet avec des attributs. On manipule les attributs à l'aide des méthodes, qui sont accessibles de l'extérieur. Par contre, les attributs, eux, ne sont modifiables qu'à l'aide des méthodes, on ne les laisse jamais publics.

En gros : Méthodes = publics
Attributs = privés

http://www.siteduzero.com/informatique/tutoriels/programmez-avec-le-langage-c/separer-prototypes-et-definitions

J'en suis là.

Séparer les prototypes et définitions de classe

On l'a vu dans une autre partie de cours : on essaie de rendre Main() lisible, donc on sépare le code dans des pages.
Ensuite, il faudra bien entendu remplir les méthodes, parce que pour l'instant il ne se passe rien.(on appelle ça "implémenter les méthodes de la classe".)

ATTENTION : il est très fortement conseillé de ne pas faire un " using namespace std;" dans le fichier .h car ça provoque des erreurs par la suite. Par conséquent, si on utilise des namespace std, on usera de la syntaxe std::: devant chaque string du .h, Sinon, le compilateur sortira une erreur de type "string does not name a type".

Une fois qu'on a implémenté le .h, on va écrire Personnage.cpp
Dans ce fichier, on peut ajouter using namespace std; (on évite dans le .h, pas dans le .cpp)
dans le .cpp, on inclut le .h...

#include "Personnage.h"
using namespace std;

Ensuite, on écrit les définitions de classe, mais attention ! Pour indiquer qu'il s'agit de la classe "Personnage", on écrit Personnage::

void Personnage::attaquer(int degats)
{
}

Eh oui. C'est pas que simple.

Ensuite, bidouillons un peu le code en lui-même :
void Personnage::recevoirDegats(int nbDegats)
{
m_vie -= nbDegats;
    if(m_vie < 0) { m_vie = 0; } // si la vie est -, on la remet à 0 càd mort.
}

Ca se complique légèrement avec les références/pointeurs
void Personnage::attaquer(Personnage &cible)
{
 cible.recevoirDegats(m_degatsArme);
// on inflige à notre cible les dégats que cause notre arme.
}

Avez-vous remarqué comme on n'utilise pas un pointeur mais une référence ? Non, ben, c'est que les références et les pointeurs c'est pas encore très au point...
Nous faisons ceci par simplicité. Et puis, quand on envoie des dégats, on appelle une méthode de l'objet Personnage : recevoirDegats, sur la référence :
cible.recevoirDegats(m_degatsArme);

Bon, j'ai écrit la suite, je mettrai le fichier ici..

Petit rappel : sur la page Main.cpp, on indique désormais #include "Personnage.h" ;)

A ce stade, il ne faut pas exécuter le programme. Nous n'avons pas intialisé les arguments...

Je pense que pour vraiment comprendre ça, faudrait que je modélise un truc par moi-même. J'imagine aisément en php qu'il soit possible de modéliser un "paragraphe" par exemple, avec des valeurs par défaut, et des valeurs qui sont modifiées en fonction des méthodes appelées...


RESUMONS


La classe est indispensable pour créer un objet.
On met dedans toute la liste de ce qu'on trouve : les méthodes "résumées", les "variables" (attributs) précédés de m_
Les éléments y sont privés ou publics. Dans le cas où ils sont publics, on y accède depuis l'extérieur. Dans le cas contraire, on n'y accède que depuis une méthode.
En POO, on suit la règle "d'encapsulation" : on rend les attributs privés, ça oblige à passer par les méthodes pour modifier ces valeurs.

On a donc appris jusque là à créer des classes, des méthodes et des attributs. On n'a pas vu comment initialiser un objet, ça, ça se passe à l'aide d'un constructeur. Après, un destructeur... Ca va de pair.
 

Par défaut, un constructeur est créé par le compilateur, mais ce constructeur est vide et ne fait rien...
Ce pourquoi on a souvent besoin d'en créer un nous-même.
Son rôle est d'initialiser les valeurs des attributs.

Dans le code dans Personnage.h, on a m_vie; etc. Ca initialise pas les attributs, on n'a pas le droit de le faire ici :s PARCE QUE !! C++, un peu conno qu'il est, n'initialise pas les attributs avec un type (int, double, char). Il initialise les objets. Donc, quand on appelle une string (m_nomArme), on initialise avec une valeur par défaut, par contre.

Le constructeur, c'est comme une méthode, un peu particulière.
Le constructeur DOIT avoir le même nom que la classe. La méthode n'a rien en retour, même pas void. Elle n'est PAS TYPEE
ex : si l'on met ce constructeur dans Personnage.h ça donne :
class Personnage
{
    public:

    Personnage(); // voilà le constructeur...
[...]

}

Il faut coder le constructeur, sa place est logiquement dans Personnage.cpp.

Personnage::Personnage()
{
m_vie = 100; etc.
}
Il n'y a pas de type de retour ici non plus, rien.

Une autre méthode existe pour le constructeur, on l'appelle :
Liste d'initialisation.
C'est un usage recommandé.

Au lieu d'écrire Personnage::Personnage() {
// blabla
}

on fait ça, qui est absolument équivalent :
Personnage::Personnage() : m_vie(100), m_mana(100), m_nomArme("Epée rouillée"), m_nomSort("Vieux rêve pourri"), m_degatsArme(10), m_degatsSort(5)
{
// rien ici, tout a déjà été fait.
}
mais attention, on ne change pas le header (fichier .h), le prototype de la classe reste inchangé, c'est seulement dans le cpp qu'on fait apparaître cette liste.


Comme lesiteduzero fonctionne pas (évidemment...) je recréé un objet pour m'entrainer.
C'est une théière (elle s'appelle cruche, parce que j'avais commencé...)
On pourra la remplir, servir un verre (donc vider) et l'aromatiser.
C'est trèèèèèèèès simple mais ça va, j'ai compris.
Ces fichiers ne m'ont pas posé beaucoup de problèmes mais je cite :

dans main.cpp, on ajoute "include "fichierheander.h"

Dans le header, on déclare simplement la classe, sans utiliser namespace std;, faut donc mettre std::string
Y a un truc bizarre aussi en entête et fin, il faut démarrer et fermer un if.
#ifndef DEF_TRUC
#define DEF_TRUC
 blabla
#endif à la fin du doc.

La description des méthodes se passe dans le docu.cpp

Voilà. Pas très compliqué en fin de compte. Ne pas oublier le constructeur.


A propos du constructeur, on dit qu'on peut le "surcharger".
C'est à dire qu'on peut créer plusieurs constructeurs dans le header.
Le constructeur par défaut étant dans notre cas Personnage();
on peut également avoir
Personnage(std::string nomArme, int degatsArme); par exemple qui nous autorise à appeler un Personnage avec déjà un nom d'arme et un nb de dégats différent du standard.
Dans ce cas, on ajoute au .cpp le constructeur suivant :
Personnage::Personnage(string nomArme, int degatsArme) : m_vie(100), m_mana(100), m_nomArme(nomArme), m_nomSort("Vieux cauchemar pourri"), m_degatsArme(degatsArme), m_degatsSort(5)

On voit très vite l'intérêt de nommer les variables de l'objet (les attributs) par m_truc et celles reçues de l'extérieur par degatsArme par exemple, on fait le cas échéant très rapidement la différence.

Les données reçues par l'utilisateur appellent une question :
Qui construit, qui utilise ?

L'objet est fait par le "constructeur".
Il est utilisé par l'utilisateur, qui se trouve en fait dans le Main.
Toujours visualiser d'après une machine fermée et un pilote...

J'ai ajouté également un constructeur qui permet à l'utilisateur de créer un personnage avec un autre niveau de vie et de mana.


Destructeur

Le destructeur, c'est une méthode qu'on appelle pour supprimer l'objet de la mémoire qui a été allouée dynamiquement.

Dans le cas de notre classe Personnage, il n'y a pas eu d'allocation dynamique. (on n'a pas fait new). Le destructeur est inutile. Dans d'autres cas, ce sera important.On verra ça plus tard, mais d'office, la syntaxe, c'est Personnage:: Personnage()
{
}  dans le .cpp et 
Personnage(); dans le .h

 Les méthodes constantes 

Les méthodes constantes sont des méthodes de lecture seule. On ajoute const à la fin de la déclaration du prototype, et de sa decription dans le cpp.
Exemple d'utilisation : une méthode constante est une méthode qui ne modifie pas les attributs.
Exemple : une méthode qui affiche des variables est constante, alors qu'une qui modifie le nombre de vie ne peut pas l'être.

A quoi ça sert :
Lors du développement, ça sert à savoir que cette méthode ne modifie pas de données, c'est plus clair.
Pour les utilisateurs, c'est très important aussi, puisque ça apparaît dans le prototype de la classe, les utilisateurs savent que ça ne modifie rien.
Pour le compilateur, c'est important,

j'en suis là :
http://www.siteduzero.com/informatique/tutoriels/programmez-avec-le-langage-c/action

La partie sur l'imbrication des constructeurs/destructeurs, ça va, mais il me reste à revoir ces histoires de classes dans les classes, je pense que je n'ai pas tout compris, je voudrais refaire avev la classe "Sort" que je n'ai pas encore créée.

En recommençant (en créant Sort.h et Sort.cpp) je comprends je crois un peu mieux, mais ça reste légèrement vaseux.
Va falloir que je me fasse un projet en POO pour mieux capter, mon projet à moi, quoi.
Idée ?!

Ok, déboggage fait sur l'ajout de la classe "Sort" fait.
J'ai pas forcément tout super super bien compris mais dans l'ensemble, ça va, ça fonctionne, en tout cas.

Aucun commentaire:

Membres