Big Tuto : Apprenez le C++

Chapitre 18 : Surcharge des opérateurs (1)

 

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

 

Retrouvez les projets complets de ce chapitre :

  

 

 

   1 – Implémentation d'une fonction d'incrémentation

 

    Avant de continuer la lecture de ce paragraphe, je vous demanderai de bien vouloir commencer par vous rendre aux corrigés des exercices du chapitre 17 à la fin de ce tutoriel et de prendre connaissance du corrigé de l'exercice 1, (Chap17Exercice_1).

 

   Bien, après avoir lu les explications de ce corrigé, et avant de vous amener petit à petit à un premier exemple de surcharge d'opérateur, nous allons reprendre l'idée de l'exemple 044Fonctions7 du chapitre 6 qui présentait de manière récursive l'implémentation d'une suite de Fibonacci. smiley

   Cette fois nous présenterons une classe dénommée ''Fibonacci'' et dans laquelle nous implémenterons une fonction d'incrémentation. Nous allons créer à cet effet 2 objets de cette classe, l'un créé par le constructeur par défaut et un autre créé par le constructeur de copie. Pour terminer, nous afficherons cette suite à l'aide d'une fonction membre de la classe.

 

   Exemple : Projet 108Surcharge_1

      Fichier Surcharge_1.h  

 

//projet 108Surcharge_1
//Fichier : Surcharge_1.h
//Déclaration de la classe Fibonacci
//Implémentation d'une fonction d'incrémentation
#ifndef DEF_FIBONACCI
#define DEF_FIBONACCI
 
using ULLong = unsigned long long;
 
class Fibonacci
{
public:
 
// Constructeur par défaut
Fibonacci()
{
nombre1 = 0;
nombre2 = 1;
result = nombre1 + nombre2;
}
 
// Constructeur de copie
Fibonacci (Fibonacci &rhs)
{
nombre1 = rhs.nombre1;
nombre2 = rhs.nombre2;
result = rhs.result;
}
 
// Fonction d'incrémentation
void Incremente()
{
nombre1 = nombre2;
nombre2 = result;
result = nombre1 + nombre2;
}
 
// Fonction d'affichage
void Affiche() { std::cout << result; }
 
 
private:
//Donnée membre privée
ULLong nombre1;
ULLong nombre2;
ULLong result;
 
};
 
#endif 

 

   Dans le fichier Surcharge_1.h, nous implémentons un constructeur par défaut et un constructeur de copie (voir chapitre 16).

   Dans le chapitre 6, nous avons vu qu'une suite de Fibonacci est une suite de nombres dans laquelle chacun d'entre-eux, à partir du second, est la somme des deux précédents. Si nous analysons le constructeur par défaut nous nous apercevons que la variable result est la somme des nombres précédents soit 0 + 1 = 1. La fonction membre Incremente(), quant-à-elle, affecte nombre2 à nombre1, remplace nombre2 par le résultat précédent et affecte la somme des deux nombres à la variable resultwink


       Fichier Surcharge_1.cpp

 

//projet 108Surcharge_1
//Fichier : Surcharge_1.cpp
//Fonction main()
//Utilisation d'une fonction d'incrémentation
 
#include <iostream>
#include "Surcharge_1.h"
 
#include <conio.h>
 
using namespace std;
 
int main()
{
// Création d'un objet avec le constructeur par défaut
Fibonacci unNombre;
 
// On crée un second objet avec le constructeur de copie
Fibonacci autreNombre(unNombre);
 
// Maximum de nombres à afficher
const ULLong maxNombre = 20;
 
cout << endl;
for (auto ctr = 0; ctr != maxNombre; ++ctr)
{
unNombre.Affiche();
unNombre.Incremente();
cout << " ";
}
 
cout << endl;
for (auto ctr = 0; ctr != maxNombre; ++ctr)
{
autreNombre.Affiche();
autreNombre.Incremente();
cout << " ";
}
 
_getch();
return 0;
}  

 

   Dans le fichier Surcharge_1.cpp, nous créons deux objets : unNombre et autreNombre, le premier à l'aide du constructeur par défaut et le second à l'aide du constructeur de copie. Il est à remarquer que la copie du constructeur réalisée ici est une copie de surface même si nous avons implémenté le constructeur de copie (nous n'étions pas obligés de le faire cheeky). Nous avons vu que sous la forme de ''copie de surface'', le compilateur se charge lui-même de créer une copie des variables membres d'un objet vers un objet créé à partir du premier (voir chapitre 16 § 4.2 pour l'implémentation d'une copie en profondeur wink).

   Pour terminer, par l'intermédiaire de deux boucles ''for'', nous appelons les fonctions Affiche() et Incrémente() se référant aux objets unNombre et autreNombre.

   Ce qui affiche dans la console :

 

 

 

   2 – La surcharge d'opérateurs

 

   Nous savons que les différents types de variables prédéfinis par le langage sont utilisés avec un certain nombre d'opérateurs qui agissent directement sur ces variables ou sur des fonctions. Parmi ces opérateurs nous rencontrons les opérateurs =, <<, >>, ++, +, -, *, (), [], →, etc..

   C++ nous permet, en outre des types prédéfinis par le langage, la gestion d'objets crées par le programmeur en surchargeant ces différents opérateurs. Nous pouvons dès lors ajouter des objets à d'autres objets de même type, les soustraire, et ainsi réaliser toute opération que le langage nous permet de faire sur les opérateurs utilisés par les types prédéfinis.

   Nous avons déjà utilisé des opérateurs surchargés sans nous en rendre compte. wink En effet, les classes string et vector parmi d'autres, possèdent déjà, dans leur implémentation, des opérateurs surchargés. Rappelons-nous dans un simple exemple, la concaténation de deux chaînes de type string ou l'affectation d'une chaîne par une autre :

                     // + est un opérateur mathématique binaire surchargé car il agit sur deux variables
                     str3 = str2 + str1;

                     // = est l'opérateur d'affectation surchargé
                     str4 = str3;

 

   Nous venons de voir l'implémentation d'une fonction d'incrémentation dans le projet précédent et ceci, nous amène tout naturellement à introduire la notion de surcharge d'opérateur. Nous commencerons par la surcharge des opérateurs unaires (qui n'agissent que sur une seule variable), par exemple ++ et - - .

 

Remarque : Dans une instruction d'affectation d'une valeur, la variable située à gauche du signe = est dénommée lvalue ou left-value (valeur de gauche) tandis que la variable ou la valeur située à droite est dénommée rvalue ou right-value (valeur de droite). wink

 

   Par la suite, dans une opération de pré-incrémentation suivie d'une opération d'affectation, le compilateur incrémente la valeur d'une rvalue avant de l'affecter à une lvalue tandis que dans une opération de post-incrémentation suivie d'une opération d'affectation, le compilateur affecte la valeur de la rvalue à une lvalue avant d'incrémenter la rvalueindecision

   Une fonction qui surcharge un opérateur est une fonction spéciale. Le mot-clé operator est suivi par le symbole de l'opérateur devant être surchargé et comme toute autre fonction, un opérateur surchargé possède un type de retour, une liste de un ou deux paramètres et un corps de fonction.

   Sa déclaration est de la forme :

type_valeur_renvoi operatoroperateur(paramètre(s) );

 


      2.1 – Surcharge de l'opérateur unaire de pré-incrémentation ++ (valeur)

 

   La déclaration d'un opérateur surchargé de pré-incrémentation est de la forme :

void operator++();

   operator est le mot résevé qui s'applique à tous les types d'opérateurs.

   Le type de renvoi est ''void'', donc aucune valeur n'est renvoyée. wink

   operator est le type d'opérateur devant être surchargé, ici ++.

   Nous voyons maintenant, dans un nouvel exemple, comment additionner des objets à l'aide de la fonction operator++(). Nous reprenons l'exemple de la suite de Fibonacci ci-dessus (projet 108Surcharge_1).

 

   Exemple : Projet 109Surcharge_2

      Fichier Surcharge_2.h  

 

//projet 109Surcharge_2
//Fichier : Surcharge_2.h
//Déclaration de la classe Fibonacci
//Implémentation d'une fonction operator++()
#ifndef DEF_FIBONACCI
#define DEF_FIBONACCI
 
using ULLong = unsigned long long;
 
class Fibonacci
{
public:
 
// Constructeur par défaut
Fibonacci()
{
nombre1 = 0;
nombre2 = 1;
result = nombre1 + nombre2;
}
 
// Constructeur de copie
Fibonacci(Fibonacci &rhs)
{
nombre1 = rhs.nombre1;
nombre2 = rhs.nombre2;
result = rhs.result;
}
 
// Fonction operator++()
void operator++()
{
nombre1 = nombre2;
nombre2 = result;
result = nombre1 + nombre2;
}
 
// Fonction d'affichage
void Affiche() { std::cout << result; }
 
 
private:
//Donnée membre privée
ULLong nombre1;
ULLlong nombre2;
ULLong result;
 
};
#endif 

 

   Nous voyons que la fonction operator++() remplace la fonction d'incrémentation. Pour le reste rien d'autre n'est changé.

      Fichier Surcharge_2.cpp   

 

//projet 109Surcharge_2
//Fichier : Surcharge_2.cpp
//Fonction main()
//Surcharge de l'opérateur de pré-incrémentation ++
 
#include <iostream>
#include "Surcharge_2.h"
 
#include <conio.h>
 
using namespace std;
 
 
int main()
{
// Création d'un objet avec le constructeur par défaut
Fibonacci unNombre;
 
// On crée un second objet avec le constructeur de copie
Fibonacci autreNombre(unNombre);
 
// Maximum de nombres à afficher
const ULLong maxNombre = 20;
 
cout << endl;
for (auto ctr = 0; ctr != maxNombre; ++ctr)
{
unNombre.Affiche();
// operator++ incrémente l'objet unNombre
++unNombre;
cout << " ";
}
 
cout << endl;
for (auto ctr = 0; ctr != maxNombre; ++ctr)
{
autreNombre.Affiche();
// operator++ incrémente l'objet autreNombre
++autreNombre;
cout << " ";
}
 
_getch();
return 0;
} 

 

     La grande différence est que dans le précédent projet, nous avons incrémenté les objets unNombre et autreNombre à l'aide d'une fonction membre Incremente() appliquée aux objets, tandis qu'ici, grâce à la surcharge de l'opérateur ++, nous pouvons directement incrémenter les objets comme n'importe quelle autre variable de type prédéfinie par le langage.

   D'où les instructions ++unNombre et ++autreNombre (opération de pré-incrémentation dans ce cas précis wink).

   Et le résultat dans la console est identique à celui du projet 108Surcharge_1. angel

 

 

      2.2 – Surcharge de l'opérateur unaire de pré-décrémentation -- (valeur)

 

   La surcharge de l'opérateur de pré-décrémentation - -(valeur) suit les mêmes règles que l'opérateur de pré-incrémentation et nous ne donnerons donc pas d'exemple complet pour ce type de surcharge mais je vous proposerai un petit exercice. wink

   Il est néanmoins important de remarquer que ces opérateurs de pré-incrémentation et pré-décrémentation sont le plus souvent implémentés pour les classes d'itérateurs. Dès lors, leur valeur de retour n'est plus ''void'' mais bien une référence sur un objet de la classe à laquelle ils appartiennent. wink

 

   Exercice 1 :

   Décrémentez 1 objet ''unNombre'' créé à partir d'une classe Compteur. Vous afficherez 20 premiers objets de 20 à 1 à l'aide de l'opérateur operator--.

 

   Bien, revenons quelques instants sur notre exemple 109Surcharge_2. Comment créer un nouvel objet autreNombre sans utiliser de constructeur de copie et lui donner par exemple la valeur de unNombre après incrémentation ? frown

   Soit :

autreNombre = ++unNombre;

   Nous avons vu que le constructeur de copie par défaut réalisait ce genre d'opération. Mais nous savons aussi que la fonction void operator++() ne renvoie aucune valeur, donc nous ne pouvons pas créer un objet autreNombre de cette façon. Alors ? surprise

   La solution consiste à créer un objet temporaire dans la fonction et de le renvoyer. Nous voyons ceci dans l'exemple suivant wink :

 

 

      2.3 – Renvoyer un objet temporaire à partir d'un opérateur surchargé

 

   Exemple : Projet 110Surcharge_3

      Fichier Surcharge_3.h   

 

//projet 110Surcharge_3
//Fichier : Surcharge_3.h
//Déclaration de la classe Armes_de_traits
//Renvoi d'un objet temporaire
#ifndef DEF_ARME_DE_TRAITS
#define DEF_ARME_DE_TRAITS
 
class Arme_de_traits
{
public:
Arme_de_traits() : _quantite(1) {}
 
~Arme_de_traits() {}
 
// Fonctions membres d'accès public
int get_Quantite() const { return _quantite; };
 
void Set_Quantite(int x) { _quantite = x; }
 
//Surcharge de l'opérateur ++
Arme_de_traits operator++()
{
++ _quantite;
Arme_de_traits temp;
temp.Set_Quantite(_quantite);
 
return temp;
}
 
private:
//Donnée membre privée
int _quantite;
 
};
#endif 

 

   La fonction operator++() renverra un objet Arme_de_traits. A cet effet, on crée la variable temporaire ''temp'' qui recevra la valeur de l'objet à incrémenter. wink


      Fichier Surcharge_3.cpp 

 

//projet 110Surcharge_3
//Fichier : Surcharge_3.cpp
//Fonction main()
//Surcharge de l'opérateur de pré-incrémentation operator++
 
#include <iostream>
#include "Surcharge_3.h"
#include <conio.h>
 
using namespace std;
 
 
int main()
{
//Création de l'objet arc_Composite
Arme_de_traits arc_Composite;
 
cout << endl;
cout << "La quantite d'arc(s) composite(s) est " << arc_Composite.get_Quantite() << endl;
 
//On incrémente l'objet arc_Composite avec l'appel de la fonction operator++()
++arc_Composite;
cout << endl;
cout << "La quantite d'arcs composites est " << arc_Composite.get_Quantite() << endl;
 
//On incrémente un objet arc_Composite et on l'affecte à un nouvel objet
Arme_de_traits arc_long = ++arc_Composite;
 
cout << endl;
cout << "Apres increment et affectation, la quantite d'arcs longs est " << arc_long.get_Quantite() << endl;
cout << "Et la quantite d'arcs composites : " << arc_Composite.get_Quantite() << endl;
 
_getch();
return 0;
} 

 

     Et dans la fonction main(), après incrémentation, la valeur de la variable temporaire arc_composite est affectée à l'objet arc_long.

   Ce qui donne comme résultat dans la console :

 

 

    2.4 – Le pointeur ''this''

 

   Késako ? blush Pas de panique, il n'a jamais mordu personne et nous allons de suite voir de quoi il retourne. laugh

   Chaque classe possède un pointeur dénommé ''this'' et ce pointeur pointe sur un objet créé par la classe. Jusqu'ici tout va bien puisque vous savez ce qu'est une classe ainsi qu'un objet créé par une classe. wink

   Supposez le pointeur this pointant vers un objet. Une fois déréférencé, soit *this, il devient alors l'objet lui-même qui peut-être renvoyé. cheeky

   Le pointeur this est automatiquement ajouté à toutes les fonctions membres d'une classe, même aux opérateurs surchargés que nous avons vus jusqu'à présent. Peut-être voyez-vous où je veux en venir ? angel Nous venons, dans l'exemple précédent (Projet 110Surcharge_3), de voir un objet temporaire renvoyé à partir d'un opérateur surchargé. Nous pouvons donc, à la place de renvoyer un objet temporaire, renvoyer le pointeur déréférencé *this lui-même. wink

   Voyons ceci dans un nouvel exemple :

 

 

      2.5 – Renvoi du pointeur this déréférencé (*this)

 

   Exemple : Projet 111Surcharge_4

      Fichier Surcharge_4.h 

 

//projet 111Surcharge_4
//Fichier : Surcharge_4.h
 
//Déclaration de la classe Arme_de_traits
//Surcharge de l'opérateur de pré-incrémentation ++
//Renvoi du pointeur déréférencé *this
 
#ifndef DEF_ARME_DE_TRAITS
#define DEF_ARME_DE_TRAITS
 
#include <string>
 
 
class Arme_de_traits
{
public:
Arme_de_traits() : _quantite(1) {}
 
~Arme_de_traits() {}
 
// Fonctions membres d'accès public
int get_Quantite() const { return _quantite; };
void Set_Quantite(int x) { _quantite = x; }
 
//Surcharge de l'opérateur ++
const Arme_de_traits& operator++()
{
++_quantite;
// Le pointeur déréférencé *this retourne l'objet incrémenté
return *this;
}
 
private:
//Donnée membre privée
int _quantite;
 
};
#endif 

 

   Le fichier Surcharge_4.h est quelque peu différent du fichier Surcharge_3.h. En effet, nous définissons cette fois une fonction operator++() renvoyant une référence à un objet Arme_de_traits. De plus, cette référence est constante: En effet, cette valeur ne pouvant pas être modifiée par la fonction qui va utiliser l'objet Arme_de_trait renvoyé, nous devons utiliser une référence constante. wink

 

   Fichier Surcharge_4.cpp

  Le fichier Surcharge_4.cpp ainsi que les résultats dans la console étant les mêmes que ceux du projet 110Surcharge_3, ils ne seront donc pas reproduits ici.

 


      2.6 – Utiliser une fonction amie pour surcharger un opérateur d'incrémentation

 

   Reprenons notre classe Fibonacci. Il est tout-à-fait possible d'implémenter la fonction de pré-incrémentation operator++() en tant que fonction amie de la classe Fibonacci. Il faut cependant savoir que les fonctions amies n'ont pas le même accès automatique aux données d'une classe que les fonctions membres de cette classe. Les arguments doivent, par conséquent, leur être explicitement passés. wink

   La fonction de surcharge d'opérateur devant modifier une ou plusieurs valeurs de la classe dont elle est amie, il est préférable de modifier ces valeurs dans la classe d'origine plutôt que dans une copie locale de la classe. Dès lors, nous passerons une référence sur la classe à la fonction amie de surcharge d'opérateur.  

   Et la déclaration de cette fonction amie de la classe Fibonacci se présentera de la façon suivante :

friend void operator++(Fibonacci &);

 

   Voyons ceci par un exemple :

      Exemple : Projet 112Surcharge_5

         Fichier Surcharge_5.h 

  

//projet 112Surcharge_5
//Fichier : Surcharge_5.h
//Déclaration de la classe Fibonacci
//Implémentation d'une fonction amie operator++(Fibonacci &)
#ifndef DEF_FIBONACCI
#define DEF_FIBONACCI
 
using ULLong = unsigned long long;
 
class Fibonacci
{
public:
 
// Constructeur par défaut
Fibonacci()
{
nombre1 = 0;
nombre2 = 1;
result = nombre1 + nombre2;
}
 
// Constructeur de copie
Fibonacci(Fibonacci &rhs)
{
nombre1 = rhs.nombre1;
nombre2 = rhs.nombre2;
result = rhs.result;
}
 
// Fonction amie operator++()
friend void operator++(Fibonacci &param)
{
param.nombre1 = param.nombre2;
param.nombre2 = param.result;
param.result = param.nombre1 + param.nombre2;
}
 
// Fonction d'affichage
void Affiche() { std::cout << result; }
 
 
private:
//Donnée membre privée
ULLong nombre1;
ULLong nombre2;
ULLong result;
 
};
#endif 

 

   En analysant ce fichier header, nous nous apercevons qu'il n'est pas très différent du fichier Surcharge_2.h du projet 109Surchage_2.

   La fonction friend void operator++(Fibonacci &) doit cependant passer une adresse explicite à la classe et nous aurons dès lors accès aux éléments de données à l'aide de l'opérateur pointwink

 

   Fichier Surcharge_5.cpp

   Et le fichier Surcharge_5.cpp est bien entendu identique à celui du projet 109Surcharge_2, donc nous ne reproduirons pas le contenu de ce fichier ici ni les résultats de la console qui sont également les mêmes que ceux de ce précédent projet.

 

   Que l'on utilise une fonction amie ou une fonction membre pour surcharger un opérateur n'aura aucune incidence sur la fonction main(). Celle-ci restera identique dans les deux cas. Les fonctions membres sont un peu plus simple à utiliser mais nous verrons dans le prochain chapitre que l'utilisation de fonctions amies est essentielle dans certains cas de surcharge d'opérateurs binaires. wink

   Allez, pourquoi pas un petit challenge ? angel

 

   Exercice 2 (un petit challenge)

   En vous basant sur le projet 112Surcharge_5, créez une classe ''Nombre''.

   Cette classe comprendra un constructeur par défaut, ainsi qu'une fonction amie friend void operator++(Nombre&)

   Dans la fonction main() créez un objet unNombre à l'aide du constructeur par défaut et un objet autreNombre à l'aide du constructeur de copie par défaut. La fonction operator++() devra calculer le double du carré des nombres de 1 à 20.

 

 

      2.7 – Surcharge de l'opérateur de post-incrémentation (valeur) ++

 

   Surcharger un opérateur de post-incrémentation est quelque peu différent de la surcharge d'un opérateur de pré-incrémentation. En effet, en nous référant au paragraphe 2.1 de ce chapitre, nous voyons que réaliser une opération de post-incrémentation signifie que l'on doit en premier affecter la valeur avant de l'incrémenter.

   Considérons l'expression y = x++ avec par exemple 2 comme valeur de y et 3 comme valeur de x. Nous devons donc en premier affecter x à y et ensuite incrémenter x. Ceci veut dire que y prendra la valeur 2 + 3 soit 5 et qu'après affectation x sera incrémenté et prendra la valeur de 3 + 1 = 4.

   Il s'ensuit que lors de la surcharge d'un opérateur de post-incrémentation, il nous faudra créer un objet temporaire qui va premièrement recevoir la valeur de l'objet à incrémenter, ensuite seulement procéder à l'incrémentation avant de renvoyer l'objet temporaire. wink

   Il reste cependant à différentier l'écriture de la déclaration d'une fonction de surcharge d'opérateur de post-incrémentation par rapport à celle d'un opérateur de pré-incrémentation car ce sera au compilateur de déterminer la différence.

   Par convention, on ajoute un paramètre entier à l'intérieur des parenthèses de la déclaration d'une fonction de post-incrémentation, valeur qui ne sera pas prise en compte par le compilateur mais qui servira seulement à reconnaître cette opération.

   Soit operator++(int) pour cette dernière.

   Nous allons maintenant écrire un simple exemple d'une classe ''Nombre'' qui nous montrera comment réaliser l'implémentation de ce qui vient d'être dit plus haut. angel

 

   Exemple : Projet 113Surcharge_6

      Fichier Surcharge_6.h 

 

//projet 113Surcharge_6
//Fichier : Surcharge_6.h
//Déclaration de la classe Nombre
//Comparaison des surcharges des opérateurs de pré et post-incrémentation ++
 
#ifndef DEF_NOMBRE
#define DEF_NOMBRE
 
 
class Nombre
{
public:
Nombre() : _valeur(0) {}
 
~Nombre() {}
 
// Fonctions membres d'accès public
int get_Val() const { return _valeur; };
void Set_Val(int x) { _valeur = x; }
 
//Surcharge de l'opérateur de pré-incrémentation ++
const Nombre& operator++()
{
++_valeur;
// Le pointeur déréférencé *this retourne l'objet incrémenté
return *this;
}
 
//Surcharge de l'opérateur de post-incrémentation ++
const Nombre operator++(int param)
{
Nombre valeur_locale(*this);
++_valeur; // On peut aussi écrire ++ *this;
// On retourne l'objet local
return valeur_locale;
}
 
 
private:
//Donnée membre privée
int _valeur;
 
};
#endif 

 

     Dans notre fichier Surcharge_6.h, la différence avec ce que nous avons vu jusqu'à présent est l'implémentation de la fonction de surcharge de l'opérateur de post-incrémentation.

   Je rappelle que la variable entière passée en paramètre à la fonction ne sera pas prise en compte par le compilateur mais n'a été ajoutée que pour différentier la déclaration de cette fonction avec une déclaration de fonction de surcharge d'opérateur de pré-incrémentation.

   L'objet local valeur_locale doit avoir la valeur de l'objet à incrémenter. Nous avons vu que le pointeur this pointait vers l'objet lui-même, donc nous assignons l'objet à valeur_locale, c'est-à-dire le pointeur déréférencé *this en écrivant Nombre valeur_locale(*this) soit Nombre valeur_locale = *this

   La valeur de l'objet étant la variable privée _valeur, nous incrémentons celle-ci ou le pointeur déréférencé, et pour terminer nous retournons la variable locale.

 

   Fichier Surcharge_6.cpp 

 

//projet 113Surcharge_6
//Fichier : Surcharge_6.cpp
//Fonction main()
//Surcharge des opérateurs de pré et post-incrémentation operator++
 
#include <iostream>
#include "Surcharge_6.h"
#include <conio.h>
 
using namespace std;
 
 
int main()
{
//Création de l'objet unNombre
Nombre unNombre;
 
cout << endl;
cout << " Valeur de l'objet unNombre : " << unNombre.get_Val() << endl;
unNombre++;
cout << " unNombre++ = " << unNombre.get_Val() << endl << endl;
 
//On crée un objet autreNombre et on lui affecte ++unNombre
cout << " On cree un objet autreNombre et on lui affecte ++unNombre" << endl;
Nombre autreNombre = ++unNombre;
cout << " autreNombre = " << autreNombre.get_Val() << endl;
cout << " unNombre = " << unNombre.get_Val() << endl << endl;
 
//Maintenant on affecte unNombre++ à autreNombre
autreNombre = unNombre++;
cout << " Maintenant on affecte unNombre++ a autreNombre" << endl;
cout << " autreNombre = " << autreNombre.get_Val() << endl;
cout << " unNombre = " << unNombre.get_Val() << endl;
 
_getch();
return 0;
} 

 

     La fonction main() ne fait qu'afficher une suite de résultats se rapportant à tout ce qui vient d'être dit auparavant.

   Vous pouvez maintenant vous-même rechercher les valeurs des objets unNombre et autreNombre dans la fonction main() en sachant que le constructeur de la classe Nombre crée l'objet unNombre en lui assignant la valeur 0.

   Comparez ensuite vos résultats avec les résultats de la console ci-dessous :

 

 

 

   5 – Corrigés des exercices du chapitre 17

 

   Exercice 1

   Pour cet exercice on demandait :

   Créez une classe ''Guerrier_des_Plaines'' et une instance de classe. Dans la fonction main() vous comptabiliserez 10 guerriers à l'aide d'un compteur.
   Le compteur devra se faire de deux manières :

1) En utilisant une fonction membre d'accès publique et une variable privée.

2) En utilisant une fonction et une variable déclarées ''public'' suivant le schéma de l'exemple 103Class_10.


   Voici le code :

      Fichier Chap17Exercice_1.h

 

//projet Chap17Exercice_1
//Fichier : Chap17Exercice_1.h
//Déclaration de la classe Guerrier_des_Plaines
//Utilisation d'une fonction et variable statiques en accès publique
#ifndef DEF_GUERRIER_DES_PLAINES
#define DEF_GUERRIER_DES_PLAINES
 
#include <string>
 
class Guerrier_des_Plaines
{
public:
 
// Fonction membre d'accès public
int get_Quantite();
 
//Fonction compteur()
void Compteur();
 
 
//Fonction et variable statiques
static void Second_compteur();
static int increment;
 
private:
//Donnée membre privée
int _ctr = 0;
 
};
#endif 

 

   Voyons le contenu du fichier Chap17Exercice_1.h. Pour la première partie de l'énoncé, nous avons besoin d'une fonction membre d'accès publique, une fonction Compteur() et une variable d'accès privé représentant un compteur.

   Pour la seconde partie, nous déclarons une fonction statique Second_compteur() ainsi qu' une variable statique d'incrémentation, toutes deux en accès publique.

 

      Fichier Chap17Exercice_1.cpp    

 

//projet Chap17Exercice_1
//Fichier : Chap17Exercice_1.cpp
//Définition des fonctions de la classe Guerrier_des_Plaines
//Utilisation d'une fonction et variable statiques en accès publique
 
#include "Chap17Exercice_1.h"
 
using namespace std;
 
 
// Fonction membre d'accès public
int Guerrier_des_Plaines::get_Quantite() { return _ctr; }
 
//Fonction compteur()
void Guerrier_des_Plaines::Compteur() { _ctr++; }
void Guerrier_des_Plaines::Second_compteur() { ++increment; };
 
//La variable statique doit être initialisée dans la fichier de définition
int Guerrier_des_Plaines::increment = 0; 

 

   Dans le fichier Chap17Exercice_1.cpp, nous définissons les fonctions et (ne pas oublier ! indecision), nous initialisons ici notre variable d'incrémentation. wink


      Fichier Chap17Exercice_1mainFile.cpp

 

//projet Chap17Exercice_1
//Fichier : Chap17Exercice_1mainFile.cpp
//Fonction main()
//Utilisation d'une fonction et variable statiques en accès publique
 
#include <iostream>
#include "Chap17Exercice_1.h"
#include <conio.h>
 
#include <vector>
 
using namespace std;
 
 
int main()
{
Guerrier_des_Plaines unGuerrier;
 
for (auto i = 0; i!= 10; ++i)
{
//On comptabilise 10 guerriers des plaines
unGuerrier.Compteur();
}
 
cout << endl;
cout << unGuerrier.get_Quantite() << " guerriers des plaines' ont ete " << endl;
cout << "comptabilises a l'aide d'une methoded'acces publique" << endl;
cout << endl;
 
 
//Maintenant on compte à l'aide de la fonction statique Second_compteur()
 
//Celle-ci n'étant pas liée à un objet, on appelle le nom de la classe
//suivi de l'opérateur de résolution de portée et du nom de la fonction statique.
for (auto i = 0; i != 10; ++i)
Guerrier_des_Plaines::Second_compteur();
 
cout << Guerrier_des_Plaines::increment;
cout << " unites, sans reference a un objet ont ete comptees a l'aide d'une" << endl;
cout << "fonction statique" << endl;
_getch();
return 0;
} 

 

    Dans la fonction main(), nous créons un objet Guerrier_des_Plaines et nous appelons dix fois la fonction Compteur(). Comme cette fonction incrémente à chaque fois la variable privée ctr, nous avons accès au nombre de fois que cette fonction a été appelée à l'aide de la méthode d'accès publique get_Quantite().

   Et les explications concernant la seconde partie de l'énoncé se retrouvent en commentaires dans la fonction main() ci-dessus. wink

   Ce qui donne dans la console :

 

Remarque : Le but de cet exercice n'est pas de créer 10 objets unGuerrier, mais de voir les différentes possibilités d'implémentation d'un compteur. Nous verrons bientôt ceci avec la surcharge d'opérateurs.


   Exercice 2 (Un petit challenge)

   Pour cet exercice il fallait créer les classes ''Menu'' et ''Personne''. Un nom de type string d'un vector de la classe Personne devait être envoyé dans la classe menu. Ceci pouvait se faire en implémentant une fonction amie des deux classes agissant comme pont entre celles-ci.

 

  Voici le code :

      Fichier Menu.h  

 

//Un exemple de fonction amie comme Pont entre deux classes
//Ce programme transfère un nom de la classe Personne dans la classe Menu
//Projet Chap17Exercice_2
//Fichier Menu.h
#ifndef DEF_MENU
#define DEF_MENU
 
#include <string>
 
using std::string;
 
//Nous déclarons la classe Personne avant toute chose
//car la classe Menu y fait référence à l'intérieur
//de la fonction amie Send() et ce, afin que le compilateur sache qu'elle existe.
class Personne;
 
class Menu //déclare la classe Menu
{
public:
Menu(std::string nom); //constructeur
~Menu(); //destructeur
 
//méthodes d'accès publique
std::string Nom_Dans_Menu_GetName() const;
void Nom_Dans_Menu_SetName(std::string nom);
//DEFINITION DE LA FONCTION AMIE EN TANT QUE PONT ENTRE LES
//CLASSES MENU ET PERSONNE
friend void Send(Menu, Personne);
 
//données membres privées
private:
std::string _unNom;
 
};
 
#endif 

 

     Les commentaires de cet exercice sont sensiblement les mêmes que ceux du projet 106Class_13 du chapitre 17 (intervertir un membre privé entre les classes Guerrier_du_Nord et Guerrier_des_Plaines). Si vous avez bien suivi ce projet vous ne devriez avoir aucune difficulté à comprendre cet exercice, nous allons simplement en signaler les particularités.

 

   Fichier Personne.h 

 

 

//Un exemple de fonction amie comme Pont entre deux classes
//Ce programme transfère un nom de la classe Personne dans la classe Menu
//Projet Chap17Ex_2
//Fichier Personne.h
#ifndef DEF_PERSONNE
#define DEF_PERSONNE
 
#include <string>
#include <vector>
 
using std::vector;
 
//Nous déclarons la classe Menu avant toute chose
//car la classe Personne y fait référence à l'intérieur
//de la fonction amie Send() et ce, afin que le compilateur sache qu'elle existe.
class Menu;
 
class Personne
{
public:
//Constructeur à qui on passe une référence sur un vecteur chaîne
Personne(vector<std::string> &noms);
~Personne(); //Destructeur
 
//méthodes d'accès publique
std::string Nom_De_Personne_GetName(vector<std::string>&noms);
void ajouter_nom(vector<std::string> &noms);
void lire_noms(vector<std::string> &noms);
 
//DEFINITION DE LA FONCTION AMIE EN TANT QUE PONT ENTRE LES
//CLASSES MENU ET PERSONNE
friend void Send(Menu, Personne);
 
private:
std::string _unNom;
 
};
#endif 

 

    Remarquons le constructeur de la classe Personne qui prend une référence sur un vecteur string en paramètre.


      Fichier Menu.cpp 

 

//Un exemple de fonction amie comme Pont entre deux classes
//Ce programme transfère un des noms du vecteur de la classe Personne dans la classe Menu
//Projet Chap17Ex_2
//Fichier Menu.cpp
 
#include "Menu.h"
using std::string;
 
//constructeur de Menu
Menu::Menu(string nom)
{
_unNom = nom;
}
 
//destructeur de Menu
Menu::~Menu() {}
 
//Menu::Nom_Dans_Menu_GetName(), méthode d'accès publique
//renvoie la valeur du membre _unNom;
 
string Menu::Nom_Dans_Menu_GetName() const
{
return _unNom;
}
 
 
//définition de Menu::Nom_Dans_Menu_SetName(), méthode d'accès publique
//définit le membre _unNom
void Menu::Nom_Dans_Menu_SetName(string nom)
{
//initialise la variable membre _unNom
//avec la valeur passée par le paramètre nom
_unNom = nom;
} 

 

    Fichier Personne.cpp

 

//Un exemple de fonction amie comme Pont entre deux classes
//Ce programme transfère un des noms du vecteur de la classe Personne dans la classe Menu
//Projet Chap17Ex_2
//Fichier Personne.cpp
 
#include "Personne.h"
#include <iostream>
 
using std::cout;
using std::string;
using std::endl;
 
//constructeur de Personne (le constructeur ajoute 4 noms dans le vector)
Personne::Personne(vector<string>&noms)
{
noms.push_back("Kalikan");
noms.push_back("Jay81");
noms.push_back("Gondulzak");
noms.push_back("tankerpat");
}
 
//destructeur de Personne
Personne::~Personne() {}
 
//Personne::Nom_De_Personne_GetName(), méthode d'accès publique
//renvoie la valeur du nom recherché dans le vector
//et passée au membre privé _unNom;
string Personne::Nom_De_Personne_GetName(vector<string>&noms)
{
for (auto &i : noms)
if (i == "Jay81")
_unNom = i;
 
return _unNom;
}
 
 
void Personne::ajouter_nom(vector<string>&noms) {}
 
 
//Lecture des éléments du vector noms
void Personne::lire_noms(vector<string>&noms)
{
for (auto &i : noms)
cout << i << endl;
} 

 

   Le fichier Personne.cpp requiert une ou deux explications. Premièrement nous implémentons le constructeur en ajoutant quatre noms dans le vector.

   Dans la méthode Nom_de_Personne_GetName(), nous parcourons le vector à l'aide d'un itérateur nous permettant de retrouver le nom que nous désirons passer à la classe Menu (ici Jay81). Nous passons ensuite ce nom à la variable privée _unNom.

 

      Fichier Pont.cpp  

 

//projet Chap17Ex_2
//Fichier : Pont.cpp
//Définition de la fonction Send()
//Utilisation d'une fonction commune comme pont entre deux classes
 
#include<iostream>
#include "Menu.h"
#include "Personne.h"
 
using std::cout;
using std::endl;
using std::string;
 
 
//DEFINITION DE LA FONCTION PONT Send(Menu, Personne)
//Cette fonction, déclarée dans les classes "Personne" et "Menu", agit comme "Pont" entre
//ces deux classes et permet le transfert d'une chaine du vector "noms" de la classe
//"Personne" vers la classe "Menu".
void Send(Menu nom_dans_menu, Personne nom_de_personne)
{
vector<string>noms;
Personne nom_de_personne(noms);
 
cout << "\nDans la classe Personne, le nom du Personnage choisi est : " << nom_de_personne.Nom_De_Personne_GetName(noms) << endl;
 
cout << "\nMaintenant on fait passer le nom de la classe Personne dans une variable de la" << endl;
cout << "classe Menu" << endl;
nom_dans_menu.Nom_Dans_Menu_SetName(nom_de_personne.Nom_De_Personne_GetName(noms));
cout << "\nDans la classe Menu, le nom du Personnage est : " << nom_dans_menu.Nom_Dans_Menu_GetName() << endl;
} 

 

   Et dans la fonction Send() du fichier Pont.cpp, on fait passer dans la classe Menu le nom choisi récupéré par la méthode Nom_De_Personne_GetName() de la classe Personne à l'aide de la méthode Nom_Dans_Menu_SetName() de la classe Menu.


      Fichier MainFile.cpp 

 

//Un exemple de fonction amie comme Pont entre deux classes
//Ce programme transfère un des noms du vecteur de la classe Personne dans la classe Menu
//Projet Chap17Ex_2
//Fichier MainFile.cpp
 
#include <iostream>
#include <string>
#include <vector>
#include "Menu.h"
#include "Personne.h"
 
#include <conio.h>
 
using namespace std;
 
//Créer un objet Menu et un objet Personne.
//On définit un nom dans la classe Personne et on appelle la fonction Send()
//pour faire passer ce nom dans la classe Menu.
int main()
{
//Déclaration d'une chaîne et d'un vector vide
string chaine;
vector<string>noms;
 
//On crée un objet de chaque classe avec le paramètre de leur constructeur.
Menu nom_dans_menu(chaine);
Personne nom_de_personne(noms);
 
//On affiche les éléments du vector à l'aide de la fonction membre lire_noms()
nom_de_personne.lire_noms(noms);
cout << endl;
 
//Appel de la fonction Pont qui se charge d'envoyer Jay depuis "Personne" vers "Menu"
Send(nom_dans_menu, nom_de_personne);
 
_getch();
return 0;
} 

 

   La fonction Main() crée les objets nom_dans_menu et nom_de_personne des classes Menu et Personne. Elle affiche les éléments contenus dans le vector et appelle la fonction Send() en lui passant les deux objets en paramètres.

   Très intéressant, non ? wink Maintenant vous devrez quand-même bien réfléchir à la pertinence ou non de créer une fonction amie entre deux classes. Ce n'est bien entendu à faire que si une autre solution n'est pas envisageable.

   Ce qui nous donne dans la console : 

 

 

   Voilà, il n'y avait rien de bien difficile dans cet exercice. Je vous propose néanmoins d'en relire attentivement les commentaires et les explications afin de bien maîtriser la notion de ''pont'' entre deux classes.

 

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

      @ bientôt pour le chapitre 19 – Surcharge des opérateurs (2).                                                                     

            Gondulzak.  angel
 
 
 

Connexion

CoalaWeb Traffic

Today58
Yesterday282
This week852
This month3145
Total1742352

19/04/24