MetaTrader 5 herunterladen
Nachschlagewerk MQL5Grundlagen der SpracheDatentypenStrukturen, Klassen und Schnittstellen 

Strukturen, Klassen und Schnittstellen

Strukturen

Eine Struktur stellt einen Set von Elementen beliebigen Typs (außer dem void Typ) dar. So gruppiert Struktur logisch verbundene Daten verschiedener Typen.

Strukturdeklaration

Strukturdatentyp ist durch die folgende Beschreibung bestimmt:

struct Strukturname 
  {
   Elementenbeschreibung
  };

Strukturname kann nicht als Identifikator verwendet werden (Name der Variable oder Funktion). Es muss in Vormerkung behalten, dass Strukturelemente in MQL5 folgen unmittelbar nacheinander ohne Ausrichtung. In der Sprache C++ wird diese Festlegung an den Compiler mit der Anweisung gerichtet.

#pragma pack(1)

Wenn es notwendig ist, eine andere Ausrichtung in Struktur durchzuführen, muss man "Fuellzeichen" notwendiger Größen verwenden.

Beispiel:

struct trade_settings
  {
   uchar  slippage;     // Wert der zulässigen Slippage - Größe 1 Byte
   char   reserved1;    // 1 Byte Leerplatzes
   short  reserved2;    // 2 Byte Leerplatzes
   int    reserved4;    // noch 4 Byte Leerplatzes. Richtung der Grenze 8 Byte garantieren
   double take;         // Preiswert der Gewinnfixierung  
   double stop;         // Preiswert des Schutzstops 
  };

Solche Beschreibung ausgerichteter Strukturen ist nur für die Übergabe in importierte dll-Funktionen notwendig.

Achtung: dieses Beispiel zeigt Falsch projektierte Daten. Es wäre besser vor allem Daten  take und stop mit dem größeren Wert zu vereinbaren der Art double zu vereinbaren, und dann das Glied slippage der Art uchar zu vereinbaren. In diesem Fall wird immer die interne Darstellung von Daten  dieselbe, unabhängig vom Wert angegeben in #pragma pack().

Wenn die Struktur Variablen der Art string und/oder Objekt des dynamischen Feldes enthält, ordnet der Compiler für diese Struktur einen impliziten Konstrukter zu, in dem alle Strukturglieder der Art htige für Objekt des dynamischen Feldes vorkommt.  Variablen einfacher Strukturen und ihre Felder können als Parameter in die aus DLL importierten  Funktionen.

Einfache Strukturen

Structures that do not contain strings, class objects, pointers and objects of dynamic arrays are called simple structures. Variables of simple structures, as well as their arrays can be passed as parameters to functions imported from DLL.

Copying of simple structures is allowed only in two cases:

  • If the objects belong to the same structure type
  • if the objects are connected by the lineage meaning that one structure is a descendant of another.

To provide an example, let's develop the CustomMqlTick custom structure with its contents identical to the built-in MqlTick one. The compiler does not allow copying the MqlTick object value to the CustomMqlTick type object. Direct typecasting to the necessary type also causes the compilation error:

      //--- copying simple structures of different types is forbidden
      my_tick1=last_tick;               // compiler returns an error here
     
      //--- typecasting structures of different types to each other is forbidden as well
      my_tick1=(CustomMqlTick)last_tick;// compiler returns an error here

Therefore, only one option is left — copying the values of the structure elements one by one. It is still allowed to copy the values of the same type of CustomMqlTick.

      CustomMqlTick my_tick1,my_tick2;
      //--- it is allowed to copy the objects of the same type of CustomMqlTick the following way
      my_tick2=my_tick1;
     
      //--- create an array out of the objects of the simple CustomMqlTick structure and write values to it
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;

The ArrayPrint() function is called for a check to display the arr[] array value in the journal.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- develop the structure similar to the built-in MqlTick
   struct CustomMqlTick
     {
      datetime          time;          // Last price update time
      double            bid;           // Current Bid price
      double            ask;           // Current Ask price
      double            last;          // Current price of the last trade (Last)
      ulong             volume;        // Volume for the current Last price
      long              time_msc;      // Last price update time in milliseconds
      uint              flags;         // Tick flags     
     };
   //--- get the last tick value
   MqlTick last_tick;
   CustomMqlTick my_tick1,my_tick2;
//--- attempt to copy data from MqlTick to CustomMqlTick
   if(SymbolInfoTick(Symbol(),last_tick))
     {
      //--- copying unrelated simple structures is forbidden
      //1. my_tick1=last_tick;               // compiler returns an error here
     
      //--- typecasting unrelated structures to each other is forbidden as well
      //2. my_tick1=(CustomMqlTick)last_tick;// compiler returns an error here
     
      //--- therefore, copy the structure members one by one     
      my_tick1.time=last_tick.time;
      my_tick1.bid=last_tick.bid;
      my_tick1.ask=last_tick.ask;
      my_tick1.volume=last_tick.volume;
      my_tick1.time_msc=last_tick.time_msc;
      my_tick1.flags=last_tick.flags;
     
      //--- it is allowed to copy the objects of the same type of CustomMqlTick the following way
      my_tick2=my_tick1;
     
      //--- create an array out of the objects of the simple CustomMqlTick structure and write values to it
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;
      ArrayPrint(arr);
//--- example of displaying values of the array containing the objects of CustomMqlTick type
      /*
                       [time]   [bid]   [ask]   [last] [volume]    [time_msc] [flags]
      [0] 2017.05.29 15:04:37 1.11854 1.11863 +0.00000  1450000 1496070277157       2
      [1] 2017.05.29 15:04:37 1.11854 1.11863 +0.00000  1450000 1496070277157       2           
      */
     }
   else
      Print("SymbolInfoTick() failed, error = ",GetLastError());
  }

The second example shows the features of copying simple structures by the lineage. Suppose that we have the Animal basic structure, from which the Cat and Dog structures are derived. We can copy the Animal and Cat objects, as well as the Animal and Dog objects to each other but we cannot copy Cat and Dog to each other, although both are descendants of the Animal structure.

//--- structure for describing dogs
struct Dog: Animal
  {
   bool              hunting;       // hunting breed
  };
//--- structure for describing cats
struct Cat: Animal
  {
   bool              home;          // home breed
  };
//--- create objects of child structures
   Dog dog;
   Cat cat;
//--- can be copied from ancestor to descendant (Animal ==> Dog)
   dog=some_animal;
   dog.swim=true;    // dogs can swim
//--- you cannot copy objects of child structures (Dog != Cat)
   cat=dog;        // compiler returns an error

Complete example code:

//--- basic structure for describing animals
struct Animal
  {
   int               head;          // number of heads
   int               legs;          // number of legs
   int               wings;         // number of wings
   bool              tail;          // tail
   bool              fly;           // flying
   bool              swim;          // swimming  
   bool              run;           // running
  };
//--- structure for describing dogs
struct Dog: Animal
  {
   bool              hunting;       // hunting breed
  };
//--- structure for describing cats
struct Cat: Animal
  {
   bool              home;          // home breed
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- create and describe an object of the basic Animal type
   Animal some_animal;
   some_animal.head=1;
   some_animal.legs=4;
   some_animal.wings=0;
   some_animal.tail=true;
   some_animal.fly=false;
   some_animal.swim=false;
   some_animal.run=true;
//--- create objects of child types
   Dog dog;
   Cat cat;
//--- can be copied from ancestor to descendant (Animal ==> Dog)
   dog=some_animal;
   dog.swim=true;    // dogs can swim
//--- you cannot copy objects of child structures (Dog != Cat)
   //cat=dog;        // compiler returns an error here
//--- therefore, it is possible to copy elements one by one only
   cat.head=dog.head;
   cat.legs=dog.legs;
   cat.wings=dog.wings;
   cat.tail=dog.tail;
   cat.fly=dog.fly;
   cat.swim=false;   // cats cannot swim
//--- it is possible to copy the values from descendant to ancestor
   Animal elephant;
   elephant=cat;
   elephant.run=false;// elephants cannot run
   elephant.swim=true;// elephants can swim
//--- create an array
   Animal animals[4];
   animals[0]=some_animal;
   animals[1]=dog;  
   animals[2]=cat;
   animals[3]=elephant;
//--- print out
   ArrayPrint(animals);
//--- execution result
/*
       [head] [legs] [wings] [tail] [fly] [swim] [run]
   [0]      1      4       0   true false  false  true
   [1]      1      4       0   true false   true  true
   [2]      1      4       0   true false  false false
   [3]      1      4       0   true false   true false
*/  
  }

Another way to copy simple types is using a union. The objects of the structures should be members of the same union — see the example in union.

Zugang zu Strukturgliedern

Strukturname ist ein neuer Datentyp und ermöglicht Variablen dieses Typs zu vereinbaren. Struktur kann nur einmal im Rahmen dieses Projekts vereinbart werden. Zugang zu Strukturgliedern ist durch die Operation Punkt (.) durchgeführt.

Beispiel:

struct trade_settings
  {
   double take;         // Preiswert von Gewinnmitnahme
   double stop;         // Preiswert von Schutzstop 
   uchar  slippage;     // Wert der zulässigen Slippage 
  };
//--- Variable des Typs trade_settings ist erzeugt und initialisiert
trade_settings my_set={0.0,0.0,5};  
if (input_TP>0) my_set.take=input_TP;

Modifier final

Das Vorhandensein des final Modifiers bei der Deklaration der Struktur verbietet weitere Vererbung. Wenn es nicht notwendig ist, die Struktur weiter zu ändern oder wenn Änderungen aus Sicherheitsgründen unzulässig sind, deklarieren Sie diese mit dem final Modifier. Dabei werden alle Glieder der Struktur implizit als final gelten.

struct settings final
  {
  //--- Strukturkörper
  };
 
struct trade_settings : public settings
  {
  //--- Strukturkörper
  };

Bei einem Versuch der Vererbung von einer Struktur mit dem final Modifier, wie im Beispiel oben, gibt der Compiler einen Fehler aus:

cannot inherit from 'settings' as it has been declared as 'final'
see declaration of 'settings'

Klassen

Klassen haben eine Reihe Unterschiede von den Strukturen:

  • in der Vereinbarung wird das Schlüsselwort class verwendet;
  • alle Glieder der Klasse haben einen impliziten Spezifikator private, wenn nicht anders angegeben wird. Glieder-Daten der Struktur haben impliziten Zugangstyp public, wenn nicht anders angegeben wird;
  • Klassenobjekte haben immer die Tabelle virtueller Funktionen, auch wenn es keine einzige virtuelle Funktion in der Klasse erklärt wird. Strukturen können keine virtuelle Funktionen haben;
  • Operator new kann für Klassenobjekte verwendet werden, für Strukturen kann dieser Operator nicht verwendet werden;
  • Klassen können nur von Klassen heritiert werden, Strukturen können nur von Strukturen heritiert werden.

Klassen können offenbaren Konstruktor und Destruktor haben. Wenn der Konstruktor bestimmt wird, ist Initialisierung der Variable des Typs Struktur oder Klasse durch Initialisierungsfolge unmöglich.

Beispiel:

struct trade_settings
  {
   double take;         // Preiswerte von Gewinnmitnahme 
   double stop;         // Preiswerte von Schutzstop 
   uchar  slippage;     // Wert der zulässigen Slippage 
   //--- Konstruktor
          trade_settings() { take=0.0; stop=0.0; slippage=5; }
   //--- Destruktor
         ~trade_settings() { Print("Das ist Ende"); } 
  };
//--- Compiler generiert eine Fehlermeldung, dass Initialisierung unmöglich ist
trade_settings my_set={0.0,0.0,5};  

Konstruktoren und Destruktoren

Konstruktor ist eine spezielle Funktion, die automatisch beim Erstellung von Struktur- oder Klassenobjekt aufgerufen. Er wird normalerweise und für Initialisierung von Klassenglieder verwendet. Weitere werden wir nur über die Klassen reden, während das gleiche gilt für Strukturen, soweit nicht anders angegeben. Der Name eines Konstruktors muss mit dem Klassennamen gleich sein. Der Konstruktor hat keinen Rückgabetyp (Sie können den Typ void angeben).

Definierte Klasseglieder — Strings, dynamische Arrays und Objekte, die Initialisierung erfordern, werden in jedem Fall initialisiert werden, unabhängig davon, ob es ein Konstruktor gibt.

Jede Klasse kann mehrere Konstruktoren mot unterschiedlichen Anzahl von Parametern und Initialisierungsliste haben. Der Konstruktor, der Angabe von Parametern erfordert, heißt parametrischer Konstruktor.

Ein Konstruktor ohne Parameter ist ein Default-Konstruktor. Wenn keine Konstruktoren in einer Klasse deklariert sind, erstellt der Compiler einen Default-Konstruktor während der Kompilierung.

//+------------------------------------------------------------------+
//| Klasse für Arbeiten mit Datum                                    |
//+------------------------------------------------------------------+
class MyDateClass
  {
private:
   int               m_year;          // Jahr
   int               m_month;         // Monat
   int               m_day;           // Tag des Monats
   int               m_hour;          // Stunde an einem Tag
   int               m_minute;        // Minuten
   int               m_second;        // Sekunden
public:
   //--- Default-Konstruktor
                     MyDateClass(void);
   //--- parametrischer Konstruktor
                     MyDateClass(int h,int m,int s);
  };

 

Sie können ein Konstruktor in der Klassebeschreibung deklarieren und dann ihren Körper definieren. Zum Beispiel zwei Konstruktoren der Klasse MyDateClass können auf folgende Weise definiert werden:

//+------------------------------------------------------------------+
//| Default-Konstruktor                                              |
//+------------------------------------------------------------------+
MyDateClass::MyDateClass(void)
  {
//---
   MqlDateTime mdt;
   datetime t=TimeCurrent(mdt);
   m_year=mdt.year;
   m_month=mdt.mon;
   m_day=mdt.day;
   m_hour=mdt.hour;
   m_minute=mdt.min;
   m_second=mdt.sec;
   Print(__FUNCTION__);
 ;}
//+------------------------------------------------------------------+
//| Parametrischer Konstruktor                                       |
//+------------------------------------------------------------------+
MyDateClass::MyDateClass(int h,int m,int s)
  {
   MqlDateTime mdt;
   datetime t=TimeCurrent(mdt);
   m_year=mdt.year;
   m_month=mdt.mon;
   m_day=mdt.day;
   m_hour=h;
   m_minute=m;
   m_second=s;
   Print(__FUNCTION__);
 ;}

Im Default-Konstruktor werden alle Klassenglieder mit Hilfe der Funktion TimeCurrent() gefüllt. Im parametrischen Konstruktor werden nur Stunde gefüllt. Andere Glieder der Klasse (m_year, m_month und m_day) werden automatisch mit dem aktuellen Datum automatisch initialisiert.

Der Default-Konstruktor hat eine besondere Bedeutung bei der Initialisierung eines Arrays von Objekten seiner Klasse. Konstruktor, denen alle Parameter Standardwerten haben, ist kein Default-Konstruktor. Wir zeigen dies am Beispiel:

//+------------------------------------------------------------------+
//| Klasse mit einem Default-Konstruktor                             |
//+------------------------------------------------------------------+
class CFoo
  {
   datetime          m_call_time;     // Zeit des letzten Aufrufs des Objektes
public:
   //--- Konstruktor mit einem Parameter, der einen Standardwert hat, ist kein Default-Konstruktor
                     CFoo(const datetime t=0){m_call_time=t;};
   //--- Copy-Konstruktor 
                     CFoo(const CFoo &foo){m_call_time=foo.m_call_time;};
 
   string ToString(){return(TimeToString(m_call_time,TIME_DATE|TIME_SECONDS));};
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
// CFoo foo; // diese Option kann nicht verwendet werden - Default-Konstruktor ist nicht definiert
//--- Gültige Optionen für Erstellung des Objekts CFoo
   CFoo foo1(TimeCurrent());     // Ein expliziter Aufruf des parametrischen Konstruktors
   CFoo foo2();                  // Ein expliziter Aufruf des parametrischen Konstruktors min einem Standardparameter
   CFoo foo3=D'2009.09.09';      // Ein impliziter Aufruf des parametrischen Konstruktors
   CFoo foo40(foo1);             // Ein expliziter Aufruf des Copy-Konstruktors
   CFoo foo41=foo1;              // Ein impliziter Aufruf des Copy-Konstruktors
   CFoo foo5;                    // Ein expliziter Aufruf des Default-Konstruktors (wenn es gibt kein Default-Konstruktor,
                                 // wird ein parametrischer Konstruktor mit Standardparameter aufgerufen)
//--- Gültige Optionen für Erlangung von Zeigern CFoo
   CFoo *pfoo6=new CFoo();       // dynamische Erstellung des Objekts und Erhaltung des Zeigers darauf
   CFoo *pfoo7=new CFoo(TimeCurrent());// Eine andere Variante der dynamischen Erstellung des Objekts
   CFoo *pfoo8=GetPointer(foo1); // pfoo8 zeigt nun auf Objekt foo1
   CFoo *pfoo9=pfoo7;            // pfoo9 und pfoo7 zeigen auf das gleiche Objekt
   // CFoo foo_array[3];         // diese Option kann nicht verwendet werden - Default-Konstruktor ist nicht definiert
//--- Ableiten Werte m_call_time
   Print("foo1.m_call_time=",foo1.ToString());
   Print("foo2.m_call_time=",foo2.ToString());
   Print("foo3.m_call_time=",foo3.ToString());
   Print("foo4.m_call_time=",foo4.ToString());
   Print("foo5.m_call_time=",foo5.ToString());
   Print("pfoo6.m_call_time=",pfoo6.ToString());
   Print("pfoo7.m_call_time=",pfoo7.ToString());
   Print("pfoo8.m_call_time=",pfoo8.ToString());
   Print("pfoo9.m_call_time=",pfoo9.ToString());
//--- Dynamisch erstellte Objekte löschen
   delete pfoo6;
   delete pfoo7;
   //delete pfoo8;  // Es ist nicht notwendig pfoo8 explizit zu löschen, da er auf das automatisch erstellten Objekt foo1 zeigt
   //delete pfoo9;  // Es ist nicht notwendig pfoo9 explizit zu löschen, da er auf das gleichen Objekt wie pfoo7
 ;}

Wenn Sie kommentieren Zeile in diesem Beispiel

   //CFoo foo_array[3];     // diese Option kann nicht verwendet sein - kein Default-Konstruktor ist angegeben

oder

   //CFoo foo_dyn_array[];  // diese Option kann nicht verwendet sein - kein Default-Konstruktor ist angegeben

dann wird der Compiler Fehler "default constructor is not defined" zurückgeben.

Wenn die Klasse hat einen Konstruktor, der vom Benutzer definiert ist, wird der Default-Konstruktor vom Compiler nicht generiert. Dies bedeutet, dass, wenn ein parametrischer Konstruktor in einer Klasse deklariert war, aber kein Default-Konstruktor war deklariert, kann man die Arrays von Objekten dieser Klasse nicht deklarieren. Auf diesem Script meldet der Compiler einen Fehler:

//+------------------------------------------------------------------+
//| Eine Klasse ohne Default-Konstruktor                             |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
                     CFoo(string name) { m_name=name;}
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Fehler "default constructor is not defined" ist während Kompilierung zurückgegeben
   CFoo badFoo[5];
 ;}

In diesem Beispiel hat die Klasse CFoo einen deklarierten parametrischen Konstruktor - in solchen Fällen erstellt der Compiler keinen Default-Konstruktor während der Kompilierung. Zur gleichen Zeit, wenn Sie ein Array von Objekten deklarieren, ist es vorausgesetzt, dass alle müssen automatisch erstellt und initialisiert werden. Beim automatischen Initialisierung eines Objekts, ein Default-Konstruktor muss aufgerufen werden, aber da der Default-Konstruktor nicht explizit deklariert war, und nicht automatisch durch den Compiler erstellt war, Erstellung eines solchen Objekts ist unmöglich. Aus diesem Grund generiert der Compiler einen Fehler bei der Kompilierung.

Es gibt eine spezielle Syntax, um das Objekt mit dem Konstruktor zu initialisieren. Konstruktor-Initializers (spezielle Konstruktionen für Initialisierung) für die Glieder einer Struktur oder Klasse können in der Initialisierungsliste angegeben werden.

Initialisierungsliste ist eine Liste von durch Kommas getrennten Initialisierern, der durch einen Doppelpunkt nach der Parameterliste des Konstruktors und voe dem Körper kommt (kommt vor der öffnenden Klammer). Es gibt mehrere Anforderungen:

  • Initialisierungslisten können nur in Konstruktoren verwendet werden.
  • Mitglieder von Eltern können nicht in der Initialisierungsliste initialisiert werden
  • Nach der Initialisierungsliste muss Definition (Implementierung) der Funktion folgen.

Hier ist ein Beispiel von mehreren Konstruktoren zur Initialisierung der Mitgliedern der Klasse.

//+------------------------------------------------------------------+
//| Klasse um die Namen des Gestalts zu speichern                    |
//+------------------------------------------------------------------+
class CPerson
  {
   string            m_first_name;     // Vorname 
   string            m_second_name;    // Familienname
public:
   //--- Ein leerer Default-Konstruktor
                     CPerson() {Print(__FUNCTION__);};
   //--- Parametrischer Konstruktor
                     CPerson(string full_name);
   //--- Ein Konstruktor mit der Initialisierungsliste
                     CPerson(string surname,string name): m_second_name(surname), m_first_name(name) {};
   void PrintName(){PrintFormat("Name=%s Surname=%s",m_first_name,m_second_name);};
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPerson::CPerson(string full_name)
  {
   int pos=StringFind(full_name," ");
   if(pos>=0)
     {
      m_first_name=StringSubstr(full_name,0,pos);
      m_second_name=StringSubstr(full_name,pos+1);
    ;}
 ;}
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Erhalten eine Fehlermeldung "default constructor is not defined"
   CPerson people[5];
   CPerson Tom="Tom Sawyer";                       // Tom Sawyer
   CPerson Huck("Huckleberry","Finn");             // Huckleberry Finn
   CPerson *Pooh = new CPerson("Winnie","Pooh");  // Winnie the Pooh
   //--- Werte ausgeben
   Tom.PrintName();
   Huck.PrintName();
   Pooh.PrintName();
   
   //--- Das dynamisch erstellte Objekt löschen
   delete Pooh;
 ;}

In diesem Fall hat die Klasse CPerson drei Konstruktoren:

  1. Expliziter Default-Konstruktor, der Erstellen eines Arrays von Objekten dieser Klasse ermöglicht.
  2. Konstruktor mit einem Parameter, der einen vollständigen Namen als Parameter nimmt und ihn auf den Vornamen und Familiennamen nach dem gefundenen Leerzeichen teilt.
  3. Konstruktor mit zwei Parametern, der Initialisierungsliste enthält. Initialisierers — m_second_name(surname) und m_first_name(name).

Beachten Sie, wie die Initialisierung mit einer Liste Zuweisung ersetzt hat. Einzelne Mitglieder sollten initialisiert werden, wie:

  Klassenglieder (Liste von Ausdrücken)

In der Initialisierungsliste können Mitglieder in beliebiger Reihenfolge gehen, aber alle Mitglieder der Klasse werden nach der Reihenfolge der Deklaration initialisiert werden. Dies bedeutet, dass in der dritten Konstruktor wird zunächst Mitglied m_first_name initialisiert, als er zuerst deklariert war, und nachdem Mitglied m_second_name initialisiert wird. Dies sollte in Fällen, in denen die Initialisierung von einigen Klassenmitgliedern von den Werten der anderen Klassenmitglieder hängt, berücksichtigt werden.

Wenn kein Default-Konstruktor in der Basisklasse deklariert ist, und ein oder mehrere Konstruktoren mit Parametern deklariert sind, sollten Sie immer einen der Basisklassenkonstruktoren in der Initialisierungsliste anrufen. Er kommt durch Komma wie übliche Listenmitglieder, und wird zuerst während Initialisierung des Objekts unabhängig von von den Ort in der Initialisierungsliste aufgerufen.

//+------------------------------------------------------------------+
//| Basisklasse                                                      |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
   //--- Konstruktor mit der Initialisierungsliste
                     CFoo(string name) : m_name(name) { Print(m_name);}
  };
//+------------------------------------------------------------------+
//| Nachfolger der Klasse CFoo                                       |
//+------------------------------------------------------------------+
class CBar : CFoo
  {
   CFoo              m_member;      // Klassenglieder ist ein Objekt von Parent
public:
   //--- Default-Konstruktor ruft den Konstruktor von Parent in der Initialisierungsliste
                     CBar(): m_member(_Symbol), CFoo("CBAR") {Print(__FUNCTION__);}
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   CBar bar;
 ;}

In diesem Beispiel während der Erstellung des Objekts bar wird Default-Konstruktor CBar() aufgerufen. In diesem Default-Konstruktor erst wird Konstruktor für den Vorfahr CFoo, dann wird Konstruktor für Klassenglied m_member augerufen.

Destruktor ist eine Sonderfunktion, die authomatisch aufgerufen wird, wenn das Klassenobjekt beseitigt wird. Destruktorname wird so geschrieben, wie der Klassenname mit Tilde(~). Zeilen, dynamische Felde und Objekte, die initialisiert werden muessen, werden aud jeden Fall deinitialisiert unabhängig von der Anwesenheit des Destruktors. Wenn es einen Destruktor gibt, werden diese Handlungen nach Destruktoraufruf durchgeführt werden.

Destruktors sind immer virtuell, unabhängig davon, ob sie mit dem Schluesselwort virtual erklärt werden oder nicht.

Methodenbestimmung der Klassen

Funktionen-Methoden der Klasse können sowohl innerhalb, als auch ausserhalb der Klassenerklärung bestimmt werden. Wenn die Methode innerhalb der Klasse bestimmt wird, folgt Ihr Koerper unmittelbar nach der Methodenerklärung.

Beispiel:

class CTetrisShape
  {
protected:
   int               m_type;
   int               m_xpos;
   int               m_ypos;
   int               m_xsize;
   int               m_ysize;
   int               m_prev_turn;
   int               m_turn;
   int               m_right_border;
public:
   void              CTetrisShape();
   void              SetRightBorder(int border) { m_right_border=border; }
   void              SetYPos(int ypos)          { m_ypos=ypos;           }
   void              SetXPos(int xpos)          { m_xpos=xpos;           }
   int               GetYPos()                  { return(m_ypos);        }
   int               GetXPos()                  { return(m_xpos);        }
   int               GetYSize()                 { return(m_ysize);       }
   int               GetXSize()                 { return(m_xsize);       }
   int               GetType()                  { return(m_type);        }
   void              Left()                     { m_xpos-=SHAPE_SIZE;    }
   void              Right()                    { m_xpos+=SHAPE_SIZE;    }
   void              Rotate()                   { m_prev_turn=m_turn; if(++m_turn>3) m_turn=0; }
   virtual void      Draw()                     { return;                }
   virtual bool      CheckDown(int& pad_array[]);
   virtual bool      CheckLeft(int& side_row[]);
   virtual bool      CheckRight(int& side_row[]);
  }; 

Funktionen mit  SetRightBorder(int border) nach Draw() werden vereinbart und bestimmt innerhalb der Klasse CTetrisShape.

Konstruktor CTetrisShape() und Methoden CheckDown(int& pad_array[]), CheckLeft(int& side_row[]) und CheckRight(int& side_row[]) werden nur innerhalb der Klasse erklärt, aber noch nicht bestimmt. Bestimmungen dieser Funktionen muessen weiter nach Kode folgen. Um die Methode ausser der Klasse zu bestimmen, wird Operation der Kontexterlaubnis verwendet , als Kontext funktioniert der Klassenname.

Beispiel:

//+------------------------------------------------------------------+
//| Konstruktor der Basisklasse                                      |
//+------------------------------------------------------------------+
void CTetrisShape::CTetrisShape()
  {
   m_type=0;
   m_ypos=0;
   m_xpos=0;
   m_xsize=SHAPE_SIZE;
   m_ysize=SHAPE_SIZE;
   m_prev_turn=0;
   m_turn=0;
   m_right_border=0;
  }
//+-----------------------------------------------------------------------+
//| Kontrolle der Moeglichkeit nach unten zu bewegen (für Stab und Kubus) |
//+-----------------------------------------------------------------------+
bool CTetrisShape::CheckDown(int& pad_array[])
  {
   int i,xsize=m_xsize/SHAPE_SIZE;
//---
   for(i=0; i<xsize; i++)
     {
      if(m_ypos+m_ysize>=pad_array[i]) return(false);
     }
//---
   return(true);
  }

 
Zugangsmodifikatoren  public, protected und private

Bei der Ausarbeitung der neuen Klasse ist es empfohlen, den Zugang zu Aussengliedern zu begrenzen. Für diese Zwecke werden Schlusselwoerter private oder protected verwendet. In diesem Fall wird der Zugang zu diesen versteckten Daten nur aus Funktionen-Methoden derselben Klasse erfolgen. Wenn das Schluesselwort protected verwendet wird, kann der Zugang zu versteckten Daten auch aus Methoden der Klassen erfolgen - Nachfolger dieser Klasse. In gleicher Weise kann der Zugang auch zu Funktionen-Metoden der Klasse begrenzt werden.

Wenn es notwendig ist, den Zugang zu Gliedern und/oder Methoden der Klasse vollkammen zu eroeffnen, wird das Schluesselwort publicverwendet.

Beispiel:

class CTetrisField
  {
private:
   int               m_score;                            // Stand
   int               m_ypos;                             // laufende Figurlage 
   int               m_field[FIELD_HEIGHT][FIELD_WIDTH]; // Dom Matrix
   int               m_rows[FIELD_HEIGHT];               // Numerierung von DOM Reihen  
   int               m_last_row;                         // Letzte freie Reihe 
   CTetrisShape     *m_shape;                            // Tetrisfigur 
   bool              m_bover;                            // Spiel beendet 
public:
   void              CTetrisField() { m_shape=NULL; m_bover=false; }
   void              Init();
   void              Deinit();
   void              Down();
   void              Left();
   void              Right();
   void              Rotate();
   void              Drop();
private:
   void              NewShape();
   void              CheckAndDeleteRows();
   void              LabelOver();
  }; 

Alle Glieder und Methoden der Klasse, vereinbart nach dem Spezifikator public: (und bis folgendem Zugangsspezifikator)), sind zugaenglich bei jeder Referenz zum Objekt dieser Klasse.  In diesem Beispiel sind es folgende Glieder:  Funktionen CTetrisField(), Init(),  Deinit(), Down(), Left(), Right(), Rotate() und Drop().

Alle Glieder der Klasse erklärt  nach dem Zugangsspezifikator zu Elementen private: (und bis folgendem Zugangsspezifikator) sind nur für die Funktionen-Glieder dieser Klasse zugaenglich. Spezifikatoren des Zugangs zu Elementen beenden immer mit dem Doppelpunkt (:) und können in der Klassenbestimmung mehrmals erscheinen.

Zugang zu Gliedern der Basisklasse kann bei Vererbung in abgeleiteten Klassen neu definiert werden.

Modifier final

Das Vorhandensein des final Modifiers bei der Deklaration der Struktur verbietet weitere Vererbung. Wenn es nicht notwendig ist, die Struktur weiter zu ändern oder wenn Änderungen aus Sicherheitsgründen unzulässig sind, deklarieren Sie diese mit dem final Modifier. Dabei werden alle Glieder der Struktur implizit auch als final gelten.

class CFoo final
  {
  //--- Körper der Klasse
  };
 
class CBar : public CFoo
  {
  //--- Körper der Klasse
  };

Bei einem Versuch der Vererbung von einer Struktur mit dem final Modifier, wie im Beispiel oben, gibt der Compiler einen Fehler aus:

cannot inherit from 'CFoo' as it has been declared as 'final'
see declaration of 'CFoo'

Unions (union)

Eine Union stellt einen besonderen Datentyp dar und setzt sich aus mehreren Variablen zusammen, die sich denselben Speicherbereich teilen.  Daraus folgt, dass eine Union die Interpretation einer und derselben Reihenfolge der Bytes auf zwei (oder mehr) verschiedenen Weisen ermöglicht. Die Deklaration einer Union ist gleich der Deklaration einer Struktur und beginnt mit dem Schlüsselwort union.

union LongDouble
{
  long   long_value;
  double double_value;
};

Im Gegensatz zu Strukturen, gehören verschiedene Komponenten einer Union zu demselben Speicherbereich. In diesem Beispiel wurde die LongDouble Union deklariert, in welcher sich der Wert vom Typ long und der Wert vom Typ double einen Speicherbereich teilen. Es ist wichtig zu verstehen, die Union kann nicht gleichzeitig den ganzzahligen Wert long und den reellen Wert double speichern (wie es in Strukturen der Fall ist), denn die Variablen long_value und double_value überschneiden sich (im Speicher). Das MQL5-Programm kann aber jederzeit die Information, die in der Union enthalten ist, als einen ganzzahligen (long) oder reellen Wert (double) bearbeiten.  Daraus folgt, dass die Union die Interpretation einer und derselben Reihenfolge von Daten auf zwei (oder mehr) verschiedenen Weisen ermöglicht.

Bei der Deklaration der Union reserviert der Compiler einen Speicherbereich, der für das Speichern des größten nach dem Volumen Typs in der Union der Variablen ausreichend ist. Für den Zugang zu einem Element der Union wird dieselbe Syntax wie für Strukturen verwendet — der Point-Operator.

union LongDouble
{
  long   long_value;
  double double_value;
};
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   LongDouble lb;
//--- erhalten wir die ungültige Zahl -nan(ind) und geben sie aus
   lb.double_value=MathArcsin(2.0);
   printf("1.  double=%f                integer=%I64X",lb.double_value,lb.long_value);
//--- die größte normalisierte Zahl (DBL_MAX)
   lb.long_value=0x7FEFFFFFFFFFFFFF;
   printf("2.  double=%.16e  integer=%I64X",lb.double_value,lb.long_value);
//--- die kleinste positive normalisierte Zahl (DBL_MIN)
   lb.long_value=0x0010000000000000;    
   printf("3.  double=%.16e  integer=%.16I64X",lb.double_value,lb.long_value);
  }
/*  Das Ergebnis der Ausführung
    1.  double=-nan(ind)                integer=FFF8000000000000
    2.  double=1.7976931348623157e+308  integer=7FEFFFFFFFFFFFFF
    3.  double=2.2250738585072014e-308  integer=0010000000000000
*/

Da Unions es dem Programm erlauben, dieselben Daten im Speicher unterschiedlich zu interpretieren, werden sie häufig in den Fällen verwendet, wenn eine ungewöhnliche Typumwandlung benötigt wird.

Die Unions können nicht in die Vererbung involviert werden sowie können per Definition keine statischen Members haben. Sonst verhält sich union wie eine Struktur, deren Members Nullpunktverschiebung haben. Die folgenden Typen können keine Union Members sein:

  • dynamische Arrays
  • Strings
  • Pointer auf Objekte und Funktionen
  • Objekte von Klassen
  • Objekte der Strukturen, die Konstruktoren und Destruktoren haben
  • Objekte der Strukturen, die Members aus den Punkten 1-5 beinhalten

Eine Union kann wie Klassen Konstruktoren und Destruktoren sowie Methoden haben. Standardmäßig haben die Members einer Union den Zugriffstyp public, für die Erstellung privater Elemente ist das Schlüsselwort private zu verwenden. Alle diese Möglichkeiten sind in einem Beispiel vorgestellt. Das Beispiel zeigt, wie man die Farbe mit dem Typ color in ARGB umwandeln kann, wie dies die ColorToARGB() Funktion macht.

//+------------------------------------------------------------------+
//| Union für die Umwandlung von color(BGR) in ARGB      |
//+------------------------------------------------------------------+
union ARGB
  {
   uchar             argb[4];
   color             clr;
   //--- Konstruktoren
                     ARGB(color col,uchar a=0){Color(col,a);};
                    ~ARGB(){};
   //--- öffentliche Methoden
public:
   uchar   Alpha(){return(argb[3]);};
   void    Alpha(const uchar alpha){argb[3]=alpha;};
   color   Color(){ return(color(clr));};
   //--- private Methoden
private:
   //+------------------------------------------------------------------+
   //| Setzen der Farbe und des Wertes des Alpha-Kanals                          |
   //+------------------------------------------------------------------+
   void    Color(color col,uchar alpha)
     {
      //--- Farbe dem clr Member setzen
      clr=col;
      //--- den Wert der Alpha Komponente setzen - Transparenzlevel
      argb[3]=alpha;
      //--- die Bytes R und B (Red und Blue) miteinander tauschen   
      uchar t=argb[0];argb[0]=argb[2];argb[2]=t;
     };
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 0x55 bedeutet 55/255=21.6 % (0% - völlig durchsichtig)
   uchar alpha=0x55; 
//--- der color Typ wird als 0x00BBGGRR dargestellt
   color test_color=clrDarkOrange;
//--- hier erhalten wir die Werte der Bytes aus der ARGB Union
   uchar argb[]; 
   PrintFormat("0x%.8X - so sieht der color Typ für %s aus, BGR=(%s)",
               test_color,ColorToString(test_color,true),ColorToString(test_color));
//--- der ARGB Typ wird als 0x00RRGGBB dargestellt, die RR und BB Komponenten haben Plätze getauscht 
   ARGB argb_color(test_color);
//--- kopieren wir das Array der Bytes
   ArrayCopy(argb,argb_color.argb);
//--- so sieht es in der ARGB Darstellung aus  
   PrintFormat("0x%.8X - ARGB Darstellung mit dem Alpha-Kanal=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- fügen wir den Wert der Transparenz hinzu
   argb_color.Alpha(alpha);
//--- versuchen wir ARGB als "color" Typ zu definieren
   Print("ARGB als color=(",argb_color.clr,")  Alpha-Kanal=",argb_color.Alpha());
//--- kopieren wir das Array der Bytes
   ArrayCopy(argb,argb_color.argb);
//--- so sieht es in der ARGB Darstellung aus
   PrintFormat("0x%.8X - ARGB Darstellung mit dem Alpha-Kanal=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- vergleichen mit dem Ergebnis der ColorToARGB() Funktion
   PrintFormat("0x%.8X - Ergebnis von ColorToARGB(%s,0x%.2x)",ColorToARGB(test_color,alpha),
               ColorToString(test_color,true),alpha);
  }
/* Das Ergebnis der Ausführung
   0x00008CFF - so sieht der Typ color für clrDarkOrange, BGR=(255,140,0)
   0x00FF8C00 - ARGB Darstellung mit dem Alpha-Kanal=0x00, ARGB=(0,255,140,0)
   ARGB als color=(0,140,255)  Alpha-Kanal=85
   0x55FF8C00 - ARGB Darstellung mit dem Alpha-Kanal=0x55, ARGB=(85,255,140,0)
   0x55FF8C00 - Ergebnis von ColorToARGB(clrDarkOrange,0x55)
*/ 

Schnittstellen

Eine Schnittstelle gibt an, welche Funktionalität eine Klasse dann umsetzen kann. In der Tat ist sie eine Klasse, die keine Mitglieder enthalten kann und keinen Konstruktor und/oder Destruktor hat. Alle in einer Schnittstelle deklarierten Methoden sind rein virtuell, auch ohne eine explizite Definition.

Eine Schnittstelle wird mit dem Schlüsselwort interface definiert. Zum Beispiel:

//--- Basisschnittstelle für die Beschreibung von Tieren
interface IAnimal
  {
//--- Die Methoden einer Schnittstelle haben public-Zugang
   void Sound();  // Tierlaut
  };
//+------------------------------------------------------------------+
//|  Klasse CCat ist von der Schnittstelle IAnimal geerbt            |
//+------------------------------------------------------------------+
class CCat : public IAnimal
  {
public:
                     CCat() { Print("Cat was born"); }
                    ~CCat() { Print("Cat is dead");  }
   //--- Implementieren die Methode Sound der Schnittstelle IAnimal
   void Sound(){ Print("meou"); }
  };
//+------------------------------------------------------------------+
//|  Klasse CDog ist von der Schnittstelle IAnimal geerbt            |
//+------------------------------------------------------------------+
class CDog : public IAnimal
  {
public:
                     CDog() { Print("Dog was born"); }
                    ~CDog() { Print("Dog is dead");  }
   //--- Implementieren die Methode Sound der Schnittstelle IAnimal
   void Sound(){ Print("guaf"); }
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Ein Array von Zeigern auf Objekte vom Typ IAnimal
   IAnimal *animals[2];
//--- Wir erzeugen abgeleiteten Klassen von IAnimal und speichern Zeiger auf sie in einem Array    
   animals[0]=new CCat;
   animals[1]=new CDog;
//--- Rufen auf die Methode Sound() der Basisschnittstelle IAnimal für jede Kindklasse  
   for(int i=0;i<ArraySize(animals);++i)
      animals[i].Sound();
//--- Objekte löschen
   for(int i=0;i<ArraySize(animals);++i)
      delete animals[i];
//--- Das Ergebnis
/*
   Cat was born
   Dog was born
   meou
   guaf
   Cat is dead
   Dog is dead
*/
  }

Wie im Fall mit abstrakte Klassen, kann man nicht eine Objekt-Schnittstelle ohne Vererbung erstellen. Die Schnittstelle kann nur von anderen Schnittstellen vererbt werden, und auch kann Eltern der Klasse sein. Eine Schnittstelle hat immer die öffentliche Sichtbarkeit.

Die Schnittstelle kann nicht innerhalb einer Klassendeklaration oder Strukturdeklaration deklariert werden, aber man kann den Zeiger auf die Schnittstelle in einer Variablen vom Typ void * speichern. Im Allgemeinen können Sie einen Zeiger auf ein Objekt einer beliebigen Klasse in der Variable vom Typ void * speichern. Um einen void * Zeiger auf einen Zeiger auf ein Objekt einer bestimmten Klasse umzuwandeln, wird der Operator dynamic_cast verwendet. Wenn die Umwandlung nicht möglich ist, wird das Ergebnis der Operation dynamic_cast NULL.

Sehen Sie auch

Objektorientiertes Programmieren