Aller au menu - Aller au contenu

Icône Classes et pointeurs

Avatar
Mise à jour : 27/05/2011
Difficulté : Difficile Difficile Durée d'étude : 3 jours Creative Commons BY-NC-SA
98 774 visites depuis 7 jours , dont 1 301 sur ce chapitre , classé 5/777
Dans les chapitres précédents, j'ai volontairement évité d'introduire les pointeurs avec les classes. En effet, les pointeurs en C++ sont un vaste sujet, et un sujet sensible. Comme vous l'avez probablement remarqué par le passé, bien gérer les pointeurs est essentiel car à la moindre erreur votre programme risque de :

  • Consommer trop de mémoire parce que vous oubliez de libérer certains éléments
  • Voire tout simplement de planter si votre pointeur pointe vers n'importe où dans la mémoire

Comment associe-t-on classes et pointeurs ? Quelles sont les règles à connaître, les bonnes habitudes à prendre ?
Voilà un sujet qui méritait au moins un chapitre à lui tout seul. :)

Attention : c'est un chapitre que je classe entre "très difficile" et "très très difficile". Bref, vous m'avez compris, les pointeurs en C++ c'est pas de la tarte, alors quadruplez d'attention lorsque vous lirez ce chapitre. Le sujet est complexe et épineux, je ne vous le dirai pas deux fois. ;)
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Pointeur d'une classe vers une autre classe

Reprenons notre classe Personnage :)
Dans les précédents chapitres, nous lui avons ajouté une Arme que nous avons directement intégré à ses attributs :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Personnage
{
    public:

    // Quelques méthodes...


    private:

    Arme m_arme; // L'Arme est "contenue" dans le Personnage
    // ...
};


Il y a plusieurs façons différentes d'associer des classes entre elles. Celle-ci fonctionne bien dans notre cas, mais l'Arme est vraiment "liée" au Personnage. Elle ne peut pas en sortir.

Schématiquement, ça donnerait quelque chose de ce genre :

Image utilisateur


L'Arme est vraiment dans le Personnage.

Il y a une autre technique, plus souple, qui permet plus de possibilités, mais qui est plus complexe : ne pas intégrer l'Arme au Personnage et utiliser un pointeur à la place. Au niveau de la déclaration de la classe, le changement correspond à... une étoile en plus :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Personnage
{
    public:

    // Quelques méthodes...


    private:

    Arme *m_arme; // L'Arme est un pointeur, l'objet n'est plus contenu dans le Personnage
    // ...
};


Notre Arme étant un pointeur, on ne peut plus dire qu'elle appartient au Personnage.
En schéma, ça donne ça :

Image utilisateur


On considère que l'arme est maintenant externe au personnage.
Les avantages de cette technique sont les suivants :

  • Le Personnage peut changer d'Arme en faisant tout simplement pointer m_arme vers un autre objet. Par exemple, si le Personnage possède un inventaire (dans un sac à dos), il peut changer son Arme à tout moment en modifiant le pointeur.
  • Le Personnage peut donner son Arme à un autre Personnage, il suffit de changer les pointeurs de chacun des personnages.
  • Si le Personnage n'a plus d'Arme, il suffit de mettre le pointeur m_arme à 0.

Les pointeurs permettent de régler le problème que l'on avait vu pour le jeu de stratégie Warcraft III. Un personnage peut avoir une cible qui change grâce à un pointeur interne, exactement comme ici.


Mais des défauts, il y en a aussi. Gérer une classe qui contient des pointeurs, c'est pas de la tarte vous pouvez me croire, et d'ailleurs vous allez le voir. :p

Alors, faut-il utiliser un pointeur ou pas pour l'arme ? Les 2 façons de faire sont valables, et ont chacune leurs avantages et défauts. Utiliser un pointeur est probablement ce qu'il y a de plus souple, mais c'est aussi plus difficile.
Retenez donc qu'il n'y a pas de "meilleure" méthode adaptée à tous les cas, ce sera à vous de choisir en fonction de votre cas si vous intégrez directement un objet dans une classe ou si vous utilisez un pointeur.

Gestion de l'allocation dynamique

On va ici voir comment on travaille quand une classe contient des pointeurs vers des objets.

On travaille là encore sur notre classe Personnage et je suppose que vous avez mis l'attribut m_arme en pointeur comme je l'ai montré un peu plus haut :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Personnage
{
    public:
 
    // Quelques méthodes...
 
 
    private:
 
    Arme *m_arme; // L'Arme est un pointeur, l'objet n'est plus contenu dans le Personnage
    // ...
};


(je ne réécris volontairement pas tout le code, juste l'essentiel pour qu'on puisse se concentrer dessus)

Notre arme étant un pointeur, il va falloir faire une allocation dynamique avec new pour créer l'objet. Sinon, l'objet ne se créera pas tout seul. ;)


Allocation de mémoire pour l'objet



L'allocation de mémoire pour notre arme se fait où à votre avis ?
Il n'y a pas 36 endroits pour ça : c'est dans le constructeur. C'est en effet le rôle du constructeur que de faire en sorte que l'objet soit bien construit, donc notamment que tous les pointeurs pointent vers quelque chose. ;)

Dans notre cas, on est obligé de faire une allocation dynamique, donc d'utiliser new. Voici ce que ça donne dans le constructeur par défaut :

Code : C++
1
2
3
4
Personnage::Personnage() : m_arme(0), m_vie(100), m_mana(100)
{
    m_arme = new Arme();
}


Si vous vous souvenez bien, on avait aussi fait un second constructeur pour ceux qui veulent que le Personnage commence avec une arme plus puissante dès le départ. Il faut là aussi y faire une allocation dynamique :

Code : C++
1
2
3
4
Personnage::Personnage(string nomArme, int degatsArme) : m_arme(0), m_vie(100), m_mana(100)
{
    m_arme = new Arme(nomArme, degatsArme);
}


Explications : new Arme() appelle le constructeur par défaut de la classe Arme, tandis que new Arme(nomArme, degatsArme) appelle le constructeur surchargé. Le new renvoie l'adresse de l'objet créé, adresse qui est stockée dans notre pointeur m_arme.

On a d'abord initialisé le pointeur à 0 dans la liste d'initialisation par sécurité, puis on a fait l'allocation avec le new entre les accolades du constructeur.


Désallocation de mémoire pour l'objet



Notre arme étant un pointeur, lorsque l'objet de type Personnage est supprimé l'arme ne disparaît pas toute seule ! Si on fait juste un new dans le constructeur, et rien dans le destructeur, il va se passer ceci lorsque l'objet de type Personnage sera détruit :

Image utilisateur


L'objet de type Personnage va bel et bien disparaître, mais l'objet de type Arme va subsister en mémoire et il n'y aura plus aucun pointeur pour se "rappeler" de son adresse. En clair, l'arme va traîner en mémoire et on ne pourra plus jamais la supprimer. C'est ce qu'on appelle une fuite de mémoire.

Pour résoudre ce problème, il faut faire un delete de l'arme dans le destructeur du personnage afin que l'arme soit supprimée avant le personnage. Le code est tout simple :

Code : C++
1
2
3
4
Personnage::~Personnage()
{
    delete m_arme;
}


Cette fois le destructeur est réellement indispensable. Maintenant, lorsque quelqu'un demandera à détruire le Personnage, il va se passer ceci :

  1. Appel du destructeur... et donc dans notre cas suppression de l'Arme (avec le delete).
  2. Puis enfin suppression du Personnage.


Au final, les 2 objets seront bel et bien supprimés et la mémoire sera propre :

Image utilisateur


N'oubliez pas que m_arme est maintenant un pointeur !



Cela implique de changer toutes les méthodes qui l'utilisent. Par exemple :

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


... devient :

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


Notez la différence : le point a été remplacé par la flèche, car m_arme est un pointeur.

Le pointeur this

Ce chapitre étant difficile, je vous propose un passage un peu plus cool. Puisqu'on parle de POO et de pointeurs, je me dois de vous parler du pointeur this.
Pas de panique, c'est très simple, ça ira vite et vous ne sentirez aucune douleur. :p

Dans toutes les classes, on dispose d'un pointeur ayant pour nom this. Ce pointeur pointe vers l'objet actuel.
Je reconnais que ce n'est pas simple à imaginer, mais je pense que ça passera mieux avec un schéma maison :

Pointeur this C++

Chaque objet (ici de type Personnage) possède un pointeur this qui pointe vers... l'objet lui-même !


this étant utilisé par le langage C++ dans toutes les classes, vous ne pouvez donc pas créer de variable appelée this car cela créerait un conflit. De même, si vous commencez à essayer d'appeler vos variables class, new, delete, return, etc. forcément ça risque de coincer un peu. ^^
Ces mots-clés sont ce qu'on appelle des mots-clés réservés. Le langage C++ se les réserve pour son usage personnel, vous n'avez donc pas le droit de créer des variables (ou des fonctions) portant ces noms-là.


Mais... à quoi peut bien servir this ???


Répondre à cette question me sera délicat. :p
Je peux vous donner un exemple : vous êtes dans une méthode de votre classe, et cette méthode doit renvoyer un pointeur vers l'objet auquel elle appartient. Sans le this, on ne pourrait pas l'écrire. Voilà ce que ça pourrait donner :

Code : C++
1
2
3
4
Personnage* Personnage::getAdresse() const
{
    return this;
}


Mais nous l'avons déjà rencontré une fois. Lors de la surcharge de l'opérateur +=. Souvenez-vous, notre opérateur ressemblait à cela :

Code : C++
1
2
3
4
5
6
Duree& Duree::operator+=(const Duree &duree2)
{
    //Des calculs compliqués...

    return *this;
}


this étant un pointeur sur un objet, *this est l'objet lui-même ! Notre opérateur renvoie donc l'objet lui-même en retour. La raison pour laquelle on doit renvoyer l'objet est compliquée, mais c'est la forme correcte des opérateurs. Je vous propose donc de simplement apprendre cette syntaxe par cœur.

À part pour la surcharge des opérateurs, vous n'avez certainement pas à utiliser this dans l'immédiat mais il arrivera un jour où, pour résoudre un problème particulier, vous aurez besoin d'un tel pointeur. Ce jour-là, souvenez-vous qu'un objet peut "retrouver" son adresse à l'aide du pointeur this.

Comme c'est l'endroit le plus adapté pour en parler dans ce cours, j'en profite. Ca ne va pas changer votre vie tout de suite, mais il se peut que bien plus tard, dans plusieurs chapitres je vous dise tel un vieillard sur sa canne "Souvenez-vous, souvenez-vous du pointeur this ! Vieillard ". Alors ne l'oubliez pas !

Le constructeur de copie

Le constructeur de copie est une surcharge particulière du constructeur.
Le constructeur de copie devient généralement indispensable dans une classe qui contient des pointeurs, et ça tombe bien vu que c'est justement notre cas ici. :)

Le problème



Pour bien comprendre l'intérêt du constructeur de copie, voyons voir concrètement ce qui se passe lorsqu'on crée un objet en l'affectant par... un autre objet ! Par exemple :

Code : C++
1
2
3
4
5
6
7
8
int main()
{
    Personnage goliath("Epée aiguisée", 20);
    
    Personnage david(goliath); // On crée david à partir de goliath. David sera une "copie" de goliath.

    return 0;
}

Lorsqu'on construit un objet en lui affectant directement un autre objet, comme on vient de le faire ici, le compilateur appelle une méthode appelée constructeur de copie.

Le rôle du constructeur de copie est de copier la valeur de tous les attributs du premier objet dans le second. Donc david récupère la vie de goliath, la mana de goliath, etc.


Dans quels cas le constructeur de copie est-il appelé ?


On vient de le voir, le constructeur de copie est appelé lorsqu'on crée un nouvel objet en l'affectant par la valeur d'un autre :

Code : C++
1
Personnage david(goliath); // Appel du constructeur de copie (cas 1)


Ceci est strictement équivalent à écrire :

Code : C++
1
Personnage david = goliath; // Appel du constructeur de copie (cas 2)


Dans ce second cas le constructeur de copie est là aussi appelé.

Mais ce n'est pas tout ! Lorsque vous envoyez un objet à une fonction sans utiliser de pointeur ni de référence, l'objet est là aussi copié !
Imaginons la fonction :

Code : C++
1
2
3
4
void maFonction(Personnage unPersonnage)
{

}


Si vous appelez cette fonction qui n'utilise pas de pointeur ni de référence, alors l'objet sera copié en utilisant un constructeur de copie au moment de l'appel de la fonction :

Code : C++
1
maFonction(Goliath); // Appel du constructeur de copie (cas 3)


Bien entendu, il est préférable d'utiliser une référence en général car l'objet n'a pas besoin d'être copié, donc ça va bien plus vite et ça prend moins de mémoire. Toutefois, il arrivera des cas où vous aurez besoin de créer une fonction comme ici qui fait une copie de l'objet.

Si vous n'écrivez pas vous-mêmes un constructeur de copie pour votre classe, il sera généré automatiquement pour vous par le compilateur. Ok, c'est sympa de sa part, mais le compilateur est... comment dire pour pas le froisser... bête. :p
En fait, le constructeur de copie généré se contente de copier la valeur de tous les attributs... même des pointeurs !


Le problème ? Eh bien justement, il se trouve qu'un des attributs est un pointeur dans notre classe Personnage ! Que fait l'ordinateur ? Il copie la valeur du pointeur, donc l'adresse de l'arme. Au final, les 2 objets ont un pointeur qui pointe vers le même objet de type Arme !
Ah les fourbes !

Image utilisateur
L'ordinateur a copié le pointeur, et donc les 2 pointeurs pointent vers la même arme !


Si on ne fait rien pour régler ça, imaginez ce qu'il va se passer lorsque les 2 personnages seront détruits... Le premier sera détruit, ainsi que son arme car le destructeur ordonnera la suppression de l'arme avec un delete. Et quand arrivera le tour du second personnage, le delete va planter (et votre programme avec :D ) parce que l'arme aura déjà été détruite !


Le constructeur de copie généré automatiquement par le compilateur n'est pas assez intelligent pour comprendre qu'il faut allouer de la mémoire pour une autre arme... Qu'à cela ne tienne, nous allons le lui expliquer. :p


Création du constructeur de copie



Le constructeur de copie, comme je vous l'ai dit un peu plus haut, est une surcharge particulière du constructeur. C'est un constructeur qui prend pour paramètre... une référence constante vers un objet du même type !
Si vous ne trouvez pas ça clair, peut-être qu'un exemple vous aidera. ^^

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Personnage
{
    public:

    Personnage();
    Personnage(Personnage const& personnageACopier); // Le prototype du constructeur de copie
    Personnage(std::string nomArme, int degatsArme);
    ~Personnage();
    
    /*
    ... plein d'autres méthodes qui ne nous intéressent pas ici
    */

    private:

    int m_vie;
    int m_mana;
    Arme *m_arme;
};


En résumé, le prototype d'un constructeur de copie est :

Code : C++
1
Objet(Objet const& objetACopier);


Le const indique juste qu'on n'a pas le droit de modifier les valeurs de l'objetACopier (c'est logique, on a juste besoin de "lire" ses valeurs pour le copier).

Écrivons l'implémentation de ce constructeur. Il va falloir copier tous les attributs du personnageACopier dans le personnage actuel. Commençons par les attributs "simples", c'est-à-dire ceux qui ne sont pas des pointeurs :

Code : C++
1
2
3
4
5
Personnage::Personnage(Personnage const& personnageACopier) 
    : m_vie(personnageACopier.m_vie), m_mana(personnageACopier.m_mana), m_arme(0)
{

}


Vous vous demandez peut-être comment cela se fait qu'on puisse accéder aux attributs m_vie et m_mana du personnageACopier ? Si vous vous l'êtes demandé, je vous félicite, ça veut dire que le principe d'encapsulation commence à rentrer dans votre tête. :D
Eh oui, en effet, m_vie et m_mana sont privés, donc on ne peut pas y accéder depuis l'extérieur de la classe... sauf qu'il y a une exception ici : on est dans une méthode de la classe Personnage, et on a le droit d'accéder à tous les éléments (même privés) d'un autre Personnage.

C'est un peu tordu je l'avoue, mais dans le cas présent ça nous simplifie grandement la vie. Retenez donc qu'un objet de type X peut accéder à tous les éléments (même privés) d'un autre objet s'il est du même type X.


Il reste maintenant à "copier" m_arme. Si on écrit :

Code : C++
1
m_arme = personnageACopier.m_arme;


... on fait exactement la même erreur que le compilateur, c'est-à-dire qu'on ne copie que l'adresse de l'objet de type Arme, et pas l'objet en entier !

Pour résoudre le problème, il va falloir copier l'objet de type Arme en faisant une allocation dynamique, donc un new. Attention, accrochez-vous parce que ce n'est pas simple. :p

Si on fait :

Code : C++
1
m_arme = new Arme();


... on va bien créer une nouvelle arme, mais on utilisera le constructeur par défaut, donc cela créera l'arme de base. Or, on veut avoir exactement la même arme que celle du personnageACopier (ben oui, c'est un constructeur de copie ^^ ).

La bonne nouvelle, comme je vous l'ai dit plus haut, c'est que le constructeur de copie est automatiquement généré par le compilateur. Tant que la classe n'utilise pas de pointeurs vers des attributs, il n'y a pas de danger. Et ça tombe bien, la classe Arme n'utilise pas de pointeurs, on va donc pouvoir se contenter du constructeur qui a été généré.

Il faut donc appeler le constructeur de copie de Arme, en envoyant en paramètre l'objet à copier. Vous pourriez penser qu'il faut faire ceci :

Code : C++
1
m_arme = new Arme(personnageACopier.m_arme);


Presque ! Sauf que m_arme est un pointeur, et le prototype du constructeur de copie est :

Code : C++
1
Arme(Arme const& arme);


... ce qui veut dire qu'il faut envoyer l'objet lui-même et pas son adresse. Vous vous souvenez comment on fait pour obtenir l'objet (ou la variable) à partir de son adresse ? On utilise l'étoile * !
Ce qui donne au final :

Code : C++
1
m_arme = new Arme(*(personnageACopier.m_arme));


Cette ligne alloue dynamiquement une nouvelle arme, en se basant sur l'arme du personnageACopier. Pas simple je le reconnais, mais relisez plusieurs fois les étapes de mon raisonnement et vous allez comprendre. ;)
Pour bien suivre tout ce que j'ai dit, il faut vraiment que vous soyez au point sur tout : les pointeurs, les références, et les... constructeurs de copie. ;)


Le constructeur de copie une fois terminé



Le bon constructeur de copie ressemblera donc à ceci au final :

Code : C++
1
2
3
4
5
Personnage::Personnage(Personnage const& personnageACopier) 
    : m_vie(personnageACopier.m_vie), m_mana(personnageACopier.m_mana), m_arme(0)
{
    m_arme = new Arme(*(personnageACopier.m_arme));
}


Ainsi, nos 2 personnages ont tous deux une arme identique, mais dupliquée afin d'éviter les problèmes que je vous ai expliqués plus haut :

Image utilisateur


L'opérateur d'affectation



Nous avons déjà parlé de la surcharge des opérateurs. Mais il y en a un que je ne vous ai pas présenté. Il s'agit de l'opérateur d'affectation (operator=).

Le compilateur écrit un opérateur d'affectation par défaut automatiquement, mais c'est un opérateur "bête". Cet opérateur bête se contente de copier les valeurs des attributs un à un dans le nouvel objet. Comme pour le constructeur de copie généré par le compilateur.


La méthode operator= sera appelée dès qu'on essaie d'affecter une valeur à notre objet. C'est le cas par exemple si on affecte à notre objet la valeur d'un autre objet :

Code : C++
1
david = goliath;


Ne confondez pas le constructeur de copie avec la surcharge de l'opérateur = (operator=). Ils se ressemblent beaucoup, mais il y a une différence : le constructeur de copie est appelé lors de l'initialisation (à la création de l'objet) tandis que la méthode operator= est appelée si on essaie d'affecter un autre objet par la suite, après son initialisation.

Code : C++
1
2
Personnage david = goliath; // Constructeur de copie
david = goliath; // operator=


Cette méthode effectue le même travail que le constructeur de copie. Écrire son implémentation est donc relativement simple. Une fois qu'on a compris le principe bien sûr. :lol:

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Personnage& Personnage::operator=(Personnage const& personnageACopier) 
{
    if(this != &personnageACopier)   //On vérifie que notre objet n'est pas le même que celui reçu en argument
    {
        m_vie = personnageACopier.m_vie;   //On copie tous les champs
        m_mana = personnageACopier.m_mana;
        delete m_arme;
        m_arme = new Arme(*(personnageACopier.m_arme));
    }

    return *this; //On renvoie l'objet lui-même
}


Il y a tout de même quatre différences :
  • Comme ce n'est pas un constructeur, on ne peut pas utiliser la liste d'initialisation et donc tout se passe entre les accolades.
  • Il faut penser à vérifier que l'on n'est pas en train de faire david=david. On doit donc vérifier que l'on travaille avec deux objets distincts. Il faut donc vérifier que leurs adresses mémoires (this et &personnageACopier) sont différentes.
  • Il faut renvoyer *this comme pour les opérateurs +=, -=, etc. C'est une règle à respecter.
  • Il faut penser à supprimer l'ancienne arme avant de créer la nouvelle. C'est ce qui est fait à l'instruction delete surlignée du code. Ceci n'était pas nécessaire dans le constructeur de copie puisque le personnage ne possédait pas d'arme avant. ;)

Cet opérateur est toujours similaire à celui que je vous donne pour la classe Personnage. Les seuls éléments qui changent d'une classe à l'autre sont les lignes qui se trouvent dans le if. Je vous ai en quelque sorte donné la recette universelle. :p

Il y a une chose importante à retenir au sujet de cet opérateur : il va toujours de paire avec le constructeur de copie.

Si l'on a besoin d'écrire un constructeur de copie, alors il faut aussi obligatoirement écrire une surcharge de operator=.

C'est une règle très importante à respecter. Vous risquez de graves problèmes de pointeurs si vous ne la respectez pas.

La POO n'est pas simple comme vous commencez à vous en rendre compte, surtout quand on commence à manipuler des objets avec des pointeurs. Heureusement, vous aurez l'occasion de pratiquer tout cela par la suite, et vous allez petit à petit prendre l'habitude d'éviter les pièges des pointeurs.

Q.C.M.

Quel mot-clé utilise-t-on en C++ pour faire une allocation dynamique de mémoire ?
Quel est le nom du pointeur généré par le compilateur dans toutes les classes et qui pointe vers l'objet actuel ?
Ma classe X contient un pointeur vers un objet Y. Cet objet Y a été alloué dynamiquement dans le constructeur de X (j'espère pour vous que vous suivez :p ). Dois-je supprimer d'abord X puis Y, ou l'inverse, ou ça n'a pas d'importance ?
Lequel de ces prototypes est celui d'un constructeur de copie ?
Dans lequel de ces cas le constructeur de copie ne sera pas appelé pour david ?

Statistiques de réponses au QCM

Si vous êtes en train de vous shooter à l'aspirine pour éviter que votre tête n'explose, je vous conseille de conserver encore des munitions :D
En effet, on n'a pas fini d'en découdre avec la POO et il vous reste encore beaucoup de choses à apprendre. Heureusement, enfin si ça peut vous rassurer, ce chapitre était probablement l'un des plus difficiles de tout le cours (mais pas nécessairement LE plus difficile :p ).

Sachez quoiqu'il en soit que les pointeurs en C++ sont de véritables casse-têtes, même pour les programmeurs plus expérimentés. Il faut faire constamment attention, car une fuite de mémoire (oubli de libérer des objets) est très vite arrivée, et je ne vous parle pas des plantages de programme que ça peut occasionner. Une très très grande part des plantages des programmes que vous connaissez sont dûs à une mauvaise gestion de la mémoire, c'est vous dire !

Dans le prochain chapitre, nous nous rapprocherons d'un des thèmes majeurs de la programmation orientée objet, quelque chose d'indispensable à quoi vous ne pouvez échapper et qui porte un bien funeste nom : l'héritage. :-°

Qu'on ne s'y trompe pas : tout ceci est peut-être complexe et pas toujours très "amusant" à apprendre, mais vous en aurez vraiment besoin dans la partie III lorsque nous travaillerons avec la librairie Qt pour créer des fenêtres, travailler en réseau, etc. Donc on se motive, et on continue ! ^^
Chapitre précédent Sommaire Chapitre suivant

Partager

59 commentaires pour "Classes et pointeurs"
Note moyenne : 3.86 / 4 (1684 votes)
Pseudo Commentaire
Hors ligne cardman # Posté le 07/09/2011 à 13:41:07

Avis : Très bon

Ville : Condécourt
Pays : France métropolitaine

Bonjour tout le monde, serait-il possible de ne pas utiliser de pointeurs dans la classe mais de renvoyer les adresses des attributs de la classe pour pouvoir les modifier à l'extérieur?

Voici un support pour une éventuelle nouvelle sous-partie:
fichier point.h:
Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Point
{
    int x;
    int y;
public:
    Point(int,int);
    int *getx();//pas de const sinon cela plante à la compilation, on retourner l'adresse de x
    int *gety();//pas de const sinon cela plante à la compilation, on retourner l'adresse de y
//pas de destructeur car ni x,ni y ne sont des pointeurs
};


fichier point.cpp:
Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include "point.h"//On inclut l'entête de point.cpp
Point::Point(int _x,int _y):x(_x),y(_y)
{
//rien à faire car tout est déjà initialisé
}
int *Point::getx()//pas de const sinon cela plante à la compilation, on retourner l'adresse de x
{
    return &x;//l'adresse de x
}
int *Point::gety()//pas de const sinon cela plante à la compilation, on retourner l'adresse de y
{
    return &y;//l'adresse de y
}

fichier main.cpp:

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
#include <QtCore> //pour pouvoir sauvegarder les résultats
#include "point.h" //On inclut la classe Point

int main(int argc,char **argv)
{
    Point pt(0,0);
    int *a=pt.getx();//On récupère l'adresse de x
    *a=1;//On assigne 1 à la valeur à l'adresse a
    //pas de libération de mémoire car "a" est l'adresse de l'attribut x de l'objet pt
    a=pt.gety();//On récupère l'adresse de y
    *a=2;//On assigne 2 à la valeur à l'adresse a
    QFile fichier_sortie("dbg.txt");
    if(fichier_sortie.open(QIODevice::WriteOnly|QIODevice::Text))//Si le fichier a pu être ouvert en mode écriture de texte
    {
        QTextStream flux(&fichier_sortie);//Initialisation du flux de sortie sur le fichier de sortie
        flux<<QString::number(*pt.getx())<<";"<<QString::number(*pt.gety());
/*on écrit les résultats dans le fichier de sortie sous la forme x;y,
où x et y sont des nombres (ils valent 1;2), les attributs de l'objet pt sont modifiés à l'extérieur de la classe.*/
        fichier_sortie.close();//on ferme le fichier
    }
//PAS de libération de MEMOIRE de a, sinon le programme plante à l'exécution.
    return 0;//fin d'exécution
}


On évite le plus possible d'utiliser des pointeurs dans la classe.

Au niveau de l'encapsulation, c'est meilleur.

François Mercier des Rochettes.
 
Hors ligne Itch'nak # Posté le 17/09/2011 à 14:22:54
Avatar

Citation : cardman

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
#include <QtCore> //pour pouvoir sauvegarder les résultats
#include "point.h" //On inclut la classe Point

int main(int argc,char **argv)
{
    Point pt(0,0);
    int *a=pt.getx();//On récupère l'adresse de x
    *a=1;//On assigne 1 à la valeur à l'adresse a
    //pas de libération de mémoire car "a" est l'adresse de l'attribut x de l'objet pt
    a=pt.gety();//On récupère l'adresse de y
    *a=2;//On assigne 2 à la valeur à l'adresse a
    QFile fichier_sortie("dbg.txt");
    if(fichier_sortie.open(QIODevice::WriteOnly|QIODevice::Text))//Si le fichier a pu être ouvert en mode écriture de texte
    {
        QTextStream flux(&fichier_sortie);//Initialisation du flux de sortie sur le fichier de sortie
        flux<<QString::number(*pt.getx())<<";"<<QString::number(*pt.gety());
/*on écrit les résultats dans le fichier de sortie sous la forme x;y,
où x et y sont des nombres (ils valent 1;2), les attributs de l'objet pt sont modifiés à l'extérieur de la classe.*/
        fichier_sortie.close();//on ferme le fichier
    }
//PAS de libération de MEMOIRE de a, sinon le programme plante à l'exécution.
    return 0;//fin d'exécution
}


On évite le plus possible d'utiliser des pointeurs dans la classe.

Au niveau de l'encapsulation, c'est meilleur.


Un petit point, quand tu mets
Code : C++
1
*a=1;

Ca signifie que tu changes la variable sur laquelle pointe a.
Pour modifier l'adresse, tu dois mettre :
Code : C++
1
a=1;


Après, je réponds pas à ta question mais bon ... XD

90% of teens today would die if Facebook was completely destroyed. If you are one of the 10% that would be laughing, copy and paste this to your signature.
 
Hors ligne -Dr3ck- # Posté le 12/01/2012 à 19:34:30
///Testostérone\\\
Avatar

C'est fou ! Je comprends tout ! Tu explique tellement bien *_* Merci mille fois Mateo21 !

U MAD BRO ? Image utilisateur
 
Hors ligne Programpriv # Posté aujourd'hui à 13:40:18
C'est beau C++ !
Avatar

Avis : Très bon

Bonjour,
Ce chapitre est selon moi, incomplet. Peut être que je me trompe, mais le sujet des pointeurs, que ce soit dans des types classiques ou des objets, sont une affaire de mémoire. Je suis assez épaté de voir qu'on en parle pas dans ce chapitre, pourtant je pense que ce serait indispensable. En pratique, quand nous analysons certains cas de conception avec les pointeurs, il serait utile de parler un peu de la mémoire.
Sinon bon tutotage !
 
Connecté Nanoc # Posté il y a 7 min
Aimez-vous le C++ ?
Avatar
Validateurs

Ville : Durham
Pays : Royaume-Uni
Études : EPFL

Tout cela est expliqué dans la partie I du tuto. Il n'y a rien de plus à ajouter à ce niveau.
 

Voir tous les commentaires