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(void) const { return(ArraySize(m_array));}
//--- Retourne une chaîne de caractères avec les valeurs
string String(void) const;
//--- 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(void) const
{
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(void) const;
//--- 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(void) const
{
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