MetaTrader 5 herunterladen

Verwendung von Objektzeigern in MQL5

11 Januar 2016, 12:52
MetaQuotes Software Corp.
0
378

Einleitung

In MQL5 können Sie Ihre eigene Klasse für den späteren Gebrauch von Variablen vom Typ Klasse in Ihrem Code erzeugen. Wie wir bereits im Beitrag Die Reihenfolge der Erstellung und Zerstörung von Objekten in MQL5, erklärt haben, können die Strukturen und Klassen auf zweierlei Arten erzeugt werden - automatisch und dynamisch.

Zur automatischen Erzeugung eines Objekts müssen Sie nur eine Variable vom Typ Klasse deklarieren - das System erzeugt und initialisiert sie dann automatisch. Zur dynamischen Erzeugung eines Objekts muss der Operator new  explizit auf den Objektzeiger angewendet werden.

Was ist der Unterschied zwischen automatisch und dynamisch erzeugten Objekten? Wann brauchen wir die Verwendung des Objektzeigers auf jeden Fall, und wann genügt es, die Objekte automatisch zu erzeugen? Darum geht es in diesem Beitrag. Betrachten wir uns zuerst die möglichen Gefahren bei der Arbeit mit Objekten und wie man sie beheben kann.

Schwerwiegender Fehler beim Zugriff auf einen ungültigen Zeiger

Wenn Sie Objektzeiger verwenden, sollten Sie zunächst immer daran denken, das Objekt vor seiner Verwendung zu initialisieren. Bei Zugriff auf einen ungültigen Zeiger schaltet sich die Arbeit des MQL-Programms mit einem schwerwiegenden Fehler ab, sodass das Programm entfernt wird. Nehmen wir als Beispiel einen Expert Advisor mit einer hier deklarierten CHello-Klasse. Der Zeiger auf die Klasseninstanz ist auf globaler Ebene deklariert.

//+------------------------------------------------------------------+
//|                                             GetCriticalError.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| A simple class                                                   |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Starting...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- calling a method to show status
   Print(pstatus.GetMessage());
//--- printing a message if Expert Advisor has been initialized successfully
   Print(__FUNCTION__," The OnInit() function is completed");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

Die PStatus-Variable ist der Objektzeiger, doch haben wir hier mit Absicht "vergessen", das Objekt an sich mit Hilfe des new Operators zu erzeugen. Wenn Sie also versuchen, diesen Expert Advisor auf dem EURUSD-Chart zu starten, sehen Sie das normale Ergebnis: der Expert Advisor wurde in der Ausführungsphase der OnInit()-Funktion sofort entladen. Die im Expert-Logbuch erscheinenden Meldungen sind wie folgt:

14:46:17 Expert GetCriticalError (EURUSD, H1) erfolgreich geladen
14:46:18 Initialisierung von GetCriticalError (EURUSD, H1) fehlgeschlagen
14:46:18 Expert GetCriticalError (EURUSD, H1) entfernt

Dieses Beispiel ist einfach, daher lässt sich der Fehler leicht feststellen. Doch wenn Ihr MQL5-Programm Hunderte oder sogar Tausende von Codezeilen enthält, dann wird das Auffinden solcher Fehler extrem kompliziert. Das ist insbesondere in Notfallsituationen wichtig, wenn das Programmverhalten von unvorhersehbaren Faktoren abhängt, wie z.B. einer bestimmten Marktsituation.

Überprüfung des Zeigers vor seiner Verwendung

Hätte man diesen schwerwiegenden Programmabbruch vermeiden können? Na klar! Dazu genügt das Einsetzen der Überprüfung des Objektzeigers vor seiner Verwendung. Ändern wir dieses Beispiel etwas und fügen die PrintStatus-Funktion hinzu:

//+------------------------------------------------------------------+
//| Prints a message using a method of CHello type object            |
//+------------------------------------------------------------------+
void PrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," the variable 'object' isn't initialized!");
   else Print(pobject.GetMessage());
  }

Diese Funktion ruft die GetMessage()-Methode auf, der Zeiger des Objekts des CHello-Typs wird an die Funktion übertragen. Die Erste prüft den Zeiger mit Hilfe der CheckPointer()-Funktion. Fügen wir einen externen Parameter hinzu und speichern den Code eines Expert Advisor in der Datei GetCriticalError_OnDemand.mq5.

//+------------------------------------------------------------------+
//|                                    GetCriticalError_OnDemand.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"

input bool GetStop=false;// To get a critical error
//+------------------------------------------------------------------+
//| A simple class                                                   |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Starting...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| Prints a message using a method of CHello type object            |
//+------------------------------------------------------------------+
void PrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," the variable 'object' isn't initialized!");
   else Print(pobject.GetMessage());
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- calling a method to show status
   if(GetStop)
      pstatus.GetMessage();
   else
      PrintStatus(pstatus);
//--- printing a message if Expert Advisor has been initialized successfully
   Print(__FUNCTION__," The OnInit() function is completed");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

Der Expert Advisor lässt sich nun auf zweierlei Arten starten:

  1. Mit einem schwerwiegenden Fehler (GetStop = true)
  2. Ohne Fehler, doch mit der Meldung 'ungültiger Zeiger' (GetStop = false)

Die Ausführung des Expert Advisors ist standardmäßig immer erfolgreich, sodass im "Expert"-Logbuch folgende Meldung erscheint:

GetCriticalError_OnDemand (EURUSD, H1) 15:01:57 PrintStatus des variablen 'Objekts' nicht initialisiert!
GetCriticalError_OnDemand (EURUSD, H1) 15:01:57 OnInit-Funktion OnInit () abgeschlossen

Durch die Überprüfung des Zeigers vor seiner Verwendung können wir schwerwiegende Fehler vermeiden.

Prüfen Sie stets die Exaktheit des Zeigers vor seiner Verwendung in der Funktion

Verweisübergabe des nicht-initialisierten Objekts

Was passiert, wenn Sie das nicht-initialisierte Objekt als Eingabe-Parameter der Funktion übertragen? (das Objekt selbst per Verweis, nicht den Objektzeiger). Komplexe Objekte, wie Klassen und Strukturen werden per Verweis mit einem ampersand übertragen. Schreiben wir also einen Teil des Codes für GetCriticalError_OnDemand.mq5 neu. Wir benennen die PrintStatus()-Funktion um und schreiben ihren Code anders.

//+------------------------------------------------------------------+
//| Prints a message using a method of CHello type object            |
//+------------------------------------------------------------------+
void UnsafePrintStatus(CHello &object)
  {
   DebugBreak();
   if(CheckPointer(GetPointer(object))==POINTER_INVALID)
      Print(__FUNCTION__," the variable 'object' isn't initialized!");
   else Print(object.GetMessage());
  }

Der Unterschied hier ist, dass die Variable dieses Typs selbst per Verweis als Eingabe-Parameter übertragen wird und eben nicht der Objektzeiger des CClassHello-Typs. Die neue Version des Expert Advisors speichern wir als GetCriticalError_Unsafe.mq5.

//+------------------------------------------------------------------+
//|                                      GetCriticalError_Unsafe.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"

input bool GetStop=false;// To get a critical error
//+------------------------------------------------------------------+
//| A simple class                                                   |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Starting...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| Prints a message using a method of CHello type object            |
//+------------------------------------------------------------------+
void UnsafePrintStatus(CHello &object)
  {
   DebugBreak();
   if(CheckPointer(GetPointer(object))==POINTER_INVALID)
      Print(__FUNCTION__," the variable 'object' isn't initialized!");
   else Print(object.GetMessage());
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- calling a method to show status
   if(GetStop)
      pstatus.GetMessage();
   else
      UnsafePrintStatus(pstatus);
//--- printing a message if Expert Advisor has been initialized successfully
   Print(__FUNCTION__," The OnInit() function is completed");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

Der Unterschied zwischen GetCriticalError_OnDemand.mq5 und GetCriticalError_Unsafe.mq5 Expert Advisors ist klar daran zu erkennen, wie Parameter an die Funktion übertragen werden. In ersten Fall wird der Objektzeiger an die Funktion übertragen; in zweiten Fall wird das Objekt an sich per Verweis übertragen. In beiden Fällen überprüft die Funktion jedoch vor der Verwendung des Objekts und seines Zeigers, die Exaktheit des Zeigers.

Bedeutet das, das Expert Advisor genauso funktionieren? Nein! Starten wir den Expert Advisor mit dem Parameter GetStop=false und wir erhalten abermals einen schwerwiegenden Fehler. Wenn ein Objekt per Verweis übertragen wird, tritt der schwerwiegende Fehler in der Phase eines Funktionsaufrufs ein, da das nicht-initialisierte Objekt als Parameter übertragen wird. Sie können das selbst überprüfen, wenn Sie ein Skript im Debug-Modus direkt vom MetaEditor5 aus, durch Drücken von F5, starten.

Um keine manuellen Haltepunkte verwenden zu müssen, modifizieren wir die Funktion durch Hinzufügen des DebugBreak () Haltepunkts innerhalb der Funktion.

//+------------------------------------------------------------------+
//| Prints a message using a method of CHello type object            |
//+------------------------------------------------------------------+
void UnsafePrintStatus(CHello &object)
  {
   DebugBreak();
   if(CheckPointer(GetPointer(object))==POINTER_INVALID)
      Print(__FUNCTION__," the variable 'object' isn't initialized!");
   else Print(object.GetMessage());
  }

Im Debug-Modus wird der Expert Advisor vor einem Aufruf der DebugBreak() Funktion entladen. Wie schreibt man einen sicheren Code für den Fall, wenn ein nicht-initialisiertes Objekt per Verweis übertragen wird? Die Antwort ist einfach: man verwendet die Funktionsüberladung.

Wird ein Zeiger eines nicht-initialisierten Objekts per Verweis als ein Parameter der Funktion übertragen, löst dies einen schwerwiegenden Fehler aus und führt zum Abbruch des mql5-Programms.

Verwendung der überladenen Funktionen für einen sicheren Code

Es wäre sehr lästig, wenn der Entwickler, der die externe Bibliothek benutzt, eine Prüfung der Eingabe-Objekte auf ihre Exaktheit zwingend vorgeschrieben hätte. Es ist nämlich viel besser, alle notwendigen Prüfungen innerhalb der Bibliothek auszuführen; das kann mit Hilfe der Funktionsüberladung implementiert werden

Implementieren wir also die UnsafePrintStatus()-Methode mit der Funktionsüberladung und schreiben zwei Versionen dieser Funktion - eine, die den übergebenen Objektzeiger anstelle des Objekts selbst nutzt und eine zweite, die die Übertragung des Objekts per Verweis nutzt. Beide Funktionen haben den gleichen Namen: "PrintStatus" doch diese Implementierung birgt keine potenzielle Gefahren mehr.

//+------------------------------------------------------------------+
//| The safe printing of a message using the CHello object pointer   |
//+------------------------------------------------------------------+
void SafePrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," the variable 'object' isn't initialized!");
   else Print(pobject.GetMessage());
  }
//+------------------------------------------------------------------+
//| Printing a message using the CHello object, passed by reference  |
//+------------------------------------------------------------------+
void SafePrintStatus(CHello &pobject)
  {
   DebugBreak();
   SafePrintStatus(GetPointer(pobject));
  }

Wird diese Funktion nun durch Übertragung des Objektzeigers aufgerufen, wird die Prüfung auf Exaktheit durchgeführt und es tritt kein schwerwiegender Fehler auf. Ruft man die überladene Funktion durch Übertragung eines Objekts per Verweis auf, erhalten wir zunächst den Objektzeiger mit Hilfe der GetPointer-Funktion und rufen dann den sicheren Code auf, der die Übertragung eines Objekts per Zeiger verwendet.

Wir können nun eine sichere Version der Funktion schreiben, die sogar noch kürzer ist,

void SafePrintStatus (CHello & pobject)
  (
   DebugBreak ();
   CHello * p = GetPointer (pobject);
   SafePrintStatus (p);
  )

anstatt (??) Schreiben wir die kompakte Version:

void SafePrintStatus (CHello & object)
  (
   DebugBreak ();
   SafePrintStatus (GetPointer (object));
  )

Beide Versionen sind gleich. Die zweite Version speichern wir unter dem Namen GetCriticalError_Safe.mq5.

//+------------------------------------------------------------------+
//|                                        GetCriticalError_Safe.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"

input bool GetStop=false;// To get a critical error
//+------------------------------------------------------------------+
//| A simple class                                                   |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Starting...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| The safe printing of a message using the CHello object pointer   |
//+------------------------------------------------------------------+
void SafePrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," the variable 'object' isn't initialized!");
   else Print(pobject.GetMessage());
  }
//+------------------------------------------------------------------+
//| Printing a message using the CHello object, passed by reference  |
//+------------------------------------------------------------------+
void SafePrintStatus(CHello &pobject)
  {
   DebugBreak();
   SafePrintStatus(GetPointer(pobject));
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- calling a method to show status
   if(GetStop)
      pstatus.GetMessage();
   else
      SafePrintStatus(pstatus);
//--- printing a message if Expert Advisor has been initialized successfully
   Print(__FUNCTION__," The OnInit() function is completed");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

Wenn Sie zwei Funktionen mit den unterschiedlichen Implementierungen nutzen (per Übertragung als Verweis und per Übertragung des Objektzeigers), gewährleisten Sie so das sichere Arbeiten der überladenen Funktion.

Bislang haben wir gelernt, wie man diese Objekte verwendet und als Parameter an die Funktion überträgt. Jetzt erfahren wir...

Wann man Zeiger braucht.

Mit Objektzeigern lässt sich der Vorgang der Objekte-Erzeugung und Objekte-Vernichtung flexibel verwalten, außerdem kann man so auch komplexere, abstrakte Objekte erzeugen. Das ganze Programm wird dadurch flexibler.

Verküpfte Liste

In einigen Fällen jedoch ist eine andere Art der Datenorganisation nötig - eine davon ist die verknüpfte Liste. Die Klasse einer verknüpften Liste CList steht in der Standard Library zur Verfügung. Wir präsentieren hier unsere eigenen Beispiele. Eine verknüpfte Liste bedeutet, dass jedes Element der Liste mit dem vorigen und nächsten Element (falls vorhanden) verknüpft ist. Die Organisation solcher Verknüpfungen geht am einfachsten, wenn man die Objektzeiger der Listen-Elemente (ListItem) verwendet.

Erzeugen wir also eine Klasse, das ein Element einer Liste darstellt, wie z.B.:

class CListItem
  {
private:
   int               m_ID;
   CListItem        *m_next;
   CListItem        *m_prev;
public:
                    ~CListItem();
   void              setID(int id){m_ID=id;}
   int               getID(){return(m_ID);}
   void              next(CListItem *item){m_next=item;}
   void              prev(CListItem *item){m_prev=item;}
   CListItem*        next(){return(m_next);}
   CListItem*        prev(){return(m_prev);}
  };

Die Liste selbst wird in einer separaten Klasse organisiert:

//+------------------------------------------------------------------+
//| Linked list                                                      |
//+------------------------------------------------------------------+
class CList
  {
private:
   int               m_counter;
   CListItem        *m_first;
public:
                     CList(){m_counter=0;}
                    ~CList();
   void              addItem(CListItem *item);
   int               size(){return(m_counter);}
  };

Die CList-Klasse enthält einen Zeiger m_first des ersten Elements der Liste. Zugriff auf die anderen Listen-Elemente ist stets via der Funktionen nächste() und vorige() der CListItem()-Klasse möglich. Die CList-Klasse besitzt zwei interessante Funktionen. 1. Die Funktion, der Liste ein neues Element hinzufügen zu können.

//+------------------------------------------------------------------+
//| Adding of an item to the list                                    |
//+------------------------------------------------------------------+
CList::addItem(CListItem *item)
  {
//--- checking of a pointer, it should be correct
   if(CheckPointer(item)==POINTER_INVALID) return;
//--- increasing the number of list items
   m_counter++;
//--- if there isn't any items in the list
   if(CheckPointer(m_first)!=POINTER_DYNAMIC)
     {
      m_first=item;
     }
   else
     {
      //--- setting for first a pointer to the previous item
      m_first.prev(item);
      //--- saving a pointer of the current first item
      CListItem *p=m_first;
      //--- placing the input item on the place of the first element
      m_first=item;
      //--- for the first item in the list, setting a pointer to the next item 
      m_first.next(p);
     }
  }

Jedes neue, der Liste hinzugefügte Element, wird zum ersten Element; der Zeiger des vorigen ersten Elements ist im m_next Feld gespeichert. Also steht ein Element, das der Liste als Erstes hinzugefügt wurde, automatisch am Ende. Das letzte, hinzugefügte Element ist daher immer das Erste und sein Zeiger ist in einer m_first Variable gespeichert. Die 2. interessante Funktion ist der ~CList()-Destruktor. Er wird aufgerufen, wenn das Objekt vernichtet wird und sollte die korrekte Vernichtung der Listenobjekte sicherstellen. Das geht recht einfach:

//+------------------------------------------------------------------+
//| List destructor                                                  |
//+------------------------------------------------------------------+
CList::~CList(void)
  {
   int ID=m_first.getID();
   if(CheckPointer(m_first)==POINTER_DYNAMIC) delete(m_first);
   Print(__FUNCTION__," The first item with ID =",ID," is destroyed");
  }

Man erkennt klar, dass, wenn m_first den korrekten Zeiger des Listen-Elements enthält, nur das erste Element entfernt wird. Alle anderen Listen-Elemente werden 'lawinenartig' entfernt, da der CListItem() -Klassen-Destruktor seinerseits die korrekte Objekt-Deinitialisierung erzeugt.

//+------------------------------------------------------------------+
//| Item destructor                                                  |
//+------------------------------------------------------------------+
CListItem::~CListItem(void)
  {
   if(CheckPointer(m_next)==POINTER_DYNAMIC)
     {
      delete(m_next);
      Print(__FUNCTION__," Removing an item with ID =",m_ID);
     }
   else
      Print(__FUNCTION__," The next item isn't defined for the item with ID=",m_ID);

  }

Die Exaktheit eines Zeigers wird innerhalb des Destruktors verifiziert (wenn er spezifiziert ist). Das Objekt mit Zeiger m_next wird mit Hilfe des delete() Operators vernichtet. Vor der Vernichtung ruft das erste Listen-Element den Destruktor auf und er vernichtet das zweite Element. Danach wird die Vernichtung des dritten Elements in Gang gesetzt, und so weiter bis das Ende der Verküpfung erreicht ist.

Die Arbeit der Liste ist im Skript SampleList.mq5 dargestellt. Die Deklarierung einer Liste (eine Variable des CListTyps) findet sich in der OnStart () Funktion. Diese Liste wird automatisch erzeugt und initialisiert. Die Population der Liste erfolgt innerhalb einer Liste, und jedes erste Listen-Element wird mit Hilfe des new Operators dynamisch erzeugt und dann der Liste hinzugefügt. 

void OnStart()
  {
//---
   CList list;
   for(int i=0;i<7;i++)
     {
      CListItem *item=new CListItem;
      item.setID(i);
      list.addItem(item);
     }
     Print("There are ",list.size()," items in the list");
  }

Starten Sie das Skript und im "Expert"-Logbuch erscheinen folgende Meldungen:

2010.03.18 11:22:05 SampleList (EURUSD, H1) CList:: ~ CList Das erste Element mit ID=6 ist vernichtet
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Ein Element mit ID = 6 wird entfernt
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Ein Element mit ID = 5 wird entfernt
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Ein Element mit ID = 4 wird entfernt
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Ein Element mit ID = 3 wird entfernt
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Ein Element mit ID = 2 wird entfernt
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Ein Element mit ID = 1 wird entfernt
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Das nächste Element ist für das Element mit ID=0 nicht definiert
2010.03.18 11:22:05 SampleList (EURUSD, H1) Es gibt 7 Elemente in der Liste

Wenn Sie den Code aufmerksam betrachtet haben, überrascht es Sie bestimmt nicht, dass das Element mit ID=0 keine nachfolgendes Element hat. Bislang haben wir jedoch nur einen Grund berücksichtigt, warum die Verwendung der Zeiger das Schreiben von Programmen bequem und lesbar macht. Es gibt jedoch noch einen zweiten Grund, den sog.

Polymorphismus

Oft ist es nämlich notwendig, die gleiche Funktionalität für unterschiedliche Objekte, die zum selben Typ gehören, zu implementieren. Beispiel: Es gibt einfache Objekte wie Linie, Dreieck, Rechteck und Kreis. Und obwohl sie alle verschieden aussehen, haben sie eins gemeinsam - man kann sie alle zeichnen. Erzeugen wir also eine Basisklasse CShape und verwenden sie zur Erzeugung seiner Nachfolger für jede geometrische Form.


Zur besseren Verständlichkeit haben die Basisklasse und ihre Nachfolger nur minimale Funktionalität.

//+------------------------------------------------------------------+
//| The base class for a Shape object                                |
//+------------------------------------------------------------------+
class CShape
  {
private:
   int               m_type;
public:
                     CShape(){m_type=0;}
   void              Draw();
   string            getTypeName(){return("Shape");}
  };
//+------------------------------------------------------------------+
//| The class for a Line object                                      |
//+------------------------------------------------------------------+
class CLine:public CShape
  {
private:
   int               m_type;
public:
                     CLine(){m_type=1;}
   void              Draw();
   string            getTypeName(){return("Line");}
  };
//+------------------------------------------------------------------+
//| The class for a Triangle object                                  |
//+------------------------------------------------------------------+
class CTriangle:public CShape
  {
private:
   int               m_type;
public:
                     CTriangle(){m_type=2;}
   void              Draw();
   string            getTypeName(){return("Triangle");}
  };
//+------------------------------------------------------------------+
//| The class for a Rectangle object                                 |
//+------------------------------------------------------------------+
class CRectangle:public CShape
  {
private:
   int               m_type;
public:
                     CRectangle(){m_type=3;}
   void              Draw();
   string            getTypeName(){return("Rectangle");}
  };
//+------------------------------------------------------------------+
//| The class for a Cirlce object                                    |
//+------------------------------------------------------------------+
class CCircle:public CShape
  {
private:
   int               m_type;
public:
                     CCircle(){m_type=4;}
   void              Draw();
   string            getTypeName(){return("Circle");}
  };

Die übergeordnete CShape-Klasse enthält zwei Funktionen, die in ihren Nachfolgern aufgehoben sind - eine Draw()- und eine getTypeName()-Funktion. Die Draw()-Funktion dient zum Zeichnen einer Form, und die getTypeName()-Funktion liefert eine String-Beschreibung der Form.

Erzeugen wir nun ein *shapes[] Array, das den Zeiger des Basistyps CShape enthält, und die Werte eines Zeigers für die unterschiedlichen Klassen spezifiziert.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- an array of pointers of objects of CShape type
   CShape *shapes[];
//--- resizing of an array 
   ArrayResize(shapes,5);
//--- filling of a pointers array
   shapes[0]=new CShape;
   shapes[1]=new CLine;
   shapes[2]=new CTriangle;
   shapes[3]=new CRectangle;
   shapes[4]=new CCircle;
//--- printing the type of each array element
   for(int i=0;i<5;i++)
     {
      Print(i,shapes[i].getTypeName());
     }
//--- deleting all objects in the array
   for(int i=0;i<5;i++) delete(shapes[i]);
  }

Innerhalb des for() Zyklus rufen wir die getTypeName ()-Methode für jedes Element des Arrays auf. Beim ersten Mal sind Sie vielleicht davon überrascht, dass die getTypeName()-Funktion einer Basisklasse jedes Objekt in der Liste aufruft, obwohl doch jede abgeleitete Klasse ihre eigene Implementierung der getTypeName()-Funktion hat.

2010.03.18 14:06:18 DemoPolymorphismus (EURUSD, H1) 4 Form
2010.03.18 14:06:18 DemoPolymorphismus (EURUSD, H1) 3 Form
2010.03.18 14:06:18 DemoPolymorphismus (EURUSD, H1) 2 Form
2010.03.18 14:06:18 DemoPolymorphismus (EURUSD, H1) 1 Form
2010.03.18 14:06:18 DemoPolymorphismus (EURUSD, H1) 0 Form

Dies lässt sich folgendermaßen erklären: Das *shapes [] Array ist deklariert als ein Array von Zeigern des CShape-Typs, daher ruft jedes Objekt des Arrays die getTypeName()-Methode einer Basisklasse auf, selbst wenn ein Nachfolger eine andere Implementierung hat. Um die getTypeName()-Funktion für den tatsächlichen Objekttyp (Nachfolger) zum Zeitpunkt der Programmausführung aufzurufen, muss man diese Funktion in einer Basisklasse als eine virtuelle Funktion deklarieren.

Also fügen wir das Schlüsselwort virtual der getTypeName()-Funktion in der Deklarierung der übergeordneten CShape-Klasse hinzu

class CShape
  (
private:
   int m_type;
public:
                     CShape () (m_type = 0;)
   void Draw ();
   virtual string getTypeName () (return ("Shape");)
  )

und starten das Skript erneut. Die Ergebnisse sind nun wie erwartet:

2010.03.18 15:01:11 DemoPolymorphismus (EURUSD, H1) 4 Kreis
2010.03.18 15:01:11 DemoPolymorphismus (EURUSD, H1) 3 Rechteck
2010.03.18 15:01:11 DemoPolymorphismus (EURUSD, H1) 2 Dreieck
2010.03.18 15:01:11 DemoPolymorphismus (EURUSD, H1) 1 Linie
2010.03.18 15:01:11 DemoPolymorphismus (EURUSD, H1) 0 Form

Die Deklarierung einer virtuellen Funktion in einer Basisklasse erlaubte also, während der Ausführung eines Programms dieselbe Funktion eines Nachfolgers aufzurufen. Jetzt können wir die vollfunktionale Draw()-Funktion für jede der abgeleiteten Klassen implementieren.

Ein Beispiel iher Arbeit findet sich im angehängten DrawManyObjects.mq5 Skript, das die zufälligen Formen auf einem Chart abbildet.

Fazit

Zeit für die Zusammenfassung Die Erzeugung und Vernichtung von Objekten geht in MQL5 automatisch. Zeiger solte man daher nur verwenden, wenn sie wirklich notwendig sind und wenn man wirklich weiß, wie man sie einsetzt.

Wenn man jedoch nicht ohne Zeiger auskommt, sollte man sieor ihrer Verwendung auf jeden Fall auf ihre Exaktheit prüfen, und zwar mit Hilfe des CheckPointer() - der speziell für diese Fälle hinzugefügt wurde.

Eine letzte Bemerkung noch: Die Zeiger in MQL5 sind keine tatsächlichen Memory-Pointer, wie sie in C++ verwendet werden, daher sollte man sie nicht an DLL als Eingabe-Parameter übertragen.

Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/36

Einführung in MQL5: Schreiben eines einfachen Expert Advisor und benutzerdefinierten Indikators Einführung in MQL5: Schreiben eines einfachen Expert Advisor und benutzerdefinierten Indikators

Die im MetaTrader 5 Client Terminal enthaltene MetaQuotes Programming Language 5 (MQL5) bietet im Vergleich zu MQL4 zahlreiche neue Möglichkeiten und bessere Performance. Dieser Beitrag hilft Ihnen dabei, sich mit dieser neuen Programmiersprache vertraut zu machen. In diesem Beitrag werden einfache Beispiele zum Schreiben eines Expert Advisors und eines benutzerdefinierten Indikators vorgestellt. Wir werden auch auf einige Details der MQL5-Sprache eingehen, die notwendig sind, um diese Beispiele zu verstehen.

MQL5 für Neueinsteiger: Leitfaden zur Verwendung technischer Indikatoren in Expert Advisors MQL5 für Neueinsteiger: Leitfaden zur Verwendung technischer Indikatoren in Expert Advisors

Um Werte eines integrierten oder benutzerdefinierten Indikators in einem Expert Advisor zu erhalten, sollte zuerst sein Handle mithilfe der entsprechenden Funktion erstellt werden. Die Beispiele in diesem Beitrag zeigen, wie diese und jene technischen Indikatoren während der Erstellung Ihrer eigenen Programme genutzt werden können. Dieser Beitrag beschreibt Indikatoren, die in MQL5 geschrieben werden. Er richtet sich an jene, die nicht viel Erfahrung in der Entwicklung von Handelsstrategien haben, und liefert einfache und klare Arten der Arbeit mit Indikatoren mithilfe der bereitgestellten Bibliothek von Funktionen.

Benutzerdefinierte Indikatoren in MQL5 für Anfänger Benutzerdefinierte Indikatoren in MQL5 für Anfänger

Als Anfänger erscheint einem jedes neue Thema immer kompliziert und schwer. Bekannte Themen sind uns klar und stellen kein Problem mehr dar. Dabei vergessen wir jedoch oft, dass jeder immer mal wieder komplett bei Null anfangen muss, das gilt selbst für unsere Muttersprache. Und nicht anders ist mit der MQL5 Programmiersprache, die eine große Auswahl bietet, wie man seine ganz persönlichen Handelsstrategien entwickeln kann - man kann sie ausgehend von grundlegenden Konzepten und einfachsten Beispielen erlernen. Dieser Beitrag erklärt die Interaktion eines technischen Indikators mit dem MetaTrader 5 Client-Terminal am Beispiel des einfachen, benutzerdefinierten SMA-Indikators (einfacher gleitender Mittelwert).

Handels-Ereignisse im Expert Advisor mit Hilfe der OnTrade() Funktion bearbeiten Handels-Ereignisse im Expert Advisor mit Hilfe der OnTrade() Funktion bearbeiten

MQL5 brachte eine Menge an Innovationen, inkl. die Bearbeitung verschiedenartiger Ereignisse (Timer-Ereignisse, Handels-Ereignisse, benutzerdefinierte Ereignisse, usw). Mit diesen Ereignissen umgehen zu können, gestattet Ihnen die Erzeugung komplett neuer Arten an Programmen für den automatischen und halb-automatischen Handel. In diesem Beitrag betrachten wir uns Handels-Ereignisse und schreiben einen Code für die OnTrade() Funktion, die das Handels-Ereignis bearbeiten wird.