• 10 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 10/02/2022

Utilisez les opérateurs d'arithmétique et de flux

Utilisez les opérateurs arithmétiques

Voici les opérateurs arithmétiques classiques qu'il faut connaître :

  1. L'addition : +  .

  2. La soustraction :-  .

  3. La multiplication : *  .

  4. La division :  /  .

  5. 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. Attaquons donc avec l'addition.

Définissez l'opérateur d'addition  +

L'opérateur d'addition se définit comme ceci :

Duree operator+(Duree const& a, Duree const& b);

Une fois que la fonction est écrite, on peut effectuer des additions de  Duree  :

int main()
{
    Duree resultat, duree1, duree2;

    resultat = duree1 + duree2;

    return 0;
}

Réfléchissons un instant à ce que cet opérateur doit effectuer. Il doit créer un nouvel objet de type  Duree dont la valeur des attributs est la somme des valeurs des attributs de a et b  . C'est en tout cas ce que nous dit le prototype. Écrivons donc ceci dans le corps de l'opérateur :

Duree operator+(Duree const& a, Duree const& b)
{
    Duree resultat;
    // Calcul des valeurs des attributs de résultat
    return resultat;
}

Il ne nous reste plus qu'à effectuer l'addition proprement dite. C'est-à-dire prendre la valeur de  m_heures des objets a et b  , puis en effectuer la somme. Et le problème usuel se pose à nouveau : cet opérateur ne peut pas accéder aux attributs de a et de b  .

Je vous vois déjà venir et proposer la même solution que précédemment : créer une méthode qui additionne deux Duree et utiliser cette méthode dans notre opérateur, comme dans le cas de  ==  ou de  <  . On aurait par exemple :

Duree operator+(Duree const& a, Duree const& b)
{
    Duree resultat;
    resultat = a.calculeAddition(b);   //Utilise une méthode de Duree pour effectuer l'addition
    return resultat;
}

Si vous avez pensé à faire ça, c'est bien. Cela veut dire que vous commencez à penser "orienté objet" et que vous commencez à maîtriser les notions d'encapsulation et de méthodes.

Cette manière de faire est correcte, mais n'est pas la meilleure manière de faire. Ce que je vais vous présenter est un poil plus complexe, mais aussi plus optimal.

Utilisez les opérateurs raccourcis

Avant de nous attaquer à nos problèmes d'accès aux attributs, revenons en arrière et jetons un œil aux différents opérateurs du C++.

Pour un entier, on pouvait écrire :

int a(2);
a += 4;
// a vaut maintenant 6

La différence est que cet opérateur change l'entier auquel il est appliqué, ce qui n'est pas le cas du  +  "normal" qui, lui, ne modifie pas ses deux opérandes.

Imaginons un instant que vous vouliez écrire un tel opérateur pour votre classe Duree  . Cet opérateur va devoir modifier l'objet qui l'utilise. Pour respecter l'encapsulation, il va donc falloir déclarer cet opérateur comme étant une méthode de la classe Duree  , et pas comme une fonction externe. Le prototype sera donc :

class Duree
{
// Le reste...
   void operator+=(Duree const& a);
};

D'ailleurs, comment on l'utilise, cette méthode ?

Comme dans le cas de  ==  ou de  +  , le compilateur fera le remplacement nécessaire :

Duree a(2,12,43), b(0,34,17);
a += b;

Ce que le compilateur traduira en :

Duree a(2,12,43), b(0,34,17);
a.operator+=(b);

Magique, je vous dis !

Essayons d'écrire le corps de cette méthode. 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 les secondes ou minutes dépassent 60.

Voici ce que donne mon implémentation :

void Duree::operator+=(const Duree& a)
{
    //1 : ajout des secondes
    m_secondes += a.m_secondes;
    //Si le nombre de secondes dépasse 60, on rajoute des minutes
    //Et on met un nombre de secondes inférieur à 60
    m_minutes += m_secondes / 60;
    m_secondes %= 60;

    //2 : ajout des minutes
    m_minutes += a.m_minutes;
    //Si le nombre de minutes dépasse 60, on rajoute des heures
    //Et on met un nombre de minutes inférieur à 60
    m_heures += m_minutes / 60;
    m_minutes %= 60;

    //3 : ajout des heures
    m_heures += a.m_heures;
}

Rajouter les secondes, c'est facile. Mais on doit ensuite ajouter un reste si on a dépassé 60 secondes (donc ajouter des minutes). Je ne vous explique pas comment cela fonctionne dans le détail, je vous laisse un peu vous remuer les méninges .

Bref, on fait de même avec les minutes ; 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).

Faites quelques tests

Avant de continuer, effectuons quelques tests. Pour ce faire, j'ai dû ajouter une méthode afficher() à la classe Duree  (qui fait un cout de la durée) :

#include <iostream>
#include "Duree.hpp"

using namespace std;

int main()
{
    Duree duree1(0, 10, 28), duree2(0, 15, 2);
    Duree resultat;

    duree1.afficher();
    cout << " et " << endl;
    duree2.afficher();

    duree1 += duree2;

    cout << " donne " << endl;
    duree1.afficher();

    return 0;
}

Et le résultat tant attendu :

0h10m28s
et 
0h15m2s
donne
0h25m30s

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

1h45m50s
et
1h15m50s
donne
3h1m40s

J'ai bien entendu testé d'autres valeurs pour être bien sûr que cela fonctionnait mais, de toute évidence, cela marche très bien et mon algorithme est donc bon.

Il y a un petit détail technique que j'ai laissé de côté jusque-là. Pour être parfait, notre opérateur ne devrait pas retourner void  , mais une référence sur une Duree  . Les raisons sont complexes et au-delà du niveau de ce cours. Prenez cela comme une règle à suivre si vous voulez continuer à briller parmi vos collègues programmeurs C++. La méthode devrait donc être :

Duree& Duree::operator+=(const Duree& a)
{
    // Comme avant... rien à changer ici
    return *this;
}

Sachez simplement que  return *this  va avec Duree&  , et qu'il fait partie des règles à appliquer pour les opérateurs raccourcis. La même règle s'appliquera donc aux opérateurs  *=  ,  /=  , etc.

Bon, on en viendrait presque à oublier l'essentiel dans tout cela. Tout ce qu'on a fait là, c'était pour pouvoir écrire cette ligne :

resultat = duree1 + duree2;

Revenez à vos moutons : l'opérateur  +

Nous voulions appeler une méthode qui effectue une addition dans notre fonction.

operator+

Ce qui tombe bien, c'est qu'on vient d'en définir une, de ces méthodes. Elle a un peu un nom barbare et elle modifie l'objet qui l'appelle, mais elle effectue une sorte d'addition. Essayons donc de l'utiliser dans notre opérateur  +  . Nous avions :

Duree operator+(Duree const& a, Duree const& b)
{
    Duree resultat;
    resultat = a.calculeAddition(b);   //Utilise une méthode de Duree pour effectuer l'addition
    return resultat;
}

Nous ne pouvons pas écrire directement a+=b puisque cela modifierait a  , ce que nous ne voulons pas. Et en plus, a est déclaré constant, donc le compilateur ne vous laissera pas faire.

Par contre, nous pouvons créer une copie de a et ensuite utiliser notre opérateur raccourci sur cette copie :

Duree operator+(Duree const& a, Duree const& b)
{
    Duree copie(a);   //On utilise le constructeur de copie de la classe Duree !
    copie += b;       //On appelle la méthode d'addition qui modifie l'objet 'copie'
    return copie;     //Et on renvoie le résultat. Ni a ni b n'ont changé.
}

Ce morceau de code n'est pas très simple à comprendre. Je vous invite à relire ce chapitre plusieurs fois si nécessaire. L'essentiel n'est pas forcément de comprendre tous les détails, mais plutôt de noter les règles importantes et de les appliquer dans vos projets.

Ce qui est vraiment sympa dans tout cela c'est que, tel que ce système est conçu, on peut très bien additionner d'un seul coup plusieurs durées sans aucun problème, ou même additionner une durée avec autre chose qu’une durée, par exemple avec un entier. Voyons tout cela dans la vidéo suivante :

Utilisez les autres opérateurs arithmétiques

Pour la plupart des autres opérateurs, 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à :

  1. La soustraction :  -  .

  2. La multiplication :  *  .

  3. La division :  /  .

  4. Le modulo :  %  , c'est-à-dire le reste de la division entière.

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 :

  1. operator-()  .

  2. operator*()  .

  3. operator/()  .

  4. operator%()  .

Nous devons bien sûr ajouter aussi les versions raccourcies correspondantes, sous forme de méthodes.

  1. operator-=()  .

  2. operator*=()  .

  3. operator/=()  .

  4. 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.

Utilisez les opérateurs de flux

Parmi les nombreuses choses qui ont dû vous choquer quand vous avez commencé le C++, dans la catégorie "Houlà, c'est bizarre cela mais on verra plus tard", il y a l'injection dans les flux d'entrée-sortie. Derrière ce nom barbare se cachent les symboles >>  et <<  .

Quand les utilise-t-on ?

Allons allons, vous n'allez pas me faire croire que vous avez la mémoire si courte :

cout << "Coucou !";
cin >> variable;

Figurez-vous justement que <<  et >>  sont des opérateurs. Le code ci-dessus revient donc à écrire :

operator<<(cout, "Coucou !");
operator>>(cin, variable);

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

Définissez vos propres opérateurs pour cout

Nous allons ici nous intéresser plus particulièrement à l'opérateur << utilisé avec cout  .

On peut en effet aussi bien écrire :

cout << "Coucou !";

… que :

cout << 15;

Le problème, c'est que cout ne connaît pas votre classe flambant neuve Duree  , et il ne possède donc pas de fonction surchargée pour les objets de ce type. Par conséquent, on ne peut pas écrire :

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.

On ne peut pas modifier la classe ostream  , mais ce n'est pas grave puisque tout ce qu'on veut faire, c'est écrire une fonction qui reçoit un de ces objets en argument. Voyons donc comment rédiger cette fameuse fonction.

Implémentez l' operator<<

Commencez par écrire la fonction :

ostream& operator<<( ostream &flux, Duree const& duree )
{
    //Affichage des attributs
    return flux;
}

C'est similaire à operator+ sauf qu'ici, le type de retour est une référence et non un objet :

  1. 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 noms).

  2. 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 renvoie une référence sur cet objet, ce qui permet de faire une chaîne :

cout << duree1 << duree2;

La suite, vous la connaissez :

  • Définir une méthode dans la classe Duree pour pouvoir accéder aux attributs et les afficher.

ostream &operator<<( ostream &flux, Duree const& duree)
{
    duree.afficher(flux) ;
    return flux;
}
  • Rajouter dans le fichier Duree.hpp une méthode afficher()  à la classe Duree  :

void afficher(std::ostream &flux) const;

Voici l'implémentation de la méthode dans Duree.cpp  :

void Duree::afficher(ostream &flux) const
{
    flux << m_heures << "h" << m_minutes << "m" << m_secondes << "s";
}

Affichez les objets de type  Duree  dans le main()

Bon, c'était un peu de gymnastique mais maintenant, ce n'est que du bonheur. Vous allez pouvoir afficher très simplement vos objets de type Duree dans votre main()  :

int main()
{
    Duree duree1(2, 25, 28), duree2(0, 16, 33);
    
    cout << duree1 << " et " << duree2 << endl;

    return 0;
}

Résultat :

2h25m28s et 0h16m33s

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 les opérateurs dans une seule expression, faire une addition et afficher le résultat directement :

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 simples à utiliser.

En résumé

  • La surcharge d'opérateurs permet de créer des méthodes pour modifier le comportement des symboles  +  ,  -  ,  *  ,  /  ,  >=  , etc.

  • Pour surcharger un opérateur arithmétique, on doit donner un nom précis à sa méthode ( operator+  pour le symbole  +  , par exemple).

  • Ces méthodes doivent prendre des paramètres et renvoyer parfois des valeurs d'un certain type précis. Cela dépend de l'opérateur que l'on surcharge.

  • Il ne faut pas abuser de la surcharge d'opérateur car elle peut avoir l'effet inverse du but recherché, qui est initialement de rendre la lecture du code plus simple.

Qu’est-ce qu’on va bien pouvoir apprendre dans le prochain chapitre ? … Et si on faisait un tour du côté des pointeurs ? J’ai évité le sujet jusqu'à maintenant, mais je pense que c’est le bon moment. J’espère que vous êtes prêt !

Exemple de certificat de réussite
Exemple de certificat de réussite