15 Surcharge d'opérateurs *
Lorsqu'on définit un type fraction, on souhaite évidemment disposer des
opérations arithmétiques de base sur ces objets, on souhaite même pouvoir
écrire dans un programme des expressions g = f*h+f où
f,g et h sont de type fraction. On
souhaite donc donner une nouvelle définition (surcharger) aux opérateurs
+ et *. C'est possible !
Seuls les opérateurs déjà définis peuvent être surchargés. De plus, les
arités et les priorités ne changent pas. La liste des opérateurs est
longue, voir [1, 3]. La surcharge d'opérateurs peut se
faire selon deux points de vue : comme fonction
globale ou comme fonction membre.
15.1 Approche ``fonction globale''
Etant donné op un opérateur unaire (par exemple +, -,
!, ++ etc.), l'expression ``op A'' peut
être vue comme la fonction globale operator op(T a) appliquée à
l'objet A.
Etant donné, op un opérateur binaire (par exemple +, -,
*, && etc.), l'expression ``A op B'' peut être
vue comme la fonction globale operator op(T a, T b) appliquée à
l'objet A et à l'objet B. Par exemple, on pourrait définir :
fraction operator -(fraction f) // def. operateur unaire -
{ return fraction(- f.Numerateur(),f.Denominateur());}
fraction operator *(fraction f, fraction g) // def. operateur binaire *
{ return fraction(f.Numerateur()*g.Numerateur(),
f.Denominateur()*g.Denominateur());}
Ces fonctions sont globales, elle ne sont donc pas définies (ni déclarées)
dans la classe mais en dehors. Elles n'ont donc pas accès aux champs
privés de fraction (à moins d'avoir été déclarée fonctions amies) et
nous devons donc utiliser (et définir) des fonctions de lecture des champs
num et den dans la classe fraction.
15.2 Approche ``fonction membre'' **
Étant donné op un opérateur unaire, une expression ``op A'' où
A est de type T peut être vue comme l'objet A sur
lequel est appliqué la fonction membre operator op().
De même, l'expression ``A op B'' peut être vue comme l'objet
A sur lequel est appliqué la fonction membre operator
op(T b) avec B comme argument. Par exemple, on pourrait avoir :
class fraction {
private :
int num,den;
public :
...
fraction operator -();
fraction operator *(fraction h);
};
fraction fraction::operator -() // def. operateur unaire -
{return fraction(-num,den);}
fraction fraction::operator *(fraction h) // def. operateur binaire *
{return fraction(num*h.num,den*h.den);}
15.3 Lequel choisir ?
En général, il est recommandé d'utiliser les fonctions membres pour
surcharger les opérateurs unaires. Pour les opérateurs binaires, on
distingue deux cas : si l'opérateur a un effet de bord sur un des opérandes
(par exemple, +=), il est préférable d'utiliser des fonctions
membre, cela souligne le fait qu'un opérateur est appliqué sur un
opérande. Pour les autres cas, les fonctions globales sont souvent
préférables pour utiliser des conversions; par exemple, si f est
de type fraction et qu'il existe des conversion int ->
fraction, alors f+3 et 3+f seront possibles si
+ est une fonction globale, mais pas si on utilise une fonction
membre...
Cependant certains opérateurs (par exemple, =, ->,
(),[]) ne peuvent être surchargés que par le biais d'une
fonction membre.
15.4 Surcharger cout <<
L'opérateur <<
ne peut être surchargé que de manière globale, le
profil de cet opérateur pour un type T est
`` ostream & << (ostream &, T )
'', c'est à dire une fonction
recevant en argument un flot de sortie et une valeur de type T et
retournant un flot de sortie (augmenté de la nouvelle valeur). Le fait de
retourner une valeur de flot permet d'emboiter les <<
successivement.
Dans le cas des fractions, on pourrait définir :
//-----------------------------------
ostream & operator <<(ostream & os,fraction f)
{
os << f.Numerateur() << "/" << f.Denominateur();
return os;
}
//-----------------------------------
La surcharge de l'opérateur est >>
est plus délicate :
voir [3].