English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Le Basi della Programmazione Orientata agli Oggetti

Le Basi della Programmazione Orientata agli Oggetti

MetaTrader 5Esempi | 9 dicembre 2021, 14:20
296 0
Dmitry Fedoseev
Dmitry Fedoseev

 

Introduzione

Possiamo presumere che chiunque abbia provato a imparare la programmazione orientata agli oggetti (OOP), abbia trovato per la prima volta parole come polimorfismo, incapsulamento, overload e ereditarietà. Forse qualcuno ha guardato alcune classi già pronte e ha cercato di capire dove si trovano effettivamente quei polimorfismi o incapsulamenti... Molto probabilmente questa può essere la fine del processo di apprendimento dell’OOP.

In realtà, tutto è molto più semplice di come sembra. Per usare l’OOP non è necessario sapere cosa significano queste parole: puoi semplicemente usare le funzionalità OOP, senza nemmeno sapere come si chiamano. Tuttavia, spero che tutti coloro che leggeranno questo articolo non solo impareranno come usare OOP, ma chiariranno anche i significati di queste parole.

 

Creazione di Librerie di Funzioni

La prima e più semplice applicazione dell’OOP è creare le proprie librerie di funzioni utilizzate di frequente. Naturalmente, puoi semplicemente memorizzare queste funzioni in un file di inclusione (mqh). Quando hai bisogno di una funzione, includi semplicemente un file e chiama questa funzione. Tuttavia, se programmi abbastanza a lungo puoi raccogliere un'enorme quantità di funzioni, quindi sarebbe difficile ricordare i loro nomi ed il loro scopo.

Puoi raccogliere funzioni in diversi file, suddividendole in categorie in base allo scopo. Ad esempio, funzioni per lavorare con gli array, funzioni per lavorare con le stringhe, funzioni per calcolare gli ordini, ecc. Nell'ultima frase la parola "categoria" può essere sostituita dalla parola "classi". Il significato rimane lo stesso, ma ci avvicineremo all'argomento - Programmazione Orientata agli Oggetti.

Quindi, le funzioni possono essere suddivise in classi: classe di funzioni per lavorare con gli array, classe di funzioni per lavorare con le stringhe, classe di funzioni per contare gli ordini, ecc. La parola "classe" ci avvicina al tema dell'OOP in quanto è il suo concetto fondamentale. Puoi cercare vari libri di riferimento, dizionari ed enciclopedie (ad esempio Wikipedia) per comprendere meglio cosa sia la "classe di programmazione".

Nella programmazione orientata agli oggetti, una classe è un costrutto utilizzato come progetto per creare istanze di se stessa.

Forse, la prima impressione sarebbe più o meno la stessa delle parole "polimorfismo", "incapsulamento", ecc. Per ora, con il termine 'classe' indicheremo un insieme di funzioni e variabili. Nel caso di utilizzo della classe per creare una libreria - un insieme di funzioni e variabili raggruppate per tipo di dati elaborati o per tipo di oggetti elaborati: array, stringhe, ordini.

 

Un Programma nel Programma

C'erano (e ci saranno) molte domande simili del Forum: come chiamare uno script da un Expert Advisor? Pur non utilizzando strumenti di terze parti, questa attività viene eseguita inserendo il codice dello script nel codice dell’ Expert Advisor. In effetti, non è un compito difficile, ma uno script può utilizzare gli stessi nomi di variabili e funzioni dell’EA, quindi sarà necessario modificare il codice dello script. Le modifiche non sono complicate, ma probabilmente significative in termini di volume.

Sarebbe fantastico chiamare semplicemente questo script come un programma indipendente separato! Questo è possibile se si programma lo script come una classe e poi si usa questa classe. La quantità di lavoro verrà aumentata solo di poche righe di codice. In questo caso, una classe combinerà funzioni non in base al tipo di dati elaborati, ma in base al loro scopo. Ad esempio: una classe per eliminare ordini in sospeso, una classe per aprire una posizione o per effettuare ordini, una classe per lavorare con oggetti grafici, ecc.

Una caratteristica importante della classe è che si distingue dallo spazio in cui si trova. La classe è come un programma in esecuzione in un sistema operativo: più programmi possono essere eseguiti contemporaneamente, ma per conto loro, indipendentemente l'uno dall'altro. Pertanto, la classe può essere definita "un programma nel programma", poiché si distingue dallo spazio in cui si trova.

 

Look and Feel di una classe

La creazione della classe inizia con la parola class, seguita dal nome della classe e, quindi, l'intero codice della classe viene inserito tra parentesi graffe:

class CName 
  {
   // Here is the entire code of the class
  };
Attenzione! Non dimenticare di mettere un punto e virgola dopo la parentesi graffa di chiusura.

 

Visibile e Nascosto (Incapsulamento)

Se prendi un qualsiasi programma, sappiamo che include una varietà di funzioni. Queste funzioni possono essere suddivise in due tipi: principali e ausiliarie. Le funzioni principali sono le funzioni di cui è effettivamente composto un programma. Queste funzioni possono richiedere molte altre funzioni che l'utente non necessita di conoscere. Ad esempio, nel client terminal per aprire una posizione, il trader deve aprire la finestra di dialogo Nuovo ordine, inserire volume, valori ​di Stop Loss e Take Profit e poi clicca su "Buy" o "Sell".

Ma cosa sta realmente succedendo tra il click sul pulsante e l'apertura di una posizione - solo gli sviluppatori di terminali possono dirlo con certezza. Possiamo supporre che il terminale esegua molte azioni: controlla la posizione del volume, controlla i valori ​​di Stop Loss e Take Profit, verifica la connessione di rete, ecc. Tantissime procedure sono nascoste o, in altre parole, incapsulate. Allo stesso modo, in una classe puoi dividere il codice in parti (funzioni e variabili) - alcune di esse saranno disponibili quando usi una classe e alcune di esse saranno nascoste.

I livelli di incapsulamento sono definiti utilizzando le seguenti parole chiave: private, protected e public. La differenza tra protected e private verrà considerata un po' più avanti, ma prima tratteremo le parole chiave private e public. Quindi, un semplice modello di classe assume la seguente forma:

class CName 
  {
private:
   // Variables and functions available only inside the class
public:
   // Variables and functions available outside the class
  };
Questo è sufficiente per sfruttare l'OOP. Invece di scrivere il tuo codice direttamente nell'Expert Advisor (Script o Indicatore), crea prima una classe e poi scrivi tutto in questa classe. Successivamente considereremo la differenza tra le sezioni private e public attraverso un esempio pratico.

 

Esempio di Creazione di una Libreria

Il template della classe presentato sopra può essere utilizzato per creare una libreria di funzioni. Creiamo una classe per lavorare con gli array. Le attività più comuni che possono sorgere quando si utilizza un array sono l'aggiunta di un nuovo elemento all'array e l'aggiunta di un nuovo elemento, a condizione che l'elemento con un dato valore non esista nell'array.

Nominiamo la funzione che aggiunge un elemento all'array come AddToEnd() e la funzione che aggiunge un elemento univoco all'array come AddToEndIfNotExists(). Nella funzione AddToEndIfNotExists() dovremo prima verificare se l'elemento aggiunto esiste già nell'array e, in caso contrario, utilizzare la funzione AddToEnd(). La funzione, che verifica se un elemento esiste già nell'array, sarà considerata ausiliaria, quindi la collocheremo nella sezione private e tutte le altre funzioni - nella sezione public. Di conseguenza, otterremo la seguente classe:

class CLibArray 
  {
private:
   // Check if an element with required value exists in array
   int Find(int &aArray[],int aValue) 
     {
      for(int i=0; i<ArraySize(aArray); i++) 
        {
         if(aArray[i]==aValue) 
           {
            return(i); // Element exists, return index of element
           }
        }
      return(-1);  // No such element, return -1
     }
public:
   // Add to end of array
   void AddToEnd(int &aArray[],int aValue) 
     {
      int m_size=ArraySize(aArray);
      ArrayResize(aArray,m_size+1);
      aArray[m_size]=aValue;
     }
   // Add to end of array if there is no such value in array already
   void AddToEndIfNotExistss(int &aArray[],int aValue) 
     {
      if(Find(aArray,aValue)==-1) 
        {
         AddToEnd(aArray,aValue);
        }
     }
  };
 

Classe di Caricamento

Per poter utilizzare una classe, deve essere caricata. Se una classe si trova in un file separato, devi includere questo file

#include <OOP_CLibArray_1.mqh>

e quindi caricare questa classe. Il caricamento della classe è simile alla dichiarazione di variabile:

CLibArray ar;

Prima viene il nome della classe, poi il nome di un puntatore per fare riferimento a questa istanza. Dopo il caricamento la classe diventa un oggetto. Per utilizzare qualsiasi funzione di un oggetto, scrivi il nome del puntatore, il punto e quindi il nome della funzione. Dopo aver digitato il punto, si aprirà un elenco a tendina delle funzioni di classe (Figura 1).

Figura 1. Elenco delle Funzioni
Figura 1. Elenco delle Funzioni

Grazie all'elenco a discesa non è necessario ricordare i nomi delle funzioni: puoi navigare nell'elenco dei nomi e ricordare lo scopo della funzione. Questo è il più grande vantaggio dell'utilizzo delle classi per creare librerie rispetto alla semplice raccolta di funzioni nei file.

Nel caso della funzione di raccolta, quando si digitano alcune lettere iniziali del nome della funzione, l'elenco a cascata mostrerà tutte le funzioni di tutte le librerie incluse e, quando si utilizzano le classi, solo le funzioni relative alla classe specificata. Si noti inoltre che la funzione Find() non è elencata: questa è la differenza tra le sezioni private e public. La funzione è scritta nella sezione private e, quindi, non è disponibile.

 

Creazione di una Libreria Universale per Diversi Tipi di Dati (Overload)

A questo punto, la nostra libreria include funzioni che funzionano solo con array di tipo int. Oltre agli array di tipo int, potrebbe essere necessario applicare le funzioni di libreria agli array dei seguenti tipi: uint, long, ulong ecc. Per gli array di altri tipi di dati dobbiamo scrivere le loro funzioni. Tuttavia, non è necessario assegnare a queste funzioni altri nomi: la funzione corretta verrà selezionata automaticamente in base al tipo di parametro passato o set di parametri (in questo esempio, a seconda del tipo di parametri). Completiamo la classe con funzioni per lavorare con array di tipo long:

class CLibArray 
  {
private:
   // Для int. Check if an element with required value exists in array
   int Find(int &aArray[],int aValue)
     {
      for(int i=0; i<ArraySize(aArray); i++) 
        {
         if(aArray[i]==aValue) 
           {
            return(i); // Element exists, return index of element
           }
        }
      return(-1); // No such element, return -1
     }
   // For long. Check if an element with required value exists in array
   int Find(long &aArray[],long aValue) 
     {
      for(int i=0; i<ArraySize(aArray); i++) 
        {
         if(aArray[i]==aValue) 
           {
            return(i); // Element exists, return index of element
           }
        }
      return(-1); // No such element, return -1
     }
public:
   // For int. Add to end of array
   void AddToEnd(int &aArray[],int aValue) 
     {
      int m_size=ArraySize(aArray);
      ArrayResize(aArray,m_size+1);
      aArray[m_size]=aValue;
     }
   // For long. Add to end of array
   void AddToEnd(long &aArray[],long aValue) 
     {
      int m_size=ArraySize(aArray);
      ArrayResize(aArray,m_size+1);
      aArray[m_size]=aValue;
     }
   // For int. Add to end of array if there is no such value in array already
   void AddToEndIfNotExistss(int &aArray[],int aValue) 
     {
      if(Find(aArray,aValue)==-1) 
        {
         AddToEnd(aArray,aValue);
        }
     }
   // For long. Add to end of array if there is no such value in array already
   void AddToEndIfNotExistss(long &aArray[],long aValue) 
     {
      if(Find(aArray,aValue)==-1) 
        {
         AddToEnd(aArray,aValue);
        }
     }
  };
Ora, usando lo stesso nome abbiamo funzionalità diverse. Queste funzioni sono chiamate overloaded, poiché un nome è caricato con più di una funzionalità, cioè overload.

Puoi trovare questo esempio nel file OOP_CLibArray_1.mqh allegato a questo articolo.

 

Un Altro Modo di Notazione di Classe

Negli esempi sopra tutte le funzioni sono state scritte all'interno della classe. Se hai molte funzioni e ognuna di esse è piuttosto massiccia, tale notazione potrebbe non essere molto comoda. In questi casi, è possibile posizionare le funzioni all'esterno della classe. All'interno della classe, si scrivono solo nomi di funzioni con parametri e le funzioni sono completamente descritte all'esterno della classe. Inoltre, devi indicare che la funzione appartiene a una classe specifica: prima scrivi il nome della classe, poi metti due punti e il nome della funzione:

class CLibArray 
  {
private:
   int               Find(int  &aArray[],int  aValue);
   int               Find(long &aArray[],long aValue);
public:
   void              AddToEnd(int  &aArray[],int  aValue);
   void              AddToEnd(long &aArray[],long aValue);
   void              AddToEndIfNotExistss(int  &aArray[],int  aValue);
   void              AddToEndIfNotExistss(long &aArray[],long aValue);
  };
//---
int CLibArray::Find(int &aArray[],int aValue) 
  {
   for(int i=0; i<ArraySize(aArray); i++) 
     {
      if(aArray[i]==aValue) 
        {
         return(i);
        }
     }
   return(-1);
  }
//---
int CLibArray::Find(long &aArray[],long aValue) 
  {
   for(int i=0; i<ArraySize(aArray); i++) 
     {
      if(aArray[i]==aValue) 
        {
         return(i);
        }
     }
   return(-1);
  }
//---
void CLibArray::AddToEnd(int &aArray[],int aValue) 
  {
   int m_size=ArraySize(aArray);
   ArrayResize(aArray,m_size+1);
   aArray[m_size]=aValue;
  }
//---
void CLibArray::AddToEnd(long &aArray[],long aValue) 
  {
   int m_size=ArraySize(aArray);
   ArrayResize(aArray,m_size+1);
   aArray[m_size]=aValue;
  }
//---
void CLibArray::AddToEndIfNotExistss(int &aArray[],int aValue) 
  {
   if(Find(aArray,aValue)==-1) 
     {
      AddToEnd(aArray,aValue);
     }
  }
//---
void CLibArray::AddToEndIfNotExistss(long &aArray[],long aValue) 
  {
   if(Find(aArray,aValue)==-1) 
     {
      AddToEnd(aArray,aValue);
     }
  }

Con una tale notazione, puoi ottenere un quadro completo della composizione della classe e dare un'occhiata più da vicino alle singole funzioni, se necessario.

Puoi trovare questo esempio nel file OOP_CLibArray_2.mqh allegato a questo articolo.

 

Dichiarazione di Variabili nella Classe

Continuiamo a considerare l'esempio citato in precedenza. C'è una differenza tra la codifica direttamente nel file e all'interno della classe. Direttamente nel file puoi assegnare variabili con valori ​come li dichiari:

int Var = 123;

Se dichiari una variabile in una classe non puoi farlo - i valori ​dovrebbero essere assegnato durante l'esecuzione di alcune funzioni di una classe. Quindi, prima di tutto devi passare i parametri alla classe (cioè preparare la classe per lavorare). Nominiamo questa funzione come Init().

Consideralo su un esempio pratico.

 

Esempio di Conversione di Script in Classe

Supponiamo che ci sia uno script che cancella gli ordini in sospeso (vedi il file OOP_sDeleteOrders_1.mq5 in allegato).

// Include file to use the CTrade class from standard delivery
#include <Trade/Trade.mqh>

// External parameters

// Select symbol. true  - delete orders for all symbols, 
//                false - only for symbol of chart, where the script is running
input bool AllSymbol=false;

// Select types of orders to delete
input bool BuyStop       = false;
input bool SellStop      = false;
input bool BuyLimit      = false;
input bool SellLimit     = false;
input bool BuyStopLimit  = false;
input bool SellStopLimit = false;

// Load the CTrade class
CTrade Trade;
//---
void OnStart()
  {
// Variable to check function result
   bool Ret=true;
// Loop by all orders in terminal
   for(int i=0; i<OrdersTotal(); i++)
     {
      ulong Ticket=OrderGetTicket(i); // Select order and get its ticket
                                      // Successfully selected
      if(Ticket>0)
        {
         long Type=OrderGetInteger(ORDER_TYPE);
         // Check order type
         if(Type == ORDER_TYPE_BUY_STOP && !BuyStop) continue;
         if(Type == ORDER_TYPE_SELL_STOP && !SellStop) continue;
         if(Type == ORDER_TYPE_BUY_LIMIT && !BuyLimit) continue;
         if(Type == ORDER_TYPE_SELL_LIMIT && !SellLimit) continue;
         if(Type == ORDER_TYPE_BUY_STOP_LIMIT && !BuyStopLimit) continue;
         if(Type == ORDER_TYPE_SELL_STOP_LIMIT && !SellStopLimit) continue;
         // Check symbol
         if(!AllSymbol && Symbol()!=OrderGetString(ORDER_SYMBOL)) continue;
         // Delete
         if(!Trade.OrderDelete(Ticket))
           {
            Ret=false; // Failed to delete
           }
        }
      // Failed to select order, unknown result,
      // function ended up with error
      else
        {
         Ret=false;
         Print("Error selecting order");
        }
     }

   if(Ret)
     {
      Alert("Script ended successfully");
     }
   else    
     {
      Alert("Script ended up with error, see details. in Journal");
     }
  }

Lo script dispone di parametri esterni che consentono di abilitare vari tipi di ordini e selezionare il simbolo, per il quale verranno eliminati gli ordini (tutti i simboli o il simbolo del grafico su cui è in esecuzione lo script).

Converti questo script in una classe chiamata COrderDelete. Nella sezione private dichiariamo le stesse variabili che sono dichiarate nello script come parametri esterni, ma prefissiamo i nomi delle variabili con "m_" (dalla parola "member", cioè membro della classe). L'aggiunta del prefisso nonè richiesto, ma è molto comodo perché permette di distinguere facilmente le variabili. Quindi, possiamo sapere con certezza che abbiamo a che fare con variabili limitate dallo spazio delle classi. Inoltre, non riceverai avvisi del compilatore che la dichiarazione della variabile nasconde la variabile dichiarata nello scopo globale.

L'uso degli stessi nomi di variabile nello scopo globale, nella definizione della classe, nel corpo della funzione non è un errore, ma rende il programma difficile da capire, ecco perché in questi casi il compilatore dà un avviso. Per assegnare variabili con valori ​​scrivi la funzione Init() con i parametri corrispondenti a queste variabili (e ai parametri esterni dello script). Se usi questa classe, devi prima chiamare la funzione Init() e passarci parametri esterni. Il resto del codice dello script rimane invariato. L'unica eccezione: invece di usare direttamente i parametri esterni dovresti usare le variabili dichiarate all'interno della classe.

Quindi otteniamo la seguente classe:

#include <Trade/Trade.mqh> 

class COrderDelete 
  {

private:
   // Variables for parameters
   bool              m_AllSymbol;
   bool              m_BuyStop;
   bool              m_SellStop;
   bool              m_BuyLimit;
   bool              m_SellLimit;
   bool              m_BuyStopLimit;
   bool              m_SellStopLimit;
   // Load the CTrade class
   CTrade            m_Trade;
public:
   // Function to set parameters
   void Init(bool aAllSymbol,bool aBuyStop,bool aSellStop,bool aBuyLimit,bool aSellLimit,bool aBuyStopLimit,bool aSellStopLimit) 
     {
      // Set parameters
      m_AllSymbol    =aAllSymbol;
      m_BuyStop      =aBuyStop;
      m_SellStop     =aSellStop;
      m_BuyLimit     =aBuyLimit;
      m_SellLimit    =aSellLimit;
      m_BuyStopLimit =aBuyStopLimit;
      m_SellStopLimit=aSellStopLimit;
     }
   Main function to delete orders
   bool Delete() 
     {
      // Variable to check function result
      bool m_Ret=true;
      // Loop by all orders in terminal
      for(int i=0; i<OrdersTotal(); i++) 
        {
         // Select order and get its ticket
         ulong m_Ticket=OrderGetTicket(i);
         // Successfully selected
         if(m_Ticket>0) 
           {
            long m_Type=OrderGetInteger(ORDER_TYPE);
            // Check order type
            if(m_Type == ORDER_TYPE_BUY_STOP && !m_BuyStop) continue;
            if(m_Type == ORDER_TYPE_SELL_STOP && !m_SellStop) continue;
            if(m_Type == ORDER_TYPE_BUY_LIMIT && !m_BuyLimit) continue;
            if(m_Type == ORDER_TYPE_SELL_LIMIT && !m_SellLimit) continue;
            if(m_Type == ORDER_TYPE_BUY_STOP_LIMIT && !m_BuyStopLimit) continue;
            if(m_Type == ORDER_TYPE_SELL_STOP_LIMIT && !m_SellStopLimit) continue;
            // Check symbol/s61>
            if(!m_AllSymbol && Symbol()!=OrderGetString(ORDER_SYMBOL)) continue;
            // Delete
            if(!m_Trade.OrderDelete(m_Ticket)) 
              {
               m_Ret=false; // Failed to delete
              }
           }
         // Failed to select order, unknown result,
         // function ended up with error
         else 
           {
            m_Ret=false;
            Print("Error selecting order");
           }
        }
      // Return function result
      return(m_Ret);
     }
  };
Puoi trovare un esempio di questa classe nel file OOP_CDeleteOrder_1.mqh allegato a questo articolo. Lo script che utilizza questa classe è ridotto al minimo (parametri esterni, classe di caricamento, chiamata dei metodi Init() e Delete()):
// External parameters

// Select symbol. true  - delete orders for all symbols, 
//                false - only for symbol of chart, where the script is running
input bool AllSymbol=false;

// Select types of orders to delete
input bool BuyStop       = false;
input bool SellStop      = false;
input bool BuyLimit      = false;
input bool SellLimit     = false;
input bool BuyStopLimit  = false;
input bool SellStopLimit = false;

// Include file with class
#include <OOP_CDeleteOrder_1.mqh> 

// Load class
COrderDelete od;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart() 
  {
// Pass external parameters to the class
   od.Init(AllSymbol,BuyStop,SellStop,BuyLimit,SellLimit,BuyStopLimit,SellStopLimit);
// Delete orders
   bool Ret=od.Delete();
// Process result of deleting
   if(Ret) 
     { 
       Alert("Script ended successfully"); 
     }
   else    
     { 
       Alert("Script ended up with error, see details in Journal"); 
     }
  }

Puoi trovare un esempio di questo script nel file OOP_sDeleteOrders_2.mq5 allegato a questo articolo. La maggior parte dello script sta elaborando i risultati della funzione Delete(), notificando così i risultati dello script.

Ora tutte le funzioni di base dello script sono progettate come una classe situata in un file separato, quindi puoi usare questa classe da qualsiasi altro programma (Expert Advisor o Script), ad esempio chiamare questo script da Expert Advisor.

 

Un Po' di Automatismo (Constructor e Destructor)

Il funzionamento del programma può essere suddiviso in tre fasi: avvio del programma, processo di lavoro e completamento del suo lavoro. L'importanza di questa separazione è evidente: quando il programma si avvia si prepara da solo (ad esempio carica e imposta i parametri con cui lavorare), quando il programma finisce deve fare una "pulizia" (es. rimuovere oggetti grafici dal grafico).

Per separare queste fasi, gli Expert Advisor e gli Indicatori hanno funzioni speciali: OnInit() (in esecuzione all'avvio) e la OnDeinit() (in esecuzione all'arresto). Le classi hanno caratteristiche simili: puoi aggiungere funzioni che verranno eseguite automaticamente quando la classe viene caricata e quando viene scaricata. Queste funzioni sono chiamate Constructor e Destructor. Aggiungere un constructor alla classe significa aggiungere una funzione con il nome esattamente come il nome della classe. Per aggiungere un destructor, fai tutto come per il constructor, ma il nome della funzione inizia con una tilde "~".

Uno script che mostra il constructor e destructor:

// Class
class CName 
  {
public:
   // Constructor
                     CName() { Alert("Constructor"); }
   // Destructor
                    ~CName() { Alert("Destructor"); }

   void Sleep() { Sleep(3000); }
  };

// Load class
CName cname;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart() 
  {
// Pause
   cname.Sleep();
  }

Questa classe ha in realtà solo una funzione Sleep() che si ferma per 3 secondi. Quando si esegue lo script, viene visualizzata una finestra di avviso con il messaggio "Constructor", quindi, dopo una pausa di tre secondi, viene visualizzata una finestra di avviso con il messaggio "Destructor". Questo nonostante il fatto che le funzioni CName() e ~CName() non siano mai state chiamate in modo esplicito.

Puoi trovare questo esempio nel file OOP_sConstDestr_1.mq5 allegato a questo articolo.

 

Passaggio di Parametri al Constructor

Nell'esempio in cui abbiamo convertito lo script in classe, puoi ancora ridurre la quantità di codice di una riga - fa a meno di chiamare la funzione Init(). I parametri possono essere passati al constructor durante il caricamento della classe. Aggiungi il costruttore alla classe:

COrderDelete(bool aAllSymbol     = false,
             bool aBuyStop       = false,
             bool aSellStop      = false,
             bool aBuyLimit      = false,
             bool aSellLimit     = false,
             bool aBuyStopLimit  = false,
             bool aSellStopLimit=false) 
  {
   Init(aAllSymbol,aBuyStop,aSellStop,aBuyLimit,aSellLimit,aBuyStopLimit,aSellStopLimit);
  }

La funzione Init() rimane com'era, ma viene chiamata dal constructor. Tutti i parametri nel constructor sono opzionali, quindi, quella classe può essere usata come prima: carica la classe senza parametri e chiama la funzione Init().

Dopo aver creato un constructor, esiste un altro modo per usare questa classe. Quando la classe viene caricata, puoi passare i parametri in essa senza bisogno di chiamare la funzione Init():

COrderDelete od(AllSymbol,BuyStop,SellStop,BuyLimit,SellLimit,BuyStopLimit,SellStopLimit);

La funzione Init() è stata lasciata nella sezione public per consentire la reinizializzazione della classe. Quando si utilizza il programma (Expert Advisor), in un caso potrebbe essere necessario rimuovere solo gli ordini Stop, in altri solo gli ordini Limit. Per fare ciò, puoi chiamare la funzione Init() con parametri diversi in modo che la funzione Delete() elimini un diverso insieme di ordini.

Puoi trovare questo esempio nei file OOP_CDeleteOrder_2.mqh e OOP_sDeleteOrders_3.mq5 allegati a questo articolo.

 

Utilizzo di Istanze Multiple di una Classe

Come accennato nella sezione precedente, la stessa classe può eseguire azioni diverse a seconda dei parametri impostati durante l'inizializzazione. Se sai per quale scopo verrà utilizzata la tua classe, puoi omettere la reinizializzazione della classe. Per fare ciò, dovresti caricare alcune istanze della classe con parametri diversi.

Ad esempio, è noto che quando il nostro EA è in esecuzione, in alcuni casi è necessario eliminare gli ordini BuyStop e BuyLimit, mentre in altri casi - gli ordini SellStop e SellLimit. In questo caso, puoi caricare due istanze della classe.

Per eliminare gli ordini BuyStop e BuyLimit:

COrderDelete DeleteBuy(false,true,false,true,false,false,false);

Per eliminare gli ordini SellStop e SellLimit:

COrderDelete DeleteSell(false,false,true,false,true,false,false);

Ora, quando desideri eliminare gli ordini Buy in sospeso, utilizza un'istanza di una classe:

DeleteBuy.Delete();

Quando desideri eliminare gli ordini Sell in sospeso - un'altra istanza:

DeleteSell.Delete();

 

Array degli Oggetti

Potresti non sapere sempre con certezza di quante istanze di classe avrai bisogno quando il tuo programma è in esecuzione. In questo caso puoi creare un array di istanze della classe (oggetti). Diamo un'occhiata a questo esempio di classe con constructor e destructor. Alterando leggermente la classe, passiamo il parametro al constructor, così possiamo monitorare ogni istanza della classe:

// Class
class CName 
  {
private:
   int               m_arg; // Variable for the instance

public:
   // Constructor
   CName(int aArg) 
     {
      m_arg=aArg;
      Alert("Constructor "+IntegerToString(m_arg));
     }
   // Destructor
  ~CName() 
     { 
      Alert("Destructor "+IntegerToString(m_arg)); 
     }
   //---
   void Sleep() 
     { 
      Sleep(3000); 
     }
  };
Usiamo questa classe. Puoi dichiarare un array di una certa dimensione, ad esempio dieci elementi:
CName* cname[10];

Osserva una differenza rispetto al solito array di dichiarazioni di variabili: un asterisco "*". Un asterisco indica che il puntatore dinamico viene utilizzato in contrasto con il puntatore automatico utilizzato in precedenza.

Puoi usare un array dinamico (senza dimensioni preallocate, non confondere l'array dinamico con il puntatore dinamico):

CName* cname[];

In questo caso sarà necessario il ridimensionamento (eseguito all'interno di qualsiasi funzione, negli script - all'interno della funzione OnStart()):

ArrayResize(cname,10);

Ora, eseguiamo il ciclo di tutti gli elementi dell'array e carichiamo l'istanza della classe in ciascuno di essi. Per farlo usa la parola chiavenew:

ArrayResize(cname,10);
for(int i=0; i<10; i++) 
  {
   cname[i]=new CName(i);
  }
Pausa:
cname[0].Sleep();

Controlla lo script. Eseguilo e vedi che ci sono dieci constructor, ma nessun destructor. Se si utilizzano puntatori dinamici, le classi non vengono scaricate automaticamente quando il programma viene terminato. Inoltre, nel tab "Experts" puoi visualizzare messaggi sulle perdite di memoria. Dovresti eliminare gli oggetti manualmente:

for(int i=0; i<10; i++) 
  {
   delete(cname[i]);
  }

Ora, alla fine dello script ci sono dieci destructor in esecuzione e nessun messaggio di errore.

Puoi trovare questo esempio nel file OOP_sConstDestr_2.mq5 allegato a questo articolo.

 

Utilizzo dell'OOP per Modificare la Logica del Programma (Funzioni Virtuali, Polimorfismo)

Polimorfismo - forse questa è la caratteristica OOP più interessante e significativa che ti consente di controllare la logica del tuo programma. Fa uso di una classe base con funzioni virtuali e più classi figlio. Una classe può assumere diverse forme definite dalle classi figlio.

Prendi un semplice esempio: confronto di due valori. Ci possono essere cinque versioni di confronto: maggiore di (>), minore di (<), maggiore o uguale a (>=), minore o uguale a (<=), uguale a (==).

Crea una classe base con una funzione virtuale. Funzione virtuale - è esattamente la stessa funzione normale, ma la sua dichiarazione inizia con la parola virtual:

class CCheckVariant 
  {
public:
   virtual bool CheckVariant(int Var1,int Var2) 
     {
      return(false);
     }
  };

La funzione virtuale non ha codice. È una specie di connettore che accetterà i vari dispositivi. A seconda del tipo di dispositivo eseguirà azioni diverse.

Crea cinque classi figlio:

//+------------------------------------------------------------------+
//|   >                                                              |
//+------------------------------------------------------------------+

class CVariant1: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1>Var2);
     }
  };
//+------------------------------------------------------------------+
//|   <                                                              |
//+------------------------------------------------------------------+
class CVariant2: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1<Var2);
     }
  };
//+------------------------------------------------------------------+
//|   >=                                                             |
//+------------------------------------------------------------------+
class CVariant3: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1>=Var2);
     }
  };
//+------------------------------------------------------------------+
//|   <=                                                             |
//+------------------------------------------------------------------+
class CVariant4: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1<=Var2);
     }
  };
//+------------------------------------------------------------------+
//|   ==                                                             |
//+------------------------------------------------------------------+
class CVariant5: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1==Var2);
     }
  };

Prima di utilizzare questa classe deve essere caricata. Se sai quale classe figlio dovrebbe essere usata, puoi dichiarare un puntatore con il tipo di questo figlio. Ad esempio, se vuoi controllare la condizione ">":

CVariant1 var; // Load class to check the ">" condition

Se, come nel nostro caso, non si conosce in anticipo il tipo di figlio, viene dichiarato un puntatore a una classe con il tipo di classe base. Ma in questo caso viene utilizzato il puntatore dinamico.

CCheckVariant* var;

In questo figlio deve essere caricato utilizzando la parola chiave new. Carica il figlio in base alla variante selezionata:

// Number of variant
int Variant=5; 
// Depending on variant number one of five children classes will be used
switch(Variant) 
  {
    case 1: 
       var = new CVariant1;
       break;
    case 2: 
       var = new CVariant2;
       break;
    case 3: 
       var = new CVariant3;
       break;
    case 4: 
       var = new CVariant4;
       break; 
    case 5: 
       var = new CVariant5;
       break; 
 }

Verifica le condizioni:

bool rv = var.CheckVariant(1,2);

Il risultato del confronto di due valori ​​dipenderà dalla classe figlio, anche se il codice che controlla le condizioni è identico per tutti i casi.

Puoi trovare questo esempio nel file OOP_sVariant_1.mq5 allegato a questo articolo.

 

Ulteriori informazioni sull'incapsulamento (privato, protetto, pubblico)

Per questo momento è abbastanza chiaro con la sezione public - contiene funzioni e variabili che devono essere visibili all'utente della classe (per utente si intende un programmatore che scrive programmi utilizzando una classe già pronta). Dal punto di vista dell'utente della classe non c'è differenza tra le sezioni protected e private - le funzioni e le variabili in queste sezioni non sono disponibili per l'utente:

//+------------------------------------------------------------------+
//|   Class with the protected keyword                               |
//+------------------------------------------------------------------+
class CName1
  {
protected:
   int ProtectedFunc(int aArg)
     {
      return(aArg);
     }
public:
   int PublicFunction(int aArg)
     {
      return(ProtectedFunc(aArg));
     }
  };
//+------------------------------------------------------------------+
//|   Class with the private keyword                                 |
//+------------------------------------------------------------------+
class CName2
  {
private:
   int PrivateFunc(int aArg)
     {
      return(aArg);
     }
public:
   int PublicFunction(int aArg)
     {
      return(PrivateFunc(aArg));
     }
  };

CName1 c1; // Load class with the protected keyword
CName2 c2; // Load class with the private keyword
In questo esempio ci sono due classi: CName1 e CName2. Ogni classe ha due funzioni: una si trova nella sezione public, l'altra - nella sezione protected (per la classe CName1) o nella sezione private (per la classe CName2). Entrambe le classi hanno solo una funzione dalla sezione public nell'elenco a cascata delle funzioni (Figure 2 e 3).

Figura 2. Funzioni della classe CName1
Figura 2. Funzioni della classe CName1

Figura 3. Funzioni della classe CName2
Figura 3. Funzioni della classe CName2

Puoi trovare questo esempio nel file OOP_sProtPriv_1.mq5 allegato a questo articolo.

Le sezioni private e protected determinano la visibilità della funzione della classe base per le sue classi figlio:

//+------------------------------------------------------------------+
//|   Base class                                                     |
//+------------------------------------------------------------------+
class CBase
  {
protected:
   string ProtectedFunc()
     {
      return("CBase ProtectedFunc");
     }
private:
   string PrivateFunc()
     {
      return("CBase PrivateFunc");
     }
public:
   virtual string PublicFunction()
     {
      return("");
     }
  };
//+------------------------------------------------------------------+
//|   Child class                                                    |
//+------------------------------------------------------------------+

class Class: public CBase
  {
public:
   string PublicFunction()
     {
      // With this line everything compiles correctly
      return(ProtectedFunc());
      // If you will uncomment this line and comment the previous one, there will be a compiler error
      // return(PrivateFunc()); 
     }
  };

In questo esempio, abbiamo una classe base chiamata CBase e una classe figlio chiamata Class. Prova a chiamare la funzione della classe base situata nelle sezioni protected e private dalla classe figlio. Se chiami la funzione dalla sezione protected, tutto viene compilato ed eseguito. Se chiami la funzione dalla sezione private, viene visualizzato un errore del compilatore (impossibile chiamare la funzione membro privato). Cioè, la funzione della sezione private non è visibile alle classi figlio.

La sezione protected protegge le funzioni solo dagli utenti della classe e la sezione private protegge anche le funzioni dalle classi figlio. La visibilità delle funzioni della classe base (ubicate in sezioni diverse) dalle classi figlio è mostrata nella Figura 4.

Figura 4. Visibilità delle funzioni della classe base dalla classe figlio
Figura 4. Visibilità delle funzioni della classe base dalla classe figlio
Frecce blu: le funzioni sono disponibili, grigie: non disponibili.

Puoi trovare questo esempio nel file OOP_sProtPriv_2.mq5 allegato a questo articolo.

 

Funzione Virtuale Predefinita ed Ereditarietà

Non tutte le funzioni virtuali nella classe base devono avere le funzioni corrispondenti nelle classi figlio. Se una classe figlio ha la stessa funzione di nome - utilizzerà proprio questa funzione, in caso contrario - eseguirà il codice dalla funzione virtuale della classe base. Consideralo sull'esempio.

//+------------------------------------------------------------------+
//|   Base Class                                                     |
//+------------------------------------------------------------------+
class CBase
  {
public:
   virtual string Function()
     {
      string str="";
      str="Function ";
      str=str+"of base ";
      str=str+"class";
      return(str);
     }
  };
//+------------------------------------------------------------------+
//|   Child class 1                                                  |
//+------------------------------------------------------------------+
class Class1: public CBase
  {
public:
   string Function()
     {
      string str="";
      str="Function ";
      str=str+"of child ";
      return(str);
     }
  };
//+------------------------------------------------------------------+
//|   Child class 2                                                  |
//+------------------------------------------------------------------+
class Class2: public CBase
  {

  };

Class1 c1; // Load class 1
Class2 c2; // Load class 2
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   Alert("1: "+c1.Function()); // Running function from Class1
   Alert("2: "+c2.Function()); // Running function from CBase
  }

Nonostante il fatto che la classe Class2 non abbia funzioni, è ancora possibile chiamare la funzione Function() da essa. Questo eseguirà la funzione dalla classe CBase. La classe Class1 eseguirà la propria funzione:

void OnStart() 
   {
    Alert("1: " + c1.Function()); // Running function from Class1
    Alert("2: " + c2.Function()); // Running function from CBase
   }

Dal punto di vista dell'utente della classe, quando si utilizza una classe figlio saranno disponibili tutte le funzioni della classe base della sezione public. Questa si chiama eredità. Se la funzione della classe base è dichiarata come virtuale, verrà sostituita dalla funzione della classe figlio se una classe figlio ha una funzione con questo nome (Figura 5).

Figura 5. Accesso alle Funzioni da parte degli Utenti della Classe
Figura 5. Accesso alle Funzioni da parte degli Utenti della Classe

Tranne il caso in cui la classe figlio non ha funzioni corrispondenti alle funzioni virtuali della classe base, la classe figlio può avere funzioni "extra" (funzioni senza funzioni virtuali con lo stesso nome all'interno della classe base). Se carichi la classe usando il puntatore al tipo di classe figlio, queste funzioni saranno disponibili. Se carichi la classe utilizzando il puntatore al tipo di classe base, queste funzioni non saranno disponibili (Figura 6).

Figura 6. Visibilità delle funzioni "Extra"
Figura 6. Viene determinata la visibilità della funzione "extra" (freccia rossa)
per tipo di puntatore utilizzato per caricare la classe.

Puoi trovare questo esempio nel file OOP_sDefaultVirtual_1.mq5 allegato a questo articolo.

 

Poco Più sul Caricamento della Classe

Quando usi le funzioni virtuali e, di conseguenza, la classe base e le classi figlio, se sai quale classe figlio dovrebbe essere usata, puoi usare un puntatore che corrisponda alla classe figlio:

Class1 c1; // Load class 1
Class2 c2; // Load class 2

Se non è noto quale classe figlio debba essere utilizzata, utilizzare un puntatore dinamico al tipo di classe base e caricare la classe utilizzando la parola chiave new:

CBase *c; // Dynamic pointer 
void OnStart() 
   {
      c=new Class1; // Load class
      ...

Se usi il puntatore automatico alla classe base

CBase c; // Automatic pointer

la classe base verrà utilizzata così com'è. Quando chiami le sue funzioni virtuali, eseguirà il codice situato all'interno di queste funzioni. Le funzioni virtuali vengono convertite in funzioni ordinarie.  

 

Elaborazione degli Oggetti in Funzione

Il titolo di questa sezione è autosufficiente. I puntatori agli oggetti possono essere passati alle funzioni e, quindi, all'interno della funzione è possibile chiamare le funzioni degli oggetti. Il parametro della funzione può essere dichiarato con il tipo di classe base. Questo rende la funzione universale. Un puntatore a una classe può essere passato alla funzione solo per riferimento (indicato dal segno &):

//+------------------------------------------------------------------+
//|   Base Class                                                     |
//+------------------------------------------------------------------+
class CBase
  {
public:
   virtual string Function()
     {
      return("");
     }
  };
//+------------------------------------------------------------------+
//|   Child class 1                                                  |
//+------------------------------------------------------------------+
class Class1: public CBase
  {
public:
   string Function()
     {
      return("Class 1");
     }
  };
//+------------------------------------------------------------------+
//|   Child class 2                                                  |
//+------------------------------------------------------------------+
class Class2: public CBase
  {
public:
   string Function()
     {
      return("Class 2");
     }
  };

Class1 c1; // Load class 1
Class2 c2; // Load class 2
//+------------------------------------------------------------------+
//|   Function to process objects                                    |
//+------------------------------------------------------------------+
void Function(CBase  &c)
  {
   Alert(c.Function());
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
// Process objects using one function.
   Function(c1);
   Function(c2);
  }
Puoi trovare questo esempio nel file OOP_sFunc_1.mq5 allegato a questo articolo.

 

Funzioni e Metodi, Variabili e Proprietà

Finora in questo articolo abbiamo usato la parola "funzione". Ma nell’OOP invece della parola "funzione" i programmatori usano spesso la parola "metodo". Se guardi la classe dall'interno dalla prospettiva di un programmatore che scrive una classe, tutte le funzioni rimangono funzioni. Se guardi la classe dal punto di vista di un programmatore che usa una classe già pronta, allora le funzioni dell'interfaccia della classe che si trovano nella sezione public (disponibile nell'elenco a cascata dopo aver digitato un punto) sono chiamate metodi.

Oltre ai metodi, l'interfaccia di classe può includere proprietà di classe. La sezione public può includere non solo funzioni, ma anche le variabili (inclusi gli array).

class CMethodsAndProperties 
   {
    public:
        int               Property1; // Property 1
        int               Property2; // Property 2
        void Function1() 
           {
            //...
            return;
           }
        void Function2() 
           {
            //...
            return;
           }
   };

Queste variabili verranno chiamate proprietà della classe e saranno disponibili anche nell'elenco a cascata (Figura 7).

 Figura 7. Metodi e proprietà della classe in un elenco
Figura 7. Metodi e proprietà della classe in una lista

Puoi utilizzare queste proprietà allo stesso modo delle variabili:

void OnStart() 
   {
    c.Property1 = 1; // Set property 1
    c.Property2 = 2; // Set property 2

    // Read properties
    Alert("Property1 = " + IntegerToString(c.Property1) + ", Property2 = " + IntegerToString(c.Property2));
   }

Puoi trovare questo esempio nel file OOP_sMethodsAndProperties.mq5 allegato a questo articolo.

 

Strutture dei Dati

Le strutture dei dati sono simili alle classi, ma solo un po' più semplici. Anche se puoi metterla così: le classi sono come strutture dei dati, ma piuttosto più complicate. La differenza è che le strutture dei dati possono includere solo le variabili. A questo proposito, non è necessario suddividerli nelle sezioni public, private e protected. Tutti i contenuti della struttura sono già presenti nella sezione public. La struttura dei dati inizia con la parola struct, seguita dal nome della struttura, all'interno delle parentesi graffe dichiarate variabili.

struct Str1 
   {
    int    IntVar;
    int    IntArr[];
    double DblVar[];
    double DblArr[];
   };

Per poter utilizzare una struttura deve essere dichiarata come variabile, ma al posto del tipo di variabile utilizzare il nome della struttura.

Str1 s1;

Puoi anche dichiarare un array di strutture:

Str1 sar1[];

Le strutture possono includere non solo variabili e array, ma anche altre strutture:

struct Str2 
   {
    int    IntVar;
    int    IntArr[];
    double DblVar[];
    double DblArr[];
    Str1   Str;
   };

In questo caso, per chiamare la variabile dalla struttura 1 che fa parte della struttura 2 devi utilizzare due punti:

s2.Str.IntVar=1;

Puoi trovare questo esempio nel file OOP_Struct.mq5 allegato a questo articolo.

Le classi possono includere non solo variabili, ma anche strutture.

 

Conclusione

Rivediamo i punti principali della programmazione orientata agli oggetti e i momenti importanti da tenere a mente:

1. La classe viene creata utilizzando la parola chiave classe, seguita dal nome della classe, quindi all'interno delle parentesi graffe viene visualizzato il codice della classe scritto in tre sezioni.

class CName 
  {
private:

protected:

public:
  };

2. Le funzioni e le variabili di classe possono essere collocate in una delle tre sezioni: private, protected e public. Le funzioni e le variabili della sezione private sono disponibili solo all'interno della classe. Funzioni e variabili dalla sezione protecteddisponibili all'interno della classe e dalle classi figlio. Le funzioni e le variabili della sezione public sono disponibili per tutti.

3. Le funzioni di classe possono trovarsi all'interno o all'esterno della classe. Se posizioni le funzioni al di fuori della classe, devi identificare a quale classe appartengono inserendo il nome della classe e due punti prima di ogni nome di funzione:

void ClassName::FunctionName() { ... }

4. La classe può essere caricata utilizzando sia il puntatore automatico che dinamico. Quando si utilizza il puntatore dinamico, la classe deve essere caricata utilizzando la parola chiave new. In questo caso, devi eliminare un oggetto utilizzando la parola chiave delete quando si termina il programma.

5. Per dire che la classe figlio appartiene alla classe base, devi aggiungere il nome della classe base dopo al nome di una classe figlio.

class Class : public CBase { ... }

6. Non puoi assegnare variabili con valori durante l'inizializzazione della classe. Puoi assegnare valori ​durante l'esecuzione di alcune funzioni, più spesso - il constructor.

7. Le funzioni virtuali vengono dichiarate utilizzando la parola chiave virtual. Se la classe figlio ha una funzione con lo stesso nome, esegue proprio questa funzione, altrimenti - esegue la funzione virtuale della classe base.

8. I puntatori alle classi possono essere passati alle funzioni. Puoi dichiarare i parametri della funzione con il tipo di classe base, in modo da poter passare un puntatore a qualsiasi classe figlio in funzione.

9. La sezione public non ha solo funzioni (metodi), ma anche variabili (proprietà).

10. Le strutture possono includere array e altre strutture.

 

Elenco dei File Allegati

  • OOP_CLibArray_1.mqh - file di inclusione, dovrebbe essere posizionato nella cartella MQL5/Include. Esempio di utilizzo della classe per creare una libreria. Le parole chiave protected e private. Overload
  • OOP_CLibArray_2.mqh - file incluso, dovrebbe essere posizionato nella cartella MQL5/Include. Esempio di posizionamento di funzioni di classe oltre la classe.
  • OOP_sDeleteOrders_1.mq5 - script, dovrebbe essere posizionato nella cartella MQL5/Scripts. Script semplice per eliminare gli ordini in sospeso.
  • OOP_CDeleteOrder_1.mqh - file di inclusione, dovrebbe essere posizionato nella cartella MQL5/Include. Esempio di conversione dello script OOP_sDeleteOrders_1 in classe.
  • OOP_sDeleteOrders_2.mq5 - file di inclusione, dovrebbe essere posizionato nella cartella MQL5/Scripts. Esempio di utilizzo della classe per eliminare gli ordini. Preso dal file OOP_CDeleteOrder_1.mqh (impostazione dei parametri tramite la funzione Init()).
  • OOP_sConstDestr_1.mq5 - script, dovrebbe essere posizionato nella cartella MQL5/Scripts. Demo Constructor and Destructor.
  • OOP_CDeleteOrder_2.mqh - file di inclusione, dovrebbe essere posizionato nella cartella MQL5/Include. Classe che elimina gli ordini con il constructor e il passaggio dei parametri tramite il constructor.
  • OOP_sDeleteOrders_3.mq5 - script, dovrebbe essere posizionato nella cartella MQL5/Scripts. Esempio di utilizzo della classe per eliminare gli ordini. Tratto dal file OOP_CDeleteOrder_2.mqh (impostazione dei parametri tramite constructor).
  • OOP_sConstDestr_2.mq5 - script, dovrebbe essere posizionato nella cartella MQL5/Scripts. Esempio di caricamento di classi in array.
  • OOP_sVariant_1.mq5 - script, dovrebbe essere posizionato nella cartella MQL5/Scripts. Esempio di classe base con figli. Funzione virtuale, polimorfismo.
  • OOP_sProtPriv_1.mq5 - script, dovrebbe essere posizionato nella cartella MQL5/Scripts. Un esempio di identità delle parole chiave protected e private quando si utilizza una classe.
  • OOP_sProtPriv_2.mq5 - script, dovrebbe essere posizionato nella cartella MQL5/Scripts. Esempio di effetto sulle parole chiave protected e private sulla classe figlio.
  • OOP_sDefaultVirtual_1.mq5 - script, dovrebbe essere posizionato nella cartella MQL5/Scripts. Esempio di classe figlio che non ha una funzione che corrisponde alla funzione virtuale della classe base.
  • OOP_sFunc_1.mq5 - script, dovrebbe essere posizionato nella cartella MQL5/Scripts. Esempio di utilizzo di oggetti in una funzione.
  • OOP_sMethodsAndProperties.mq5 - script, dovrebbe essere posizionato nella cartella MQL5/Scripts. Esempio di proprietà.
  • OOP_Struct.mq5 - script, dovrebbe essere posizionato nella cartella MQL5/Scripts. Esempio di strutture.

Dopo aver sperimentato questi file, puoi eliminarli tutti tranne OOP_CDeleteOrder_2.mqh e OOP_sDeleteOrders_3.mq5. I file OOP_CDeleteOrder_2.mqh e OOP_sDeleteOrders_3.mq5 possono essere utili nella programmazione pratica.

Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/351

File allegati |
files_en.zip (11.04 KB)
Approccio Orientato agli Oggetti per la Creazione di Pannelli Multi-Timeframe e Multi-Valuta Approccio Orientato agli Oggetti per la Creazione di Pannelli Multi-Timeframe e Multi-Valuta
Questo articolo descrive come la programmazione orientata agli oggetti può essere utilizzata per la creazione di pannelli multi-timeframe e multi-valuta per MetaTrader 5. L'obiettivo principale è quello di costruire un pannello universale, che può essere utilizzato per visualizzare diversi tipi di dati, come prezzi, variazioni di prezzo, valori degli indicatori o condizioni di acquisto/vendita personalizzate senza la necessità di modificare il codice del pannello stesso.
Velocizza i Calcoli con il Cloud Network MQL5 Velocizza i Calcoli con il Cloud Network MQL5
Quanti core hai sul tuo computer di casa? Quanti computer puoi utilizzare per ottimizzare una strategia di trading? Qui mostriamo come utilizzare il Cloud Network MQL5 per accelerare i calcoli ricevendo la potenza di calcolo in tutto il mondo con un click del mouse. La frase "Il tempo è denaro" diventa ancora più attuale con il passare degli anni e non possiamo permetterci di aspettare calcoli importanti per decine di ore o addirittura giorni.
Sistemi di Trading Semplici che Utilizzano Indicatori Semaforici Sistemi di Trading Semplici che Utilizzano Indicatori Semaforici
Se esaminiamo nel dettaglio qualsiasi sistema di trading complesso, vedremo che si basa su una serie di semplici segnali di trading. Pertanto, non è necessario che gli sviluppatori alle prime armi inizino immediatamente a scrivere algoritmi complessi. Questo articolo fornisce un esempio di un sistema di trading che utilizza indicatori semaforici per eseguire operazioni.
Controlli Grafici Personalizzati. Parte 3. Moduli Controlli Grafici Personalizzati. Parte 3. Moduli
Questo è l'ultimo dei tre articoli dedicati ai controlli grafici. Esso tratta la creazione del componente principale dell'interfaccia grafica - il modulo - e il suo utilizzo in combinazione con altri controlli. Oltre alle classi form, alla libreria di controllo sono state aggiunte le classi CFrame, CButton, CLabel.