Les surcharges d'opérateurs Ou comment se compliquer un peu la vie pour se la simplifier énormément…
Classes et types Une classe joue le rôle d'un type (struct) État + comportement Comportement : méthodes Les types de base aussi ! Types numériques : opérations arithmétiques et numériques + comportements de base : afficher / saisir
Classes et types Utiliser la même syntaxe pour un même comportement. Or, comportement = méthode… Et les opérateurs ? En C : mots-clef du langage , ou fonctions En C++ : tout est méthode !
Classes et types Un opérateur est une méthode. On peut le surcharger : adaptation à la classe Exemple avec les nombres complexes. //complex.h class Complex { double re, im; public : Complex(double=0.0,double=0.0); ~Complex(); Complex(const Complex &); // pas forcément utile void afficher() const; }
Exemple : Complex //complex.cpp #include "complex.h" #include <iostream> using namespace std; Complex::Complex(double r, double i):re(r),im(i) {} Complex::~Complex() {} Complex::complex(const Complex &z) { re = z.re; im = z.im; } void Complex::afficher() const cout << re << "+i" << im << endl; // version naïve
Exemple : Complex << est un opérateur, donc une méthode ! // main.cpp #include "complex.h" int main(int argc, char *argv[]) { Complex z(1.0,-3.5); z.afficher(); Au lieu de : cout << z << endl; return 0; } << est un opérateur, donc une méthode ! Ce serait bien plus pratique à utiliser
Opérateur << Utilisation générale : Compris comme : flot << expression; Compris comme : operator<<(flot, expression); Méthode non membre d'une classe Nom de méthode paramètres
Opérateur << : implémentation // complex.h class Complex { … friend ostream& operator<<(ostream&, const Complex &); }; // complex.cpp ostream& operator<<(ostream& os, const Complex &z) os << z.re << "+" << z.im << "i"; return os; } Donc déclarée amie de la classe Méthode non membre de classe Pas de this, un paramètre const car pas modifié Accède à re et im qui sont private
Opérateur << : implémentation // complex.cpp ostream& operator<<(ostream& os, const Complex &z) { os << z.re << "+" << z.im << "i"; return os; } Paramètre os : ostream& : référence vers un flot de sortie cout ou un fichier ! Le flot est modifié par l'écriture des données, il est donc retourné pour être traité de nouveau
Opérateur >> // complex.h class Complex { … friend istream& operator>>(istream&, Complex &); }; // complex.cpp istream& operator>>(istream& is, Complex &z) is >> z.re; is >> z.im; return is; } Le paramètre n'est plus const car modifié par la saisie !
Opérateurs mathématiques Sont également surchargeables : + - * / += -= *= /= Exemple avec + : operator+ Méthode membre ou méthode amie ? Méthode membre de classe : class Complex {… Complex operator+(const Complez &); // un seul paramètre ? } Méthode amie Class Complex { friend Complex operator+(const Complex &, const Complex &); Pas de piège : c'est simplement le type de retour de la méthode !
Operateur + : méthode membre // complex.h class Complex {… Complex operator+(const Complez &); // un paramètre ? }; //complex.cpp Complex Complex::operator+(const Complex &z) { Complex resul; resul.re = this->re + z.re; resul.im = im + z.im; return resul; } Oui, car appelé par un objet de type complexe : this est le premier terme Ou return(Complex(re+z.re,im+z.im));
Operateur + : méthode membre // main.cpp #include "complex.h" int main(int argc, char *argv[]) { Complex z1(1,1), z2(4,3), z3; z3 = z1+z2; // ok z3 = z1+z2+z3; // ok return 0; } Inconvénient : le premier terme de + doit être de classe Complex (car méthode membre) Compris comme : z3 = z1.operator+(z2); Compris comme : z3 = z1.operator+(z2.operator+(z3));
Operateur + : méthode amie // complex.h class Complex {… friend Complex operator+(const Complez &, const Complex &); }; //complex.cpp Complex operator+(const Complex &z1, const Complex &z2) { Complex resul; resul.re = z1.re + z2.re; resul.im = z1.im + z2.im; return resul; } Rappel : accède à re et im qui sont private ! Ou return(Complex(z1.re+z2.re,z1.im+z2.im));
Operateur + : méthode amie // main.cpp #include "complex.h" int main(int argc, char *argv[]) { Complex z1(1,1), z2(4,3), z3; z3 = z1+z2; // ok z3 = z1+z2+z3; // ok return 0; } Les 2 termes doivent pouvoir être convertis en Complex. Addition double+Complex ? Compris comme : z3 = operator+(z1,z2); Compris comme : z3 = operator+(z1,operator+(z2,z3));
Opérateurs modifiant l'objet Exemple : opérateur += Doit respecter l'associativité : retour de référence Retour de la valeur de l'objet modifié : c'est *this Prototype : Complex& operator+=(const Complex &); Complex& Complex::operator+=(const Complex& z) { re = re+z.re; // ou re += z.re; im = im+z.im; // ou im += z.im; return *this; }
Conversions Constructeurs de conversion Convertir une valeur d'un type (ou classe) T vers un type (ou classe) D :créer un constructeur de D ayant un argument de type T Application : //complex.h : Class Complex{… Complex(double r); }; //complexe.cpp Complex::Complex(double r):re(r),im(0) {} Assure la conversion double-> Complex
Conversions Méthodes de conversion Convertir un Complex en double (pour calcul de module par exemple) Méthode nommé operator T() où T est le type de destination : operator double(); Complex::operator double() { return sqrt((re*re)+(im*im)); } Sans type de retour (le nom l'indique)
Conversions : utilisation void f(double val) // fonction non membre de classe { cout << val << endl; } int main(int argc, char *argv[]) Complex z(1,1); f(z); // conversion en double 1.414
L'opérateur d'affectation = Similaire au constructeur par recopie : à fournir lorsque la classe contient des attributs de type pointeur // exemple.h class Toto {…}; class Exemple { Toto *var; public : Exemple(…); Exemple(const Exemple &); // par recopie ~Exemple(); Exemple& operator=(const Exemple&); // affectation };
L'opérateur d'affectation = //exemple.cpp Exemple& Exemple::operator=(const Exemple &source) { if (var) delete var; } var = new Toto(*(source.var)); return *this; source Exemple Toto (à recopier) var Toto (recopié) Exemple Toto (avant affectation) this var
L'opérateur d'affectation = Et si &source == this ??? (auto-affectation) Exemple& Exemple::operator=(const Exemple &source) { if (var) delete var; } var = new Toto(*(source.var)); return *this; source Exemple Toto (à recopier) var Exemple Toto (avant affectation) this var
L'opérateur d'affectation = //exemple.cpp Exemple& Exemple::operator=(const Exemple &source) { if (&source != this) if (var) delete var; } var = new Toto(*(source.var)); return *this;
L'opérateur [] Indexation : choisir le type de l'index et le type de retour : sécuriser les accès à un tableau class TableauSec { long *valeurs; // plus tard, on mettra ce qu'on veut long taiMax; long taiUtil; public : TableauSec(long=10); TableauSec(const TableauSec&); ~TableauSec(); TableauSec& operator=(const TableauSec&); long& operator[](long); TableauSec& operator+(long); };
Digression : opérateur+ Ajouter un élément au tableau en faisant tab = tab+element ! Prototype : TableauSec& operator+(long); // dans tableausec.cpp TableauSec& TableauSec::operator+(long val) { if (taiUtil < taiMax) valeurs[taiUtil++] = val; } return *this; // retour du tableau modifié Message du Ministère du C++ : l'abus de surcharge peut être dangereux pour la santé mentale.
L'opérateur [] Prototype : long& operator[](long); // dans tableausec.cpp long& TableauSec::operator[](long index) { if ((index >=0) && (index < taiutil)) return valeurs[index]; } else // que faire ici ? On verra plus tard !