Jeux Libres
       
           

» Les Tutoriels » Apprenez à programmer en C++ ! » La surcharge d'opérateurs

Note : Vous vous apprêtez à lire un tutoriel de M@teo21 et Nanoc initialement publié à cette adresse sous la licence Creative Commons BY-NC-SA 2.0.

La surcharge d'opérateurs


On l'a vu, le langage C++ propose beaucoup de fonctionnalités qui peuvent se révéler très utiles, si on arrive à s'en servir correctement.

Une des fonctionnalités les plus étonnantes est la surcharge des opérateurs, que nous allons étudier dans ce chapitre. C'est une technique qui permet de réaliser des opérations mathématiques intelligentes entre vos objets lorsque vous utilisez dans votre code des symboles comme +, -, *, etc.

Au final, votre code sera plus court et plus clair, et gagnera donc en lisibilité vous allez voir. :)





Chapitre précédent     Sommaire     Chapitre suivant


Petits préparatifs


Qu'est-ce que c'est ?



Le principe est très simple. Supposons que vous ayez créé une classe pour stocker une durée (ex. : 4h23m), et que vous avez 2 objets de type Duree. Vous voulez les additionner entre eux pour connaître la durée totale.

En temps normal, il faudrait créer une fonction "additionner" :
1
resultat = additionner(duree1, duree2);

La fonction additionner ferait ici la somme de duree1 et duree2 et stockerait ça dans resultat.
Ca fonctionne, mais ce n'est pas franchement lisible. Ce que je vous propose dans ce chapitre, c'est d'être capable d'écrire ça :
1
resultat = duree1 + duree2;

En clair, on fait ici comme si nos objets étaient de simples "nombres". Mais comme un objet c'est plus complexe qu'un nombre (vous avez eu l'occasion de vous en rendre compte :p ), il va falloir expliquer à l'ordinateur comment effectuer l'opération.

La classe Duree pour nos exemples



Toutes les classes ne sont pas forcément adaptées à la surcharge d'opérateurs. Ainsi, ajouter des objets de type Personnage entre eux serait pour le moins un peu louche. o_O
Nous allons donc changer d'exemple, ça sera l'occasion de vous aérer un peu l'esprit sinon vous allez finir par croire que le C++ ne sert qu'à créer des RPG. :D

Cette classe Duree sera capable de stocker des heures, des minutes et des secondes. Rassurez-vous, c'est une classe relativement facile à écrire (plus facile que Personnage en tout cas !), ça ne devrait vous poser aucun problème si vous avez compris les chapitres précédents.

Duree.h


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef DEF_DUREE
#define DEF_DUREE
 
class Duree
{
   public:
 
   Duree(int heures = 0, int minutes = 0, int secondes = 0);
 
   private:
 
   int m_heures;
   int m_minutes;
   int m_secondes;
};
 
#endif

Chaque objet de type Duree stockera un certain nombre d'heures, minutes et secondes.

Vous noterez que j'ai utilisé des valeurs par défaut au cas où l'utilisateur aurait la flemme de les préciser. :-°
On pourra donc créer un objet de plusieurs façons différentes :
1
2
3
4
Duree chrono; // Pour stocker 0 heures, 0 minutes et 0 secondes
Duree chrono(5); // Pour stocker 5 heures, 0 minutes et 0 secondes
Duree chrono(5, 30); // Pour stocker 5 heures, 30 minutes et 0 secondes
Duree chrono(0, 12, 55); // Pour stocker 0 heures, 12 minutes et 55 secondes

Duree.cpp



L'implémentation de notre constructeur est expédiée en 30 secondes montre en main. ^^
1
2
3
4
5
#include "Duree.h"
 
Duree::Duree(int heures, int minutes, int secondes) : m_heures(heures), m_minutes(minutes), m_secondes(secondes)
{
}

Et dans main.cpp ?



Pour l'instant notre main.cpp ne va déclarer que 2 objets de type Duree, que j'initialise un peu au pif :
1
2
3
4
5
6
int main()
{
   Duree duree1(0, 10, 28), duree2(0, 15, 2);
 
   return 0;
}

Voilà, nous sommes prêts à affronter les surcharges d'opérateurs maintenant !

Les plus perspicaces d'entre vous auront remarqué que rien ne m'interdit de créer un objet avec 512 minutes et 1455 secondes. En effet, on peut écrire Duree chrono(0, 512, 1455); sans être inquiété. Normalement, cela devrait être interdit, ou tout du moins notre constructeur devrait être assez intelligent pour "découper" les minutes et les convertir en heures/minutes, et de même pour les secondes, afin qu'elles ne dépassent pas 60.
Je ne le fais pas ici, mais je vous encourage à modifier votre constructeur pour faire cette conversion si nécessaire, ça vous entraînera ! Etant donné qu'il faut faire des if et quelques petites opérations mathématiques dans le constructeur, vous ne pourrez pas utiliser que la liste d'initialisation.


Les opérateurs arithmétiques (+, -, *, /, %)


Nous allons commencer par voir les opérateurs mathématiques les plus classiques, à savoir l'addition, la soustraction, la multiplication, la division et le modulo.
Une fois que vous aurez appris à vous servir de l'un d'entre eux, vous verrez que vous saurez vous servir de tous les autres. ;)

Pour être capable d'utiliser le symbole "+" entre 2 objets, vous devez créer une fonction ayant précisément pour nom operator+ qui a pour prototype :
1
Objet operator+(Objet const& a, Objet const& b);

Même si l'on parle de classe, ceci n'est pas une méthode. C'est une fonction normale située à l'extérieur de toute classe.


La fonction reçoit deux références sur les objets (constantes, donc on ne peut pas les modifier) à additionner.
A coté de notre classe Duree, on doit donc rajouter cette fonction (ici dans le .h) :
1
Duree operator+(Duree const& a, Duree const& b);

C'est la première fois que vous utilisez des références constantes. Dans la sous-partie sur les références, je vous avais expliqué que lors d'un passage par référence, la variable (ou l'objet) n'est pas copié. Notre classe Duree contient trois entiers, utiliser une référence permet donc d'éviter la copie inutile de ces trois entiers. Ici, le gain est assez négligeable, mais si vous prenez un objet de type string, il peut contenir un très long texte. La copie prendra alors un temps important. C'est pour cela que lorsque l'on manipule des objets, on préfère utiliser des références. Cependant, on aimerait bien que les fonctions ou méthodes ne modifient pas l'objet reçu. C'est pour cela que l'on utilise une référence constante.
Quand on fait [math=inline]a+b[/math], [math=inline]a[/math] et [math=inline]b[/math] ne doivent pas être modifiés. Le mot-clé const est donc essentiel ici.

Mode d'utilisation



Comment ça marche ce truc ? o_O


Dès le moment où vous avez créé cette fonction operator+, vous pouvez additionner 2 objets de type Duree entre eux :
1
resultat = duree1 + duree2;

Ce n'est pas de la magie. En fait le compilateur "traduit" ça par :
1
resultat = operator+(duree1, duree2);

... ce qui est beaucoup plus classique et compréhensible pour lui :D
Il appelle donc la fonction operator+ en passant duree1 et duree2 en paramètre. La fonction, elle, va retourner un résultat de type Duree.

Les opérateurs raccourcis



Ce n'est pas le seul moyen d'effectuer une addition ! Rappelez-vous des versions raccourcies des opérateurs. A côté de +, il y avait += et de même pour les autres. Contrairement à + qui est une fonction, += est une méthode de la classe. Voici son prototype :
1
Duree& operator+=(Duree const& duree);

Elle reçoit en argument une autre Duree à additionner et renvoie une référence sur l'objet lui-même. Nous verrons plus loin à quoi sert cette référence.

Nous voici donc avec deux manières d'effectuer une addition.
1
2
3
resultat = duree1 + duree2; //Utilisation de operator+
 
duree1 += duree2;           //Utilisation de la mthode operator+= de l'objet duree1

Vous vous en doutez peut-être les corps de ces fonctions seront très semblables. Si l'on sait faire le calcul avec +, il ne faut qu'une petite modification pour obtenir celui de += et vice-versa. C'est somme toute deux fois la même opération mathématique.

Les programmeurs sont des fainéants et écrire deux fois la même fonction est vite ennuyeux. C'est pourquoi on va généralement utiliser une de ces deux opérations pour définir l'autre. Et la règle veut que l'on définisse operator+ en appelant la méthode operator+=.

Mais comment est-ce possible ?


Prenons un exemple plus simple que des Duree. Des int par exemple et analysons ce qui se passe quand on cherche à les additionner.
1
2
int a(4), b(5), c(0);
c = a + b;  //c vaut 9

On prend la variable a, on lui ajoute b et on met le tout dans c. Ce qui revient presque à écrire :
1
2
3
int a(4), b(5), c(0);
a += b;
c = a;   //c vaut 9 mais a vaut maintenant aussi 9

La différence étant que dans ce deuxième exemple, la variable a a changé de valeur. Si par contre on effectue une copie de a avant de la modifier, ce problème disparaît.
1
2
3
4
int a(4), b(5), c(0);
int copie(a);
copie += b;
c = copie; //c vaut 9 et a vaut toujours 4

Le même principe est valable pour * et *=, - et -=, etc.



On peut donc effectuer l'opération + en faisant une copie suivi d'un +=. C'est ce principe que l'on va utiliser pour définir la fonction operator+ pour notre classe Duree. Des fois il faut réfléchir beaucoup pour être fainéant. :p
1
2
3
4
5
6
Duree operator+(Duree const& a, Duree const& b)
{
   Duree copie(a);
   copie += b;
   return copie;
}

Et voilà ! Il ne nous reste plus qu'à définir la méthode operator+=. ;)

Ce passage est peut-être un peu difficile à saisir au premier abord. L'élément important dont il faut se rappeler c'est la manière dont on écrit la définition de operator+ en utilisant la méthode operator+=. Vous pourrez toujours revenir plus tard sur la justification.


Implémentation de +=



L'implémentation n'est pas vraiment compliquée, mais il va quand même falloir réfléchir un peu. En effet, ajouter des secondes, minutes et heures ça va, mais il faut faire attention à la retenue si ça dépasse 60.
Je vous recommande d'essayer d'écrire la méthode vous-même, c'est un excellent exercice algorithmique, ça entretient le cerveau, ça vous rend meilleur programmeur (je vous ai convaincus là ? :D ).

Voici ce que donne mon implémentation pour ceux qui ont besoin de la solution :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Duree& Duree::operator+=(const Duree &duree2)
{
   // 1 : ajout des secondes
   m_secondes += duree2.m_secondes; // Exceptionnellement autoris car mme classe
   // Si le nombre de secondes dpasse 60, on rajoute des minutes et on met un nombre de secondes infrieur 60
   m_minutes += m_secondes / 60;
   m_secondes %= 60;
 
   // 2 : ajout des minutes
   m_minutes += duree2.m_minutes;
   // Si le nombre de minutes dpasse 60, on rajoute des heures et on met un nombre de minutes infrieur 60
   m_heures += m_minutes / 60;
   m_minutes %= 60;
 
   // 3 : ajout des heures
   m_heures += duree2.m_heures;
 
   return *this;
}

Ce n'est pas un algorithme ultra-complexe, mais comme je vous avais dit il faut réfléchir un tout petit peu pour pouvoir l'écrire quand même. ;)

Comme nous sommes dans une méthode de la classe, nous pouvons directement modifier les attributs. On va y ajouter les heures, minutes et secondes de l'objet reçu en paramètre, à savoir duree2. On a ici exceptionnellement le droit d'accéder directement aux attributs de cet objet car on se trouve dans une méthode de la même classe. C'est un peu tordu mais ça nous aide bien (sinon il aurait fallu créer des méthodes "accesseur" comme getHeures()).

Rajouter les secondes, c'est facile. Mais ensuite on doit rajouter un reste si on a dépassé 60 secondes (donc rajouter des minutes). Je ne vous explique pas comment ça fonctionne dans le détail, je vous laisse vous remuer les méninges un peu, ce n'est vraiment pas bien difficile (c'est du niveau des tous premiers chapitres du cours ^^ ). Vous noterez que c'est un cas où l'opérateur modulo (%), à savoir le reste de la division, est très utile.

Bref, on fait de même avec les minutes, et quant aux heures c'est encore plus facile vu qu'il n'y a pas de reste (on peut dépasser les 24 heures donc pas de problème).

Finalement, il n'y a que la dernière ligne qui devrait vous surprendre. La méthode renvoie l'objet lui-même à l'aide de *this. this est un mot-clé particulier du langage dont nous reparlerons dans un prochain chapitre. C'est un pointeur vers l'objet qu'on est en train de manipuler. Cette ligne peut-être traduite en français par : "Renvoie l'objet pointé par le pointeur this". Les raisons profonde de l'existence cette ligne ainsi que de la référence comme type de retour sont assez compliquées. Au niveau de ce cours, prenez ça comme une recette de cuisine pour vos opérateurs.

Quelques tests



Pour mes tests, j'ai dû rajouter une méthode afficher() à la classe Duree (elle fait un cout de la durée tout bêtement).

Voilà mon bôôô main :) :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include "Duree.h"
 
using namespace std;
 
int main()
{
   Duree duree1(0, 10, 28), duree2(0, 15, 2);
   Duree resultat;
 
   duree1.afficher();
   cout << "+" << endl;
   duree2.afficher();
 
   resultat = duree1 + duree2;
 
   cout << "=" << endl;
   resultat.afficher();
 
   return 0;
}

Et le tant attendu résultat à l'écran :
0h10m28s

+

0h15m2s

=

0h25m30s

Cool, ça marche. :)
Bon mais ça c'était trop facile, il n'y avait pas de reste dans mon calcul. Corsons un peu les choses avec d'autres valeurs :
1h45m50s

+

1h15m50s

=

3h1m40s

Yeahhh ! Ça marche ! (et du premier coup pour moi nananère :-° ).
J'ai bien entendu testé d'autres valeurs pour être bien sûr que ça fonctionnait, mais de toute évidence ça marche très bien et mon algo est donc bon. :D

Bon, on en viendrait presque à oublier l'essentiel dans tout ça. Tout ce qu'on a fait là, c'était pour pouvoir écrire cette ligne :
1
resultat = duree1 + duree2;

La surcharge de l'opérateur + nous a permis de rendre notre code clair, simple et lisible, alors qu'on aurait dû utiliser une méthode en temps normal.

N'oublions pas non plus l'opérateur +=. On peut tout à fait l'utiliser directement.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include "Duree.h"
 
using namespace std;
 
int main()
{
   Duree duree1(0, 10, 28), duree2(0, 15, 2);
 
   duree1.afficher();
   cout << "+=" << endl;
   duree2.afficher();
 
   duree1 += duree2;      //Utilisation directe de l'oprateur +=
 
   cout << "=" << endl;
   duree1.afficher();
 
   return 0;
}

Ce code affiche bien sûr la même chose que notre premier test. Je vous laisse essayer d'autres valeurs pour vous convaincre que tout est correct. ;)

Télécharger le projet



Pour ceux d'entre vous qui n'auraient pas bien suivi la procédure, ou qui sont tout simplement fainéants ( :-° ), je vous propose de télécharger le projet contenant :

  • main.cpp
  • Duree.cpp
  • Duree.h
  • Ainsi que le fichier .cbp de Code::Blocks (si vous utilisez cet IDE comme moi)


Bonus track #1



Ce qui est vraiment sympa dans tout ça, c'est que tel que notre système est fait, on peut très bien additionner plusieurs durées en même temps sans aucun problème.

Par exemple, je rajoute juste une troisième durée dans mon main et je l'additionne avec les autres :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main()
{
   Duree duree1(1, 45, 50), duree2(1, 15, 50), duree3 (0, 8, 20);
   Duree resultat;
 
   duree1.afficher();
   cout << "+" << endl;
   duree2.afficher();
   cout << "+" << endl;
   duree3.afficher();
 
   resultat = duree1 + duree2 + duree3;
 
   cout << "=" << endl;
   resultat.afficher();
 
   return 0;
}
1h45m50s

+

1h15m50s

+

0h8m20s

=

3h10m0s

C'est cool non vous trouvez pas ?
En fait, la ligne-clé :
1
resultat = duree1 + duree2 + duree3;

... revient à écrire :
1
resultat = operator+(operator+(duree1, duree2), duree3);

Le tout s'imbrique dans une logique implacable et vient se placer finalement dans l'objet resultat. ^^

Notez que le C++ ne vous permet pas de changer la priorité des opérateurs.


Bonus track #2



Et pour notre seconde bonus track, sachez qu'on n'est pas obligé d'additionner des Duree avec des Duree, du temps que ça reste logique et compatible.
Par exemple, on pourrait très bien additionner une Duree et un int. On considérerait dans ce cas que le nombre int est un nombre de secondes à ajouter.

Cela nous permettra d'écrire par exemple :
1
resultat = duree + 30;

Vive la surcharge des fonctions et des méthodes! La fonction operator+ se défini en utilisant le même "truc" qu'avant :
1
2
3
4
5
6
Duree operator+(Duree const& duree, int secondes)
{
   Duree copie(duree);
   copie += secondes;
   return copie;
}

et tous les calculs sont reportés dans la méthode operator+=, comme précédemment.
1
Duree& operator+=(int secondes);

... mais vous croyiez tout de même pas que j'allais vous écrire l'implémentation. Allez hop hop hop au boulot ! :p

Les autres opérateurs arithmétiques



Maintenant que vous avez vu assez en détail le cas d'un opérateur (celui d'addition pour ceux qui ont la mémoire courte :-° ), vous allez voir que pour la plupart des autres opérateurs c'est très facile et qu'il n'y a pas de difficulté supplémentaire. Le tout est de s'en servir correctement pour la classe que l'on manipule.

Ces opérateurs sont du même "type" que l'addition. Vous les connaissez déjà :

  • La soustraction (-)
  • La multiplication (*)
  • La division (/)
  • Le modulo (%), c'est-à-dire le reste de la division

Pour surcharger ces opérateurs, rien de plus simple : créez une fonction dont le nom commence par operator suivi de l'opérateur en question. Cela donne donc :

  • operator-()
  • operator*()
  • operator/()
  • operator%()

Avec bien sûr les versions raccourcies correspondantes sous forme de méthodes.

  • operator-=()
  • operator*=()
  • operator/=()
  • operator%=()

Pour notre classe Duree, il peut être intéressant de définir la soustraction (operator-).
Je vous laisse le soin de le faire, en vous basant sur l'addition ça ne devrait pas être trop compliqué. ;)

En revanche, les autres opérateurs ne servent a priori à rien : en effet, on ne multiplie pas des durées entre elles, et on les divise encore moins. Comme quoi, tous les opérateurs ne sont pas utiles à toutes les classes : ne définissez donc que ceux qui vous seront vraiment utiles.

Si multiplier une Duree par une Duree n'a pas de sens, en revanche on peut imaginer que l'on multiplie une Duree par un nombre entier. Ainsi, l'opération 2h25m50s * 3 est envisageable. Attention à utiliser le bon prototype, en l'occurence :
1
Duree operator*(Duree const& duree, int nombre);



Les opérateurs de flux (<<)


Parmi les nombreuses choses qui ont dû vous choquer quand vous avez commencé le C++, dans la catégorie "oulah c'est bizarre ça mais on verra plus tard", il y a l'injection dans les flux d'entrée-sortie. Derrière ce nom barbare se cachent ces petits symboles >> et <<.
Quand les utilise-t-on ? Allons allons, vous n'allez pas me faire croire que vous avez la mémoire si courte. ;)
1
2
cout << "Coucou !";
cin >> variable;

Figurez-vous justement que << et >> sont des opérateurs. Le code ci-dessus revient donc à écrire :
1
2
operator<<(cout, "Coucou !");
operator>>(cin, variable);

On a donc fait appel aux fonctions operator<< et operator>> ! :)

Définir ses propres opérateurs pour cout



Nous allons ici nous intéresser plus particulièrement à l'opérateur << utilisé avec cout.
Les opérateurs de flux sont définis par défaut pour les types de variables int, double, char, ainsi que pour les objets comme string. C'est ainsi que l'on peut aussi bien écrire :
1
cout << "Coucou !";

... que :
1
cout << 15;

(et c'est là qu'on dit "merci la surcharge des fonctions !" :p )

Bon, le problème c'est que cout ne connaît pas votre classe flambant neuve Duree, et donc qu'il ne possède pas de fonction surchargée pour les objets de ce type. On ne peut donc pas écrire :
1
2
Duree chrono(0, 2, 30);
cout << chrono; // Erreur : il n'existe pas de fonction operator<<(cout, Duree &duree)

Qu'à cela ne tienne, nous allons écrire cette fonction !

Quoi ?! Mais on ne peut pas modifier le code de la bibliothèque standard ?


Déjà si vous vous êtes posé la question, bravo, c'est que vous commencez à bien vous repérer. En effet, c'est une fonction utilisant un objet de la classe ostream (dont cout est une instance) que l'on doit définir, et on n'a pas accès au code correspondant.

Lorsque vous incluez <iostream>, un objet cout est automatiquement déclaré comme ceci :
1
ostream cout;

ostream est la classe, cout est l'objet.


On ne peut pas modifier la classe ostream, mais on peut très bien écrire une fonction qui reçoit un de ces objets en argument. Voyons donc comment écrire cette fameuse fonction.

Implémentation d'operator<<



Commencez par écrire la fonction :
1
2
3
4
5
ostream& operator<<( ostream &flux, Duree const& duree )
{
   flux << duree.m_heures << "h" << duree.m_minutes << "m" << duree.m_secondes << "s"; // Erreur
   return flux;
}

Comme vous pouvez le voir, c'est similaire à operator+, sauf qu'ici le type de retour est une référence et pas un objet.

Le premier paramètre (référence sur un objet de type ostream) qui vous sera automatiquement passé est en fait l'objet cout (que l'on appelle ici flux dans la fonction pour éviter les conflits de nom). Le second paramètre est une référence constante vers l'objet de type Duree que vous tentez d'afficher en utilisant l'opérateur <<.

La fonction doit récupérer les attributs qui l'intéressent dans l'objet et les envoyer à l'objet flux (qui n'est autre que cout). Ensuite, elle retourne une référence sur cet objet, ce qui permet de pouvoir faire une chaîne :
1
cout << duree1 << duree2;

Si je compile ça plante ! Ça me dit que je n'ai pas le droit d'accéder aux attributs de l'objet duree depuis la fonction !



Eh oui c'est parfaitement normal, car on est à l'extérieur de la classe, et les attributs m_heures, m_minutes et m_secondes sont privés. On ne peut donc pas les lire de cet endroit du code.

3 solutions :

  • Ou bien vous créez des accesseurs comme on l'a vu (ces fameuses méthodes getHeures(), getMinutes(), ...), ça marche bien mais c'est un peu ennuyeux à écrire.
  • Ou bien utiliser le concept d'amitié, que nous verrons dans un autre chapitre.
  • Ou bien vous utilisez la technique que je vais vous montrer. ;)

On va opter ici pour la troisième solution. :p
Changez la 1ère ligne de la fonction comme ceci :
1
2
3
4
5
ostream &operator<<( ostream &flux, Duree const& duree)
{
   duree.afficher(flux) ; // <- Changement ici
   return flux;
}

Et rajoutez une méthode afficher dans la classe Duree.
Prototype à mettre dans Duree.h :
1
void afficher(std::ostream &flux) const;

Implémentation de la méthode dans Duree.cpp :
1
2
3
4
void Duree::afficher(ostream &flux) const
{
   flux << m_heures << "h" << m_minutes << "m" << m_secondes << "s";
}

On passe donc le relai à une méthode à l'intérieur de la classe, qui, elle, a le droit d'accéder aux attributs. La méthode prend en paramètre la référence vers l'objet flux pour pouvoir lui envoyer les valeurs qui nous intéressent. Ce qu'on n'a pas pu faire dans la fonction operator<<, on le donne à faire à une méthode de la classe Duree. Exactement comme pour operator+ en somme ! On a délégué le travail à une méthode de la classe qui elle a accès aux attributs.

Ouf ! Maintenant dans le main, que du bonheur !



Bon, c'était un peu gymnastique, mais maintenant c'est que du bonheur. :D
Vous allez pouvoir dans votre main afficher vos objets de type Duree très simplement :
1
2
3
4
5
6
7
8
int main()
{
   Duree duree1(2, 25, 28), duree2(0, 16, 33);
   
   cout << duree1 << " et " << duree2 << endl;
 
   return 0;
}

Résultat :
2h25m28s et 0h16m33s

Enfantin. ^^
Comme quoi, on prend un peu de temps pour écrire la classe, mais ensuite quand on doit l'utiliser c'est extrêmement simple !

Et l'on peut même combiner nos opérateurs dans une seule expression. Faire une addition et afficher le résultat directement :
1
2
3
4
5
6
7
8
int main()
{
   Duree duree1(2, 25, 28), duree2(0, 16, 33);
   
   cout << duree1 + duree2 << endl;
 
   return 0;
}

Comme pour les int, double, etc. Nos objets deviennent réellement simples à utiliser.


Les opérateurs de comparaison (==, >, <, ...)


Ces opérateurs vont vous permettre de comparer des objets entre eux. Le plus utilisé d'entre eux est probablement l'opérateur d'égalité (==) qui permet de vérifier si 2 objets sont égaux. C'est à vous d'écrire le code de la méthode qui détermine si les objets sont identiques, l'ordinateur ne peut pas le deviner pour vous car il ne connaît pas la "logique" de vos objets. ;)

Tous ces opérateurs de comparaison ont un point en commun particulier : ils renvoient un booléen (bool). C'est normal, ces opérateurs répondent à des questions du type "a est-il plus grand que b ?" ou "a est-il égal à b?"

L'opérateur ==



On va écrire l'implémentation de l'opérateur d'égalité pour commencer. Vous allez voir qu'on va beaucoup s'inspirer de la technique utilisée pour l'opérateur <<. Le recyclage des idées c'est bien. ;)
1
2
3
4
5
6
7
8
bool operator==(Duree const& a, Duree const& b)
{
   //Teste si a.m_heure == b.m_heure etc.  
   if (a.m_heures == b.m_heures && a.m_minutes == b.m_minutes && a.m_secondes == b.m_secondes)
       return true;
   else
       return false;
}

On compare à chaque fois un attribut de l'objet dans lequel on se trouve avec un attribut de l'objet auquel on se compare (les heures avec les heures, les minutes avec les minutes...). Si ces 3 valeurs sont identiques alors on peut considérer que les objets sont identiques et renvoyer true (vrai).

Sauf qu'il y a un petit souci. Il nous faudrait lire les attributs des objets a et b. Comme le veut la règle, ils sont privés et donc inaccessible depuis l'extérieur de la classe. Appliquons donc la même stratégie que pour l'opérateur <<.
On commence par créer une méthode estEgal() qui renvoie true si b est égal à l'objet dont on a appelé la méthode.
1
2
3
4
5
6
7
8
bool Duree::estEgal(Duree const& b) const
{
   //Teste si a.m_heure == b.m_heure etc.  
   if (m_heures == b.m_heures && m_minutes == b.m_minutes && m_secondes == b.m_secondes)
       return true;
   else
       return false;
}

Et on utilise cette méthode dans notre opérateur d'égalité :
1
2
3
4
bool operator==(Duree const& a, Duree const& b)
{
   return a.estEgal(b);
}

Dans le main, on peut faire un simple test de comparaison pour vérifier si on a fait les choses correctement :
1
2
3
4
5
6
7
8
9
10
11
int main()
{
   Duree duree1(0, 10, 28), duree2(0, 10, 28);
 
   if (duree1 == duree2)
       cout << "Les durees sont identiques";
   else
       cout << "Les durees sont differentes";
 
   return 0;
}

Résultat :
Les durees sont identiques

L'opérateur !=



Tester l'égalité, c'est bien, mais parfois on aime savoir si deux objets sont différents. On écrit alors un opérateur !=. Celui-là, il est très simple à écrire. :) Pour tester si deux objets sont différents, il suffit de tester si ils ne sont pas égaux !
1
2
3
4
5
6
7
bool operator!=(Duree const& a, Duree const& b)
{
   if(a == b)         //On utilise l'oprateur == qu'on a dfini prcdemment !
       return false;  //Si ils sont gaux, alors ils ne sont pas diffrents
   else
       return true;   //Et si ils ne sont pas gaux, c'est qu'ils sont diffrents ;-)
}

Je vous avais dit que ce serait facile. Réutiliser des opérateurs déjà écrits est une bonne habitude à prendre. D'ailleurs on l'avait déjà fait pour + qui utilisait +=.

L'opérateur <



Je vous préviens on va pas tous les faire sinon on y est encore demain. ^^

Si l'opérateur == peut s'appliquer à la plupart des objets, il n'est pas certain que l'on puisse dire de tous nos objets qui est le plus grand. Tous n'ont pas forcément une notion de grandeur, prenez par exemple notre classe Personnage, il serait je pense assez stupide de vouloir vérifier si un Personnage est "inférieur" à un autre ou non (à moins que vous ne compariez les vies... à vous de voir).

En tout cas avec la classe Duree on a de la chance, il est facile et "logique" de vérifier si une Duree est inférieure à une autre.

Voici mon implémentation pour l'opérateur "est strictement inférieur à" (<) :
1
2
3
4
5
6
7
bool operator<(Duree const &a, Duree const& b)
{
   if(a.estPlusPetitQue(b))
       return true;
   else
       return false;
}

Et la méthode estPlusPetitQue() de la classe Duree :
1
2
3
4
5
6
7
8
9
10
11
bool Duree::estPlusPetitQue(Duree const& b) const
{
   if (m_heures < b.m_heures)
       return true;
   else if (m_heures == b.m_heures && m_minutes < b.m_minutes)
       return true;
   else if (m_heures == b.m_heures && m_minutes == b.m_minutes && m_secondes < b.m_secondes)
       return true;
   else
       return false;
}

Avec un peu de réflexion on finit par trouver cet algorithme, il suffit d'activer un peu ses méninges. ;)
Vous noterez que la méthode renvoie false si les durées sont identiques : c'est normal, car il s'agit de l'opérateur "strictement inférieur à" (<). En revanche, si ça avait été la méthode de l'opérateur "inférieur ou égal à" (<=), il aurait fallu renvoyer true.

Je vous laisse le soin de tester dans le main si ça fonctionne correctement. :)

Les autres opérateurs de comparaison




On ne va pas les écrire ici, ça surchargerait inutilement. Mais comme pour != et ==, il suffit d'utiliser correctement < pour tous les implémenter. Je vous invite à essayer de les implémenter pour notre classe Duree, ça fera un bon exercice sur le sujet. Il reste notamment :

  • operator>()
  • operator<=()
  • operator>=()

Si vous avez un peu du mal à vous repérer dans le code, ce que je peux comprendre, je mets à votre disposition le projet complet comme tout à l'heure dans ce zip :





Il y a énormément d'autres opérateurs surchargeables en C++, en fait presque tout peut être surchargé. Chaque opérateur étant particulier, il serait impossible de tout voir dans ce chapitre. Au moins avons-nous pu voir les principaux. ;)

A titre d'information, sachez qu'il est aussi possible de surcharger :

  • new et delete : l'allocation dynamique, s'il y a besoin de faire des vérifications spéciales lors d'une allocation de mémoire
  • & et * : opérateurs d'indirection et de déréférencement pour manipuler les pointeurs
  • ++ et -- : opérateurs d'incrémentation et de décrémentation
  • [] : pour parcourir l'objet comme un tableau. Le type string s'en sert d'ailleurs pour que l'on puisse écrire monString[3] et ainsi accéder au 4ème caractère comme si c'était un tableau, alors que c'est en fait un objet. Malin, il fallait y penser !
  • etc.

Bref, vous l'aurez compris, la surcharge des opérateurs est un outil puissant, pour ne pas dire très puissant si on commence à s'en servir sur l'allocation dynamique ou encore les opérateurs d'indirection et de déréférencement.

Mon conseil serait : ne faites la surcharge que si elle vous sera vraiment utile. C'est certes un outil puissant, mais il n'est pas nécessaire de le mettre à toutes les sauces. Votre classe doit proposer des fonctionnalités utiles et non pas farfelues !



Chapitre précédent     Sommaire     Chapitre suivant



Distribué et adapté par David
Consulté 5723 fois



Hébergeur du site : David
Version PHP : 5.4.45-0+deb7u2
Uptime : 240 jours 11 heures 42 minutes
Espace libre : 1570 Mo
Dernière sauvegarde : inconnue
Taille de la sauvegarde : 1112 Mo


5357161 pages ont été consultées sur le site !
Dont 2689 pages pendant les 24 dernières heures.

Page générée en 0.613 secondes


Nos sites préférés
- Création d'un jeu de plateforme de A à Z avec SDL
- Zelda ROTH : Jeux amateurs sur le thème de Zelda
- Zeste de Savoir : la connaissance pour tous et sans pépins
- YunoHost : s'héberger soi-même en toute simplicité
- Site de Fvirtman : recueil de projets et de codes en C et C++
- Par ici la sortie : le site des idées de sorties


  © 2005-2019 linor.fr - Toute reproduction totale ou partielle du contenu de ce site est strictement interdite.