Überladen von Operatoren
Zur leichteren Lesen und Schreiben von Code ist Überladen einiger Operationen erlaubt. Ein Überladenoperator wird mit dem Schlüsselwort Betreiber geschrieben. Die folgenden Operatoren können überladen werden:
- binäre +,-,/,*,%,<<,>>,==,!=,<,>,<=,>=,=,+=,-=,/=,*=,%=,&=,|=,^=,<<=,>>=,&&,||,&,|,^;
- unäre +,-,++,--,!,~;
- Zuweisungsoperator =;
- Indizierungsoperator [].
Überladen von Operatoren ermöglicht den Einsatz des Betriebssystems Notation (geschrieben in der Form von einfachen Ausdrücken) zu komplexen Objekte - Strukturen und Klassen. Schreiben von Ausdrücken mit überladenen Operationen vereinfacht den Blick auf den Quellcode, weil eine komplexere Implementierung verborgen ist.
Betrachten wir zum Beispiel komplexe Zahlen, die aus Real- und Imaginärteil bestehen. Sie sind weit verbreitet in der Mathematik weit verbreitet. Die MQL5 Sprache hat keinen Datentyp für Darstellung der komplexen Zahlen, aber es ist möglich, einen neuen Datentyp in Form einer Struktur oder Klasse zu erstellen. Wir deklarieren die Struktur complex und definieren vier Methoden in dieser Struktur, die die vier arithmetischen Operationen implementieren:
//+------------------------------------------------------------------+
//| Struktur für Operationen mit komplexen Zahlen |
//+------------------------------------------------------------------+
struct complex
{
double re; // Realteil
double im; // Imaginärteil
//--- Konstruktoren
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) { }
//--- Rechenoperationen
complex Add(const complex &l,const complex &r) const; // Addition
complex Sub(const complex &l,const complex &r) const; // Subtraktion
complex Mul(const complex &l,const complex &r) const; // Multiplikation
complex Div(const complex &l,const complex &r) const; // Division
};
|
Jetzt können wir Variablen, die komplexen Zahlen vertreten, in unserem Code deklarieren und mit ihnen arbeiten.
Zum Beispiel:
void OnStart()
{
//--- Deklarieren und initialisieren Variablen eines komplexen Typs
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 zweier Zahlen
complex z;
z=a.Add(a,b);
PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- Multiplikation zweier Zahlen
z=a.Mul(a,b);
PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- Division zweier Zahlen
z=a.Div(a,b);
PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
}
|
Aber es wäre bequemer für die üblichen arithmetischen Operationen mit komplexen Zahlen die üblichen Operatoren "+", "-", "*" und "/" zu verwenden.
Das Schlüsselwort operator wird verwendet um die Memberfunktion, die eine Typkonvertierung führt, zu definieren. Unäre und binäre Operationen für Variable-Klassenobjekte können als nicht-statische Memberfunktionen überladen werden. Sie implizit auf das Objekt der Klasse wirken.
Die meisten binären Operatoren können wie normale Funktionen, die eine oder beide Argumente als Variable der Klasse oder als ein Zeiger auf ein Objekt dieser Klasse nehmen, überladen werden. Für unseren Typ complex wird Überladen im Deklarieren wie folgt aussehen:
//--- Operatoren
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)); }
|
Ein komplettes Beispiel des Skripts:
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
//--- deklarieren und initialisieren Variablen eines komplexen Typs
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;
//--- addieren zwei Zahlen
complex z=a+b;
PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- Multiplikation zweier Zahlen
z=a*b;
PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- Division zweier Zahlen
z=a/b;
PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
}
//+------------------------------------------------------------------+
//| eine Struktur für Operationen mit komplexen Zahlen |
//+------------------------------------------------------------------+
struct complex
{
double re; // Realteil
double im; // Imaginärteil
//--- Konstruktoren
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) { }
//--- arithmetische Operationen
complex Add(const complex &l,const complex &r) const; // Addition
complex Sub(const complex &l,const complex &r) const; // Subtraktion
complex Mul(const complex &l,const complex &r) const; // Multiplikation
complex Div(const complex &l,const complex &r) const; // Division
//--- binäre Operatoren
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;
//--- Ergebnis
return res;
}
//+------------------------------------------------------------------+
//| Subtraktion |
//+------------------------------------------------------------------+
complex complex::Sub(const complex &l,const complex &r) const
{
complex res;
//---
res.re=l.re-r.re;
res.im=l.im-r.im;
//--- Ergebnis
return res;
}
//+------------------------------------------------------------------+
//| Multiplikation |
//+------------------------------------------------------------------+
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;
//--- Ergebnis
return res;
}
//+------------------------------------------------------------------+
//| Division |
//+------------------------------------------------------------------+
complex complex::Div(const complex &l,const complex &r) const
{
//--- leere komplexe Zahl
complex res(EMPTY_VALUE,EMPTY_VALUE);
//--- prüfen zu Null
if(r.re==0 && r.im==0)
{
Print(__FUNCTION__+": number is zero");
return(res);
}
//--- Hilfsvariable
double e;
double f;
//--- Wahl der Rechenvariante
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;
}
//--- Ergebnis
return res;
}
|
Die meisten unären Operationen für Klassen können als die üblichen Funktionen, die ein einziges Argument-Klassenobjekt oder einen Zeiger auf es nehmen, überladen werden. Fügen wir eine Überladen unäre Operationen "-" und "!".
//+------------------------------------------------------------------+
//| Eine Struktur für Operationen mit komplexen Zahlen |
//+------------------------------------------------------------------+
struct complex
{
double re; // Realteil
double im; // Imaginärteil
...
//--- unäre Operatoren
complex operator-() const; // unäres Minus
bool operator!() const; // Negation
};
...
//+------------------------------------------------------------------+
//| Überladen von Operator "unäres Minus" |
//+------------------------------------------------------------------+
complex complex::operator-() const
{
complex res;
//---
res.re=-re;
res.im=-im;
//--- Ergebnis
return res;
}
//+------------------------------------------------------------------+
//| Überladen von Operator "logische Negation" |
//+------------------------------------------------------------------+
bool complex::operator!() const
{
//--- Sind die realen und imaginären Teile der komplexen Zahl gleich Null?
return (re!=0 && im!=0);
}
|
Jetzt können wir den Wert einer komplexen Zahl durch Null prüfen und einen negativen Wert erhalten:
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
//--- deklarieren und initialisieren Variablen eines komplexen Typs
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 zweier Zahlen z=a/b;
complex z=a/b;
PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//--- Eine komplexe Zahl wird standardmäßig Null gleich (im Standardkonstruktor re==0 und im==0)
complex zero;
Print("!zero=",!zero);
//--- einen negativen Wert anweisen
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);
//--- prüfen wir zu Null noch einmal
Print("!zero=",!zero);
//---
}
|
Bitte beachten Sie, dass wir nicht in diesem Fall den Zuweisungsoperator "=" überladen haben, wie Strukturen von einfachen Typen können eins in andere direkt kopiert werden. So können wir nun den Code für Berechnungen mit komplexen Zahlen in der üblichen Weise schreiben.
Überladen von Index-Operator ermöglicht Erhalten von im Objekt eingeschlossenen Arrays auf einfache und gewohnte Art, und dies trägt auch zu einem besseren Verständnis und Lesbarkeit des Quellcodes. Zum Beispiel müssen wir den Zugang zu einem Symbol im String an der angegebenen Position vorsehen. Ein String in der Sprache MQL5 ist ein eigener Typ string, der nicht ein Array von Symbole ist, sondern mit Hilfe einer überladenen Index-Operation in der generierten Klasse CString können wir eine einfache und transparente Arbeit stellen:
//+------------------------------------------------------------------+
//| Klasse, um Symbole im String als Array in Symbole zu zugreifen |
//+------------------------------------------------------------------+
class CString
{
string m_string;
public:
CString(string str=NULL):m_string(str) { }
ushort operator[] (int x) { return(StringGetCharacter(m_string,x)); }
};
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
//--- Array für Erhalten der Symbole aus einem String
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;
//--- Bilden einen Satz mit Symbolen aus der Variable str
for(int i=0,n=ArraySize(x);i<n;i++)
{
res+=ShortToString(str[x[i]]);
}
//--- Ausgabe von Ergebnisse
Print(res);
}
|
Ein weiteres Beispiel für ein Überladen der Index-Operation ist Arbeit mit Matrizen. Die Matrix ist ein zweidimensionales dynamisches Array. Größe des Arrays sind nicht im Voraus definiert. So kann man nicht ein Array von Form array[][] ohne Angabe der Größe der zweiten Dimension deklarieren, und dann dieses Array als Parameter übergeben. Eine mögliche Lösung ist eine spezielle Klasse CMatrix, die ein Array von Objekten der Klasse CRow enthält.
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
//--- Operationen der Addition und Multiplikation von Matrizen
CMatrix A(3),B(3),C();
//--- bereiten die Arrays für Strings
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};
//--- füllen die Matrizen
A[0]=a1; A[1]=a2; A[2]=a3;
B[0]=b1; B[1]=b2; B[2]=b3;
//--- Ausgabe von Matrizen in "Experts" Journal
Print("---- Elemente der Matrix A");
Print(A.String());
Print("---- Elemente der Matrix B");
Print(B.String());
//--- Multiplikation von Matrizen
Print("---- Multiplikation von Matrizen A und B");
C=A+B;
//--- Ausgabe der formatierten String-Darstellung
Print(C.String());
//---Multiplikation von Matrizen
Print("---- Produkt von Matrizen A und B");
C=A*B;
Print(C.String());
//--- jetzt zeigen wir wie Werte im Stil des dynamischen Arrays matrix[i][j] zu erhalten
Print("die Werte der Matrix C elementweise ausgeben");
//--- Durchschleifen der Strings der Matrix - Objekte CRow
for(int i=0;i<3;i++)
{
string com="| ";
//--- bilden Strings aus der Matrix für den Wert
for(int j=0;j<3;j++)
{
//--- erhalten ein Element der Matrix aus Nummer von Zeile und Spalte
double element=C[i][j];// [i] - Zugriff zu CRow im Array m_rows[] ,
// [j] - Überladener Index-Operator in CRow
com=com+StringFormat("a(%d,%d)=%G ; ",i,j,element);
}
com+="|";
//--- Die Werte von der Zeile ausgeben
Print(com);
}
}
//+------------------------------------------------------------------+
//| Klasse "Zeile" |
//+------------------------------------------------------------------+
class CRow
{
private:
double m_array[];
public:
//--- Konstruktoren und Destruktor
CRow(void) { ArrayResize(m_array,0); }
CRow(const CRow &r) { this=r; }
CRow(const double &array[]);
~CRow(void){};
//--- Anzahl der Elementen in der Zeile
int Size(void) const { return(ArraySize(m_array));}
//--- Gibt den String mit Werte zurück
string String(void) const;
//--- Index-Operator
double operator[](int i) const { return(m_array[i]); }
//--- Zuweisungsoperatoren
void operator=(const double &array[]); // Array
void operator=(const CRow & r); // ein anderes Objekt CRow
double operator*(const CRow &o); // Objekt CRow für Multiplikation
};
//+------------------------------------------------------------------+
//| Konstruktor zur Initialisierung einer Zeile mit einem Array |
//+------------------------------------------------------------------+
void CRow::CRow(const double &array[])
{
int size=ArraySize(array);
//--- Wenn das Array nicht leer ist
if(size>0)
{
ArrayResize(m_array,size);
//--- Füllen mit Arrays
for(int i=0;i<size;i++)
m_array[i]=array[i];
}
//---
}
//+------------------------------------------------------------------+
//| Zuweisungsoperation für das Array |
//+------------------------------------------------------------------+
void CRow::operator=(const double &array[])
{
int size=ArraySize(array);
if(size==0) return;
//--- füllen das Array mit Werten
ArrayResize(m_array,size);
for(int i=0;i<size;i++) m_array[i]=array[i];
//---
}
//+------------------------------------------------------------------+
//| Zuweisungsoperation für CRow |
//+------------------------------------------------------------------+
void CRow::operator=(const CRow &r)
{
int size=r.Size();
if(size==0) return;
//--- füllen das Array mit Werten
ArrayResize(m_array,size);
for(int i=0;i<size;i++) m_array[i]=r[i];
//---
}
//+------------------------------------------------------------------+
//| Operator der Multiplikation auf einer anderen Zeile |
//+------------------------------------------------------------------+
double CRow::operator*(const CRow &o)
{
double res=0;
//--- Prüfung
int size=Size();
if(size!=o.Size() || size==0)
{
Print(__FUNCSIG__,": Fehler bei der Multiplikation zweier Matrizen, Die Größe sind nicht gleich");
return(res);
}
//--- multiplizieren die Arrays elementweise und addieren die Produkte
for(int i=0;i<size;i++)
res+=m_array[i]*o[i];
//--- Ereignis
return(res);
}
//+------------------------------------------------------------------+
//| Gibt die formatierte String-Darstellung zurück |
//+------------------------------------------------------------------+
string CRow::String(void) const
{
string out="";
//--- wenn die Größe des Arrays mehr als Null ist
int size=ArraySize(m_array);
//--- arbeiten nur mir Nicht-Null Anzahl der Elemente im Array
if(size>0)
{
out="{";
for(int i=0;i<size;i++)
{
//--- sammeln Werte in einen String
out+=StringFormat(" %G;",m_array[i]);
}
out+=" }";
}
//--- Ergebnis
return(out);
}
//+------------------------------------------------------------------+
//| Klasse "Matrix" |
//+------------------------------------------------------------------+
class CMatrix
{
private:
CRow m_rows[];
public:
//--- Konstruktoren und Destruktor
CMatrix(void);
CMatrix(int rows) { ArrayResize(m_rows,rows); }
~CMatrix(void){};
//--- Erhalten von Größen der Matrix
int Rows() const { return(ArraySize(m_rows)); }
int Cols() const { return(Rows()>0? m_rows[0].Size():0); }
//--- gibt den Wert der Spalte als String CRow zurück
CRow GetColumnAsRow(const int col_index) const;
//--- gibt einen String mit den Werten der Matrix zurück
string String(void) const;
//--- Index-Operator gibt eine Zeile auf ihrer Nummer zurück
CRow *operator[](int i) const { return(GetPointer(m_rows[i])); }
//--- Additionoperator
CMatrix operator+(const CMatrix &m);
//--- Multiplikationsoperator
CMatrix operator*(const CMatrix &m);
//--- Zuweisungsoperator
CMatrix *operator=(const CMatrix &m);
};
//+------------------------------------------------------------------+
//| Standardkonstruktor, erstellt Array von Strings mit Null-Größe |
//+------------------------------------------------------------------+
CMatrix::CMatrix(void)
{
//--- Null Zeilen in der Matrix
ArrayResize(m_rows,0);
//---
}
//+------------------------------------------------------------------+
//| Gibt den Wert der Spalte als String CRow zurück |
//+------------------------------------------------------------------+
CRow CMatrix::GetColumnAsRow(const int col_index) const
{
//--- Variable, um die Werte aus Spalte zu erhalten
CRow row();
//--- Anzahl der Zeile in der Matrix
int rows=Rows();
//--- Wenn die Anzahl der Zeilen größer als Null ist, führen die Operation
if(rows>0)
{
//--- Array für Erhalten von Werte der Spalte mit Index col_index
double array[];
ArrayResize(array,rows);
//--- füllen das Array
for(int i=0;i<rows;i++)
{
//--- Prüfung der Spaltennummer für Zeile i, ob es über die Grenzen des Arrays überschritt
if(col_index>=this[i].Size())
{
Print(__FUNCSIG__,": Fehler! Nummer der Spalte ",col_index,"> Größe der Zeile ",i);
break; // row bleibt ein nicht initialisiertes Objekt
}
array[i]=this[i][col_index];
}
//--- erstellen wir eine Zeile CRow basierend auf den Werten des Arrays
row=array;
}
//--- Ergebnis
return(row);
}
//+------------------------------------------------------------------+
//| Multiplikation zweier Matrizen |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator+(const CMatrix &m)
{
//--- die Anzahl der Zeilen und Spalten in der übergebene Matrix
int cols=m.Cols();
int rows=m.Rows();
//--- Matrix, um das Ergebnis der Addition zu erhalten
CMatrix res(rows);
//--- Dimensionen der Matrix müssen identisch sein
if(cols!=Cols() || rows!=Rows())
{
//--- kann nicht addieren
Print(__FUNCSIG__,": Fehler bei der Addition zweier Matrizen, die Größe sind nicht gleich");
return(res);
}
//--- Hilfsarray
double arr[];
ArrayResize(arr,cols);
//--- Iteration durch die Zeilen für Addition
for(int i=0;i<rows;i++)
{
//--- wir schreiben das Ergebnis der Addition von Zeilen der Matrix in ein Array
for(int k=0;k<cols;k++)
{
arr[k]=this[i][k]+m[i][k];
}
//--- stellen das Array in der Zeile der Matrix
res[i]=arr;
}
//--- Ergebnis der Addition zweier Matrizes zurückgeben
return(res);
}
//+------------------------------------------------------------------+
//| Multiplikation zweier Matrizen |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator*(const CMatrix &m)
{
//--- Anzahl der Spalten der ersten Matrix Anzahl der Zeilen in der übergebenen Matrix
int cols1=Cols();
int rows2=m.Rows();
int rows1=Rows();
int cols2=m.Cols();
//--- Matrix, um das Ergebnis der Addition zu erhalten
CMatrix res(rows1);
//--- Matrizes sollten abgestimmt werden
if(cols1!=rows2)
{
//--- Kann nicht multiplizieren
Print(__FUNCSIG__,": Fehler bei der Multiplikation zweier Matrizen, ist das Format nicht kompatibel "
"- Anzahl der Spalten in den ersten Faktor sollte gleich der Anzahl der Zeilen in der zweiten sein");
return(res);
}
//--- Hilfsarray
double arr[];
ArrayResize(arr,cols1);
//--- füllen Zeilen in der Matrix von Multiplikation
for(int i=0;i<rows1;i++)// iterieren durch die Zeilen
{
//--- Array-Empfänger auf Null setzen
ArrayInitialize(arr,0);
//--- iterieren Elemente in der Zeile
for(int k=0;k<cols1;k++)
{
//--- Nehmen Werte der Spalte k der Matrix m als ein String CRow
CRow column=m.GetColumnAsRow(k);
//--- multiplizieren die beiden Zeilen und schreibt das Ergebnis der Skalarmultiplikation von Vektoren in der i-te Element
arr[k]=this[i]*column;
}
//--- setzen Array arr[] in der Zeile i der Matrix
res[i]=arr;
}
//--- Produkt zweier Matrizen zurückgeben
return(res);
}
//+------------------------------------------------------------------+
//| Zuweisungsoperation |
//+------------------------------------------------------------------+
CMatrix *CMatrix::operator=(const CMatrix &m)
{
//--- finden und definieren die Anzahl der Zeilen
int rows=m.Rows();
ArrayResize(m_rows,rows);
//--- füllen unsere Strings mit den Werten der Zeilen der übergebenen Matrix
for(int i=0;i<rows;i++) this[i]=m[i];
//---
return(GetPointer(this));
}
//+------------------------------------------------------------------+
//| Eine String-Darstellung der Matrix |
//+------------------------------------------------------------------+
string CMatrix::String(void) const
{
string out="";
int rows=Rows();
//--- bilden Zeile für Zeile
for(int i=0;i<rows;i++)
{
out=out+this[i].String()+"\r\n";
}
//--- Ergebnis
return(out);
}
|
Sehen Sie auch
Überladen, Arithmetische Operationen, Überladen von Funktionen, Prioritäten und die Reihenfolge der Operationen