Surcharge d'Opération

Pour faciliter la lecture et l'écriture du code, la surcharge de certaines opérations est autorisée. Un opérateur surchargé est écrit avec le mot-clé operator. Les opérateurs suivants peuvent être surchargés :

  • binaire +,-,/,*,%,<<,>>,==,!=,<,>,<=,>=,=,+=,-=,/=,*=,%=,&=,|=,^=,<<=,>>=,&&,||,&,|,^
  • unaire +,-,++,--,!,~
  • opérateur d'assignation =
  • opérateur d'indexation []

 

La surcharge d'opération permet d'utiliser la notation des opérations (écrite sous la forme d'expressions simples) pour les objets complexes - structures et classes. L'écriture d'expressions en utilisant des opérations surchargées simplifie la lisibilité du code source car la complexité de l'implémentation est cachée.

Par exemple, considérons les nombres complexes qui consistent en une partie réelle et une partie imaginaire. Ils sont communément utilisés en mathématiques. Le langage MQL5 n'a pas de type de données pour représenter les nombres complexes, mais il est possible de créer un nouveau type de données sous la forme d'une structure ou classe. Déclaration de la structure complexe et définition de 4 méthodes qui implémentent quatre opérations arithmétiques :

//+------------------------------------------------------------------+
//| Structure pour utiliser les nombres complexes                    |
//+------------------------------------------------------------------+
struct complex
  {
   double            re; // Partie réelle
   double            im; // Partie imaginaire
   //--- Constructeurs
                     complex():re(0.0),im(0.0) {  }
                     complex(const double r):re(r),im(0.0) {  }
                     complex(const double r,const double i):re(r),im(i) {  }
                     complex(const complex &o):re(o.re),im(o.im) { }
   //--- Opérations arithmétiques
   complex           Add(const complex &l,const complex &r) const;  // Addition
   complex           Sub(const complex &l,const complex &r) const;  // Soustraction
   complex           Mul(const complex &l,const complex &r) const;  // Multiplication
   complex           Div(const complex &l,const complex &r) const;  // Division
  };

Nous pouvons maintenant déclarer dans notre code des variables représentant des nombres complexes et travailler avec eux.

Par exemple :

void OnStart()
  {
//--- Déclare et initialise des variables de type complexe
   complex a(2,4),b(-4,-2);
   PrintFormat("a=%.2f+i*%.2f,   b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- Addition de deux nombres
   complex z;
   z=a.Add(a,b);
   PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- Multiplication de deux nombres
   z=a.Mul(a,b);
   PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- Division de deux nombres
   z=a.Div(a,b);
   PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
  }

Mais il serait plus pratique d'utiliser les opérateurs habituels "+", "-", "*" et "/" pour les opérations arithmétiques ordinaires avec des nombres complexes.

Le mot-clé operator est utilisé pour définir une fonction membre qui effectue une conversion de type. Les opérations unaires et binaires pour des variables de type objet de classe peuvent être surchargées comme des fonctions membres non statiques. Ils agissent implicitement sur l'objet de classe.

La plupart des opérations binaires peuvent être surchargées comme des fonctions classiques qui prennent un ou deux arguments comme variable de classe ou un pointeur vers un objet de cette classe. Pour notre type complexe, la surcharge dans la déclaration ressemblera à :

   //--- Opérateurs
   complex operator+(const complex &r) const { return(Add(this,r)); }
   complex operator-(const complex &r) const { return(Sub(this,r)); }
   complex operator*(const complex &r) const { return(Mul(this,r)); }
   complex operator/(const complex &r) const { return(Div(this,r)); }

L'exemple complet du script :

//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Déclare et initialise des variables de type complexe
   complex a(2,4),b(-4,-2);
   PrintFormat("a=%.2f+i*%.2f,   b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
   //a.re=5;
   //a.im=1;
   //b.re=-1;
   //b.im=-5;
//--- Addition de deux nombres
   complex z=a+b;
   PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- Multiplication de deux nombres
 
   z=a*b;
   PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- Division de deux nombres
   z=a/b;
   PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
  }
//+------------------------------------------------------------------+
//| Structure pour utiliser les nombres complexes                    |
//+------------------------------------------------------------------+
struct complex
  {
   double            re; // Partie réelle
   double            im; // Partie imaginaire
   //--- Constructeurs
                     complex():re(0.0),im(0.0) {  }
                     complex(const double r):re(r),im(0.0) {  }
                     complex(const double r,const double i):re(r),im(i) {  }
                     complex(const complex &o):re(o.re),im(o.im) { }
   //--- Opérations arithmétiques
   complex           Add(const complex &l,const complex &r) const;  // Addition
   complex           Sub(const complex &l,const complex &r) const;  // Soustraction
   complex           Mul(const complex &l,const complex &r) const;  // Multiplication
   complex           Div(const complex &l,const complex &r) const;  // Division
   //--- Opérateurs binaires
   complex operator+(const complex &r) const { return(Add(this,r)); }
   complex operator-(const complex &r) const { return(Sub(this,r)); }
   complex operator*(const complex &r) const { return(Mul(this,r)); }
   complex operator/(const complex &r) const { return(Div(this,r)); }
  };
//+------------------------------------------------------------------+
//| Addition                                                         |
//+------------------------------------------------------------------+
complex complex::Add(const complex &l,const complex &r) const
  {
   complex res;
//---
   res.re=l.re+r.re;
   res.im=l.im+r.im;
//--- Résultat
   return res;
  }
//+------------------------------------------------------------------+
//| Soustraction                                                     |
//+------------------------------------------------------------------+
complex complex::Sub(const complex &l,const complex &r) const
  {
   complex res;
//---
   res.re=l.re-r.re;
   res.im=l.im-r.im;
//--- Résultat
   return res;
  }
//+------------------------------------------------------------------+
//| Multiplication                                                   |
//+------------------------------------------------------------------+
complex complex::Mul(const complex &l,const complex &r) const
  {
   complex res;
//---
   res.re=l.re*r.re-l.im*r.im;
   res.im=l.re*r.im+l.im*r.re;
//--- Résultat
   return res;
  }
//+------------------------------------------------------------------+
//| Division                                                         |
//+------------------------------------------------------------------+
complex complex::Div(const complex &l,const complex &r) const
  {
//--- Nombre complexe vide
   complex res(EMPTY_VALUE,EMPTY_VALUE);
//--- Vérifie si zéro
   if(r.re==0 && r.im==0)
     {
      Print(__FUNCTION__+" : le nombre est zéro");
      return(res);
     }
//--- Variables auxiliaires
   double e;
   double f;
//--- Sélection de la variante de calcul
   if(MathAbs(r.im)<MathAbs(r.re))
     {
      e = r.im/r.re;
      f = r.re+r.im*e;
      res.re=(l.re+l.im*e)/f;
      res.im=(l.im-l.re*e)/f;
     }
   else
     {
      e = r.re/r.im;
      f = r.im+r.re*e;
      res.re=(l.im+l.re*e)/f;
      res.im=(-l.re+l.im*e)/f;
     }
//--- Résultat
   return res;
  }

 

La plupart des opérations unaires pour les classes peuvent être surchargées comme des fonctions ordinaires qui acceptent un seul argument de type objet ou un pointeur dessus. Ajout de la surcharge des opérations unaires "-" et "!".

//+------------------------------------------------------------------+
//| Structure pour utiliser les nombres complexes                    |
//+------------------------------------------------------------------+
struct complex
  {
   double            re;       // Partie réelle
   double            im;       // Partie imaginaire
...
   //--- Opérateurs unaires
   complex operator-()  const// Opérateur Moins unaire
   bool    operator!()  const// Négation
  };
...
//+------------------------------------------------------------------+
//| Surcharge de l'opérateur unaire "Moins"                          |
//+------------------------------------------------------------------+
complex complex::operator-() const
  {
   complex res;
//---
   res.re=-re;
   res.im=-im;
//--- Résultat
   return res;
  }
//+------------------------------------------------------------------+
//| Surcharge de l'opérateur "négation logique"                      |
//+------------------------------------------------------------------+
bool complex::operator!() const
  {
//--- Les parties réelle et imaginaire du nombre complexe sont-elles égales à zéro ?
   return (re!=0 && im!=0);
  }

 

Nous pouvons maintenant vérifier la valeur d'un nombre complexe pour zéro et récupérer une valeur négative :

//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Déclare et initialise des variables de type complexe
   complex a(2,4),b(-4,-2);
   PrintFormat("a=%.2f+i*%.2f,   b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- Division des deux nombres
   complex z=a/b;
   PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//--- Un nombre complexe est égal à zéro par défaut (dans le constructeur par défaut re==0 et im==0)
   complex zero;
   Print("!zero=",!zero);
//--- Assigne une valeur négative
   zero=-z;
   PrintFormat("z=%.2f+i*%.2f,  zero=%.2f+i*%.2f",z.re,z.im, zero.re,zero.im);
   PrintFormat("-zero=%.2f+i*%.2f",-zero.re,-zero.im);
//--- Vérifie si zéro encore une fois  
   Print("!zero=",!zero);
//---
  }

Notez qu'il n'est pas besoin de surcharger l'opérateur d'assignement "=", puisque les structures de types simples peuvent être copiées directement l'une dans l'autre. Nous pouvons donc maintenant écrire du code pour les calculs impliquant des nombres complexes d'une manière habituelle.

La surcharge de l'opérateur d'indexation permet d'obtenir les valeurs des tableaux inclus dans un objet, d'une façon simple et familière, et permet également de contribuer à une meilleure lisibilité du code source. Par exemple, nous devons fournir l'accès à un symbole dans la chaîne de caractères à la position spécifiée. Une chaîne de caractères en MQL5 est représentée par le type string, qui n'est pas un tableau de symboles, mais grâce à un opérateur d'indexation surchargé, nous pouvons fournir une utilisation simple et transparente dans la classe CString générée :

//+------------------------------------------------------------------+
//| Classe d'accès aux symboles d'une string comme dans un tableau   |
//+------------------------------------------------------------------+
class CString
  {
   string            m_string;
  
public:
                     CString(string str=NULL):m_string(str) { }
   ushort operator[] (int x) { return(StringGetCharacter(m_string,x)); }
  };
//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()  
  {
//--- Un tableau pour prendre les symboles d'une chaîne de caractères
   int     x[]={ 19,4,18,19,27,14,15,4,17,0,19,14,17,27,26,28,27,5,14,
                 17,27,2,11,0,18,18,27,29,30,19,17,8,13,6 };
   CString str("abcdefghijklmnopqrstuvwxyz[ ]CS");
   string  res;
//--- Construit une phrase en utilisant les symboles de la variable str
   for(int i=0,n=ArraySize(x);i<n;i++)
     {
      res+=ShortToString(str[x[i]]);
     }
//--- Affiche le résultat
   Print(res);
  }

 

Un autre exemple de surcharge de l'opération d'indexation est les opérations sur les matrices. La matrice représente un tableau dynamique à 2 dimensions, dont la taille n'est pas définie à l'avance. Vous ne pouvez donc pas déclarer un tableau de la forme array[][] sans spécifier la taille de la deuxième dimension, et passer ensuite ce tableau comme paramètre. Une solution possible est une classe spéciale CMatrix, contenant un tableau d'objets de la classe CRow.

//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Addition et multiplication de matrices
   CMatrix A(3),B(3),C();
//--- Prépare un tableau pour les lignes
   double a1[3]={1,2,3}, a2[3]={2,3,1}, a3[3]={3,1,2};
   double b1[3]={3,2,1}, b2[3]={1,3,2}, b3[3]={2,1,3};
//--- Remplit les matrices
   A[0]=a1; A[1]=a2; A[2]=a3;
   B[0]=b1; B[1]=b2; B[2]=b3;
//--- Ecrit les matrices dans le journal des Experts
   Print("---- Eléments de la matrice A");
   Print(A.String());
   Print("---- Eléments de la matrice B");
   Print(B.String());
 
//--- Addition des matrices
   Print("---- Addition des matrices A et B");
   C=A+B;
//--- Affiche la représentation sous forme d'une chaîne de caractères
   Print(C.String());
 
//--- Multiplication des matrices
   Print("---- Multiplication des matrices A et B");
   C=A*B;
   Print(C.String());
 
//--- Affiche comment les valeurs sont récupérées comme dans des tableaux dynamiques matrix[i][j]
   Print("Affiche les valeurs des éléments de la matrice C");
//--- Parcours les rangées de la matrice - des objets CRow - dans une boucle
   for(int i=0;i<3;i++)
     {
      string com="| ";
      //--- Parcours les rangées de la matrice pour la valeur
      for(int j=0;j<3;j++)
        {
         //--- Récupère l'élément de la matrice par son numéro de ligne et de colonne
         double element=C[i][j];// [i] - Accès à l'élément CRow du tableau m_rows[] ,
                                // [j] - Opérateur surchargé pour l'indexation dans CRow
         com=com+StringFormat("a(%d,%d)=%G ; ",i,j,element);
        }
      com+="|";
      //--- Affiche les valeurs de la rangée
      Print(com);
     }
  }
//+------------------------------------------------------------------+
//| Classe "Row"                                                     |
//+------------------------------------------------------------------+
class CRow
  {
private:
   double            m_array[];
public:
   //--- Constructeurs et un destructeur
                     CRow(void)          { ArrayResize(m_array,0);    }
                     CRow(const CRow &r) { this=r;                    }
                     CRow(const double &array[]);
                    ~CRow(void){};
   //--- Nombre d'éléments dans la rangée
   int               Size(voidconst    { return(ArraySize(m_array));}
   //--- Retourne une chaîne de caractères avec les valeurs  
   string            String(voidconst;
   //--- Opérateur d'indexation
   double            operator[](int i) const  { return(m_array[i]);   }
   //--- Opérateurs d'assignement
   void              operator=(const double  &array[]); // An array
   void              operator=(const CRow & r);         // Another CRow object
   double            operator*(const CRow &o);          // CRow object for multiplication
  };
//+------------------------------------------------------------------+
//| Constructeur pour initialiser une rangée à partir d'un tableau   |
//+------------------------------------------------------------------+
void  CRow::CRow(const double &array[])
  {
   int size=ArraySize(array);
//--- Si le tableau n'est pas vide
   if(size>0)
     {
      ArrayResize(m_array,size);
      //--- Remplissage avec les valeurs
      for(int i=0;i<size;i++)
         m_array[i]=array[i];
     }
//---
  }
//+------------------------------------------------------------------+
//| Opération d'assignement pour le tableau                          |
//+------------------------------------------------------------------+
void CRow::operator=(const double &array[])
  {
   int size=ArraySize(array);
   if(size==0) return;
//--- Remplissage du tableau avec les valeurs
   ArrayResize(m_array,size);
   for(int i=0;i<size;i++) m_array[i]=array[i];
//--- 
  }
//+------------------------------------------------------------------+
//| Opération d'assignement pour CRow                                |
//+------------------------------------------------------------------+
void CRow::operator=(const CRow  &r)
  {
   int size=r.Size();
   if(size==0) return;
//--- Remplissage du tableau avec les valeurs
   ArrayResize(m_array,size);
   for(int i=0;i<size;i++) m_array[i]=r[i];
//--- 
  }
//+------------------------------------------------------------------+
//| Opérateur de multiplication par une autre rangée                 |
//+------------------------------------------------------------------+
double CRow::operator*(const CRow &o)
  {
   double res=0;
//--- Vérifications
   int size=Size();
   if(size!=o.Size() || size==0)
     {
      Print(__FUNCSIG__," : Echec de la multiplication de deux matrices, leurs tailles sont différentes");
      return(res);
     }
//--- Multiplie les tableaux élément par élément et ajoute les produits
   for(int i=0;i<size;i++)
      res+=m_array[i]*o[i];
//--- Résultat
   return(res);
  }
//+------------------------------------------------------------------+
//| Retourne une une chaîne de caractères formatée                   |
//+------------------------------------------------------------------+
string CRow::String(voidconst
  {
   string out="";
//--- Si la taille du tableau est supérieure à zéro
   int size=ArraySize(m_array);
//--- On ne travaille qu'avec un tableau contenant des éléments
   if(size>0)
     {
      out="{";
      for(int i=0;i<size;i++)
        {
         //--- Collecte les valeurs dans une chaîne de caractères
         out+=StringFormat(" %G;",m_array[i]);
        }
      out+=" }";
     }
//--- Résultat
   return(out);
  }
//+------------------------------------------------------------------+
//| Classe "Matrix"                                                  |
//+------------------------------------------------------------------+
class CMatrix
  {
private:
   CRow              m_rows[];
 
public:
   //--- Constructeurs et un destructeur
                     CMatrix(void);
                     CMatrix(int rows)  { ArrayResize(m_rows,rows);             }
                    ~CMatrix(void){};
   //--- Récupère les tailles des matrices
   int               Rows()       const { return(ArraySize(m_rows));            }
   int               Cols()       const { return(Rows()>0? m_rows[0].Size():0); }
   //--- Retourne la valeur de la colonne sous la forme d'une rangée CRow
   CRow              GetColumnAsRow(const int col_index) const;
   //--- Retourne une chaîne de caractères avec les valeurs de la matrice
   string            String(voidconst;
   //--- L'opérateur d'indexation retourne une chaîne de caractères par son numéro
   CRow *operator[](int i) const        { return(GetPointer(m_rows[i]));        }
   //--- Opérateur d'addition
   CMatrix           operator+(const CMatrix &m);
   //--- Opérateur de multiplication
   CMatrix           operator*(const CMatrix &m);
   //--- Opérateur d'assignation
   CMatrix          *operator=(const CMatrix &m);
  };
//+------------------------------------------------------------------+
//| Constructeur par défaut, crée un tableau de rangées de taille 0  |
//+------------------------------------------------------------------+
CMatrix::CMatrix(void)
  {
//--- La rangée numéro 0 dans la matrice
   ArrayResize(m_rows,0);
//---  
  }
//+------------------------------------------------------------------+
//| Retourne la valeur de la colonne sous la forme de CRow           |
//+------------------------------------------------------------------+
CRow  CMatrix::GetColumnAsRow(const int col_index) const
  {
//--- Variable pour récupérer les valeurs de la colonne
   CRow row();
//--- Le nombre de rangées dans la matrice
   int rows=Rows();
//--- Si le nombre de rangées est supérieur à 0, exécute l'opération
   if(rows>0)
     {
      //--- Tableau pour récupérer les valeurs de la colonne ayant l'index col_index
      double array[];
      ArrayResize(array,rows);
      //--- Remplissage du tableau
      for(int i=0;i<rows;i++)
        {
         //--- Vérifie le numéro de la colonne pour la rangée i - il peut être supérieur aux limites du tableau
>         if(col_index>=this[i].Size())
           {
            Print(__FUNCSIG__," : Erreur ! Numéro de colonne ",col_index,"> taille de la rangée ",i);
            break// rangée sera un objet non initialisé
           }
         array[i]=this[i][col_index];
        }
      //--- Crée une rangée CRow basée sur les valeurs du tableau
      row=array;
     }
//--- Résultat
   return(row);
  }
//+------------------------------------------------------------------+
//| Addition de deux matrices                                        |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator+(const CMatrix &m)
  {
//--- Le nombre de rangées et de colonnes dans la matrice passée
   int cols=m.Cols();
   int rows=m.Rows();
//--- La matrice recevant les résultats de l'addition
   CMatrix res(rows);
//--- Les tailles des matrices doivent correspondre
   if(cols!=Cols() || rows!=Rows())
     {
      //--- Addition impossible
      Print(__FUNCSIG__," : Echec de l'addition des 2 matrices, leurs tailles sont différentes");
      return(res);
     }
//--- Tableau supplémentaire
   double arr[];
   ArrayResize(arr,cols);
//--- Se rend aux rangées à ajouter
   for(int i=0;i<rows;i++)
     {
      //--- Ecrit les résultats de l'addition des chaînes de caractères des matrices dans le tableau
      for(int k=0;k<cols;k++)
        {
         arr[k]=this[i][k]+m[i][k];
        }
      //--- Place le tableau dans la rangée de la matrice
      res[i]=arr;
     }
//--- retourne le résultat de l'addition des matrices
   return(res);
  }
//+------------------------------------------------------------------+
//| Multiplication de deux matrices                                  |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator*(const CMatrix &m)
  {
//--- Nombre de colonnes de la première matrics, nombre de rangées de la matrice passée
   int cols1=Cols();
   int rows2=m.Rows();
   int rows1=Rows();
   int cols2=m.Cols();
//--- La matrice recevant les résultats de la multiplication
   CMatrix res(rows1);
//--- Les matrices doivent être coordonnées
   if(cols1!=rows2)
     {
      //--- Multiplication impossible
      Print(__FUNCSIG__," : Echec de la multiplication des 2 matrices, les formats ne sont pas compatibles"<t6>);</t6>
            "- le nombre de colonnes du premier facteur doit être égal au nombre de rangées du second");
      return(res);
     }
//--- Tableau supplémentaire
   double arr[];
   ArrayResize(arr,cols1);
//--- Remplit les rangées de la matrice de multiplication
   for(int i=0;i<rows1;i++)// Boucle sur les rangées
     {
      //--- Réinitialise le tableau de destination
      ArrayInitialize(arr,0);
      //--- Boucle sur les éléments de la rangée
      for(int k=0;k<cols1;k++)
        {
         //--- Prend la valeur de la colonne k de la matrice m dans la rangée CRow
         CRow column=m.GetColumnAsRow(k);
         //--- Multiplie deux rangées et écrit le résultat de la multiplication scalaire des vecteurs du i-ème élément
         arr[k]=this[i]*column;
        }
      //--- place le tableau arr[] à la i-ème rangée de la matrice
      res[i]=arr;
     }
//--- Retourne le produit des deux matrices
   return(res);
  }
//+------------------------------------------------------------------+
//| Opération d'assignation                                          |
//+------------------------------------------------------------------+
CMatrix *CMatrix::operator=(const CMatrix &m)
  {
//--- Trouve et définit le nombre de rangées
   int rows=m.Rows();
   ArrayResize(m_rows,rows);
//--- Remplit les rangées avec les valeurs des rangées de la matrice passée en argument
   for(int i=0;i<rows;i++) this[i]=m[i];
//---
   return(GetPointer(this));
  }
//+------------------------------------------------------------------+
//| Représentation textuelle de la matrice                           |
//+------------------------------------------------------------------+
string CMatrix::String(voidconst
  {
   string out="";
   int rows=Rows();
//--- Forme la chaîne de caractères
   for(int i=0;i<rows;i++)
     {
      out=out+this[i].String()+"\r\n";
     }
//--- Résultat
   return(out);
  }

Voir aussi

Surcharge, Opérations Arithmétiques, Surcharge de Fonction, Règles de Précédence