Strutture, Classi e Interfacce

Strutture

Una struttura è un insieme di elementi di qualsiasi tipo (ad eccezione del tipo void). Così, la struttura combina dati logicamente correlati di tipo diverso.

Dichiarazione Struttura

Il tipo di dati struttura è determinato dalla seguente descrizione:

struct structure_name
  {
   elements_description
  };

Il nome struttura non può essere usato come un identificatore (nome di una variabile o funzione). Va notato che in MQL5 elementi strutturali si susseguono direttamente, senza allineamento. In C++ un tale ordine è fatto per il compilatore utilizzando la seguente istruzione:

#pragma pack(1)

Se si vuole fare un altro allineamento nella struttura, utilizzare i membri ausiliari, "fillers" per la giusta dimensione.

Esempio:

struct trade_settings
  {
   uchar  slippage;     // valore grandezza-slippage permissibile 1 byte
   char   reserved1;    // salta 1 byte
   short  reserved2;    // salta 2 bytes
   int    reserved4;    // altri 4 bytes vengono saltati. garantisce l'allineamento del bordo 8 byte
   double take;         // valore del prezzo del fissaggio del profitto
   double stop;         // valore del prezzo dello stop protettivo
  };

Tale descrizione delle strutture allineate è necessaria solo per il trasferimento di funzioni importate dll.

Attenzione: Questo esempio illustra dati non correttamente designati. Sarebbe meglio prima dichiarare take e stop dati di grandi dimensioni del tipo double, e quindi dichiarare lo slippagemembro del tipo uchar. In questo caso, la rappresentazione interna dei dati sarà sempre la stessa indipendentemente dal valore specificato in #pragma pack().

Se una struttura contiene variabili di tipo string e/o oggetto di un array dinamico, il compilatore assegna un costruttore implicito a tale struttura. Questo costruttore resetta tutti i membri della struttura di tipo string ed inizializza correttamente oggetti della matrice dinamica.

Strutture Semplici

Le strutture che non contengono stringhe, oggetti delle classi, puntatori ed oggetti di array dinamici sono chiamate Strutture Semplici. Le variabili di strutture semplici e le loro array possono essere passate come parametri alle funzioni importate da DLL.

La copia di strutture semplici è consentita solo in due casi:

  • Se gli oggetti appartengono allo stesso tipo di struttura
  • Se gli oggetti sono collegati dalla linea che significa che una struttura è un discendente di un'altra.

Per fornire un esempio, sviluppiamo la struttura personalizzata CustomMqlTick con il suo contenuto identico a quella built-in MqlTick. Il compilatore non consente di copiare il valore dell'oggetto MqlTick nell'oggetto di tipo CustomMqlTick. Typecasting diretto al tipo necessario provoca anche l'errore di compilazione:

      //--- la copia di strutture semplici di diversi tipi è vietata
      my_tick1=last_tick;               // il compilatore restituisce un errore qui
     
      //--- fare il typecasting di strutture di tipi differenti l'un l'altra è vietato anche
      my_tick1=(CustomMqlTick)last_tick;// il compilatore restituisce un errore qui

Pertanto, resta una sola opzione – la copia dei valori degli elementi della struttura uno ad uno. È comunque permesso copiare i valori dello stesso tipo di CustomMqlTick.

      CustomMqlTick my_tick1,my_tick2;
      //--- è consentito copiare gli oggetti dello stesso tipo di CustomMqlTick nel seguente modo
      my_tick2=my_tick1;
     
      //--- creare un array dagli oggetti della struttura semplice CustomMqlTick e scrivere valori in esso
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;

La funzione ArrayPrint() è chiamata per un controllo per visualizzare la funzione arr[] valore array nel journal.

//+------------------------------------------------------------------+
//| Funzione start programma Script                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
// --- sviluppa la struttura simile al built-in MqlTick
   struct CustomMqlTick
     {
      datetime          time;          // Ora di aggiornamento del prezzo Last 
      double            bid;           // Prezzo Bid corrente
      double            ask;           // Prezzo Ask corrente
      double            last;          // Prezzo corrente dell'ultimo trade (Last)
      ulong             volume;        // Volume per il corrente prezzo Last 
      long              time_msc;      // Tempo di aggiornamento del prezzo Last in millisecondi
      uint              flags;         // Tick flags     
     };
   //--- ottiene l'ultimo valore tick
   MqlTick last_tick;
   CustomMqlTick my_tick1,my_tick2;
//--- tentativo di copiare dati da MqlTick a CustomMqlTick
   if(SymbolInfoTick(Symbol(),last_tick))
     {
      //--- la copia di strutture semplici non correlate è vietata
      //1. my_tick1=last_tick;               // il compilatore restituisce un errore qui
     
      //--- il typecastin di strutture non correlate l'una all'altra, è vietato, pure
      //2. my_tick1=(CustomMqlTick)last_tick;// il compilatore restituisce un errore qui
     
      //--- quindi copia i membri della struttura uno per uno 
      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;
     
      //--- è consentito copiare gli oggetti dello stesso tipo di CustomMqlTick nel seguente modo
      my_tick2=my_tick1;
     
      //--- creare un array dagli oggetti della struttura semplice CustomMqlTick e scrivere valori in esso
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;
      ArrayPrint(arr);
/ --- esempio di visualizzazione dei valori dell'array contenente gli oggetti del tipo 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());
  }

Il secondo esempio mostra le caratteristiche della copia di strutture semplici dalla discendenza. Supponiamo di avere la struttura base Animal, da cui derivano le strutture Cat e Dog. Possiamo copiare gli oggetti Animal e Cat, così come gli oggetti Animal e Dog, ma non possiamo copiare Cat e Dog a vicenda, anche se entrambi sono discendenti della struttura Animal.

//--- struttura per descrivere i cani
struct Dog: Animal
  {
   bool              hunting;       // razza da caccia
  };
//--- struttura per descrivere i gatti
struct Cat: Animal
  {
   bool              home;          // animale domestico
  };
//--- creare oggetti di strutture figlio
   Dog dog;
   Cat cat;
//--- può essere copiato da antenato a discendente (Animal ==> Dog)
   dog=some_animal;
   dog.swim=true;    // i cani possono nuotare
//--- non è possibile copiare oggetti di strutture figlie (Dog != Cat)
   cat=dog;        // il compilatore restituisce un errore

Codice completo di esempio:

//--- struttura di base per la descrizione degli animali
struct Animal
  {
   int               head;          // numero di teste
   int               legs;          // numero di zampe
   int               wings;         // numero di ali
   bool              tail;          // coda
   bool              fly;           // che vola
   bool              swim;          // che nuota  
   bool              run;           // che corre
  };
//--- struttura per descrivere i cani
struct Dog: Animal
  {
   bool              hunting;       // razza da caccia
  };
//--- struttura per descrivere i gatti
struct Cat: Animal
  {
   bool              home;          // animale domestico
  };
//+------------------------------------------------------------------+
//| Funzione start programma Script                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- crea e descrive un oggetto del tipo di base Animal
   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;
//--- crea oggetti di tipo figlio
   Dog dog;
   Cat cat;
//--- può essere copiato da antenato a discendente (Animal ==> Dog)
   dog=some_animal;
   dog.swim=true;    // i cani possono nuotare
//--- non è possibile copiare oggetti di strutture figlie (Dog != Cat)
   //cat=dog;        // il compilatore restituisce errore qui
//--- è pertanto possibile copiare gli elementi uno per uno, solamente
   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
//--- è possibile copiare i valori dal discendente all'antenato
   Animal elephant;
   elephant=cat;
   elephant.run=false;// gli elefanti non possono correre
   elephant.swim=true;// gli elefanti possono nuotare
//--- crea un array
   Animal animals[4];
   animals[0]=some_animal;
   animals[1]=dog;  
   animals[2]=cat;
   animals[3]=elephant;
//--- stampare
   ArrayPrint(animals);
//--- risultato dell' esecuzione
/*
       [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
*/  
  }

Un altro modo per copiare i tipi semplici è l'utilizzo dell' unione. Gli oggetti delle strutture dovrebbero essere membri della stessa unione - vedere l'esempio in union.

L'accesso ai Membri Struttura

La struttura è un nuovo tipo di dati che consente di dichiarare variabili di questo tipo. La struttura può essere dichiarata solo una volta all'interno di un progetto. I membri della struttura sono accessibili usando l'operazione point(.).

Esempio:

struct trade_settings
  {
   double take;         // valori della fissazione dei prezzi profitto
   double stop;         // valore del prezzo di stop protettivo
   uchar  slippage;     // valore dello scostamento accettabile
  };
//--- creare ed inizializza una variabile di tipo trade_settings
trade_settings my_set={0.0,0.0,5};  
if (input_TP>0) my_set.take=input_TP;

'pack' per allineare i campi struttura e classe #

Lo speciale l'attributo pacck consente di impostare l'allineamento dei campi della struttura o della classe.

 pack([n])

dove n è uno dei seguenti valori: 1, 2, 4, 8 o 16. Potrebbe essere assente.

Esempio:

   struct pack(sizeof(long)) MyStruct
     {
      // i membri della struttura devono essere allineati al limite di 8 byte
     };
or
   struct MyStruct pack(sizeof(long))
     {
      // i membri della struttura devono essere allineati al limite di 8 byte
     };

'pack (1)' è applicato di default per le strutture. Ciò significa che i membri della struttura si trovano uno dopo l'altro in memoria e la dimensione della struttura è uguale alla somma della dimensione dei membri.

Esempio:

//+------------------------------------------------------------------+
// | Funzione Start del programma di Script                          |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- struttura semplice senza allineamento
   struct Simple_Structure
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
   //--- dichiara una semplice istanza di struttura   
   Simple_Structure s;  
//--- mostra la dimensione di ogni membro della struttura  
   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));
//--- assicurati che la dimensione della struttura POD sia uguale alla somma della dimensione dei suoi membri
   Print("sizeof(simple_structure)=",sizeof(simple_structure));
/*
  Result:
   sizeof(s.c)=1
   sizeof(s.s)=2
   sizeof(s.i)=4
   sizeof(s.d)=8
   sizeof(simple_structure)=15 
*/    
  }

L'allineamento dei campi della struttura può essere necessario quando si scambiano dati con librerie di terze parti (*.DLL) in cui viene applicato tale allineamento.

Usiamo alcuni esempi per mostrare come funziona l'allineamento. Applicheremo una struttura composta da quattro membri senza allineamento.

//--- struttura semplice senza allineamento
   struct Simple_Structure pack() // nessuna dimensione è specificata, l'allineamento al limite di 1 byte deve essere impostato
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
//--- dichiara una semplice istanza di struttura  
   Simple_Structure s;

I campi della struttura devono essere collocati in memoria uno dopo l'altro in base all'ordine di dichiarazione e dimensione del tipo. La dimensione della struttura è 15, mentre un offset rispetto ai campi della struttura negli array non è definito.

simple_structure_alignment

Ora dichiariamo la stessa struttura con l'allineamento di 4 byte ed eseguire il codice.

//+------------------------------------------------------------------+
// | Funzione Start del programma di Script                          |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- struttura semplice con l'allineamento a 4 byte
   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
     };
   //--- dichiara una semplice istanza di struttura   
   Simple_Structure s;  
//--- mostra la dimensione di ogni membro della struttura  
   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));
//--- assicurati che la dimensione della struttura POD non sia ora uguale alla somma della dimensione dei suoi membri
   Print("sizeof(simple_structure)=",sizeof(simple_structure));
/*
  Result:
   sizeof(s.c)=1
   sizeof(s.s)=2
   sizeof(s.i)=4
   sizeof(s.d)=8
   sizeof(simple_structure)=16 // la dimensione della struttura è cambiata
*/    
  }

La dimensione della struttura è cambiata in modo che tutti i membri di 4 byte e più abbiano un offset dall'inizio della struttura multipla di 4 byte. I membri più piccoli devono essere allineati al loro limite di dimensione (ad esempio, 2 per "short"). Ecco come appare (il byte aggiunto è mostrato in grigio).

simple_structure_alignment_pack

In questo caso, 1 byte viene aggiunto dopo il sc membro, in modo che il campo s.s (sizeof (short)==2) ha il limite di 2 byte (allineamento per il tipo 'short').

Anche l'offset all'inizio della struttura nell'array è allineato al limite di 4 byte, ovvero gli indirizzi degli elementi a [0], a[1] ed a[n] devono essere multipli di 4 byte per Simple_Structure arr[].

Consideriamo altre due strutture costituite da tipi simili con un allineamento di 4 byte ma un ordine di membro diverso. Nella prima struttura, i membri si trovano nell'ordine crescente della dimensione del tipo.

//+------------------------------------------------------------------+
// | Funzione Start del programma di Script                          |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- semplice struttura allineata al limite di 4 byte
   struct CharShortInt pack(4)
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(double)=4
     };
//--- dichiara una semplice istanza di struttura  
   CharShortInt ch_sh_in;
//--- mostra la dimensione di ogni membro della struttura  
   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));
 
//--- assicurati che la dimensione della struttura POD sia uguale alla somma della dimensione dei suoi membri
   Print("sizeof(CharShortInt)=",sizeof(CharShortInt));
/*
  Result:
   sizeof(ch_sh_in.c)=1
   sizeof(ch_sh_in.s)=2
   sizeof(ch_sh_in.i)=4
   sizeof(CharShortInt)=8
*/   
  }

Come possiamo vedere, la dimensione della struttura è 8 e consiste dei due blocchi da 4 byte. Il primo blocco contiene i campi con i tipi "char" e "short", mentre il secondo contiene il campo con il tipo 'int'.

charshortint

Ora trasformiamo la prima struttura nella seconda, che differisce solo nell'ordine dei campi, spostando il membro di tipo 'short' alla fine.

//+------------------------------------------------------------------+
// | Funzione Start del programma di Script                          |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- semplice struttura allineata al limite di 4 byte
   struct CharIntShort pack(4)
     {
      char              c; // sizeof(char)=1
      int               i; // sizeof(double)=4
      short             s; // sizeof(short)=2
     };
//--- dichiara una semplice istanza di struttura  
   CharIntShort ch_in_sh;
//--- mostra la dimensione di ogni membro della struttura  
   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));
//--- assicurati che la dimensione della struttura POD sia uguale alla somma della dimensione dei suoi membri
   Print("sizeof(CharIntShort)=",sizeof(CharIntShort));
/*
  Result:
   sizeof(ch_in_sh.c)=1
   sizeof(ch_in_sh.i)=4
   sizeof(ch_in_sh.s)=2
   sizeof(CharIntShort)=12
*/   
  }

Sebbene il contenuto della struttura non sia cambiato, l'alterazione della sequenza dei membri ha aumentato le sue dimensioni.

charintshort

L'allineamento dovrebbe anche essere considerato quando si eredita. Dimostriamolo usando la semplice struttura Parent con un singolo membro di tipo 'char'. La dimensione della struttura senza allineamento è 1.

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

Creiamo la classe figlia Children con il membro di tipo "short" (sizeof (short)=2).

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

Di conseguenza, quando si imposta l'allineamento su 2 byte, la dimensione della struttura è uguale a 4, sebbene la dimensione dei suoi membri sia 3. In questo esempio, 2 byte devono essere allocati alla classe Parent, in modo che l'accesso al campo "short" della classe figlia sia allineato a 2 byte.

La conoscenza di come viene allocata la memoria per i membri della struttura è necessaria se un'applicazione MQL5 interagisce con i dati di terze parti scrivendo/leggendo sul livello dei file o degli stream.

La directory MQL5\Include\WinAPI della Libreria standard contiene le funzioni per lavorare con le funzioni WinAPI. Queste funzioni applicano le strutture con un allineamento specificato per i casi quando è richiesto per lavorare con WinAPI.  

offsetof è un comando speciale direttamente correlato all'attributo pack. Ci consente di ottenere un offset membro dall'inizio della struttura.

//--- dichiara la variabile di tipo Children
   Children child;  
//--- rileva gli offset dall'inizio della struttura
   Print("offsetof(Children,c)=",offsetof(Children,c));
   Print("offsetof(Children,s)=",offsetof(Children,s));  
/*
  Result:
   offsetof(Children,c)=0
   offsetof(Children,s)=2
*/   

Modificatore 'final' #

L'uso del modificatore 'final' durante la dichiarazione struttura vieta ulteriori eredità da questa struttura. Se una struttura non necessita di ulteriori modifiche, o le modifiche non sono ammesse per motivi di sicurezza, dichiarare tale struttura con il modificatore 'final'. Inoltre, tutti i componenti della struttura saranno implicitamente considerati definitivi.

struct settings final
  {
  //--- Corpo della struttura
  };
 
struct trade_settings : public settings
  {
  //--- Corpo della struttura
  };

Se si tenta di ereditare da una struttura con il modificatore 'final', come mostrato nell'esempio precedente, il compilatore restituirà un errore:

non può ereditare da 'Impostazioni', siccome è stato dichiarato come 'final'
vedi dichiarazione di 'impostazioni'

Classi #

Le classi differiscono dalle strutture in base a quanto riportato qui di seguito:

  • la parola chiave class viene utilizzata nella dichiarazione;
  • Per impostazione predefinita, tutti i membri della classe hanno lo specificatore di accesso private, se non diversamente indicato. Data-membri della struttura hanno il tipo predefinito di accesso come public, salvo diversa indicazione;
  • oggetti della classe hanno sempre una tabella di funzioni virtuali, anche se non ci sono funzioni virtuali dichiarate nella classe. Le strutture non possono avere funzioni virtuali;
  • l'operatore new può essere applicato ad oggetti della classe, questo operatore non può essere applicato a strutture;
  • le classi possono essere ereditate solo dalle classi; le strutture possono essere ereditate solo dalle strutture.

Le classi e le strutture possono avere un costruttore e distruttore espliciti. Se il costruttore è definito in modo esplicito, l'inizializzazione di una struttura o variabile della classe, utilizzando la sequenza di inizializzazione, è impossibile.

Esempio:

struct trade_settings
  {
   double take;         // valori della fissazione dei prezzi profitto
   double stop;         // valore del prezzo di stop protettivo
   uchar  slippage;     // valore dello scostamento accettabile
   //--- Costruttore
          trade_settings() { take=0.0; stop=0.0; slippage=5; }
   //--- Distruttore
         ~trade_settings() { Print("Questa è la fine"); } 
  };
//--- Il compilatore genera un messaggio di errore riferendo che l'inizializzazione è impossibile
trade_settings my_set={0.0,0.0,5};  

Costruttori e distruttori

Un costruttore è una funzione speciale, che viene chiamata automaticamente quando si crea un oggetto di una struttura o di una classe, e di solito è usato per inizializzare membri della classe. Inoltre parleremo solo di classi, mentre lo stesso vale per le strutture, se non diversamente indicato. Il nome di un costruttore deve corrispondere al nome della classe. Il costruttore non ha alcun tipo di ritorno (è possibile specificare il tipo void).

I membri definiti della classe - stringhe, array dinamici ed oggetti che richiedono inizializzazioni - verranno in ogni caso inizializzati, indipendentemente dal fatto che vi sia un costruttore.

Ogni classe può avere multipli costruttori, differendo per il numero di parametri e la lista di inizializzazione. Un costruttore che richiede il fatto di specificare i parametri, viene chiamato un costruttore parametrico.

Un costruttore senza parametri viene chiamato un costruttore di default. Se non vi sono costruttori dichiarati in una classe, il compilatore crea un costruttore di default durante la compilazione.

//+--------------------------------------------------------------------------------+
//| La classe per lavorare con una data                                            |
//+--------------------------------------------------------------------------------+
class MyDateClass
  {
private:
   int               m_year;          // Anno
   int               m_month;         // Mese
   int               m_day;           // Giorno del mese
   int               m_hour;          // Ore in un giorno
   int               m_minute;        // Minuti
   int               m_second;        // Secondi
public:
   //--- Costruttore default
                     MyDateClass(void);
   //--- Costruttore parametrico
                     MyDateClass(int h,int m,int s);
  };

 

Un costruttore può essere dichiarato nella descrizione della classe e poi il suo corpo può essere definito. Ad esempio, due costruttori di MyDateClass possono essere definiti nel modo seguente:

//+--------------------------------------------------------------------------------+
//| Costruttore default                                                            |
//+--------------------------------------------------------------------------------+
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__);
  }
//+--------------------------------------------------------------------------------+
//| Costruttore parametrico                                                        |
//+--------------------------------------------------------------------------------+
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__);
  }

Nel costruttore di default, tutti i membri della classe vengono riempiti con la funzione TimeCurrent(), nel costruttore parametrico solo i valori orari vengono compilati Altri membri della classe (m_year, m_month e m_day) viene automaticamente inizializzati con la data corrente.

Il costruttore predefinito ha uno scopo speciale durante l'inizializzazione di un array di oggetti della sua classe. Il costruttore, di cui tutti i parametri hanno valori predefiniti, non è un costruttore di default. Ecco un esempio:

//+--------------------------------------------------------------------------------+
//| La classe per il costruttore di default                                        |
//+--------------------------------------------------------------------------------+
class CFoo
  {
   datetime          m_call_time;     // Ora della chiamata ultimo oggetto
public:
   //--- Un costruttore con un parametro che ha un valore predefinito non è un costruttore di default
                     CFoo(const datetime t=0){m_call_time=t;};
   //--- Un costruttore di copia
                     CFoo(const CFoo &foo){m_call_time=foo.m_call_time;};
 
   string ToString(){return(TimeToString(m_call_time,TIME_DATE|TIME_SECONDS));};
  };
//+--------------------------------------------------------------------------------+
//| Funzione di avvio del programma Script                                         |
//+--------------------------------------------------------------------------------+
voidOnStart()
  {
// CFoo foo; // Questa variante non può essere utilizzata - un costruttore di default non è impostato
//--- Le possibili opzioni per creare l'oggetto CFoo
   CFoo foo1(TimeCurrent());     // Una chiamata esplicita di un costruttore parametrico
   CFoo foo2();                  // Una chiamata esplicita di un costruttore parametrico con un parametro di default
   CFoo foo3=D'2009.09.09';      // Una chiamata implicita di un costruttore parametrico
   CFoo foo40(foo1);             // Una chiamata esplicita di un costruttore di copia
   CFoo foo41=foo1;              // Una chiamata implicita di un costruttore di copia
   CFoo foo5;                    // Una chiamata esplicita di un costruttore di default (se non c'è costruttore di default,
                                 // allora viene chiamato un costruttore parametrico con un valore predefinito)
//--- Le opzioni possibili per ricevere puntatori CFoo
   CFoo *pfoo6=new CFoo();       // Creazione dinamica di un oggetto e la ricezione di un puntatore ad esso
   CFoo *pfoo7=new CFoo(TimeCurrent());// Un'altra opzione di creazione di oggetti dinamici
   CFoo *pfoo8=GetPointer(foo1); // Ora pfoo8 punta all'oggetto foo1
   CFoo *pfoo9=pfoo7;            // pfoo9 e pfoo7 puntano all'unico e stesso oggetto
   // CFoo foo_array[3];         // Questa opzione non può essere utilizzata - un costruttore di default non è specificato
//--- Mostra il valore di 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());
//--- Elimina array creati dinamicamente
   delete pfoo6;
   delete pfoo7;
   //delete pfoo8;  // Non è necessario eliminare pfoo8 in modo esplicito, in quanto indica l'oggetto creato automaticamente foo1
   //delete pfoo9;  // Non è necessario eliminare pfoo9 esplicitamente. in quanto punta allo stesso oggetto pfoo7
  }

Se si toglie il commento alle stringhe

  //CFoo foo_array[3];     // Questa variante non può essere utilizzata - un costruttore di default non è impostato

o

  //CFoo foo_dyn_array[];  // Questa variante non può essere utilizzata - un costruttore di default non è impostato

il compilatore restituisce un errore per loro "il costruttore di default non è definito".

Se una classe ha un costruttore definito-dall'utente, il costruttore di default non viene generato dal compilatore. Ciò significa che se un costruttore parametrico è dichiarato in una classe, ma un costruttore di default non è dichiarato, non è possibile dichiarare gli array di oggetti di questa classe. Il compilatore restituirà un errore per questo script:

//+--------------------------------------------------------------------------------+
//| Una classe senza un costruttore di default                                     |
//+--------------------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
                     CFoo(string name) { m_name=name;}
  };
//+--------------------------------------------------------------------------------+
//| Funzione di avvio del programma Script                                         |
//+--------------------------------------------------------------------------------+
voidOnStart()
  {
//--- Ottiene durante la compilazione l'errore "il costruttore di default non è definito"
   CFoo badFoo[5];
  }

In questo esempio, la classe CFoo dispone di un costruttore parametrico dichiarato - in questi casi, il compilatore non crea un costruttore di default automaticamente durante la compilazione. Allo stesso tempo, quando si dichiara un array di oggetti, si presume che tutti gli oggetti debbano essere creati ed inizializzati automaticamente. Durante l'auto-inizializzazione di un oggetto, è necessario chiamare un costruttore di default, ma dal momento che il costruttore di default non è esplicitamente dichiarato e non generato automaticamente dal compilatore, non è possibile creare un tale oggetto. Per questo motivo, il compilatore genera un errore in fase di compilazione.

C'è una sintassi speciale per inizializzare un oggetto utilizzando un costruttore. Inizializzatori Costruttore (costruzioni speciali per l'inizializzazione) per i membri di una struttura o classe, possono essere specificato nella lista di inizializzazione.

Un elenco di inizializzazione è un elenco di inizializzatori separati da virgole, che arriva dopo i due punti dopo la lista dei parametri di un costruttore e precede il corpo (va prima di una parentesi graffa di apertura). Ci sono diversi requisiti:

  • Elenchi di inizializzazione possono essere utilizzati solo nei costruttori;
  • Genitore membri non possono essere inizializzati nella lista di inizializzazione;
  • L'elenco di inizializzazione deve essere seguito da una definizione (implementazione) di una funzione.

Ecco un esempio di diversi costruttori per inizializzare i membri della classe.

//+--------------------------------------------------------------------------------+
//| Una classe per memorizzare il nome di un personaggior                          |
//+--------------------------------------------------------------------------------+
class CPerson
  {
   string            m_first_name;     // Primo nome 
   string            m_second_name;    // Secondo nome
public:
   //--- Un costruttore di default vuoto
                     CPerson() {Print(__FUNCTION__);};
   //--- Un costruttore parametrico
                     CPerson(string full_name);
   //--- Un costruttore con un elenco di inizializzazione
                     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);
     }
  }
//+--------------------------------------------------------------------------------+
//| Funzione di avvio del programma Script                                         |
//+--------------------------------------------------------------------------------+
voidOnStart()
  {
//--- Riceve un errore "il costruttore di default non è definito"
   CPerson people[5];
   CPerson Tom="Tom Sawyer";                       // Tom Sawyer
   CPerson Huck("Huckleberry","Finn");             // Huckleberry Finn
   CPerson *Pooh = new CPerson("Winnie","Pooh");  // Winnie the Pooh
   //--- Valori in Output
   Tom.PrintName();
   Huck.PrintName();
   Pooh.PrintName();
   
   //--- Eliminazione di un oggetto creato in modo dinamico
   delete Pooh;
  }

In questo caso, la classe CPerson ha tre costruttori:

  1. Un esplicito costruttore di default, che permette di creare un array di oggetti di questa classe;
  2. Un costruttore con un parametro, che ottiene un nome completo come parametro e lo divide per il nome e il secondo nome secondo lo spazio trovato;
  3. Un costruttore con due parametri che contiene un elenco di inizializzazione. Inizializzatori - m_second_name(surname) e m_first_name(name).

Si noti che l'inizializzazione utilizzando una lista ha sostituito un' assegnazione. I singoli membri devono essere inizializzati come:

 class_member (una lista di espressioni)

Nella lista di inizializzazione, i membri possono andare in qualsiasi ordine, ma tutti i membri della classe verranno inizializzati secondo l'ordine del loro annuncio. Ciò significa che nel terzo costruttore, prima il membro m_first_name verrà inizializzato, come è annunciato prima, e solo dopo che m_second_name viene inizializzato. Ciò deve essere tenuto in considerazione nel caso in cui l'inizializzazione di alcuni membri della classe dipendono dai valori di altri membri della classe.

Se un costruttore di default non viene dichiarato nella classe base, ed al tempo stesso vengono dichiarati uno o più costruttori con parametri, si dovrebbe sempre chiamare uno dei costruttori della classe base nella lista di inizializzazione. Passa attraverso la virgola come membri ordinari della lista e verrà chiamato la prima volta durante l'inizializzazione dell'oggetto, non importa dove si trova nell'elenco di inizializzazione.

//+--------------------------------------------------------------------------------+
//| Classe Base                                                                    |
//+--------------------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
   //--- Un costruttore con un elenco di inizializzazione
                     CFoo(string name) : m_name(name) { Print(m_name);}
  };
//+--------------------------------------------------------------------------------+
//| Classe derivata da CFoo                                                        |
//+--------------------------------------------------------------------------------+
class CBar : CFoo
  {
   CFoo              m_member;      // Un membro della classe è un oggetto del genitore
public:
   //--- Un costruttore di default nella lista di inizializzazione chiama il costruttore di un genitore
                     CBar(): m_member(_Symbol), CFoo("CBAR") {Print(__FUNCTION__);}
  };
//+--------------------------------------------------------------------------------+
//| Funzione di avvio del programma Script                                         |
//+--------------------------------------------------------------------------------+
voidOnStart()
  {
   CBar bar;
  }

In questo esempio, quando si crea l'oggetto bar, verrà chiamato un costruttore di default CBar(), in cui prima viene chiamato un un costruttore per la CFoo padre, e poi arriva un costruttore per il membro della classe m_member.

Un distruttore è una funzione speciale che viene chiamata automaticamente quando un oggetto della classe viene distrutto. Il nome del distruttore è scritto come il nome della classe con una tilde (~). Stringhe, array dinamici ed oggetti, richiedenti la deinizializzazione, verranno de-inizializzati in ogni caso, indipendentemente dalla presenza o assenza del distruttore. Se c'è un distruttore, queste azioni verranno eseguite dopo aver chiamato il distruttore.

I distruttori sono sempre virtuali, indipendentemente dal fatto che siano dichiarati con la parola chiave virtual o meno.

Definizione dei Metodi della Classe

I metodi-funzione della classe possono essere definiti sia all'interno della classe che all'esterno della dichiarazione della classe. Se il metodo viene definito all'interno di una classe, allora il suo corpo viene subito dopo la dichiarazione del metodo.

Esempio:

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[]);
  }; 

Le funzioni da SetRightBorder (confine int) per Draw() vengono dichiarate e definite direttamente all'interno della classe CTetrisShape.

Il costruttore CTetrisShape() ed i metodi CheckDown(int& pad_array[]), CheckLeft(int& side_row[]) and CheckRight(int& side_row[]) vengono solo dichiarati all'interno della classe, ma non ancora definiti. Le definizioni di queste funzioni ci saranno ulteriormente, nel codice. Per definire il metodo all'esterno della classe, viene utilizzato l' operatore di risoluzione ambito, il nome della classe viene utilizzato come ambito.

Esempio:

//+--------------------------------------------------------------------------------+
//| Costruttore della classe base                                                  |
//+--------------------------------------------------------------------------------+
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;
  }
//+--------------------------------------------------------------------------------+
//| Controlla l'abilità di spostarsi giù (per il "lungo" ed il cubo)               |
//+--------------------------------------------------------------------------------+
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);
  }

Modificatori di accesso Public, Protected e Private

Nello sviluppo di una nuova classe, si consiglia di limitare l'accesso ai membri dall'esterno. Per questo scopo vengono utilizzate le parole chiave private oprotected. In questo caso, i dati nascosti possono essere acceduti solo dai metodi-funzione della stessa classe. Se viene utilizzata la parola chiave protected, i dati nascosti possono essere acceduti anche dai metodi delle classi - eredi di questa classe. Lo stesso metodo può essere utilizzato per limitare l'accesso ai metodi-funzione di una classe.

Se è necessario aprire completamente l'accesso ai membri e/o metodi di una classe, utilizzare la parola chiave public.

Esempio:

class CTetrisField
  {
private:
   int               m_score;                            // Punteggio
   int               m_ypos;                             // Posizione corrente delle figure
   int               m_field[FIELD_HEIGHT][FIELD_WIDTH]; // Matrice del pozzo
   int               m_rows[FIELD_HEIGHT];               // Numerazione delle righe del pozzo
   int               m_last_row;                         // Ultime righe libere
   CTetrisShape     *m_shape;                            // Figura Tetris
   bool              m_bover;                            // Game over
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();
  }; 

I membri della classe e metodi dichiarati dopo l'identificatore public: (e prima dello specificatore di accesso successivo) sono disponibili in qualsiasi riferimento all'oggetto classe dal programma. In questo esempio questi sono i seguenti membri: funzioni CTetrisField(), Init(), Deinit(), Down(), Left(), Right(), Rotate() and Drop().

I membri che vengono dichiarati dopo lo specificatore di accesso agli elementi private: (e prima dello specificatore di accesso successivo) sono disponibili solo per i membri funzioni di questa classe. Gli secificatori di accesso agli elementi finiscono sempre con i due punti (:) e possono comparire nella definizione della classe molte volte.

Qualsiasi membro della classe dichiarato dopo l' identificatore di accesso protected:(e fino al successivo identificatore di accesso) sono disponibili solo per le funzioni-membro di questa classe e per le funzioni-membro delle classi discendenti. Quando si tenta di fare riferimento ai membri che presentano gli specificatori private e protected dall'esterno, otteniamo l'errore della fase di compilazione. Esempio:

class A
  {
protected:
// --- l'operatore copy è disponibile solo all'interno della classe A e suoi discendenti
   void operator=(const A &)
     {
     }
  };
class B
  {
   //--- oggetto della classe A dichiarato
   A                 a;
  };
//+------------------------------------------------------------------+
//| Funzione di avvio del programma di script                        |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- dichiara due variabili di tipo B.
   B b1b2;
//--- tenta di copiare un oggetto in un altro
   b2=b1;
  }

Durante la compilazione del codice, viene visualizzato il messaggio di errore - tentativo di chiamare l'operatore copy remoto:

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

La seconda stringa di seguito fornisce una descrizione più dettagliata — l'operatore copy nella classe B è stato eliminato esplicitamente, poiché viene chiamato l'operatore copy non disponibile della classe A:

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

L'accesso ai membri della classe base può essere ridefinito duranteeredità nelle classi derivate.

'delete' specifier

L' identificatore delete contrassegna le funzioni dei membri della classe che non possono essere utilizzate. Ciò significa che se il programma fa riferimento a tale funzione in modo esplicito o implicito, l'errore viene già ricevuto in fase di compilazione. Ad esempio, questo identificatore consente di rendere i metodi parent non disponibili in una classe child. Lo stesso risultato può essere ottenuto se dichiariamo la funzione nell'area privata della classe genitrice (dichiarazioni nella sezione private). Qui, usare delete rende il codice più leggibile e gestibile a livello di discendenti.

class A
  {
public:
                     A(void) {value=5;};
   double            GetValue(void) {return(value);}
private:
   double            value;
  };
class Bpublic A
  {
   double            GetValue(void)=delete;
  };
//+------------------------------------------------------------------+
//| Funzione di avvio del programma di script                        |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- dichiara la variabile di tipo A.
   A a;
   Print("a.GetValue()="a.GetValue());
//--- tenta di ottenere valore dalla variabile di tipo B.
   B b;
   Print("b.GetValue()="b.GetValue()); // il compilatore mostra un errore in questa stringa
  }

Il messaggio del compilatore:

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

Lo specificatore 'delete' consente di disabilitare il casting automatico o il costruttore copy, che altrimenti dovrebbe essere nascosto nella sezione private anche. Esempio:

class A
  {
public:
   void              SetValue(double v) {value=v;}
//--- disabilita la chiamata di tipo int
   void              SetValue(int) = delete;
//--- disabilita l'operatore copy
   void              operator=(const A&) = delete;
private:
   double            value;
  };
//+------------------------------------------------------------------+
//| Funzione di avvio del programma di script                        |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- dichiara due variabili di tipo A.
   A a1a2;
   a1.SetValue(3);      // errore!
   a1.SetValue(3.14);   // OK
   a2=a1;               // errore!
  }

Durante la compilazione, riceviamo i messaggi di errore:

tentativodiriferimento ad una funzione 'void cancellataUna::funzione SetValue(int)' 
    '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  

Modificatore 'final' #

L'uso del modificatore 'final' durante la dichiarazione della classe vieta ulteriori eredità da questa classe. Se l'interfaccia della classe non richiede ulteriori modifiche, o le modifiche non sono ammesse per motivi di sicurezza, dichiarare questa classe con il modificatore 'final'. Inoltre, tutti i membri della classe saranno implicitamente considerati definitivi.

class CFoo final
  {
  //--- Corpo della classe
  };
 
class CBar : public CFoo
  {
  //--- Corpo della classe
  };

Se si tenta di ereditare da una classe con il modificatore 'final', come mostrato nell'esempio precedente, il compilatore restituirà un errore:

non si può ereditare da 'CFoo' siccome è stato dichiarato come 'final'
vedi dichiarazione di 'CFoo'

Unioni (union) #

Union è un tipo di dati speciale costituito da diverse variabili che condividono la stessa area di memoria. Pertanto, l'unione fornisce la capacità di interpretare la stessa sequenza di bit in due (o più) modi diversi. La dichiarazione dell'unione è simile alla dichiarazione della struttura ed inizia con la parola chiave union.

union LongDouble
{
  long   long_value;
  double double_value;
};

A differenza della struttura, i vari membri sindacali appartengono alla stessa area di memoria. In questo esempio, l'unione di LongDouble è dichiarata con valori di tipo long e double che condividono la stessa area di memoria. Si prega di notare che è impossibile fare in modo che union memorizzi (a differenza di una struttura) simultaneamente valori integer long e valori real double , poiché le variabili long_value e double_value si sovrappongono (in memoria). D'altro canto, un programma MQL5 è in grado di elaborare in qualsiasi momento i dati contenenti nell'unione un valore intero(long) o reale(double). Pertanto, l'unione consente di ricevere due (o più) opzioni per rappresentare la stessa sequenza di dati.

Durante la dichiarazione dell' unione, il compilatore assegna automaticamente l'area di memoria sufficiente per memorizzare il tipo più grande (per volume) nella variabile union. La stessa sintassi viene utilizzata per accedere all'elemento union come per le strutture – operatore point.

union LongDouble
{
  long   long_value;
  double double_value;
};
//+------------------------------------------------------------------+
//| Funzione start programma Script                  |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   LongDouble lb;
//--- ottiene e visualizza il numero -nan(ind) non valido
   lb.double_value=MathArcsin(2.0);
   printf("1.  double=%f                integer=%I64X",lb.double_value,lb.long_value);
//--- il più ampio valore normalizzato (DBL_MAX)
   lb.long_value=0x7FEFFFFFFFFFFFFF;
   printf("2.  double=%.16e  integer=%I64X",lb.double_value,lb.long_value);
//--- il più piccolo positivo normalizzato (DBL_MIN)
   lb.long_value=0x0010000000000000;    
   printf("3.  double=%.16e  integer=%.16I64X",lb.double_value,lb.long_value);
  }
/* Risultato dell'esecuzione
    1.  double=-nan(ind)                integer=FFF8000000000000
    2.  double=1.7976931348623157e+308  integer=7FEFFFFFFFFFFFFF
    3.  double=2.2250738585072014e-308  integer=0010000000000000
*/

Poiché le unioni consentono al programma di interpretare gli stessi dati di memoria in modi diversi, esse vengono spesso utilizzate quando un' inusuale conversione di tipo è richiesta.

Le unioni non possono essere coinvolte nella eredità(inheritance), e loro anche non possono avere membri statici a causa della loro stessa natura. In tutti gli altri aspetti, l' unione si comporta come una struttura con tutti i suoi membri con un offset di zero. I seguenti tipi non possono essere i membri dell'unione:

Simillmente alle classi, l'unione è in grado di avere costruttori e distruttori, così come metodi. Per default, i membri dell'unione sono di tipo di accesso public. Per creare elementi privati, utilizzare la parola chiave private. Tutte queste possibilità sono visualizzate nell'esempio che illustra come convertire un colore del tipo color in ARGB come fa la funzione ColorToARGB().

//+------------------------------------------------------------------+
//| Unione per la conversione in colori (BGR) in ARGB |
//+------------------------------------------------------------------+
union ARGB
  {
   uchar             argb[4];
   color             clr;
   //--- costruttori
                     ARGB(color col,uchar a=0){Color(col,a);};
                    ~ARGB(){};
   //--- metodi pubblici
public:
   uchar   Alpha(){return(argb[3]);};
   void    Alpha(const uchar alpha){argb[3]=alpha;};
   color   Color(){ return(color(clr));};
   //--- metodi privati
private:
   //+------------------------------------------------------------------+
   //| imposta il valore del canale alpha ed il colore |
   //+------------------------------------------------------------------+
   void    Color(color col,uchar alpha)
     {
      //--- imposta il colore al membro clr
      clr=col;
      //--- imposta il valore del componente Alpha - livello d'opacità
      argb[3]=alpha;
      //--- scambia i bite dei componenti R e B (Red e Blue)     
      uchar t=argb[0];argb[0]=argb[2];argb[2]=t;
     };
  };
//+------------------------------------------------------------------+
//| Funzione start programma Script                  |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 0x55 significa 55/255=21.6 % (0% - completamente trasparente)
   uchar alpha=0x55; 
//--- il tipo di colore è rappresentato come 0x00BBGGRR
   color test_color=clrDarkOrange;
//--- qui vengono accettati i valori di byte provenienti dall'unione ARGB
   uchar argb[]; 
   PrintFormat("0x%.8X - qui è come il tipo 'color' appare per %s, BGR=(%s)",
               test_color,ColorToString(test_color,true),ColorToString(test_color));
//--- Il tipo ARGB è rappresentato da 0x00RRGGBB, RR e BB vengono scambiati
   ARGB argb_color(test_color);
//--- copia l'array di byte
   ArrayCopy(argb,argb_color.argb);
//--- ecco come appare nella rappresentazione ARGB  
   PrintFormat("0x%.8X - rappresentazione ARGB con il canale alpha=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- aggiungi livello opacità
   argb_color.Alpha(alpha);
//--- prova a definire ARGB come tipo 'color' 
   Print("ARGB как color=(",argb_color.clr,")  alpha channel=",argb_color.Alpha());
//--- copia l'array di byte
   ArrayCopy(argb,argb_color.argb);
//--- ecco come appare nella rappresentazione ARGB
   PrintFormat("0x%.8X - rappresentazione ARGB con il canale alpha=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- controlla i risultati della funzione ColorToARGB()
   PrintFormat("0x%.8X - risultato di ColorToARGB(%s,0x%.2x)",ColorToARGB(test_color,alpha),
               ColorToString(test_color,true),alpha);
  }
/* Risultato dell'esecuzione
   0x00008CFF - qui è come il tipo color appare per clrDarkOrange, BGR=(255,140,0)
  0x00FF8C00 - rappresentazione ARGB con il canale alpha=0x00, ARGB=(0,255,140,0)
   ARGB as color=(0,140,255)  alpha channel=85
   0x55FF8C00 - rappresentazione ARGB con il canale alfa = 0x55, ARGB = (85,255,140,0)
   0x55FF8C00 - result of ColorToARGB(clrDarkOrange,0x55)
*/ 

Interfacce #

Un'interfaccia consente la determinazione di specifiche funzionalità, che una classe può quindi implementare. Infatti, un'interfaccia è una classe che non può contenere membri, e non può avere un costruttore e/o un distruttore. Tutti i metodi dichiarati in una interfaccia sono puramente virtuali, anche in assenza di una definizione esplicita.

Un'interfaccia è definita utilizzando la parola chiave "interfaccia". Esempio:

//--- Interfaccia base per descrivere animali
interface IAnimal
  {
//--- I metodi dell'interfaccia hanno accesso pubblico per default
   void Sound();  // Il suono prodotto dall'animale
  };
//+------------------------------------------------------------------+
//|  The classe CCat è ededitata dalla classe IAnimal interface      |
//+------------------------------------------------------------------+
class CCat : public IAnimal
  {
public:
                     CCat() { Print("Il gatto è nato"); }
                    ~CCat() { Print("Il gatto è morto");  }
   //--- Implementare il metodo Sound di IAnimal interface
   void Sound(){ Print("meou"); }
  };
//+------------------------------------------------------------------+
//|  La classe CDog è ereditata dalla classe IAnimal interface       |
//+------------------------------------------------------------------+
class CDog : public IAnimal
  {
public:
                     CDog() { Print("Il cane è nato"); }
                    ~CDog() { Print("Il cane è morto");  }
   //--- Implementare il metodo Sound di IAnimal interface
   void Sound(){ Print("guaf"); }
  };
//+------------------------------------------------------------------+
//| Programma Script funzione start                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Un array di puntatori a oggetti del tipo IAnimal
   IAnimal *animals[2];
//--- La creazione di classi figlio di IAnimal e risparmio di puntatori a loro, in un array    
   animals[0]=new CCat;
   animals[1]=new CDog;
//--- Chiamata al metodo Suono() dell'interfaccia base IAnimal per ogni bambino  
   for(int i=0;i<ArraySize(animals);++i)
      animals[i].Sound();
//--- Eliminazione oggetti
   for(int i=0;i<ArraySize(animals);++i)
      delete animals[i];
//--- Risultato dell'esecuzione
/*
  Il gatto è nato
  Il cane è nato
   meou
   guaf
  Il gatto è morto
  Il cane è morto
*/
  }

Come le classi astratte, un oggetto interfaccia non può essere creato senza eredità. Un'interfaccia può essere ereditata solo dalle altre interfacce e può essere un genitore per una classe. Un interfaccia è sempre con visibilità pubblica.

Un'interfaccia non può essere dichiarata all'interno di una dichiarazione della classe o di una struttura, ma un puntatore per l'interfaccia può essere salvato in una variabile di tipo void *. In generale, un puntatore ad un oggetto di qualsiasi classe può essere salvato in una variabile di tipo void *. Per convertire un puntatore void * a un puntatore ad un oggetto di una classe particolare, utilizzare l'operatore dynamic_cast. Se la conversione non è possibile, il risultato dell'operazione dynamic_cast sarà NULL.

Vedi anche

Programmazione Ad Oggetti (OOP Object-Oriented-Programming)