Strukturen, Klassen und Schnittstellen

Strukturen

Eine Struktur umfasst eine Reihe von Elementen beliebigen Typs (außer dem Typ void). Auf diese Weise gruppiert eine Struktur logisch verbundene Daten unterschiedlicher Typen.

Strukturdeklaration

Der Strukturdatentyp wird wie folgt bestimmt:

struct Strukturname 
  {
   Elementenbeschreibung
  };

Der Strukturname kann nicht als Identifikator verwendet werden (Name der Variable oder Funktion). Man bedenke, dass die Strukturelemente in MQL5 ohne Ausrichtung direkt aufeinander folgen. In der Sprache C++ wird diese Festlegung an den Kompiler mit der Anweisung gerichtet.

#pragma pack(1)

Wenn es notwendig ist, eine andere Ausrichtung in der Struktur durchzuführen, muss man "Platzhalter" der gewünschten Größen verwenden.

Beispiel:

struct trade_settings
  {
   uchar  slippage;     // Wert der zulässigen Slippage - Größe 1 Byte
   char   reserved1;    // 1 Byte Platzhalter
   short  reserved2;    // 2 Byte Platzhalter
   int    reserved4;    // noch 4 Byte Platzhalter. Bereitgestellte Ausrichtung von 8 Byte
   double take;         // Preis eines fixierten Take-Profits
   double stop;         // Preis eines schützenden Stop-Loss
  };

Eine solche Beschreibung ausgerichteter Strukturen ist nur für die Übergabe an importierte dll-Funktionen notwendig.

Achtung: dieses Beispiel zeigt falsch entworfene Daten. Es ist besser, zuerst die Daten von Take-Profit und Stop-Loss mit dem Typ double und dann das Slippage-Member des Uchartyps zu deklarieren. In diesem Fall ist die interne Darstellung der Daten immer gleich, unabhängig von dem im #pragma pack() angegebenen Wert.

Wenn die Struktur Variablen der Art string und/oder Objekt des dynamischen Feldes enthält, weist der Kompiler für eine solche Struktur einen impliziten Konstruktor zu, in dem alle Mitglieder vom Typ string zurückgesetzt werden und die dynamischen Array-Objekt werden korrekt initialisiert.  

Einfache Strukturen

Strukturen, die keine Zeichenketten, Klassenobjekte, Pointer und Objekte von dynamischen Arrays enthalten, werden als einfache Strukturen bezeichnet. Variablen einfacher Strukturen sowie deren Arrays können als Parameter an importierte Funktionen aus DLL. übergeben werden.

Das Kopieren von einfachen Strukturen ist nur in zwei Fällen erlaubt:

  • Wenn die Objekte zum gleichen Strukturtyp gehören.
  • Wenn die Objekte linear miteinander verbunden sind, was bedeutet, dass eine Struktur ein Nachkomme einer anderen ist.

Um ein Beispiel zu geben, entwickeln wir die nutzerdefinierte Struktur CustomMqlTick mit ihrem Inhalt, der identisch ist mit der integrierten Struktur MqlTick. Der Kompiler erlaubt es nicht, den Wert des MqlTick-Objekts in das Objekt vom Typ CustomMqlTick zu kopieren. Auch die direkte Typenumwandlung zum benötigten Typ verursacht einen Kompilerfehler:

      //--- Kopieren einfacher Strukturen verschiedener Typen ist verboten
      my_tick1=last_tick;               // der Kompiler wirft hier den Fehler aus
     
      //--- Eine Typenumwandlung von unterschiedlichen Strukturen ist auch verboten
      my_tick1=(CustomMqlTick)last_tick;// der Kompiler wirft hier den Fehler aus

Daher bleibt nur eine Option - das Kopieren der Werte der Strukturelemente, eines nach dem anderen. Es ist weiterhin erlaubt, die Werte des gleichen Typs von CustomMqlTick zu kopieren.

      CustomMqlTick my_tick1,my_tick2;
      //--- Es ist erlaubt, die Objekte des gleichen Typs CustomMqlTick wie folgt zu kopieren
      my_tick2=my_tick1;
     
      //--- Erstellen Sie ein Array aus den Objekten der einfachen CustomMqlTick-Struktur und schreiben Sie Werte in diese Struktur.
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;

Die Funktion ArrayPrint() wird aufgerufen, um die Werte des Arrays arr[] im Journal auszudrucken.

//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Entwickeln einer Struktur ähnlich der integrierten MqlTick
   struct CustomMqlTick
     {
      datetime          time;          // Aktualisierungszeit des letzten Preises
      double            bid;           // Aktueller Bid-Preis
      double            ask;           // Aktueller Ask-Preis
      double            last;          // Aktueller Preis des letzten Handels (Last)
      ulong             volume;        // Volumen des aktuellen, letzten Preises
      long              time_msc;      // Aktualisierungszeit des letzten Preises in Millisekunden
      uint              flags;         // Tick-Flags     
     };
   //--- Abrufen des letzten Tickwertes
   MqlTick last_tick;
   CustomMqlTick my_tick1,my_tick2;
//--- Versuch die Daten von MqlTick nach CustomMqlTick zu kopieren
   if(SymbolInfoTick(Symbol(),last_tick))
     {
      //--- Kopieren von unverbundenen Strukturen ist verboten
      //1. my_tick1=last_tick;               // der Kompiler wirft hier den Fehler aus
     
      //--- Auch eine Typenumwandlung von unverbundenen Strukturen untereinander ist verboten
      //2. my_tick1=(CustomMqlTick)last_tick;// Der Kompiler wirft hier den Fehler aus
     
      //--- Daher werden die Strukturmitglieder einzeln kopiert     
      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;
     
      //--- Es ist erlaubt, die Objekte des gleichen Typs CustomMqlTick wie folgt zu kopieren
      my_tick2=my_tick1;
     
      //--- Erstellen Sie ein Array aus den Objekten der einfachen CustomMqlTick-Struktur und schreiben Sie Werte in diese Struktur.
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;
      ArrayPrint(arr);
//--- Beispiel einer Anzeige der Werte des Arrays mit den Objekten vom Typ CustomMqlTick
      /*
                       [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());
  }

Das zweite Beispiel zeigt die Eigenschaften des linearen Kopierens einfacher Strukturen. Angenommen, wir haben die Grundstruktur Tier, von der die Strukturen für Katze und Hund abgeleitet werden. Wir können die Objekte Tier und Katze sowie Tier und Hund ineinander kopieren, aber wir können Katze und Hund nicht ineinander kopieren, obwohl beide Nachkommen der Struktur Tier sind.

//--- Struktur zur Beschreibung von Hund
struct Hund: Tier
  {
   bool              hunting;       // Beutejäger
  };
//--- Struktur der Beschreibung der Katzen
struct Katze: Tier
  {
   bool              home;          // Eigenzüchtung
  };
//--- Erstellen der abgeleiteten Strukturen
  Hund hund;
  Katze katze;
//--- Kann vom Vor- auf den Nachfahren kopiert werden (Tier ==> Hund)
  hund=ein_Tier;
   hund.swim=true;    // Hunde können schwimmen
//--- Objekte der Nachfahren können nicht aufeinander kopiert werden (Hund != Katze)
   katze=hund;        // der Kompiler wirft einen Fehler aus

Der ganze Code des Beispiels:

//--- Basisstruktur zur Beschreibung von Tier
struct Tier
  {
   int               head;          // Anzahl der Köpfe
   int               legs;          // Anzahl der Beine
   int               wings;         // Anzahl der Flügel
   bool              tail;          // Schwanz
   bool              fly;           // fliegt
   bool              swim;          // schwimmt  
   bool              run;           // rennt
  };
//--- Struktur zur Beschreibung von Hund
struct Hund: Tier
  {
   bool              hunting;       // Beutejäger
  };
//--- Struktur der Beschreibung der Katzen
struct Katze: Tier
  {
   bool              home;          // Eigenzüchtung
  };
//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Erstellen und beschreiben des Basistyps von Tier
  Tier ein_Tier;
  ein_Tier.head=1;
  ein_Tier.legs=4;
  ein_Tier.wings=0;
   ein_Tier.tail=true;
   ein_Tier.fly=false;
   ein_Tier.swim=false;
   ein_Tier.run=true;
//--- create objects of child types
  Hund hund;
  Katze katze;
//--- Kann vom Vor- auf den Nachfahren kopiert werden (Tier ==> Hund)
  hund=ein_Tier;
   hund.swim=true;    // Hunde können schwimmen
//--- Objekte der Nachfahren können nicht aufeinander kopiert werden (Hund != Katze)
   //katze=hund;        // der Kompiler wirft einen Fehler aus
//--- Daher können die Elemente nur eines nach dem anderen kopiert werden
  katze.head=hund.head;
  katze.legs=hund.legs;
  katze.wings=hund.wings;
  katze.tail=hund.tail;
  katze.fly=hund.fly;
   katze.swim=false;   // Katzen können nicht schwimmen
//--- Man kann die Werte vom Nach- auf den Vorfahren zu kopieren
  Tier elephant;
  elephant=katze;
   elephant.run=false;// Elefanten können nicht rennen
   elephant.swim=true;// Elefanten können schwimmen
//--- Erstellen eines Arrays
  Tier tiere[4];
  tiere[0]=ein_tier;
  tiere[1]=hund;  
  tiere[2]=katze;
  tiere[3]=elephant;
//--- Ausdrucken
   ArrayPrint(animals);
//--- Berechnungsergebnis
/*
       [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
*/  
  }

Ein anderer Weg einfache Strukturen zu kopieren ist die Verwendung von unions. Die Objekte der Struktur sollten Mitglieder der gleichen union sein - siehe das Beispiel für 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. Zugriff auf die Strukturmitglieder erhält man durch die Operation Punkt (.) durchgeführt.

Beispiel:

struct trade_settings
  {
   double take;         // Preis für Take-Profit
   double stop;         // Preis für Stop-Loss
   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;

'pack' zum Ausrichten von Struktur- und Klassenfeldern #

Das spezielle Attribut pack ermöglicht die Ausrichtung von Struktur- oder Klassenfeldern.

 pack([n])

wobei n einer der folgenden Werte ist: 1, 2, 4, 8 oder 16. Es kann auch fehlen.

Beispiel:

   struct pack(sizeof(long)) MyStruct
     {
      // Strukturmitglieder müssen auf eine 8-Byte-Grenze ausgerichtet werden
     };
oder
   struct MyStruct pack(sizeof(long))
     {
      // Strukturmitglieder müssen auf eine 8-Byte-Grenze ausgerichtet werden
     };

'pack(1)' wird standardmäßig auf Strukturen angewendet. Das bedeutet, dass sich die Strukturelemente nacheinander im Speicher befinden und die Strukturgröße gleich der Summe der Größe ihrer Elemente ist.

Beispiel:

//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Einfache Struktur ohne Ausrichtung
   struct Simple_Structure
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
   //--- Deklaration einer einfachen Struktur   
   Simple_Structure s;  
//--- Anzeige der Größe von jedem Strukturmitglied  
   Print("sizeof(s.c)=",sizeof(s.c));
   Print("sizeof(s.s)=",sizeof(s.s));
   Print("sizeof(s.i)=",sizeof(s.i));
   Print("sizeof(s.d)=",sizeof(s.d));
//--- Sicherstellen, dass die Größe der POD-Struktur gleich der Summe der Mitgliedergrößen ist
   Print("sizeof(simple_structure)=",sizeof(simple_structure));
/*
  Ergebnis:
   sizeof(s.c)=1
   sizeof(s.s)=2
   sizeof(s.i)=4
   sizeof(s.d)=8
   sizeof(simple_structure)=15 
*/    
  }

Das Ausrichten der Strukturfelder kann erforderlich sein, wenn Daten mit Bibliotheken von Drittanbietern (*.DLL) ausgetauscht werden, bei denen diese Ausrichtung angewendet wird.

Lassen Sie uns anhand einiger Beispiele zeigen, wie die Ausrichtung funktioniert. Wir werden eine Struktur verwenden, die aus vier Mitgliedern besteht und nicht ausgerichtet ist.

//--- Einfache Struktur ohne Ausrichtung
   struct Simple_Structure pack() // es wird keine Größe angegeben, eine Ausrichtung auf den Grenzwert von 1 Byte wird gesetzt
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
//--- Deklaration einer einfachen Struktur  
   Simple_Structure s;

Strukturfelder sind gemäß der Deklarationsreihenfolge und Typengröße nacheinander im Speicher angeordnet. Die Strukturgröße ist 15, während ein Offset zu den Strukturfeldern in den Arrays undefiniert ist.

simple_structure_alignment

Deklarieren wir nun die gleiche Struktur mit der Ausrichtung von 4 Bytes und führen den Code aus.

//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Eine einfache Struktur mit einer Ausrichtung von 4 Bytes
   struct Simple_Structure pack(4)
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
   //--- Deklaration einer einfachen Struktur   
   Simple_Structure s;  
//--- Anzeige der Größe von jedem Strukturmitglied  
   Print("sizeof(s.c)=",sizeof(s.c));
   Print("sizeof(s.s)=",sizeof(s.s));
   Print("sizeof(s.i)=",sizeof(s.i));
   Print("sizeof(s.d)=",sizeof(s.d));
//--- make sure the size of POD structure is now not equal to the sum of its members' size
   Print("sizeof(simple_structure)=",sizeof(simple_structure));
/*
  Ergebnis:
   sizeof(s.c)=1
   sizeof(s.s)=2
   sizeof(s.i)=4
   sizeof(s.d)=8
   sizeof(simple_structure)=16 // structure size has changed
*/    
  }

Die Strukturgröße hat sich so verändert, dass alle Mitglieder von 4 Bytes und mehr einen Offset vom Anfang des Strukturfachs von 4 Bytes haben. Kleinere Elemente sind auf ihre eigene Größengrenze auszurichten (z.B. 2 für 'short'). So sieht es aus (das hinzugefügte Byte wird grau dargestellt).

simple_structure_alignment_pack

In diesem Fall wird 1 Byte nach dem Mitglied s.c hinzugefügt, so dass das Feld s.s (sizeof(short)==2) die Grenze von 2 Bytes hat (Ausrichtung für den Typ 'short').

Der Offset zum Anfang der Struktur im Array ist ebenfalls auf die 4-Byte-Grenze ausgerichtet, d.h. die Adressen der Elemente a[0], a[1] und a[n] sollen bei Simple_Structure arr[] ein Vielfaches von 4 Bytes sein.

Betrachten wir zwei weitere Strukturen, die aus ähnlichen Typen mit 4-Byte-Ausrichtung, aber mit einer unterschiedlichen Reihenfolge der Mitglieder. In der ersten Struktur befinden sich die Elemente in aufsteigender Reihenfolge der Typengröße.

//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Eine einfache Struktur, ausgerichtet auf eine Begrenzung von 4 Bytes
   struct CharShortInt pack(4)
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(double)=4
     };
//--- Deklaration einer einfachen Struktur  
   CharShortInt ch_sh_in;
//--- Anzeige der Größe von jedem Strukturmitglied  
   Print("sizeof(ch_sh_in.c)=",sizeof(ch_sh_in.c));
   Print("sizeof(ch_sh_in.s)=",sizeof(ch_sh_in.s));
   Print("sizeof(ch_sh_in.i)=",sizeof(ch_sh_in.i));
 
//--- Sicherstellen, dass die Größe der POD-Struktur gleich der Summe der Mitgliedergrößen ist
   Print("sizeof(CharShortInt)=",sizeof(CharShortInt));
/*
  Ergebnis:
   sizeof(ch_sh_in.c)=1
   sizeof(ch_sh_in.s)=2
   sizeof(ch_sh_in.i)=4
   sizeof(CharShortInt)=8
*/   
  }

Wie wir sehen können, ist die Strukturgröße 8 und besteht aus den beiden 4-Byte-Blöcken. Der erste Block enthält die Felder mit den Typen 'char' und 'short', während der zweite Block das Feld mit dem Typ 'int' enthält.

charshortint

Nun verwandeln wir die erste Struktur in die zweite, die sich nur in der Feldreihenfolge unterscheidet, indem wir das Element mit dem Typ 'short' an das Ende verschieben.

//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Eine einfache Struktur, ausgerichtet auf eine Begrenzung von 4 Bytes
   struct CharIntShort pack(4)
     {
      char              c; // sizeof(char)=1
      int               i; // sizeof(double)=4
      short             s; // sizeof(short)=2
     };
//--- Deklaration einer einfachen Struktur  
   CharIntShort ch_in_sh;
//--- Anzeige der Größe von jedem Strukturmitglied  
   Print("sizeof(ch_in_sh.c)=",sizeof(ch_in_sh.c));
   Print("sizeof(ch_in_sh.i)=",sizeof(ch_in_sh.i));
   Print("sizeof(ch_in_sh.s)=",sizeof(ch_in_sh.s));
//--- Sicherstellen, dass die Größe der POD-Struktur gleich der Summe der Mitgliedergrößen ist
   Print("sizeof(CharIntShort)=",sizeof(CharIntShort));
/*
  Ergebnis:
   sizeof(ch_in_sh.c)=1
   sizeof(ch_in_sh.i)=4
   sizeof(ch_in_sh.s)=2
   sizeof(CharIntShort)=12
*/   
  }

Obwohl sich der Strukturinhalt nicht geändert hat, hat sich die Größe durch die Änderung der Elementreihenfolge erhöht.

charintshort

Die Ausrichtung sollte auch bei der Vererbung berücksichtigt werden. Lassen Sie uns dies anhand der einfachen übergeordneten Struktur mit einem einzigen Element vom Typ 'char' demonstrieren. Die Strukturgröße ohne Ausrichtung ist 1.

   struct Eltern
     {
      char              c;    // sizeof(char)=1
     };

Erstellen wir die abgeleitete Klasse Kind mit dem Mitglied vom Typ 'short' (sizeof(short)=2).

   struct Kind pack(2) : Parent
     {
      short             s;   // sizeof(short)=2
     };

Im Ergebnis ist die Strukturgröße gleich 4, wenn die Ausrichtung auf 2 Bytes festgelegt wird, obwohl die Größe der Elemente 3 ist. In diesem Beispiel sollen der übergeordneten Klasse 2 Bytes zugeordnet werden, so dass der Zugriff auf das Feld 'short' der Unterklasse auf 2 Bytes ausgerichtet ist.

Das Wissen darüber, wie Speicher für die Strukturmitglieder zugeteilt wird, ist notwendig, wenn eine MQL5-Anwendung mit gelesenen/geschriebenen Daten von Dateien oder Streams arbeitet.

Das MQL5\Include\WinAPI-Verzeichnis der Standardbibliothek enthält die Funktionen zum Arbeiten mit den WinAPI-Funktionen. Diese Funktionen wenden die Strukturen mit einer bestimmten Ausrichtung auf die Fälle an, in denen sie für die Arbeit mit WinAPI erforderlich sind.  

offsetof ist ein spezieller Befehl, der sich direkt auf das Attribut pack bezieht. Es ermöglicht uns, das Offset eines Elements am Anfang der Struktur zu abzurufen.

//--- Deklaration der Variablen vom Typ Kind
   Children child;  
//--- Erkennen des Offsets vom Anfang der Struktur
   Print("offsetof(Children,c)=",offsetof(Children,c));
   Print("offsetof(Children,s)=",offsetof(Children,s));  
/*
  Ergebnis:
   offsetof(Children,c)=0
   offsetof(Children,s)=2
*/   

Modifikator 'final' #

Das Vorhandensein des Modifikators 'final' 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 Modifikator 'final'. Dadurch werden alle Mitglieder der Struktur implizit als endgültig gelten.

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

Bei einem Versuch der Vererbung von einer Struktur mit dem Modifikator 'final', wie im Beispiel oben, wirft der Kompiler einen Fehler aus:

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

Klassen #

Klassen unterscheiden sich von den Strukturen wie folgt:

  • Das Schlüsselwort Klasse wird in der Deklaration verwendet;
  • Standardmäßig haben alle Klassenmitglieder Zugriff auf die Spezifikation private, sofern nicht anders angegeben. Datenelemente der Struktur haben die standardmäßig die Zugriffsart 'public', sofern nicht anders angegeben;
  • Klassenobjekte haben immer eine Tabelle mit virtuellen Funktionen, auch wenn in der Klasse keine virtuellen Funktionen deklariert sind. Strukturen können keine virtuellen Funktionen haben;
  • Der Operator new kann auf Klassenobjekte angewendet werden, aber nicht auf Strukturen;

Klassen und Strukturen können einen expliziten Konstruktor und Destruktor haben. Wenn Ihr Konstruktor explizit definiert ist, ist die Initialisierung einer Struktur- oder Klassenvariablen mit der Initialisierungssequenz nicht möglich.

Beispiel:

struct trade_settings
  {
   double take;         // Preis von Take-Profit 
   double stop;         // Preis von Stop-Loss 
   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"); } 
  };
//--- Der Kompiler generiert eine Fehlermeldung, dass eine 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 – Zeichenketten, 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 mit einer unterschiedlichen Anzahl von Parametern und Initialisierungsliste haben. Ein Konstruktor, der die Angabe von Parametern erfordert, heißt parametrischer Konstruktor.

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

//+------------------------------------------------------------------+
//| Klasse für Arbeiten mit einem 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:
   //--- Standard-Konstruktor
                     MyDateClass(void);
   //--- parametrischer Konstruktor
                     MyDateClass(int h,int m,int s);
  };

 

Sie können einen Konstruktor in der Klassenbeschreibung deklarieren und dann dessen Körper definieren. Beispielsweise können zwei Konstruktoren der Klasse MyDateClass auf folgende Weise definiert werden:

//+------------------------------------------------------------------+
//| Standard-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 Standard-Konstruktor werden alle Klassenglieder mit Hilfe der Funktion TimeCurrent() gefüllt. Im parametrischen Konstruktor werden nur die Werte der Stunde übergeben. Die anderen Mitglieder der Klasse (m_year, m_month und m_day) werden automatisch mit dem aktuellen Datum initialisiert.

Der Standard-Konstruktor hat eine besondere Bedeutung bei der Initialisierung eines Arrays von Objekten seiner Klasse. Ein Konstruktor, der für alle Parameter Standardwerte setzt, ist kein Standard-Konstruktor. Wir zeigen dies am Beispiel:

//+------------------------------------------------------------------+
//| Klasse mit einem Standard-Konstruktor                             |
//+------------------------------------------------------------------+
class CFoo
  {
   datetime          m_call_time;     // Zeit des letzten Aufrufs des Objektes
public:
   //--- Konstruktor mit einem Parameter, der einen Standardwert hat, ist kein Standard-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 Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
// CFoo foo; // diese Option kann nicht verwendet werden - Standard-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 Standard-Konstruktors (wenn es gibt kein Standard-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 - Standard-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 es auf das automatisch erstellten Objekt foo1 zeigt
   //delete pfoo9;  // Es ist nicht notwendig pfoo9 explizit zu löschen, da es auf das gleichen Objekt wie pfoo7 zeigt.
  }

Wenn Sie in diesem Beispiel diese Zeile entkommentieren

  //CFoo foo_array[3];     // diese Variante kann nicht verwendet werden - es wurde kein Standard-Konstruktor angegeben

oder

  //CFoo foo_dyn_array[];  // diese Variante kann nicht verwendet sein - es wurde kein Standard-Konstruktor angegeben

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

Wenn die Klasse einen Konstruktor hat, der vom Benutzer definiert ist, wird der Standard-Konstruktor vom Kompiler nicht generiert. Dies bedeutet, dass, wenn ein parametrischer Konstruktor in einer Klasse deklariert worden ist, aber kein Standard-Konstruktor, kann man die Arrays von Objekten dieser Klasse nicht deklarieren. Der Kompiler für diesen Skript einen Fehler auswerfen:

//+------------------------------------------------------------------+
//| Eine Klasse ohne Standard-Konstruktor                             |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
                     CFoo(string name) { m_name=name;}
  };
//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Fehler "default constructor is not defined" wird beim Kompilieren zurückgegeben
   CFoo badFoo[5];
  }

In diesem Beispiel hat die Klasse CFoo einen deklarierten parametrischen Konstruktor - in solchen Fällen erstellt der Kompiler keinen Standard-Konstruktor während der Kompilierung. Gleichzeitig wird bei der Deklaration eines Arrays von Objekten davon ausgegangen, dass alle Objekte automatisch angelegt und initialisiert werden sollen. Bei der automatischen Initialisierung eines Objekts ist es notwendig, einen Standard-Konstruktor aufzurufen, aber da der Standard-Konstruktor nicht explizit deklariert und nicht automatisch vom Kompiler generiert wird, ist es unmöglich, ein solches Objekt zu erstellen. Aus diesem Grund erzeugt der Kompiler bei der Kompilierung einen Fehler.

Es gibt eine spezielle Syntax, um Objekte durch dem Konstruktor zu initialisieren. Konstruktor-Initialisierer (Sonderkonstruktionen zur Initialisierung) für die Mitglieder einer Struktur oder Klasse können in der Initialisierungsliste angegeben werden.

Eine Initialisierungsliste ist eine durch Kommata getrennte Liste von Initialisatoren, die nach dem Doppelpunkt nach der Liste der Parameter eines Konstruktors steht und dem Körper vorangestellt wird (steht vor einer ö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 für mehrere Konstruktoren zur Initialisierung von Klassenmitgliedern.

//+------------------------------------------------------------------+
//| Klasse um den Namen einer Person zu speichern                    |
//+------------------------------------------------------------------+
class CPerson
  {
   string            m_first_name;     // Vorname 
   string            m_second_name;    // Familienname
public:
   //--- Ein leerer Standard-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 Programm Start Funktion                                   |
//+------------------------------------------------------------------+
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. Einen expliziten Standard-Konstruktor, der das Erstellen eines Arrays von Objekten dieser Klasse ermöglicht.
  2. Ein Konstruktor mit einem Parameter, der einen vollständigen Namen als Parameter erhält und ihn entsprechend der gefundenen Anordnung in den Namen und Vornamen teilt;
  3. Ein Konstruktor mit zwei Parametern, der Initialisierungsliste enthält. Initialisieren – m_second_name(surname) und m_first_name(name).

Beachten Sie, wie die Initialisierung mit einer Liste die Zuweisung ersetzt. Die einzelnen Mitglieder werden wie folgt initialisiert:

  Klassenglieder (Liste von Ausdrücken)

In der Initialisierungsliste können die Mitglieder in beliebiger Reihenfolge stehen, aber alle Mitglieder der Klasse werden nach der Reihenfolge der Deklaration initialisiert. Dies bedeutet, dass im dritten Konstruktor zuerst das Mitglied m_first_name initialisiert, da es an erster Stelle steht, und danach m_second_name. Dies sollte in Fällen berücksichtigt werden, in denen die Initialisierung von einigen Klassenmitgliedern von den Werten der anderen Klassenmitglieder abhängt.

Wenn kein Standard-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. Sie ist eine Kommata getrennte, reguläre Mitgliederliste und wird bei der Initialisierung des Objekts unabhängig von der Position in der Initialisierungsliste zuerst 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:
   //--- Standard-Konstruktor ruft den Konstruktor von Eltern in der Initialisierungsliste
                     CBar(): m_member(_Symbol), CFoo("CBAR") {Print(__FUNCTION__);}
  };
//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
   CBar bar;
  }

In diesem Beispiel während der Erstellung des Objekts bar wird Standard-Konstruktor CBar() aufgerufen. In diesem Standard-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 müssen, werden auf jeden Fall deinitialisiert, unabhängig von der Anwesenheit des Destruktors. Wenn es einen Destruktor gibt, werden diese Handlungen nach Destruktoraufruf durchgeführt werden.

Destruktoren sind immer virtuell, unabhängig davon, ob sie mit dem Schlüuesselwort 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 Körper 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 müssen 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 Möglichkeit 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 Schlüsselwörter 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 Mitgliedern und/oder Methoden der Klasse vollkommen zu eröffnen, wird das Schlüsselwort 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 Mitglieder und Methoden der Klasse, definiert nach dem Spezifikator public: (und bis nächsten Spezifikator), sind für jeden Zugriff auf ein Objekt dieser Klasse zugänglich. In diesem Beispiel sind es folgende Mitglieder: Die Funktionen CTetrisField(), Init(),  Deinit(), Down(), Left(), Right(), Rotate() und Drop().

Alle Mitglieder der Klasse, deklariert nach dem Spezifikator private: (und bis nächsten Spezifikator) sind nur für die Funktions-Mitglieder dieser Klasse zugänglich. Spezifikatoren für einen Zugriff auf die Elemente werden immer mit einem Doppelpunkt (:) und können in der Klassenbestimmung mehrmals auftreten.

Alle Klassenmitglieder, die nach 'protected' deklariert wurden: Zugriffsspezifikation (und bis zum nächsten Zugriffsspezifikation) sind nur für Mitgliederfunktionen dieser Klasse und Mitgliederfunktionen der abgeleiteten Klassen verfügbar. Bei einem versuch auf die Mitglieder zu zuzugreifen, die innerhalb der Spezifikatoren 'private' oder 'protected' deklariert wurden, erhalten wir einen Fehler bei der Kompilierung. Beispiel:

class A
  {
protected:
   //--- der Kopieroperator ist nur innerhalb der Klasse A und ihrer Nachkommen verfügbar
   void operator=(const A &)
     {
     }
  };
class B
  {
   //--- Klassenobjekt A deklarieren
   A                 a;
  };
//+------------------------------------------------------------------+
//| Skript Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
   //--- zwei Variablen des Typs B deklarieren
   B b1b2;
   //--- Versuch, das eine Objekt in das andere zu kopieren
   b2=b1;
  }

Beim Kompilieren des Codes wird eine Fehlermeldung ausgeworfen — ein Versuch, die ein Objekt auf anderes zu kopieren:

attempting to reference deleted function 'void B::operator=(const B&)'   trash3.mq5   32   6

Die zweite Zeile unten bietet eine detailliertere Beschreibung — der Kopieroperator in Klasse B wurde explizit gelöscht, da der nicht verfügbare Kopieroperator der Klasse A aufgerufen wird:

   function 'void B::operator=(const B&)' was implicitly deleted because it invokes inaccessible function 'void A::operator=(const A&)' 

Der Zugriff auf die Mitglieder der Basisklasse kann während Vererbung in abgeleiteten Klassen neu definiert werden.

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

'delete' Spezifikator

Der Spezifikator delete markiert Funktionen einer Klasse, die nicht verwendet werden können. Das heißt, wenn das Programm explizit oder implizit auf eine solche Funktion verweist, wird der Fehler bereits bei der Kompilierung ausgeworfen. Mit diesem Spezifikator können Sie beispielsweise Methoden der Eltern in einer Unterklasse nicht verfügbar machen. Das gleiche Ergebnis kann erreicht werden, wenn wir die Funktion im privaten Bereich der Elternklasse deklarieren (Deklarationen im Abschnitt private). Hier macht die Verwendung von delete den Code auf der Ebene der Nachkommen lesbarer und verwaltbarer.

class A
  {
public:
                     A(void) {value=5;};
   double            GetValue(void) {return(value);}
private:
   double            value;
  };
class Bpublic A
  {
   double            GetValue(void)=delete;
  };
//+------------------------------------------------------------------+
//| Skript Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Variable vom Typ A deklarieren
   A a;
   Print("a.GetValue()="a.GetValue());
//--- Versuch, den Wert der Variablen des Typs B abzufragen
   B b;
   Print("b.GetValue()="b.GetValue()); // Der Kompiler zeigt für diese Zeile einen Fehler
  }

Die Kompilernachricht

attempting to reference deleted function 'double B::GetValue()'   
   function 'double B::GetValue()' was explicitly deleted here   

Der Spezifikator 'delete' erlaubt es, das Auto-Casting oder den Kopierkonstruktor zu deaktivieren, die sonst auch im Abschnitt 'private' versteckt werden müssten. Beispiel:

class A
  {
public:
   void              SetValue(double v) {value=v;}
   //--- alle Aufrufe für den Typ int verhindern
   void              SetValue(int) = delete;
   //--- den Kopieroperator verhindern
   void              operator=(const A&) = delete;
private:
   double            value;
  };
//+------------------------------------------------------------------+
//| Skript Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- zwei Variablen des Typs A deklarieren
   A a1a2;
   a1.SetValue(3);      // error!
   a1.SetValue(3.14);   // OK
   a2=a1;               // error!
  }

Während der Kompilierung erhalten wir die Fehlermeldungen:

attempting to reference deleted function 'void A::SetValue(int)' 
   function 'void A::SetValue(int)' was explicitly deleted here 
attempting to reference deleted function 'void A::operator=(const A&)'  
   function 'void A::operator=(const A&)' was explicitly deleted here  

Modifikator 'final' #

Das Vorhandensein des Modifikators 'final' 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 Modifikator 'final'. 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 Modifikator 'final', wie im Beispiel oben, wirft der Kompiler 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 Programm Start Funktion                                   |
//+------------------------------------------------------------------+
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.

Auf Unions kann keine Vererbung angewendet werden und sie können per Definition keine statischen Members haben. Sonst verhält sich union wie eine Struktur, deren Mitglieder eine Nullpunktverschiebung haben. Die folgenden Typen können nicht Mitglieder einer Union sein:

  • dynamische Arrays
  • Strings
  • Pointer auf Objekte und Funktionen
  • Objekte von Klassen
  • Objekte der Strukturen, die Konstruktoren und Destruktoren haben
  • Objekte der Strukturen, die Mitglieder der Punkte 1-5 beinhalten

Eine Union kann, so wie Klassen, Konstruktoren und Destruktoren und 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 Programm Start Funktion                                   |
//+------------------------------------------------------------------+
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 (Interfaces) #

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 ITier
  {
//--- Die Methoden einer Schnittstelle haben public-Zugang
   void Sound();  // Tierlaut
  };
//+------------------------------------------------------------------+
//|  Klasse CKatze ist von der Schnittstelle ITier geerbt            |
//+------------------------------------------------------------------+
class CKatze : public ITier
  {
public:
                     CKatze() { Print("Katze was born"); }
                    ~CKatze() { Print("Katze is dead");  }
   //--- Implementieren die Methode Sound der Schnittstelle ITier
   void Sound(){ Print("meou"); }
  };
//+------------------------------------------------------------------+
//|  Klasse CHund ist von der Schnittstelle ITier geerbt            |
//+------------------------------------------------------------------+
class CHund : public ITier
  {
public:
                     CHund() { Print("Hund was born"); }
                    ~CHund() { Print("Hund is dead");  }
   //--- Implementieren die Methode Sound der Schnittstelle ITier
   void Sound(){ Print("guaf"); }
  };
//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Ein Array von Zeigern auf Objekte vom Typ ITier
  ITier *animals[2];
//--- Wir erzeugen abgeleiteten Klassen von ITier und speichern Zeiger auf sie in einem Array    
   animals[0]=new CKatze;
   animals[1]=new CHund;
//--- Rufen auf die Methode Sound() der Basisschnittstelle ITier 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
/*
  Katze was born
  Hund was born
   meou
   guaf
  Katze is dead
  Hund 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.

Siehe auch

Objektorientiertes Programmieren