English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Usare i Puntatori di Oggetti in MQL5

Usare i Puntatori di Oggetti in MQL5

MetaTrader 5Esempi | 8 dicembre 2021, 17:18
115 0
MetaQuotes
MetaQuotes

Introduzione

In MQL5, puoi creare la tua classe per l'ulteriore utilizzo delle variabili del tipo di classe nel tuo codice. Come già sappiamo dall'articolo L’Ordine della Creazione dell’Oggetto e della Distruzione in MQL5, le strutture e le classi possono essere create in due modi: automaticamente e dinamicamente.

Per creare un oggetto automaticamente, è sufficiente dichiarare una variabile di tipo classe: il sistema la creerà e la inizializzerà automaticamente. Per creare un oggetto in modo dinamico è necessario applicare esplicitamente l'operatore new al puntatore dell'oggetto.

Tuttavia, qual è la differenza tra gli oggetti creati automaticamente e dinamicamente e quando è necessario l'uso obbligatorio del puntatore all'oggetto e quando è sufficiente creare gli oggetti automaticamente? Questo argomento è l'oggetto di questo articolo. Innanzitutto, discutiamo alcune possibili insidie quando si lavora con gli oggetti e consideriamo i metodi per risolverli.

Un Errore Critico nell'Accesso a un Puntatore non Valido

in primo luogo, dovresti ricordare quando usi i puntatori agli oggetti: è l'inizializzazione obbligatoria dell'oggetto prima del suo utilizzo. Quando si accede a un puntatore non valido, il lavoro del programma MQL termina con un errore critico, quindi il programma viene rimosso. Ad esempio, consideriamo un semplice Expert Advisor, con classe CHello, dichiarato lì. Il puntatore all'istanza della classe è dichiarato a livello globale.

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

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

La variabile pstatus è il puntatore all'oggetto, ma abbiamo intenzionalmente "dimenticato" di creare l'oggetto stesso utilizzando l'operatore new. Se proverai a lanciare questo Expert Advisor sul grafico EURUSD, vedrai il risultato naturale: l'Expert Advisor è stato immediatamente scaricato nella fase di esecuzione della funzione OnInit(). I messaggi che appaiono nel Journal degli Expert sono i seguenti:

14:46:17 Expert GetCriticalError (EURUSD, H1) caricato con successo
14:46:18 Inizializzazione di GetCriticalError (EURUSD, H1) non riuscita
14:46:18 Expert GetCriticalError (EURUSD, H1) rimosso

Questo esempio è molto semplice, la cattura degli errori è facile. Tuttavia, se il tuo programma MQL5 contiene centinaia o addirittura migliaia di righe di codice, la cattura di questi errori potrebbe essere molto complicata. È importante, soprattutto per i casi di situazione di emergenza, le condizioni nel comportamento del programma dipendono da fattori imprevedibili, ad esempio dalla particolare struttura del mercato.

Controllo del Puntatore Prima del Suo Utilizzo

È stato possibile evitare la chiusura del programma critico? Sì, naturalmente! E' sufficiente inserire il controllo del puntatore oggetto prima del suo utilizzo. Modifichiamo questo esempio aggiungendo la funzione PrintStatus:

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

Ora questa funzione chiama il metodo GetMessage(), il puntatore dell'oggetto di tipo CHello viene passato alla funzione. In primo luogo, esso controlla il puntatore usando la funzione CheckPointer(). Aggiungiamo un parametro esterno e salviamo il codice di un Expert Advisor nel file GetCriticalError_OnDemand.mq5.

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

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

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

Ora, possiamo avviare l'Expert Advisor in due modi:

  1. Con un errore critico (GetStop = true)
  2. Senza un errore, ma con il messaggio sul puntatore non valido (GetStop = false)

Di default, l'esecuzione dell’Expert Advisor ha esito positivo e nel Journal "Experts" compare il seguente messaggio:

GetCriticalError_OnDemand (EURUSD, H1) 15:01:57 PrintStatus la variabile 'oggetto' non è inizializzata!
GetCriticalError_OnDemand (EURUSD, H1) 15:01:57 La funzione OnInit OnInit () è completata

Pertanto, il controllo del puntatore prima del suo utilizzo consente di evitare gli errori critici.

Verificare sempre la correttezza del puntatore prima del suo utilizzo nella funzione

Passaggio dell'Oggetto non Inizializzato per Riferimento

Cosa succede se passi l'oggetto non inizializzato come parametro di input della funzione? (l'oggetto stesso per riferimento, non il puntatore all'oggetto). Gli oggetti complessi, come classi e strutture, vengono passati per riferimento con una e commerciale. Riscriviamo un po' di codice del GetCriticalError_OnDemand.mq5. Rinominiamo la funzione PrintStatus() e riscriviamo il suo codice in un modo diverso.

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

Ora, la differenza è che la variabile stessa di questo tipo viene passata per riferimento come parametro di input invece del puntatore all'oggetto di tipo CClassHello. Salviamo la nuova versione di un Expert Advisor come GetCriticalError_Unsafe.mq5.

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

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

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

Si può vedere la differenza tra gli Expert Advisor GetCriticalError_OnDemand.mq5 e GetCriticalError_Unsafe.mq5 nel modo in cui i parametri passano alla funzione. Per il primo caso, il puntatore all'oggetto viene passato alla funzione e per il secondo caso l'oggetto stesso viene passato per riferimento. In entrambi i casi, prima dell'utilizzo dell'oggetto e del suo puntatore, la funzione ne verifica la correttezza del puntatore.

Significa che gli Expert Advisor funzioneranno allo stesso modo? No, non è così! Lanciamo l'Expert Advisor con il parametro GetStop=false e di nuovo avremo un errore critico. Il fatto è che se un oggetto viene passato per riferimento, l'errore critico si verifica nella fase di una chiamata di funzione, perché l'oggetto non inizializzato viene passato come parametro. Puoi verificarlo, lanciando lo script in modalità debug direttamente da MetaEditor5, utilizzando il tasto F5.

Per evitare l'uso dei breakpoint manuali, modifichiamo la funzione aggiungendo il breakpoint DebugBreak() all'interno della funzione.

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

In modalità debug, l’Expert Advisor verrà scaricato prima di una chiamata della funzione DebugBreak(). Come scrivere il codice sicuro per il caso se un oggetto non inizializzato viene passato dal riferimento? La risposta è semplice: utilizzare la funzione sovraccarico.

Se un puntatore di un oggetto non inizializzato viene passato per riferimento come parametro della funzione, porterà a un errore critico e interromperà il programma mql5.

Utilizzo delle Funzioni Sovraccaricate per Safe Code

Sarebbe molto scomodo, se lo sviluppatore, che utilizza la libreria esterna, fosse costretto a effettuare il controllo della correttezza degli oggetti di input. È molto meglio eseguire tutti i controlli necessari all'interno della libreria; può essere implementato utilizzando la funzione di overload.

Implementiamo il metodo UnsafePrintStatus() con la funzione overloading e scriviamo due versioni di questa funzione: la prima che utilizza il puntatore all'oggetto passato invece dell'oggetto stesso, la seconda che utilizza il passaggio dell'oggetto per riferimento. Entrambe le funzioni avranno lo stesso nome "PrintStatus", ma questa implementazione non sarà più potenzialmente pericolosa.

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

Ora per il caso se la funzione viene chiamata passando il puntatore all'oggetto, viene eseguita la verifica della correttezza e l'errore critico non si verifica. Se chiami la funzione in overload con passaggio di un oggetto per riferimento, prima otteniamo il puntatore all'oggetto utilizzando la funzione GetPointer e poi chiamiamo il codice sicuro, che utilizza il passaggio di un oggetto da parte del puntatore.

Possiamo riscrivere una versione sicura della funzione è ancora più breve, invece

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

scriviamo la versione compatta:

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

Le due versioni sono uguali. Salviamo la seconda versione con il nome GetCriticalError_Safe.mq5.

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

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

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

Se si utilizzano due funzioni con le diverse implementazioni (passando il riferimento e passando il puntatore all'oggetto), consente di garantire il lavoro sicuro della funzione sovraccaricata.

Infine, abbiamo imparato ad usare gli oggetti, passati alla funzione come parametri, ora è il momento di imparare:

Quando Hai Bisogno di Puntatori?

I puntatori agli oggetti consentono di eseguire una gestione flessibile del processo di creazione e distruzione di oggetti e consentono di creare oggetti astratti più complessi. Rende il programma più flessibile.

Lista Collegata

Ma in alcuni casi è richiesto un modo diverso di organizzazione dei dati, l'elenco collegato è quello loro. La classe di una lista collegata CList è disponibile nella Libreria Standard, qui presenteremo i nostri esempi. Un elenco collegato significa che ogni elemento dell'elenco è collegato agli elementi successivi e precedenti, se esistenti. Per organizzare tali collegamenti, è conveniente utilizzare i puntatori all'oggetto degli elementi dell'elenco (ListItem).

Creiamo una classe, che rappresenterà un elemento di una lista, come questa:

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

L'elenco stesso sarà organizzato in una classe separata:

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

La classe CList contiene un puntatore m_first del primo elemento dell'elenco, un accesso agli altri elementi dell'elenco è sempre disponibile tramite le funzioni next() e prev() della classe CListItem(). La classe CList ha due funzioni interessanti. La prima è la funzione per l'aggiunta di un nuovo elemento alla lista.

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

Ogni elemento, aggiunto all'elenco, diventa il primo elemento, il puntatore del primo elemento precedente viene memorizzato nel campo m_next. Pertanto, un elemento, aggiunto per primo all'elenco, sarà alla fine dell'elenco. L'ultimo elemento aggiunto sarà il primo e il suo puntatore viene memorizzato in una variabile m_first. La seconda funzione interessante è il destructor ~CList(). Chiama quando l'oggetto viene distrutto, dovrebbe garantire la corretta distruzione degli oggetti dell'elenco. Si può fare in modo molto semplice:

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

Si può vedere se m_first contiene il puntatore corretto dell'elemento dell'elenco, viene rimosso solo il primo elemento dell'elenco. Tutti gli altri elementi dell'elenco vengono rimossi a valanga, perché il destructor di classe CListItem() a sua volta, produce la corretta reinizializzazione dell'oggetto.

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

  }

La correttezza di un puntatore viene verificata all'interno del destructor, se specificato, l'oggetto con il puntatore m_next viene distrutto utilizzando l'operatore delete(). Prima della distruzione, il primo elemento della lista chiama il destructor, eliminerà il secondo elemento, poi causerà l'eliminazione del terzo elemento e così via fino alla fine della catena.

Il lavoro della lista è mostrato nello script SampleList.mq5. La dichiarazione di una lista (una variabile di CListType) è nella funzione OnStart(). Questo elenco verrà creato e inizializzato automaticamente. La compilazione dell'elenco viene eseguita all'interno di un elenco e il primo ogni elemento dell'elenco viene creato dinamicamente utilizzando l'operatore new e quindi aggiunto all'elenco. 

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

Avvia lo script e vedrai i seguenti messaggi nel journal "Experts".

18.03.2010 11:22:05 SampleList (EURUSD, H1) CList:: ~ CList Il primo elemento con ID=6 viene distrutto
18.03.2010 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Rimozione di un elemento con ID = 6
18.03.2010 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Rimozione di un elemento con ID = 5
18.03.2010 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Rimozione di un elemento con ID = 4
18.03.2010 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Rimozione di un elemento con ID = 3
18.03. 2010 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Rimozione di un elemento con ID = 2
18.03. 2010 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Rimozione di un elemento con ID = 1
18.03. 2010 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem L'elemento successivo non è definito per l'elemento con ID=0
18.03. 2010 11:22:05 SampleList (EURUSD, H1) Ci sono 7 elementi nell'elenco

Se hai studiato attentamente il codice, non è una sorpresa che l'elemento con ID=0 non abbia l'elemento successivo. Tuttavia, abbiamo considerato solo un motivo, quando l'uso dei puntatori rende la scrittura dei programmi comoda e leggibile. C'è una seconda ragione e si chiama

Polimorfismo

Spesso è necessario implementare la stessa funzionalità per oggetti diversi, appartenenti allo stesso tipo. Ad esempio, ci sono oggetti semplici come Linea, Triangolo, Rettangolo e Cerchio. Nonostante abbiano un aspetto diverso, hanno una caratteristica comune: possono essere disegnati. Creiamo una classe base CShape e usiamola per la creazione dei suoi discendenti per ogni tipo di forme geometriche.


Ai fini didattici, la classe base e i suoi discendenti hanno una funzionalità minima.

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

La classe CShape padre contiene due funzioni che vengono sovrascritte nei suoi discendenti: Draw() e getTypeName(). La funzione Draw() ha lo scopo di disegnare una forma e la funzione getTypeName() restituisce una descrizione stringa della forma.

Creiamo un array *shapes[] che contiene puntatori del tipo base CShape e specifica i valori di un puntatore per le diverse classi.

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

All'interno del ciclo for() chiamiamo il metodo getTypeName() per ogni elemento dell'array *shapes[]. La prima volta potresti essere sorpreso dal fatto che la funzione getTypeName() di una classe base chiama per ogni oggetto nella lista, nonostante il fatto che ogni classe derivata abbia la propria implementazione della funzione getTypeName().

18.03.2010 14:06:18 DemoPolymorphism (EURUSD, H1) 4 Shape
18.03.2010 14:06:18 DemoPolymorphism (EURUSD, H1) 3 Shape
18.03.2010 14:06:18 DemoPolymorphism (EURUSD, H1) 2 Shape
18.03.2010 14:06:18 DemoPolymorphism (EURUSD, H1) 1 Shape
18.03.2010 14:06:18 DemoPolymorphism (EURUSD, H1) 0 Shape

La spiegazione di questo fatto è la seguente: l'array *shapes [] è dichiarato come un array di puntatori di tipo CShape, e quindi ogni oggetto array chiama il metodo getTypeName() di una classe base, anche se un discendente ha un'implementazione diversa. Per chiamare la funzione getTypeName() corrispondente al tipo di oggetto effettivo (discendente) al momento dell'esecuzione del programma, è necessario dichiarare questa funzione in una classe base come funzione virtuale.

Aggiungiamo la parola chiave virtual alla funzione getTypeName() nella dichiarazione di una classe CShape genitore

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

e lancia di nuovo lo script. Ora, i risultati sono coerenti con quelli attesi:

18.03.2010 15:01:11 DemoPolymorphism (EURUSD, H1) 4 Circle
18.03.2010 15:01:11 DemoPolymorphism (EURUSD, H1) 3 Rectangle
18.03.2010 15:01:11 DemoPolymorphism (EURUSD, H1) 2 Triangle
18.03.2010 15:01:11 DemoPolymorphism (EURUSD, H1) 1 Line
18.03.2010 15:01:11 DemoPolymorphism (EURUSD, H1) 0 Shape

Quindi, la dichiarazione di una funzione virtuale in una classe base ha permesso di chiamare la stessa funzione di un discendente durante l'esecuzione del programma. Ora, possiamo implementare la funzione Draw() completa per ciascuna delle classi derivate.

Un esempio del suo lavoro può essere trovato nello script DrawManyObjects.mq5 allegato, che mostra una forma casuale sul grafico.

Conclusione

Quindi, è ora di riassumere. In MQL5, la creazione e la distruzione degli oggetti viene eseguita automaticamente, quindi dovresti usare i puntatori solo se sono realmente necessari e se capisci come lavorarci.

Tuttavia, se non puoi farlo senza l'uso dei puntatori, assicurati di controllare la correttezza del puntatore prima del suo utilizzo, usando il CheckPointer() - è stato aggiunto speciale per questi casi.

Un'ultima cosa: in MQL5 i puntatori non sono veri e propri puntatori di memoria, come si usavano in C++, quindi non dovresti passarli a DLL come parametri di input.

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

Indicatori Personalizzati in MQL5 per Principianti Indicatori Personalizzati in MQL5 per Principianti
Qualsiasi nuovo argomento sembra complicato e difficile da imparare per un principiante. Gli argomenti che conosciamo ci sembrano molto semplici e chiari. Ma semplicemente non ricordiamo che tutti devono studiare qualcosa da zero, persino la nostra lingua madre. Lo stesso avviene con il linguaggio di programmazione MQL5 che offre ampie possibilità di sviluppare le proprie strategie di trading: puoi iniziare a impararlo dalle nozioni di base e dagli esempi più semplici. L'interazione di un indicatore tecnico con il client terminal MetaTrader 5 viene considerata in questo articolo sull'esempio del semplice indicatore personalizzato SMA.
Introduzione a MQL5: Come scrivere un semplice Expert Advisor e un Indicatore Personalizzato Introduzione a MQL5: Come scrivere un semplice Expert Advisor e un Indicatore Personalizzato
Il Linguaggio di Programmazione MetaQuotes 5 (MQL5), incluso nel Client Terminal MetaTrader 5, ha molte nuove possibilità e prestazioni più elevate rispetto a MQL4. Questo articolo ti aiuterà a familiarizzare con questo nuovo linguaggio di programmazione. I semplici esempi di come scrivere un Expert Advisor e un Indicatore Personalizzato vengono presentati in questo articolo. Considereremo anche alcuni dettagli del linguaggio MQL5, necessari per comprendere questi esempi.
Elaborazione di eventi di trading nell'Expert Advisor utilizzando la funzione OnTrade() Elaborazione di eventi di trading nell'Expert Advisor utilizzando la funzione OnTrade()
MQL5 ha fornito una miriade di innovazioni, incluso il lavoro con eventi di vario tipo (eventi timer, eventi di trading, eventi personalizzati, ecc.). La capacità di gestire gli eventi ti consente di creare tipi completamente nuovi di programmi per il trading automatico e semi-automatico. In questo articolo, considereremo gli eventi di trading e scriveremo del codice per la funzione OnTrade(), che elaborerà l'evento Trade.
MQL5 per Principianti Guida all'Utilizzo degli Indicatori Tecnici negli Expert Advisor MQL5 per Principianti Guida all'Utilizzo degli Indicatori Tecnici negli Expert Advisor
Per ottenere i valori di un indicatore integrato o personalizzato in un Expert Advisor, innanzitutto il suo handle deve essere creato utilizzando la funzione corrispondente. Gli esempi nell'articolo mostrano come utilizzare questo o quell'indicatore tecnico durante la creazione dei propri programmi. L'articolo descrive gli indicatori creati nel linguaggio MQL5. È destinato a coloro che non hanno molta esperienza nello sviluppo di strategie di trading e offre modi semplici e chiari di lavorare con gli indicatori utilizzando la libreria di funzioni offerta.