
Forum sulla programmazione MQL5 Liste
Introduzione
La nuova versione del linguaggio MQL ha fornito agli sviluppatori di sistemi di trading automatizzato strumenti efficaci per l'implementazione di attività complesse. Non si può negare il fatto che le funzionalità di programmazione del linguaggio siano state notevolmente ampliate. Anche solo le caratteristiche OOP di MQL5 valgono molto. Da non sottovalutare è l’importanza della Standard Library. A giudicare dal codice di errore 359, i modelli di classe saranno presto supportati.
In questo articolo, vorrei parlare di quella che potrebbe essere in qualche modo un'espansione o una continuazione degli argomenti che descrivono i tipi di dati e i loro insiemi. Vorrei qui fare riferimento ad un articolo pubblicato sul sito MQL5.community. Una descrizione completa molto dettagliata dei principi e della logica del lavoro con gli array è stata fornita da Dmitry Fedoseev (Integer) nel suo articolo "MQL5 Programming Basics: Arrays".
Quindi, oggi propongo di passare alle liste e, più precisamente, alle liste lineari collegate. Inizieremo con la struttura, il significato e la logica dell'elenco. Successivamente, prenderemo in considerazione gli strumenti correlati già disponibili nella Standard Library. In conclusione, fornirò esempi di come è possibile utilizzare gli elenchi quando si lavora con MQL5.
- Concetto di Lista e Nodo: Teoria
- 1.1 Nodo in una lista collegato singolarmente
- 1.2 Nodo in una lista doppiamente collegata
- 1.3 Nodo in una lista circolare doppiamente collegata
- 1.4 Operazioni principali della lista
- Concetto di Lista e Nodo: Programmazione
- 2.1 Nodo in un elenco collegato singolarmente
- 2.2 Nodo in una lista doppiamente collegata
- 2.3 Nodo in una lista non rotolata doppiamente collegata
- 2.4 Lista collegata singolarmente
- 2.5 Lista doppiamente collegata
- 2.6 Lista doppiamente collegata srotolata
- 2.7 Lista circolare doppiamente collegata
- Lista nella libreria standard MQL5
- Esempi di utilizzo di liste in MQL5
1. Concetto di Lista e Nodo: Teoria
Quindi cos'è una lista per uno sviluppatore e come si fa? Farò riferimento alla fonte pubblica di informazione, Wikipedia, per una generica definizione di questo termine:
In informatica, una ista è un tipo di dati astratto che implementa una raccolta ordinata finita di valori, in cui lo stesso valore può verificarsi più di una volta. Un'istanza di una lista è una rappresentazione al computer del concetto matematico di una sequenza finita - una tupla. Ogni istanza di un valore nella lista viene solitamente chiamata elemento, voce o elemento della lista; se lo stesso valore si verifica più volte, ogni occorrenza è considerata un elemento distinto.
Il termine "lista" viene utilizzato anche per diverse strutture concrete di dati che possono essere utilizzate per implementare elenchi astratti, in particolare liste collegate.
Credo che sarete d'accordo sul fatto che questa definizione sia un po' troppo accademica.
Ai fini di questo articolo, siamo più interessati all'ultima frase di questa definizione. Quindi soffermiamoci su di essa:
In informatica, una lista concatenata è una struttura di dati dinamica di base costituita da nodi, dove ogni nodo è composto da dati e uno o due referenze ("links") al nodo successivo e/o precedente dell'elenco. [1] Il principale vantaggio di una lista collegata rispetto a un array convenzionale è una flessibilità strutturale: la sequenza degli elementi della lista collegata non deve necessariamente corrispondere alla sequenza degli elementi di dati nella memoria del computer, mentre i collegamenti interni degli elementi dell'elenco vengono sempre mantenuti per l'attraversamento dell'elenco.
Proviamo ad esaminarlo passo a passo.
In informatica, una lista in sé è un tipo di dati. Lo abbiamo stabilito. È piuttosto un tipo di dati sintetico in quanto include altri tipi di dati. Una lista è in qualche modo simile a un array. Se un array di dati di un tipo fosse mai classificato come un nuovo tipo di dati, quello sarebbe un elenco. Ma non del tutto.
Il vantaggio principale di un elenco è che consente l'inserimento o la rimozione di nodi in qualsiasi punto dell'elenco, a seconda del bisogno. Qui, la lista è simile a un array dinamico, tranne per il fatto che per una lista non è necessario utilizzare sempre la funzione ArrayResize().
Parlando in termini di ordine degli elementi di memoria, i nodi di lista non sono memorizzati e non devono essere memorizzati allo stesso modo degli elementi di matrice, memorizzati in aree di memoria adiacenti.
E questo è più o meno tutto quello che c’è da dire sull’argomento. Scendendo più in basso nell'elenco.
1.1 Nodo in una lista collegato singolarmente
Le liste ti consentono di memorizzare i nodi, anziché gli elementi. Il nodo è un tipo di dati costituito da due parti.
La prima parte è un campo dati e la seconda parte è utilizzata per i collegamenti con altri nodi (Fig. 1). Il primo nodo nell'elenco è chiamato 'testa' e l'ultimo nodo nell'elenco è chiamato 'coda'. Il campo del collegamento in coda contiene un riferimento NULL. Fondamentalmente viene utilizzato per indicare la mancanza di ulteriori nodi nella lista. Altre fonti specializzate si riferiscono al resto dell'elenco dopo la testa come 'coda'.
Fig. 1 Nodi in una lista collegata singolarmente
Oltre ai nodi di liste collegati singolarmente, esistono altri tipi di nodi. Un nodo in una lista doppiamente collegata è forse il più comune.
1.2 Nodo in una lista doppiamente collegata
Avremo anche bisogno di un nodo che servirà alle esigenze di una lista doppiamente collegata. È diverso dal tipo precedente in quanto contiene un altro collegamento che punta al nodo precedente. E naturalmente, il nodo dell’inizio della lista conterrà un riferimento NULL. Nel diagramma che mostra la struttura dell'elenco contenente tali nodi (Fig. 2), i collegamenti che puntano ai nodi precedenti sono visualizzati come frecce rosse.
Fig. 2 Nodi in una lista doppiamente collegata
Quindi le capacità di un nodo in una lista doppiamente collegata saranno simili a quelle di un nodo di una lista collegata singola. Dovrai solo gestire un ulteriore collegamento al nodo precedente.
1.3 Nodo in una lista circolare doppiamente collegata
Ci sono casi in cui i nodi di cui abbiamo parlato possono essere utilizzati anche in elenchi non lineari. E sebbene l'articolo descriverà principalmente elenchi lineari, fornirò anche un esempio di elenchi circolari.
Fig. 3 Nodi in una lista circolare doppiamente collegata
Il diagramma di una lista circolare doppiamente collegata (Fig. 3) mostra che i nodi con due campi di collegamento sono semplicemente collegati circolarmente. Questo viene fatto usando le frecce arancione e verde. Pertanto, il nodo della testa sarà collegato alla coda (come l'elemento precedente). E il campo di collegamento del nodo di coda non sarà vuoto poiché punterà alla testa.
1.4 Operazioni principali dell'elenco
Come riportato nella letteratura specializzata, tutte le operazioni di lista possono essere suddivise in 3 gruppi di base:
- Aggiunta (di un nuovo nodo alla lista);
- Cancellazione (di un nodo dalla lista);
- Verifica (dati di un nodo).
I metodi di aggiunta includono:
- aggiungere un nuovo nodo all'inizio della lista;
- aggiungere un nuovo nodo alla fine della lista;
- aggiungere un nodo alla posizione specificata nella lista;
- aggiungere un nodo a una lista vuota;
- costruttore parametrizzato.
Per quanto riguarda le operazioni di cancellazione, esse rispecchiano virtualmente le corrispondenti operazioni del gruppo di addizione:
- eliminare il nodo di testa;
- eliminare il nodo di coda;
- eliminare un nodo dalla posizione specificata nell'elenco;
- Distruttore.
Qui, vorrei farvi notare che il distruttore serve non solo a completare e terminare correttamente l'operazione di un elenco, ma anche a eliminare correttamente tutti i suoi elementi.
Il terzo gruppo di varie operazioni di controllo infatti dà accesso ai nodi o ai valori dei nodi nell'elenco:
- ricerca di un dato valore;
- controllando che l'elenco sia vuoto;
- ottenere il valore dell'i-esimo nodo nella lista;
- ottenere il puntatore all'i-esimo nodo nell'elenco;
- ottenere la dimensione dell'elenco;
- stampa dei valori degli elementi della lista.
Oltre ai gruppi di base, separerei anche il quarto gruppo di servizio. Esso serve i gruppi precedenti:
- Operatore di assegnazione
- costruttore di copie;
- lavorare con il puntatore dinamico;
- copia dell'elenco per valori;
- ordinamento.
Questo è tutto. Lo sviluppatore può ovviamente espandere le funzionalità e le caratteristiche di una classe di lista in qualsiasi momento, se necessario.
2. Concetto di Lista e Nodo: Programmazione
In questa parte, suggerisco di procedere direttamente alla programmazione di nodi e liste. Se necessario, verranno fornite illustrazioni al codice.
2.1 Nodo in una lista collegata singolarmente
Gettiamo le basi per la classe del nodo (Fig. 4) che soddisfa le esigenze di una lista concatenata singolarmente. Puoi familiarizzare con la notazione del diagramma di classe (modello) nell'articolo intitolato "Come sviluppare un Expert Advisor utilizzando gli strumenti UML" (vedi Fig. 5. modello UML della classe CTradeExpert).
Fig. 4 Modello di classe CiSingleNode
Proviamo ora a lavorare con il codice. Si basa sull'esempio fornito nel libro di Art Friedman e altri autori. "Archivi annotati C/C++".
//+------------------------------------------------------------------+ //| CiSingleNode class | //+------------------------------------------------------------------+ class CiSingleNode { protected: int m_val; // data CiSingleNode *m_next; // pointer to the next node public: void CiSingleNode(void); // default constructor void CiSingleNode(int _node_val); // parameterized constructor void ~CiSingleNode(void); // destructor void SetVal(int _node_val); // set-method for data void SetNextNode(CiSingleNode *_ptr_next); // set-method for the next node virtual void SetPrevNode(CiSingleNode *_ptr_prev){}; // set-method for the previous node virtual CiSingleNode *GetPrevNode(void) const {return NULL;}; // get-method for the previous node CiSingleNode *GetNextNode(void) const; // get-method for the next node int GetVal(void){TRACE_CALL(_t_flag) return m_val;} // get-method for data };
Non spiegherò ogni metodo della classe CiSingleNode. Potrai prenderne visione più da vicino nel file CiSingleNode.mqh in allegato. Tuttavia, vorrei attirare la vostra attenzione su una sfumatura interessante. La classe contiene metodi virtuali che funzionano con i nodi precedenti. Sono infatti fittizzi e la loro presenza è piuttosto ai fini del polimorfismo per i futuri discendenti.
Il codice utilizza la direttiva del preprocessore TRACE_CALL(f) necessaria per tracciare le chiamate di ciascun metodo utilizzato.
#define TRACE_CALL(f) if(f) Print("Calling: "+__FUNCSIG__);
Con solo la classe CiSingleNode disponibile, sei in grado di creare una lista concatenata singolarmente. Ti faccio un esempio del codice.
//=========== Example 1 (processing the CiSingleNode type ) CiSingleNode *p_sNodes[3]; // #1 p_sNodes[0]=NULL; srand(GetTickCount()); // initialize a random number generator //--- create nodes for(int i=0;i<ArraySize(p_sNodes);i++) p_sNodes[i]=new CiSingleNode(rand()); // #2 //--- links for(int j=0;j<(ArraySize(p_sNodes)-1);j++) p_sNodes[j].SetNextNode(p_sNodes[j+1]); // #3 //--- check values for(int i=0;i<ArraySize(p_sNodes);i++) { int val=p_sNodes[i].GetVal(); // #4 Print("Node #"+IntegerToString(i+1)+ // #5 " value = "+IntegerToString(val)); } //--- check next-nodes for(int j=0;j<(ArraySize(p_sNodes)-1);j++) { CiSingleNode *p_sNode_next=p_sNodes[j].GetNextNode(); // #9 int snode_next_val=p_sNode_next.GetVal(); // #10 Print("Next-Node #"+IntegerToString(j+1)+ // #11 " value = "+IntegerToString(snode_next_val)); } //--- delete nodes for(int i=0;i<ArraySize(p_sNodes);i++) delete p_sNodes[i]; // #12
Nella stringa #1, dichiariamo un array di puntatori a oggetti di tipo CiSingleNode. Nella stringa n. 2, l'array viene riempito con i puntatori creati. Per i dati di ciascun nodo, prendiamo un intero pseudocasuale compreso tra 0 e 32767 utilizzando la rand() function. I nodi sono collegati al puntatore successivo nella stringa #3. Nelle stringhe #4-5, controlliamo i valori dei nodi e nelle stringhe #9-11 controlliamo le prestazioni dei collegamenti. I puntatori vengono eliminati nella stringa #12.
Questo è ciò che è stato stampato sul registro.
DH 0 23:23:10 test_nodes (EURUSD,H4) Node #1 value = 3335 KP 0 23:23:10 test_nodes (EURUSD,H4) Node #2 value = 21584 GI 0 23:23:10 test_nodes (EURUSD,H4) Node #3 value = 917 HQ 0 23:23:10 test_nodes (EURUSD,H4) Next-Node #1 value = 21584 HI 0 23:23:10 test_nodes (EURUSD,H4) Next-Node #2 value = 917
La struttura del nodo risultante può essere schematicamente mostrata come segue (Fig. 5).
Fig. 5 Collegamenti tra i nodi nell'array CiSingleNode *p_sNodes[3]
Procediamo ora ai nodi in una lista doppiamente collegata.
2.2 Nodo in una lista doppiamente collegata
Innanzitutto, dobbiamo rispolverare il fatto che un nodo in una lista doppiamente collegata è distinto in quanto ha due puntatori: puntatore al nodo successivo e puntatore al nodo precedente. Ad esempio, oltre al collegamento al nodo successivo, è necessario aggiungere un puntatore al nodo precedente a un nodo di elenco con collegamento singolo.
Nel fare ciò, propongo di utilizzare l'ereditarietà come relazione di classe. Quindi il modello di classe per un nodo in una lista doppiamente collegata può apparire come segue (Fig. 6).
Fig. 6 Modello di classe CDDoubleNode
Ora è il momento di dare un'occhiata al codice.
//+------------------------------------------------------------------+ //| CDoubleNode class | //+------------------------------------------------------------------+ class CDoubleNode : public CiSingleNode { protected: CiSingleNode *m_prev; // pointer to the previous node public: void CDoubleNode(void); // default constructor void CDoubleNode(int node_val); // parameterized constructor void ~CDoubleNode(void){TRACE_CALL(_t_flag)};// destructor virtual void SetPrevNode(CiSingleNode *_ptr_prev); // set-method for the previous node virtual CiSingleNode *GetPrevNode(void) const; // get-method for the previous node CDoubleNode };
Ci sono pochissimi metodi aggiuntivi: sono virtuali e sono legati al lavoro con il nodo precedente. La descrizione completa della classe è fornita in CDoubleNode.mqh.
Proviamo a creare una lista doppiamente collegata basata sulla classe CDoubleNode. Ti faccio un esempio del codice.
//=========== Example 2 (processing the CDoubleNode type) CiSingleNode *p_dNodes[3]; // #1 p_dNodes[0]=NULL; srand(GetTickCount()); // initialize a random number generator //--- create nodes for(int i=0;i<ArraySize(p_dNodes);i++) p_dNodes[i]=new CDoubleNode(rand()); // #2 //--- links for(int j=0;j<(ArraySize(p_dNodes)-1);j++) { p_dNodes[j].SetNextNode(p_dNodes[j+1]); // #3 p_dNodes[j+1].SetPrevNode(p_dNodes[j]); // #4 } //--- check values for(int i=0;i<ArraySize(p_dNodes);i++) { int val=p_dNodes[i].GetVal(); // #4 Print("Node #"+IntegerToString(i+1)+ // #5 " value = "+IntegerToString(val)); } //--- check next-nodes for(int j=0;j<(ArraySize(p_dNodes)-1);j++) { CiSingleNode *p_sNode_next=p_dNodes[j].GetNextNode(); // #9 int snode_next_val=p_sNode_next.GetVal(); // #10 Print("Next-Node #"+IntegerToString(j+1)+ // #11 " value = "+IntegerToString(snode_next_val)); } //--- check prev-nodes for(int j=0;j<(ArraySize(p_dNodes)-1);j++) { CiSingleNode *p_sNode_prev=p_dNodes[j+1].GetPrevNode(); // #12 int snode_prev_val=p_sNode_prev.GetVal(); // #13 Print("Prev-Node #"+IntegerToString(j+2)+ // #14 " value = "+IntegerToString(snode_prev_val)); } //--- delete nodes for(int i=0;i<ArraySize(p_dNodes);i++) delete p_dNodes[i]; // #15
In linea di principio, questo è simile alla creazione di una lista collegata singolarmente, ma ci sono alcune peculiarità. Nota come l'array di puntatori p_dNodes[] è dichiarato nella stringa #1. Il tipo di puntatori può essere impostato identico alla classe base. Il principio del polimorfismo nella stringa n. 2 ci aiuterà a riconoscerli in futuro. I nodi precedenti vengono controllati nelle stringhe #12-14.
Le seguenti informazioni sono state aggiunte al registro.
GJ 0 16:28:12 test_nodes (EURUSD,H4) Node #1 value = 17543 IQ 0 16:28:12 test_nodes (EURUSD,H4) Node #2 value = 1185 KK 0 16:28:12 test_nodes (EURUSD,H4) Node #3 value = 23216 DS 0 16:28:12 test_nodes (EURUSD,H4) Next-Node #1 value = 1185 NH 0 16:28:12 test_nodes (EURUSD,H4) Next-Node #2 value = 23216 FR 0 16:28:12 test_nodes (EURUSD,H4) Prev-Node #2 value = 17543 LI 0 16:28:12 test_nodes (EURUSD,H4) Prev-Node #3 value = 1185
La struttura del nodo risultante può essere schematicamente mostrata come segue (Fig. 7):
Fig. 7 Collegamenti tra i nodi nell'array CDoubleNode *p_sNodes[3]
Suggerisco ora di considerare un nodo che sarà richiesto nella creazione di una lista srotolata a doppio collegamento.
2.3 Nodo in una lista non rotolata doppiamente collegata
Si pensi a un nodo che conterrebbe un dato che, invece di un singolo valore, è attribuibile all'intero array, cioè contiene e descrive l'intero array. Tale nodo può quindi essere utilizzato per creare una lista srotolata. Ho deciso di non fornire alcuna illustrazione qui poiché questo nodo è esattamente lo stesso di un nodo standard in una lista doppiamente collegata. L'unica differenza è che l'attributo 'data' incapsula l'intero array.
Userò di nuovo l'ereditarietà. La classe CDoubleNode fungerà da classe base per un nodo in un elenco srotolato a doppio collegamento. E il modello di classe per un nodo in una lista doppiamente collegata srotolata apparirà come segue (Fig. 8).
Fig. 8 Modello di classe CiUnrollDoubleNode
La classe CiUnrollDoubleNode può essere definita utilizzando il seguente codice:
//+------------------------------------------------------------------+ //| CiUnrollDoubleNode class | //+------------------------------------------------------------------+ class CiUnrollDoubleNode : public CDoubleNode { private: int m_arr_val[]; // data array public: void CiUnrollDoubleNode(void); // default constructor void CiUnrollDoubleNode(int &_node_arr[]); // parameterized constructor void ~CiUnrollDoubleNode(void); // destructor bool GetArrVal(int &_dest_arr_val[])const; // get-method for data array bool SetArrVal(const int &_node_arr_val[]); // set-method for data array };
Puoi esaminare ogni metodo in modo più dettagliato in CiUnrollDoubleNode.mqh.
Consideriamo un costruttore parametrizzato come esempio.
//+------------------------------------------------------------------+ //| Parameterized constructor | //+------------------------------------------------------------------+ void CiUnrollDoubleNode::CiUnrollDoubleNode(int &_node_arr[]) : CDoubleNode(ArraySize(_node_arr)) { ArrayCopy(this.m_arr_val,_node_arr); TRACE_CALL(_t_flag) }
Qui, usando l'elenco di inizializzazione, inseriamo la dimensione di un array unidimensionale nel membro dati this.m_val.
Dopodiché creiamo "manualmente" una lista srotolata a doppio collegamento e controlliamo i collegamenti al suo interno.
//=========== Example 3 (processing the CiUnrollDoubleNode type) //--- data arrays int arr1[],arr2[],arr3[]; // #1 int arr_size=15; ArrayResize(arr1,arr_size); ArrayResize(arr2,arr_size); ArrayResize(arr3,arr_size); srand(GetTickCount()); // initialize a random number generator //--- fill the arrays with pseudorandom integers for(int i=0;i<arr_size;i++) { arr1[i]=rand(); // #2 arr2[i]=rand(); arr3[i]=rand(); } //--- create nodes CiUnrollDoubleNode *p_udNodes[3]; // #3 p_udNodes[0]=new CiUnrollDoubleNode(arr1); p_udNodes[1]=new CiUnrollDoubleNode(arr2); p_udNodes[2]=new CiUnrollDoubleNode(arr3); //--- links for(int j=0;j<(ArraySize(p_udNodes)-1);j++) { p_udNodes[j].SetNextNode(p_udNodes[j+1]); // #4 p_udNodes[j+1].SetPrevNode(p_udNodes[j]); // #5 } //--- check values for(int i=0;i<ArraySize(p_udNodes);i++) { int val=p_udNodes[i].GetVal(); // #6 Print("Node #"+IntegerToString(i+1)+ // #7 " value = "+IntegerToString(val)); } //--- check array values for(int i=0;i<ArraySize(p_udNodes);i++) { int t_arr[]; // destination array bool isCopied=p_udNodes[i].GetArrVal(t_arr); // #8 if(isCopied) { string arr_str=NULL; for(int n=0;n<ArraySize(t_arr);n++) arr_str+=IntegerToString(t_arr[n])+", "; int end_of_string=StringLen(arr_str); arr_str=StringSubstr(arr_str,0,end_of_string-2); Print("Node #"+IntegerToString(i+1)+ // #9 " array values = "+arr_str); } } //--- check next-nodes for(int j=0;j<(ArraySize(p_udNodes)-1);j++) { int t_arr[]; // destination array CiUnrollDoubleNode *p_udNode_next=p_udNodes[j].GetNextNode(); // #10 bool isCopied=p_udNode_next.GetArrVal(t_arr); if(isCopied) { string arr_str=NULL; for(int n=0;n<ArraySize(t_arr);n++) arr_str+=IntegerToString(t_arr[n])+", "; int end_of_string=StringLen(arr_str); arr_str=StringSubstr(arr_str,0,end_of_string-2); Print("Next-Node #"+IntegerToString(j+1)+ " array values = "+arr_str); } } //--- check prev-nodes for(int j=0;j<(ArraySize(p_udNodes)-1);j++) { int t_arr[]; // destination array CiUnrollDoubleNode *p_udNode_prev=p_udNodes[j+1].GetPrevNode(); // #11 bool isCopied=p_udNode_prev.GetArrVal(t_arr); if(isCopied) { string arr_str=NULL; for(int n=0;n<ArraySize(t_arr);n++) arr_str+=IntegerToString(t_arr[n])+", "; int end_of_string=StringLen(arr_str); arr_str=StringSubstr(arr_str,0,end_of_string-2); Print("Prev-Node #"+IntegerToString(j+2)+ " array values = "+arr_str); } } //--- delete nodes for(int i=0;i<ArraySize(p_udNodes);i++) delete p_udNodes[i]; // #12 }
La quantità di codice è diventata leggermente più grande. Questo ha a che fare con il fatto che dobbiamo creare e riempire un array per ogni nodo.
Il lavoro con gli array di dati inizia nella stringa #1. È sostanzialmente simile a quello che avevamo nei nodi precedenti considerati. È solo che abbiamo bisogno di stampare i valori dei dati di ciascun nodo per l'intero array (ad esempio, stringa #9).
Questo è quello che abbiamo:
IN 0 00:09:13 test_nodes (EURUSD.m,H4) Node #1 value = 15 NF 0 00:09:13 test_nodes (EURUSD.m,H4) Node #2 value = 15 CI 0 00:09:13 test_nodes (EURUSD.m,H4) Node #3 value = 15 FQ 0 00:09:13 test_nodes (EURUSD.m,H4) Node #1 array values = 31784, 4837, 25797, 29079, 4223, 27234, 2155, 32351, 12010, 10353, 10391, 22245, 27895, 3918, 12069 EG 0 00:09:13 test_nodes (EURUSD.m,H4) Node #2 array values = 1809, 18553, 23224, 20208, 10191, 4833, 25959, 2761, 7291, 23254, 29865, 23938, 7585, 20880, 25756 MK 0 00:09:13 test_nodes (EURUSD.m,H4) Node #3 array values = 18100, 26358, 31020, 23881, 11256, 24798, 31481, 14567, 13032, 4701, 21665, 1434, 1622, 16377, 25778 RP 0 00:09:13 test_nodes (EURUSD.m,H4) Next-Node #1 array values = 1809, 18553, 23224, 20208, 10191, 4833, 25959, 2761, 7291, 23254, 29865, 23938, 7585, 20880, 25756 JD 0 00:09:13 test_nodes (EURUSD.m,H4) Next-Node #2 array values = 18100, 26358, 31020, 23881, 11256, 24798, 31481, 14567, 13032, 4701, 21665, 1434, 1622, 16377, 25778 EH 0 00:09:13 test_nodes (EURUSD.m,H4) Prev-Node #2 array values = 31784, 4837, 25797, 29079, 4223, 27234, 2155, 32351, 12010, 10353, 10391, 22245, 27895, 3918, 12069 NN 0 00:09:13 test_nodes (EURUSD.m,H4) Prev-Node #3 array values = 1809, 18553, 23224, 20208, 10191, 4833, 25959, 2761, 7291, 23254, 29865, 23938, 7585, 20880, 25756
Suggerisco di tracciare una linea sotto il lavoro con i nodi e procedere direttamente alle definizioni di classe di liste diverse. Gli esempi 1-3 possono essere trovati nello script test_nodes.mq5.
2.4 Lista collegata singolarmente
È giunto il momento di creare un modello di classe di una lista collegata singolarmente dai principali gruppi di operazioni di elenco (Fig. 9).
Fig. 9 Modello di classe CiSingleList
È facile vedere che la classe CiSingleList utilizza il nodo di tipo CiSingleNode. Parlando di tipi di relazioni tra classi, possiamo dire che:
- la classe CiSingleList contiene la classe CiSingleNode (composizione);
- la classe CiSingleList utilizza i metodi della classe CiSingleNode (dipendenza).
L'illustrazione delle relazioni di cui sopra è fornita in Fig. 10.
Fig. 10 Tipi di relazioni tra la classe CiSingleList e la classe CiSingleNode
Creiamo una nuova classe - CiSingleList. Guardando al futuro, tutte le altre classi di elenco utilizzate nell'articolo saranno basate su questa classe. Ecco perché è così 'ricco'.
//+------------------------------------------------------------------+ //| CiSingleList class | //+------------------------------------------------------------------+ class CiSingleList { protected: CiSingleNode *m_head; // head CiSingleNode *m_tail; // tail uint m_size; // number of nodes in the list public: //--- constructor and destructor void CiSingleList(); // default constructor void CiSingleList(int _node_val); // parameterized constructor void ~CiSingleList(); // destructor //--- adding nodes void AddFront(int _node_val); // add a new node to the beginning of the list void AddRear(int _node_val); // add a new node to the end of the list virtual void AddFront(int &_node_arr[]){TRACE_CALL(_t_flag)}; // add a new node to the beginning of the list virtual void AddRear(int &_node_arr[]){TRACE_CALL(_t_flag)}; // add a new node to the end of the list //--- deleting nodes int RemoveFront(void); // delete the head node int RemoveRear(void); // delete the node from the end of the list void DeleteNodeByIndex(const uint _idx); // delete the ith node from the list //--- checking virtual bool Find(const int _node_val) const; // find the required value bool IsEmpty(void) const; // check the list for being empty virtual int GetValByIndex(const uint _idx) const; // value of the ith node in the list virtual CiSingleNode *GetNodeByIndex(const uint _idx) const; // get the ith node in the list virtual bool SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insert the new ith node in the list CiSingleNode *GetHeadNode(void) const; // get the head node CiSingleNode *GetTailNode(void) const; // get the tail node virtual uint Size(void) const; // list size //--- service virtual void PrintList(string _caption=NULL); // print the list virtual bool CopyByValue(const CiSingleList &_sList); // copy the list by values virtual void BubbleSort(void); // bubble sorting //---templates template<typename dPointer> bool CheckDynamicPointer(dPointer &_p); // template for checking a dynamic pointer template<typename dPointer> bool DeleteDynamicPointer(dPointer &_p); // template for deleting a dynamic pointer protected: void operator=(const CiSingleList &_sList) const; // assignment operator void CiSingleList(const CiSingleList &_sList); // copy constructor virtual bool AddToEmpty(int _node_val); // add a new node to an empty list virtual void addFront(int _node_val); // add a new "native" node to the beginning of the list virtual void addRear(int _node_val); // add a new "native" node to the end of the list virtual int removeFront(void); // delete the "native" head node virtual int removeRear(void); // delete the "native" node from the end of the list virtual void deleteNodeByIndex(const uint _idx); // delete the "native" ith node from the list virtual CiSingleNode *newNode(int _val); // new "native" node virtual void CalcSize(void) const; // calculate the list size };
La definizione completa dei metodi di classe è fornita in CiSingleList.mqh.
Quando ho iniziato a sviluppare questa classe, c'erano solo 3 membri dati e solo pochi metodi. Ma poiché questa classe fungeva da base per altre classi, ho dovuto aggiungere diverse funzioni membro virtuale. Non ho intenzione di descrivere questi metodi in dettaglio. Un esempio dell'utilizzo di questa classe di elenchi collegati singolarmente può essere trovato nello script test_sList.mq5.
Se viene eseguito senza il flag di traccia, nel registro verranno visualizzate le seguenti voci:
KG 0 12:58:32 test_sList (EURUSD,H1) =======List #1======= PF 0 12:58:32 test_sList (EURUSD,H1) Node #1, val=14 RL 0 12:58:32 test_sList (EURUSD,H1) Node #2, val=666 MD 0 12:58:32 test_sList (EURUSD,H1) Node #3, val=13 DM 0 12:58:32 test_sList (EURUSD,H1) Node #4, val=11 QE 0 12:58:32 test_sList (EURUSD,H1) KN 0 12:58:32 test_sList (EURUSD,H1) LR 0 12:58:32 test_sList (EURUSD,H1) =======List #2======= RE 0 12:58:32 test_sList (EURUSD,H1) Node #1, val=14 DQ 0 12:58:32 test_sList (EURUSD,H1) Node #2, val=666 GK 0 12:58:32 test_sList (EURUSD,H1) Node #3, val=13 FP 0 12:58:32 test_sList (EURUSD,H1) Node #4, val=11 KF 0 12:58:32 test_sList (EURUSD,H1) MK 0 12:58:32 test_sList (EURUSD,H1) PR 0 12:58:32 test_sList (EURUSD,H1) =======renewed List #2======= GK 0 12:58:32 test_sList (EURUSD,H1) Node #1, val=11 JP 0 12:58:32 test_sList (EURUSD,H1) Node #2, val=13 JI 0 12:58:32 test_sList (EURUSD,H1) Node #3, val=14 CF 0 12:58:32 test_sList (EURUSD,H1) Node #4, val=34 QL 0 12:58:32 test_sList (EURUSD,H1) Node #5, val=35 OE 0 12:58:32 test_sList (EURUSD,H1) Node #6, val=36 MR 0 12:58:32 test_sList (EURUSD,H1) Node #7, val=37 KK 0 12:58:32 test_sList (EURUSD,H1) Node #8, val=38 MS 0 12:58:32 test_sList (EURUSD,H1) Node #9, val=666 OF 0 12:58:32 test_sList (EURUSD,H1) QK 0 12:58:32 test_sList (EURUSD,H1)
Lo script ha riempito 2 liste collegate singolarmente e poi ha esteso e ordinato la seconda lista.
2.5 Lista doppiamente collegata
Proviamo ora a creare una lista doppiamente collegata in base alla lista del tipo precedente. L'illustrazione del modello di classe di una lista doppiamente collegata è fornita in Fig. 11:
Fig. 11 Modello di classe CDDoubleList
La classe discendente contiene molti meno metodi, mentre i membri dei dati sono del tutto assenti. Di seguito è riportata la definizione della classe CDoubleList.
//+------------------------------------------------------------------+ //| CDoubleList class | //+------------------------------------------------------------------+ class CDoubleList : public CiSingleList { public: void CDoubleList(void); // default constructor void CDoubleList(int _node_val); // parameterized constructor void ~CDoubleList(void){}; // destructor virtual bool SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insert the new ith node in the list protected: virtual bool AddToEmpty(int _node_val); // add a node to an empty list virtual void addFront(int _node_val); // add a new "native" node to the beginning of the list virtual void addRear(int _node_val); // add a new "native" node to the end of the list virtual int removeFront(void); // delete the "native" head node virtual int removeRear(void); // delete the "native" tail node virtual void deleteNodeByIndex(const uint _idx); // delete the "native" ith node from the list virtual CiSingleNode *newNode(int _node_val); // new "native" node };
La descrizione completa dei metodi della classe CDoubleList è fornita in CDoubleList.mqh.
In generale, le funzioni virtuali vengono utilizzate qui solo per soddisfare le esigenze del puntatore al nodo precedente che non esiste nelle liste concatenate singolarmente.
Un esempio di utilizzo dell'elenco di tipo CDoubleList può essere trovato nello script test_dList.mq5. Essp dimostra tutte le operazioni di elenco comuni relative a questo tipo di elenco. Il codice dello script contiene una stringa particolare:
CiSingleNode *_new_node=new CDoubleNode(666); // create a new node of CDoubleNode type
Non c'è alcun errore perché tale costruzione è abbastanza accettabile nei casi in cui il puntatore della classe base descrive un oggetto della classe discendente. Questo è uno dei vantaggi dell'ereditarietà.
In MQL5, così come in С++, il puntatore alla classe base può puntare all'oggetto della sottoclasse derivata da quella classe base. Ma il contrario non è valido.
Se scrivi la stringa come segue:
CDoubleNode*_new_node=new CiSingleNode(666);
il compilatore non segnalerà un errore o un avviso ma il programma verrà eseguito finché non raggiunge questa stringa. In questo caso, vedrai un messaggio sul casting errato dei tipi a cui fanno riferimento i puntatori. Poiché il meccanismo di associazione tardiva entra in azione solo quando il programma è in esecuzione, è necessario considerare attentamente la gerarchia delle relazioni tra le classi.
Dopo aver eseguito lo script, il registro conterrà le seguenti voci:
DN 0 13:10:57 test_dList (EURUSD,H1) =======List #1======= GO 0 13:10:57 test_dList (EURUSD,H1) Node #1, val=14 IE 0 13:10:57 test_dList (EURUSD,H1) Node #2, val=666 FM 0 13:10:57 test_dList (EURUSD,H1) Node #3, val=13 KD 0 13:10:57 test_dList (EURUSD,H1) Node #4, val=11 JL 0 13:10:57 test_dList (EURUSD,H1) DG 0 13:10:57 test_dList (EURUSD,H1) CK 0 13:10:57 test_dList (EURUSD,H1) =======List #2======= IL 0 13:10:57 test_dList (EURUSD,H1) Node #1, val=14 KH 0 13:10:57 test_dList (EURUSD,H1) Node #2, val=666 PR 0 13:10:57 test_dList (EURUSD,H1) Node #3, val=13 MI 0 13:10:57 test_dList (EURUSD,H1) Node #4, val=11 DO 0 13:10:57 test_dList (EURUSD,H1) FR 0 13:10:57 test_dList (EURUSD,H1) GK 0 13:10:57 test_dList (EURUSD,H1) =======renewed List #2======= PR 0 13:10:57 test_dList (EURUSD,H1) Node #1, val=11 QI 0 13:10:57 test_dList (EURUSD,H1) Node #2, val=13 QP 0 13:10:57 test_dList (EURUSD,H1) Node #3, val=14 LO 0 13:10:57 test_dList (EURUSD,H1) Node #4, val=34 JE 0 13:10:57 test_dList (EURUSD,H1) Node #5, val=35 HL 0 13:10:57 test_dList (EURUSD,H1) Node #6, val=36 FK 0 13:10:57 test_dList (EURUSD,H1) Node #7, val=37 DR 0 13:10:57 test_dList (EURUSD,H1) Node #8, val=38 FJ 0 13:10:57 test_dList (EURUSD,H1) Node #9, val=666 HO 0 13:10:57 test_dList (EURUSD,H1) JR 0 13:10:57 test_dList (EURUSD,H1)
Come nel caso di una lista concatenata singola, lo script ha riempito la prima lista (doppiamente collegata), l'ha copiata e passata alla seconda lista. Quindi ha aumentato il numero di nodi nel secondo elenco, ordinato e stampato l'elenco.
2.6 Lista doppiamente collegata srotolata
Questo tipo di lista è conveniente in quanto consente di memorizzare non solo un valore ma un intero array.
Gettiamo le basi per l'elenco di tipo CiUnrollDoubleList (Fig. 12).
Fig. 12 Modello di classe CiUnrollDoubleList
Poiché qui ci occuperemo di un array di dati, dovremo ridefinire i metodi definiti nella classe base indiretta CiSingleList.
Di seguito è riportata la definizione della classe CiUnrollDoubleList.
//+------------------------------------------------------------------+ //| CiUnrollDoubleList class | //+------------------------------------------------------------------+ class CiUnrollDoubleList : public CDoubleList { public: void CiUnrollDoubleList(void); // default constructor void CiUnrollDoubleList(int &_node_arr[]); // parameterized constructor void ~CiUnrollDoubleList(void){TRACE_CALL(_t_flag)}; // destructor //--- virtual void AddFront(int &_node_arr[]); // add a new node to the beginning of the list virtual void AddRear(int &_node_arr[]); // add a new node to the end of the list virtual bool CopyByValue(const CiSingleList &_udList); // copy by values virtual void PrintList(string _caption=NULL); // print the list virtual void BubbleSort(void); // bubble sorting protected: virtual bool AddToEmpty(int &_node_arr[]); // add a node to an empty list virtual void addFront(int &_node_arr[]); // add a new "native" node to the beginning of the list virtual void addRear(int &_node_arr[]); // add a new "native" node to the end of the list virtual int removeFront(void); // delete the "native" node from the beginning of the list virtual int removeRear(void); // delete the "native" node from the end of the list virtual void deleteNodeByIndex(const uint _idx); // delete the "native" ith node from the list virtual CiSingleNode *newNode(int &_node_arr[]); // new "native" node };
La definizione completa dei metodi di classe è fornita in CiUnrollDoubleList.mqh.
Lanciamo lo script test_UdList.mq5 per verificare il funzionamento dei metodi della classe. Qui, le operazioni sui nodi sono simili a quelle usate negli script precedenti. Dovremmo forse spendere qualche parola sui metodi di smistamento e stampa. Il metodo di ordinamento ordina i nodi in base al numero di elementi in modo che il nodo contenente l'array di valori della dimensione più piccola sia all'inizio dell'elenco.
Il metodo di stampa stampa una stringa di valori di array contenuti in un determinato nodo.
Dopo aver eseguito lo script, il registro avrà le seguenti voci:
II 0 13:22:23 test_UdList (EURUSD,H1) =======List #1======= FN 0 13:22:23 test_UdList (EURUSD,H1) List node #1, array: 55, 12, 1, 2, 11, 114, 33, 113, 14, 15, 16, 17, 18, 19, 20 OO 0 13:22:23 test_UdList (EURUSD,H1) List node #2, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 GG 0 13:22:23 test_UdList (EURUSD,H1) GP 0 13:22:23 test_UdList (EURUSD,H1) GR 0 13:22:23 test_UdList (EURUSD,H1) =======List #2 before sorting======= JO 0 13:22:23 test_UdList (EURUSD,H1) List node #1, array: 55, 12, 1, 2, 11, 114, 33, 113, 14, 15, 16, 17, 18, 19, 20 CH 0 13:22:23 test_UdList (EURUSD,H1) List node #2, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 CF 0 13:22:23 test_UdList (EURUSD,H1) List node #3, array: -89, -131, -141, -139, -129, -25, -105, -24, -122, -120, -118, -116, -114, -112, -110 GD 0 13:22:23 test_UdList (EURUSD,H1) GQ 0 13:22:23 test_UdList (EURUSD,H1) LJ 0 13:22:23 test_UdList (EURUSD,H1) =======List #2 after sorting======= FN 0 13:22:23 test_UdList (EURUSD,H1) List node #1, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 CJ 0 13:22:23 test_UdList (EURUSD,H1) List node #2, array: 55, 12, 1, 2, 11, 114, 33, 113, 14, 15, 16, 17, 18, 19, 20 II 0 13:22:23 test_UdList (EURUSD,H1) List node #3, array: -89, -131, -141, -139, -129, -25, -105, -24, -122, -120, -118, -116, -114, -112, -110 MD 0 13:22:23 test_UdList (EURUSD,H1) MQ 0 13:22:23 test_UdList (EURUSD,H1)
Come puoi vedere, dopo essere stato ordinato, la lista udList2 è stata stampata partendo dal nodo con l'array più piccolo fino al nodo che contiene l'array più grande.
2.7 Lista circolare doppiamente collegata
Sebbene le liste non lineari non siano considerate in questo articolo, suggerisco di lavorare anche con loro. Come sia possibile collegare i nodi circolarmente è già stato mostrato sopra (Fig. 3).
Creiamo un modello della classe CiCircleDoubleList class (Fig. 13). Questa classe sarà una classe discendente della classe CDoubleList.
Fig. 13 Modello di classe CiCircleDoubleList
A causa del fatto che i nodi in questa lista sono di carattere specifico (la testa e la coda sono collegate), quasi tutti i metodi della classe base di origine CiSingleList dovranno essere resi virtuali.
//+------------------------------------------------------------------+ //| CiCircleDoubleList class | //+------------------------------------------------------------------+ class CiCircleDoubleList : public CDoubleList { public: void CiCircleDoubleList(void); // default constructor void CiCircleDoubleList(int _node_val); // parameterized constructor void ~CiCircleDoubleList(void){TRACE_CALL(_t_flag)}; // destructor //--- virtual uint Size(void) const; // list size virtual bool SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insert the new ith node in the list virtual int GetValByIndex(const uint _idx) const; // value of the ith node in the list virtual CiSingleNode *GetNodeByIndex(const uint _idx) const; // get the ith node in the list virtual bool Find(const int _node_val) const; // find the required value virtual bool CopyByValue(const CiSingleList &_sList); // copy the list by values protected: virtual void addFront(int _node_val); // add a new "native" node to the beginning of the list virtual void addRear(int _node_val); // add a new "native" node to the end of the list virtual int removeFront(void); // delete the "native" head node virtual int removeRear(void); // delete the "native" tail node virtual void deleteNodeByIndex(const uint _idx); // delete the "native" ith node from the list protected: void CalcSize(void) const; // calculate the list size void LinkHeadTail(void); // link head to tail };
La descrizione completa della classe è fornita in CiCircleDoubleList.mqh.
Consideriamo alcuni metodi della classe. Il metodo CiCircleDoubleList::LinkHeadTail() collega il nodo di coda al nodo di testa. Dovrebbe essere chiamato quando c'è una nuova coda o testa e il collegamento precedente viene perso.
//+------------------------------------------------------------------+ //| Linking head to tail | //+------------------------------------------------------------------+ void CiCircleDoubleList::LinkHeadTail(void) { TRACE_CALL(_t_flag) this.m_head.SetPrevNode(this.m_tail); // link head to tail this.m_tail.SetNextNode(this.m_head); // link tail to head }
Pensa a come sarebbe questo metodo se avessimo a che fare con un elenco circolare collegato singolarmente.
Si consideri, ad esempio, il metodo CiCircleDoubleList::addFront().
//+------------------------------------------------------------------+ //| New "native" node to the beginning of the list | //+------------------------------------------------------------------+ void CiCircleDoubleList::addFront(int _node_val) { TRACE_CALL(_t_flag) CDoubleList::addFront(_node_val); // call a similar method of the base class this.LinkHeadTail(); // link head and tail }
Puoi vedere nel corpo del metodo che viene chiamato un metodo simile della classe base CDoubleList. A questo punto, potremmo completare l'operazione del metodo (il metodo in quanto tale non è sostanzialmente necessario qui), se non fosse per una cosa. Il collegamento tra la testa e la coda è perso e l'elenco non può essere collegato circolarmente senza di esso. Questo è il motivo per cui dobbiamo chiamare il metodo di collegamento della testa e della coda.
L'utilizzo della lista circolare doppiamente collegata è verificato nello script test_UdList.mq5.
In termini di compiti e obiettivi, gli altri metodi utilizzati sono gli stessi degli esempi precedenti.
Di conseguenza, il registro contiene le seguenti voci:
PR 0 13:34:29 test_CdList (EURUSD,H1) =======List #1======= QS 0 13:34:29 test_CdList (EURUSD,H1) Node #1, val=14 QI 0 13:34:29 test_CdList (EURUSD,H1) Node #2, val=666 LQ 0 13:34:29 test_CdList (EURUSD,H1) Node #3, val=13 OH 0 13:34:29 test_CdList (EURUSD,H1) Node #4, val=11 DP 0 13:34:29 test_CdList (EURUSD,H1) DK 0 13:34:29 test_CdList (EURUSD,H1) DI 0 13:34:29 test_CdList (EURUSD,H1) =======List #2 before sorting======= MS 0 13:34:29 test_CdList (EURUSD,H1) Node #1, val=38 IJ 0 13:34:29 test_CdList (EURUSD,H1) Node #2, val=37 IQ 0 13:34:29 test_CdList (EURUSD,H1) Node #3, val=36 EH 0 13:34:29 test_CdList (EURUSD,H1) Node #4, val=35 EO 0 13:34:29 test_CdList (EURUSD,H1) Node #5, val=34 FF 0 13:34:29 test_CdList (EURUSD,H1) Node #6, val=14 DN 0 13:34:29 test_CdList (EURUSD,H1) Node #7, val=666 GD 0 13:34:29 test_CdList (EURUSD,H1) Node #8, val=13 JK 0 13:34:29 test_CdList (EURUSD,H1) Node #9, val=11 JM 0 13:34:29 test_CdList (EURUSD,H1) JH 0 13:34:29 test_CdList (EURUSD,H1) MS 0 13:34:29 test_CdList (EURUSD,H1) =======List #2 after sorting======= LE 0 13:34:29 test_CdList (EURUSD,H1) Node #1, val=11 KL 0 13:34:29 test_CdList (EURUSD,H1) Node #2, val=13 QS 0 13:34:29 test_CdList (EURUSD,H1) Node #3, val=14 NJ 0 13:34:29 test_CdList (EURUSD,H1) Node #4, val=34 NQ 0 13:34:29 test_CdList (EURUSD,H1) Node #5, val=35 NH 0 13:34:29 test_CdList (EURUSD,H1) Node #6, val=36 NO 0 13:34:29 test_CdList (EURUSD,H1) Node #7, val=37 NF 0 13:34:29 test_CdList (EURUSD,H1) Node #8, val=38 JN 0 13:34:29 test_CdList (EURUSD,H1) Node #9, val=666 RJ 0 13:34:29 test_CdList (EURUSD,H1) RE 0 13:34:29 test_CdList (EURUSD,H1)
Quindi il diagramma finale dell'ereditarietà tra le classi di liste introdotte è il seguente (Fig. 14).
Non sono sicuro che tutte le classi debbano essere collegate per eredità, ma ho deciso di lasciare tutto così com'è.
Fig. 14 Ereditarietà tra le classi della lista
Tracciando la linea sotto questa sezione dell'articolo che si è occupata della descrizione degli elenchi personalizzati, vorrei farvi notare che abbiamo appena accennato al gruppo di elenchi non lineari, elenchi concatenati multipli e altri. Man mano che raccolgo le informazioni pertinenti e acquisisco più esperienza lavorando con strutture di dati così dinamiche, proverò a scrivere un altro articolo.
3. Elenchi nella libreria standard (Standard Library) MQL5
Diamo un'occhiata alla classe della lista disponibile nella Standard Library (Fig. 15).
Appartiene alle classi di dati.
Fig. 15 Modello classe CList
Curiosamente, CList è un discendente della classe CObject. Cioè l'elenco eredita dati e metodi della classe, che è un nodo.
La classe list contiene un insieme impressionante di metodi. Ad essere onesti, non mi aspettavo di trovare una classe così grande nella Standard Library.
La classe CList ha 8 membri di dati. Vorrei sottolineare un paio di cose. Gli attributi della classe contengono l'indice del nodo corrente (int m_curr_idx) e il puntatore al nodo corrente (CObject* m_curr_node). Si può dire che l'elenco è "intelligente" - può indicare il luogo in cui è localizzato il controllo. Inoltre, è dotato di meccanismo di gestione della memoria (possiamo eliminare fisicamente un nodo o semplicemente escluderlo dall'elenco), flag di elenco ordinato e modalità di ordinamento.
Parlando di metodi, tutti i metodi della classe CList sono suddivisi nei seguenti gruppi:
- attributi;
- Crea metodi;
- Aggiungi metodi;
- Elimina metodi;
- Navigazione;
- Modalità di ordinazione;
- Confronta metodi;
- Metodi di ricerca;
- Input Output.
Come al solito, c'è un costruttore e un distruttore standard.
Il primo svuota (NULL) tutti i puntatori. Lo stato del flag di gestione della memoria è impostato su eliminazione. Il nuovo elenco non sarà ordinato.
Nel suo corpo, il distruttore chiama solo il metodo Clear() per svuotare l'elenco dei nodi. La fine dell'esistenza della lista non comporta necessariamente la "morte" dei suoi elementi (nodi). Pertanto, il flag di gestione della memoria impostato durante l'eliminazione degli elementi della lista trasforma la relazione di classe da composizione ad aggregazione.
Possiamo gestire questo flag usando i metodi set e get FreeMode().
Ci sono due metodi nella classe che ti permettono di estendere l'elenco: Add() e Insert). Il primo è simile al metodo AddRear() utilizzato nella prima sezione dell'articolo. Il secondo metodo è simile al metodo SetNodeByIndex().
Cominciamo con un piccolo esempio. Dobbiamo prima creare una classe del nodo CNodeInt, un discendente della classe dell'interfaccia CObject. Memorizzerà il valore del tipo intero.
//+------------------------------------------------------------------+ //| CNodeInt class | //+------------------------------------------------------------------+ class CNodeInt : public CObject { private: int m_val; // node data public: void CNodeInt(void){this.m_val=WRONG_VALUE;}; // default constructor void CNodeInt(int _val); // parameterized constructor void ~CNodeInt(void){}; // destructor int GetVal(void){return this.m_val;}; // get-method for node data void SetVal(int _val){this.m_val=_val;}; // set-method for node data }; //+------------------------------------------------------------------+ //| Parameterized constructor | //+------------------------------------------------------------------+ void CNodeInt::CNodeInt(int _val):m_val(_val) { };
Lavoreremo con l'elenco CList nello script test_MQL5_List.mq5.
L'esempio 1 mostra una creazione dinamica dell'elenco e dei nodi. L'elenco viene quindi riempito con i nodi e il valore del primo nodo viene verificato prima e dopo l'eliminazione dell'elenco.
//--- Example 1 (testing memory management) CList *myList=new CList; // myList.FreeMode(false); // reset flag bool _free_mode=myList.FreeMode(); PrintFormat("\nList \"myList\" - memory management flag: %d",_free_mode); CNodeInt *p_new_nodes_int[10]; p_new_nodes_int[0]=NULL; for(int i=0;i<ArraySize(p_new_nodes_int);i++) { p_new_nodes_int[i]=new CNodeInt(rand()); myList.Add(p_new_nodes_int[i]); } PrintFormat("List \"myList\" has as many nodes as: %d",myList.Total()); Print("=======Before deleting \"myList\"======="); PrintFormat("The 1st node value is: %d",p_new_nodes_int[0].GetVal()); delete myList; int val_to_check=WRONG_VALUE; if(CheckPointer(p_new_nodes_int[0])) val_to_check=p_new_nodes_int[0].GetVal(); Print("=======After deleting \"myList\"======="); PrintFormat("The 1st node value is: %d",val_to_check);
Se la stringa che ripristina il flag rimane commentata (inattiva), otterremo le seguenti voci nel registro:
GS 0 14:00:16 test_MQL5_List (EURUSD,H1) EO 0 14:00:16 test_MQL5_List (EURUSD,H1) List "myList" - memory management flag: 1 FR 0 14:00:16 test_MQL5_List (EURUSD,H1) List "myList" has as many nodes as: 10 JH 0 14:00:16 test_MQL5_List (EURUSD,H1) =======Before deleting "myList"======= DO 0 14:00:16 test_MQL5_List (EURUSD,H1) The 1st node value is: 7189 KJ 0 14:00:16 test_MQL5_List (EURUSD,H1) =======After deleting "myList"======= QK 0 14:00:16 test_MQL5_List (EURUSD,H1) The 1st node value is: -1
Si noti che dopo aver cancellato dinamicamente la lista myList, anche tutti i nodi in essa contenuti sono stati cancellati dalla memoria.
Tuttavia, se decommentiamo la stringa del flag di ripristino:
// myList.FreeMode(false); // reset flag
l'output nel log sarà il seguente:
NS 0 14:02:11 test_MQL5_List (EURUSD,H1) CN 0 14:02:11 test_MQL5_List (EURUSD,H1) List "myList" - memory management flag: 0 CS 0 14:02:11 test_MQL5_List (EURUSD,H1) List "myList" has as many nodes as: 10 KH 0 14:02:11 test_MQL5_List (EURUSD,H1) =======Before deleting "myList"======= NL 0 14:02:11 test_MQL5_List (EURUSD,H1) The 1st node value is: 20411 HJ 0 14:02:11 test_MQL5_List (EURUSD,H1) =======After deleting "myList"======= LI 0 14:02:11 test_MQL5_List (EURUSD,H1) The 1st node value is: 20411 QQ 1 14:02:11 test_MQL5_List (EURUSD,H1) 10 undeleted objects left DD 1 14:02:11 test_MQL5_List (EURUSD,H1) 10 objects of type CNodeInt left DL 1 14:02:11 test_MQL5_List (EURUSD,H1) 400 bytes of leaked memory
È facile notare che il nodo head mantiene il suo valore sia prima che dopo che la lista è stata cancellata. In questo caso, rimarranno anche oggetti non eliminati se lo script non contiene il codice per eliminarli correttamente.
Proviamo ora a lavorare con il metodo di ordinamento.
//--- Example 2 (sorting) CList *myList=new CList; CNodeInt *p_new_nodes_int[10]; p_new_nodes_int[0]=NULL; for(int i=0;i<ArraySize(p_new_nodes_int);i++) { p_new_nodes_int[i]=new CNodeInt(rand()); myList.Add(p_new_nodes_int[i]); } PrintFormat("\nList \"myList\" has as many nodes as: %d",myList.Total()); Print("=======List \"myList\" before sorting======="); for(int i=0;i<myList.Total();i++) { CNodeInt *p_node_int=myList.GetNodeAtIndex(i); int node_val=p_node_int.GetVal(); PrintFormat("Node #%d is equal to: %d",i+1,node_val); } myList.Sort(0); Print("\n=======List \"myList\" after sorting======="); for(int i=0;i<myList.Total();i++) { CNodeInt *p_node_int=myList.GetNodeAtIndex(i); int node_val=p_node_int.GetVal(); PrintFormat("Node #%d is equal to: %d",i+1,node_val); } delete myList;
Di conseguenza, il registro contiene le seguenti voci:
OR 0 22:47:01 test_MQL5_List (EURUSD,H1) FN 0 22:47:01 test_MQL5_List (EURUSD,H1) List "myList" has as many nodes as: 10 FH 0 22:47:01 test_MQL5_List (EURUSD,H1) =======List "myList" before sorting======= LG 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #1 is equal to: 30511 CO 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #2 is equal to: 17404 GF 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #3 is equal to: 12215 KQ 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #4 is equal to: 31574 NJ 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #5 is equal to: 7285 HP 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #6 is equal to: 23509 IH 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #7 is equal to: 26991 NS 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #8 is equal to: 414 MK 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #9 is equal to: 18824 DR 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #10 is equal to: 1560 OR 0 22:47:01 test_MQL5_List (EURUSD,H1) OM 0 22:47:01 test_MQL5_List (EURUSD,H1) =======List "myList" after sorting======= QM 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #1 is equal to: 26991 RE 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #2 is equal to: 23509 ML 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #3 is equal to: 18824 DD 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #4 is equal to: 414 LL 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #5 is equal to: 1560 IG 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #6 is equal to: 17404 PN 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #7 is equal to: 30511 II 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #8 is equal to: 31574 OQ 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #9 is equal to: 12215 JH 0 22:47:01 test_MQL5_List (EURUSD,H1) Node #10 is equal to: 7285
Anche se non è stato fatto alcun ordinamento, la tecnica di smistamento rimane un mistero per me. Spiegherò perché. Senza entrare nei dettagli sull'ordine delle chiamate, il metodo CList::Sort() chiama il metodo virtuale CObject::Compare() che non è in alcun modo implementato nella classe base. Quindi, il programmatore deve occuparsi da solo dell'implementazione di un metodo di ordinamento.
E ora, qualche parola sul metodo Total(). Restituisce il numero di elementi (nodi) di cui è responsabile il membro di dati m_data_total. È un metodo molto breve e conciso. Il conteggio degli elementi in questa implementazione sarà molto più veloce rispetto a quello che ho proposto in precedenza. Infatti, perché ogni volta passano attraverso la lista e contano i nodi, mentre il numero esatto di nodi nell'elenco può essere impostato quando si aggiungono o si eliminano nodi.
L'esempio 3 confronta la velocità di riempimento delle liste di tipo CList e CiSingleList e calcola il tempo per ottenere la dimensione di ciascuna lista.
//--- Example 3 (nodes number) int iterations=1e7; // 10 million iterations //--- the new CList CList *p_mql_List=new CList; uint start=GetTickCount(); // starting value for(int i=0;i<iterations;i++) { CNodeInt *p_node_int=new CNodeInt(rand()); p_mql_List.Add(p_node_int); } uint time=GetTickCount()-start; // time spent, msec Print("\n=======the CList type list======="); PrintFormat("Filling the list of %.3e nodes has taken %d msec",iterations,time); //--- get the size start=GetTickCount(); int list_size=p_mql_List.Total(); time=GetTickCount()-start; PrintFormat("Getting the size of the list has taken %d msec",time); delete p_mql_List; //--- the new CiSingleList CiSingleList *p_sList=new CiSingleList; start=GetTickCount(); // starting value for(int i=0;i<iterations;i++) p_sList.AddRear(rand()); time=GetTickCount()-start; // time spent, msec Print("\n=======the CiSingleList type list======="); PrintFormat("Filling the list of %.3e nodes has taken %d msec",iterations,time); //--- get the size start=GetTickCount(); list_size=(int)p_sList.Size(); time=GetTickCount()-start; PrintFormat("Getting the size of the list has taken %d msec",time); delete p_sList;
Questo è quello che ho nel registro:
KO 0 22:48:24 test_MQL5_List (EURUSD,H1) CK 0 22:48:24 test_MQL5_List (EURUSD,H1) =======the CList type list======= JL 0 22:48:24 test_MQL5_List (EURUSD,H1) Filling the list of 1.000e+007 nodes has taken 2606 msec RO 0 22:48:24 test_MQL5_List (EURUSD,H1) Getting the size of the list has taken 0 msec LF 0 22:48:29 test_MQL5_List (EURUSD,H1) EL 0 22:48:29 test_MQL5_List (EURUSD,H1) =======the CiSingleList type list======= KK 0 22:48:29 test_MQL5_List (EURUSD,H1) Filling the list of 1.000e+007 nodes has taken 2356 msec NF 0 22:48:29 test_MQL5_List (EURUSD,H1) Getting the size of the list has taken 359 msec
Il metodo per ottenere la dimensione funziona istantaneamente nell'elenco CList. A proposito, anche l'aggiunta di nodi alla lista è abbastanza veloce.
Nel blocco successivo (Esempio 4), suggerisco di prestare attenzione a uno dei principali svantaggi della lista come contenitore di dati, ovvero la poca velocità di accesso agli elementi. Il fatto è che si accede agli elementi della lista in modo lineare. Nella classe CList, vi si accede in modo binario, il che diminuisce leggermente la laboriosità dell'algoritmo.
Quando si effettua una ricerca lineare, la laboriosità è O(N). Una ricerca implementata in modo binario risulta nella laboriosità di log2(N).
Questo è un esempio del codice per accedere agli elementi di un set di dati:
//--- Example 4 (speed of accessing the node) const uint Iter_arr[]={1e3,3e3,6e3,9e3,1e4,3e4,6e4,9e4,1e5,3e5,6e5}; for(uint i=0;i<ArraySize(Iter_arr);i++) { const uint cur_iterations=Iter_arr[i]; // iterations number uint randArr[]; // array of random numbers uint idxArr[]; // array of indexes //--- set the arrays size ArrayResize(randArr,cur_iterations); ArrayResize(idxArr,cur_iterations); CRandom myRand; // random number generator //--- fill the array of random numbers for(uint t=0;t<cur_iterations;t++) randArr[t]=myRand.int32(); //--- fill the array of indexes with random numbers (from 0 to 10 million) int iter_log10=(int)log10(cur_iterations); for(uint r=0;r<cur_iterations;r++) { uint rand_val=myRand.int32(); // random value (from 0 to 4 294 967 295) if(rand_val>=cur_iterations) { int val_log10=(int)log10(rand_val); double log10_remainder=val_log10-iter_log10; rand_val/=(uint)pow(10,log10_remainder+1); } //--- check the limit if(rand_val>=cur_iterations) { Alert("Random value error!"); return; } idxArr[r]=rand_val; } //--- time spent for the array uint start=GetTickCount(); //--- accessing the array elements for(uint p=0;p<cur_iterations;p++) uint random_val=randArr[idxArr[p]]; uint time=GetTickCount()-start; // time spent, msec Print("\n=======the uint type array======="); PrintFormat("Random accessing the array of elements %.1e has taken %d msec",cur_iterations,time); //--- the CList type list CList *p_mql_List=new CList; //--- fill the list for(uint q=0;q<cur_iterations;q++) { CNodeInt *p_node_int=new CNodeInt(randArr[q]); p_mql_List.Add(p_node_int); } start=GetTickCount(); //--- accessing the list nodes for(uint w=0;w<cur_iterations;w++) CNodeInt *p_node_int=p_mql_List.GetNodeAtIndex(idxArr[w]); time=GetTickCount()-start; // time spent, msec Print("\n=======the CList type list======="); PrintFormat("Random accessing the list of nodes %.1e has taken %d msec",cur_iterations,time); //--- free the memory ArrayFree(randArr); ArrayFree(idxArr); delete p_mql_List; }
Sulla base dei risultati dell'operazione di blocco, le seguenti voci sono state stampate nel registro:
MR 0 22:51:22 test_MQL5_List (EURUSD,H1) QL 0 22:51:22 test_MQL5_List (EURUSD,H1) =======the uint type array======= IG 0 22:51:22 test_MQL5_List (EURUSD,H1) Random accessing the array of elements 1.0e+003 has taken 0 msec QF 0 22:51:22 test_MQL5_List (EURUSD,H1) IQ 0 22:51:22 test_MQL5_List (EURUSD,H1) =======the CList type list======= JK 0 22:51:22 test_MQL5_List (EURUSD,H1) Random accessing the list of nodes 1.0e+003 has taken 0 msec MJ 0 22:51:22 test_MQL5_List (EURUSD,H1) QD 0 22:51:22 test_MQL5_List (EURUSD,H1) =======the uint type array======= GO 0 22:51:22 test_MQL5_List (EURUSD,H1) Random accessing the array of elements 3.0e+003 has taken 0 msec QN 0 22:51:22 test_MQL5_List (EURUSD,H1) II 0 22:51:22 test_MQL5_List (EURUSD,H1) =======the CList type list======= EP 0 22:51:22 test_MQL5_List (EURUSD,H1) Random accessing the list of nodes 3.0e+003 has taken 16 msec OR 0 22:51:22 test_MQL5_List (EURUSD,H1) OL 0 22:51:22 test_MQL5_List (EURUSD,H1) =======the uint type array======= FG 0 22:51:22 test_MQL5_List (EURUSD,H1) Random accessing the array of elements 6.0e+003 has taken 0 msec CF 0 22:51:22 test_MQL5_List (EURUSD,H1) GQ 0 22:51:22 test_MQL5_List (EURUSD,H1) =======the CList type list======= CH 0 22:51:22 test_MQL5_List (EURUSD,H1) Random accessing the list of nodes 6.0e+003 has taken 31 msec QJ 0 22:51:22 test_MQL5_List (EURUSD,H1) MD 0 22:51:22 test_MQL5_List (EURUSD,H1) =======the uint type array======= MO 0 22:51:22 test_MQL5_List (EURUSD,H1) Random accessing the array of elements 9.0e+003 has taken 0 msec EN 0 22:51:22 test_MQL5_List (EURUSD,H1) MJ 0 22:51:22 test_MQL5_List (EURUSD,H1) =======the CList type list======= CP 0 22:51:22 test_MQL5_List (EURUSD,H1) Random accessing the list of nodes 9.0e+003 has taken 47 msec CR 0 22:51:22 test_MQL5_List (EURUSD,H1) KL 0 22:51:22 test_MQL5_List (EURUSD,H1) =======the uint type array======= JG 0 22:51:22 test_MQL5_List (EURUSD,H1) Random accessing the array of elements 1.0e+004 has taken 0 msec GF 0 22:51:22 test_MQL5_List (EURUSD,H1) KR 0 22:51:22 test_MQL5_List (EURUSD,H1) =======the CList type list======= MK 0 22:51:22 test_MQL5_List (EURUSD,H1) Random accessing the list of nodes 1.0e+004 has taken 343 msec GJ 0 22:51:22 test_MQL5_List (EURUSD,H1) GG 0 22:51:22 test_MQL5_List (EURUSD,H1) =======the uint type array======= LO 0 22:51:22 test_MQL5_List (EURUSD,H1) Random accessing the array of elements 3.0e+004 has taken 0 msec QO 0 22:51:24 test_MQL5_List (EURUSD,H1) MJ 0 22:51:24 test_MQL5_List (EURUSD,H1) =======the CList type list======= NP 0 22:51:24 test_MQL5_List (EURUSD,H1) Random accessing the list of nodes 3.0e+004 has taken 1217 msec OS 0 22:51:24 test_MQL5_List (EURUSD,H1) KO 0 22:51:24 test_MQL5_List (EURUSD,H1) =======the uint type array======= CP 0 22:51:24 test_MQL5_List (EURUSD,H1) Random accessing the array of elements 6.0e+004 has taken 0 msec MG 0 22:51:26 test_MQL5_List (EURUSD,H1) ER 0 22:51:26 test_MQL5_List (EURUSD,H1) =======the CList type list======= PG 0 22:51:26 test_MQL5_List (EURUSD,H1) Random accessing the list of nodes 6.0e+004 has taken 2387 msec GK 0 22:51:26 test_MQL5_List (EURUSD,H1) OG 0 22:51:26 test_MQL5_List (EURUSD,H1) =======the uint type array======= NH 0 22:51:26 test_MQL5_List (EURUSD,H1) Random accessing the array of elements 9.0e+004 has taken 0 msec JO 0 22:51:30 test_MQL5_List (EURUSD,H1) NK 0 22:51:30 test_MQL5_List (EURUSD,H1) =======the CList type list======= KO 0 22:51:30 test_MQL5_List (EURUSD,H1) Random accessing the list of nodes 9.0e+004 has taken 3619 msec HS 0 22:51:30 test_MQL5_List (EURUSD,H1) DN 0 22:51:30 test_MQL5_List (EURUSD,H1) =======the uint type array======= RP 0 22:51:30 test_MQL5_List (EURUSD,H1) Random accessing the array of elements 1.0e+005 has taken 0 msec OD 0 22:52:05 test_MQL5_List (EURUSD,H1) GS 0 22:52:05 test_MQL5_List (EURUSD,H1) =======the CList type list======= DE 0 22:52:05 test_MQL5_List (EURUSD,H1) Random accessing the list of nodes 1.0e+005 has taken 35631 msec NH 0 22:52:06 test_MQL5_List (EURUSD,H1) RF 0 22:52:06 test_MQL5_List (EURUSD,H1) =======the uint type array======= FI 0 22:52:06 test_MQL5_List (EURUSD,H1) Random accessing the array of elements 3.0e+005 has taken 0 msec HL 0 22:54:20 test_MQL5_List (EURUSD,H1) PD 0 22:54:20 test_MQL5_List (EURUSD,H1) =======the CList type list======= FN 0 22:54:20 test_MQL5_List (EURUSD,H1) Random accessing the list of nodes 3.0e+005 has taken 134379 msec RQ 0 22:54:20 test_MQL5_List (EURUSD,H1) JI 0 22:54:20 test_MQL5_List (EURUSD,H1) =======the uint type array======= MR 0 22:54:20 test_MQL5_List (EURUSD,H1) Random accessing the array of elements 6.0e+005 has taken 15 msec NE 0 22:58:48 test_MQL5_List (EURUSD,H1) FL 0 22:58:48 test_MQL5_List (EURUSD,H1) =======the CList type list======= GE 0 22:58:48 test_MQL5_List (EURUSD,H1) Random accessing the list of nodes 6.0e+005 has taken 267589 msec
Puoi vedere che l'accesso casuale agli elementi dell'elenco richiede più tempo man mano che la dimensione dell'elenco aumenta (Fig. 16).
Fig. 16 Tempo impiegato per l'accesso casuale agli elementi di array e di lista
Consideriamo ora i metodi per salvare e caricare i dati.
La classe dell'elenco di base CList contiene tali metodi ma essi sono virtuali. Quindi, per testare il loro funzionamento usando un esempio, dobbiamo fare un po' di preparazione.
Dovremmo ereditare le capacità della classe CList usando la classe discendente CIntList. Quest'ultimo avrà solo 1 metodo per creare un nuovo elemento CIntList::CreateElement().
//+------------------------------------------------------------------+ //| CIntList class | //+------------------------------------------------------------------+ class CIntList : public CList { public: virtual CObject *CreateElement(void); }; //+------------------------------------------------------------------+ //| New element of the list | //+------------------------------------------------------------------+ CObject *CIntList::CreateElement(void) { CObject *new_node=new CNodeInt(); return new_node; }
Sarà inoltre necessario aggiungere i metodi virtuali CNodeInt::Save() e CNodeInt::Load() al tipo di nodo derivato CNodeInt. Verranno chiamati dalle funzioni membro CList::Save() e CList::Load(), rispettivamente.
Di conseguenza, l'esempio sarà il seguente (Esempio 5):
//--- Example 5 (saving list data) //--- the CIntList type list CList *p_int_List=new CIntList; int randArr[1000]; // array of random numbers ArrayInitialize(randArr,0); //--- fill the array of random numbers for(int t=0;t<1000;t++) randArr[t]=(int)myRand.int32(); //--- fill the list for(uint q=0;q<1000;q++) { CNodeInt *p_node_int=new CNodeInt(randArr[q]); p_int_List.Add(p_node_int); } //--- save the list to the file int file_ha=FileOpen("List_data.bin",FILE_WRITE|FILE_BIN); p_int_List.Save(file_ha); FileClose(file_ha); p_int_List.FreeMode(true); p_int_List.Clear(); //--- load the list from the file file_ha=FileOpen("List_data.bin",FILE_READ|FILE_BIN); p_int_List.Load(file_ha); int Loaded_List_size=p_int_List.Total(); PrintFormat("Nodes loaded from the file: %d",Loaded_List_size); //--- free the memory delete p_int_List;
Dopo aver eseguito lo script sul grafico, la seguente voce verrà aggiunta al registro:
ND 0 11:59:35 test_MQL5_List (EURUSD,H1) As many as 1000 nodes loaded from the file.
Pertanto, abbiamo visto l'implementazione di metodi di input/output per un membro dati del tipo di nodo CNodeInt.
Nella prossima sezione, vedremo esempi di come gli elenchi possono essere utilizzati per risolvere i problemi quando si lavora con MQL5.
4. Esempi di utilizzo di liste in MQL5
Nella sezione precedente, parlando dei metodi della classe CList della libreria standard, ho fornito alcuni esempi.
Ora prenderò in considerazione i casi in cui la lista viene utilizzata per risolvere un problema specifico. E qui non posso non sottolineare ancora una volta il vantaggio della lista come tipo di dati contenitore. Possiamo rendere più efficiente il lavoro con il codice, sfruttando la flessibilità di una lista.
4.1 Gestione di oggetti grafici
Immagina di dover creare a livello di codice oggetti grafici nel grafico. Questi possono essere oggetti diversi che possono apparire nel grafico per vari motivi.
Ricordo come una volta l'elenco mi abbia aiutato a risolvere un problema con oggetti grafici. E vorrei condividere con voi questa mia esperienza.
Avevo il compito di creare linee verticali in base alla condizione specificata. A seconda della condizione, le linee verticali fungevano da limiti per un dato intervallo di tempo che variava in lunghezza caso per caso. Detto questo, l'intervallo non era sempre completamente formato.
Dovevo raccogliere statistiche per lo studio del comportamento di EMA21.
Ero particolarmente interessato alla lunghezza della pendenza della media mobile. Ad esempio, in un movimento verso il basso il punto di partenza è stato individuato registrando il movimento negativo della media mobile (cioè diminuzione di valore) per cui è stata tracciata una linea verticale. La Fig. 17 mostra tale punto identificato per EURUSD, H1 il 5 settembre 2013 alle 16:00, all'apertura di una candela.
Fig. 17 Primo punto dell'intervallo discendente
Il secondo punto che suggerisce la fine del movimento al ribasso è stato individuato in base al principio inverso, registrando un movimento positivo della media mobile, cioè un aumento di valore (Fig. 18).
Fig. 18 Secondo punto dell'intervallo discendente
Pertanto, l'intervallo di destinazione era dal 5 settembre 2013 alle 16:00 al 6 settembre 2013 alle 17:00.
Il sistema di identificazione dei diversi intervalli può essere più complesso o più semplice. Questo non è il punto. Ciò che è importante è il fatto che questa tecnica per lavorare con oggetti grafici e raccogliere contemporaneamente dati statistici comporta uno dei maggiori vantaggi della lista: la flessibilità della composizione.
Per quanto riguarda l'esempio attuale, ho prima creato un nodo di tipo CVertLineNode che è responsabile di 2 oggetti grafici "Linea verticale".
La definizione della classe è la seguente:
//+------------------------------------------------------------------+ //| CVertLineNode class | //+------------------------------------------------------------------+ class CVertLineNode : public CObject { private: SVertLineProperties m_vert_lines[2]; // array of structures of vertical line properties uint m_duration; // frame duration bool m_IsFrameFormed; // flag of frame formation public: void CVertLineNode(void); void ~CVertLineNode(void){}; //--- set-methods void SetLine(const SVertLineProperties &_vert_line,bool IsFirst=true); void SetDuration(const uint _duration){this.m_duration=_duration;}; void SetFrameFlag(const bool _frame_flag){this.m_IsFrameFormed=_frame_flag;}; //--- get-methods void GetLine(SVertLineProperties &_vert_line_out,bool IsFirst=true) const; uint GetDuration(void) const; bool GetFrameFlag(void) const; //--- draw the line bool DrawLine(bool IsFirst=true) const; };
Fondamentalmente, questa classe di nodi descrive un frame (qui interpretato come un numero di candele confinate all'interno di due linee verticali). I limiti del frame sono rappresentati da una coppia di strutture di proprietà della linea verticale, durata e flag di formazione.
Oltre al costruttore e al distruttore standard, la classe ha diversi metodi set e get, nonché il metodo per disegnare la linea nel grafico.
Vi ricordo che il nodo delle linee verticali (cornice) nel mio esempio si può considerare formato quando c'è una prima linea verticale che indica l'inizio del movimento discendente e la seconda linea verticale che indica l'inizio del movimento ascendente.
Utilizzando lo script Stat_collector.mq5, ho visualizzato tutti i frame nel grafico e ho contato quanti nodi (frame) corrispondono a un certo limite di durata nelle ultime 2mila barre.
A titolo illustrativo, ho creato 4 elenchi che potrebbero contenere qualsiasi frame. Il primo elenco conteneva frame con il numero di candele fino a 5, il secondo - fino a 10, il terzo - fino a 15 e il quarto - numero illimitato.
NS 0 15:27:32 Stat_collector (EURUSD,H1) =======List #1======= RF 0 15:27:32 Stat_collector (EURUSD,H1) Duration limit: 5 ML 0 15:27:32 Stat_collector (EURUSD,H1) Nodes number: 65 HK 0 15:27:32 Stat_collector (EURUSD,H1) OO 0 15:27:32 Stat_collector (EURUSD,H1) =======List #2======= RI 0 15:27:32 Stat_collector (EURUSD,H1) Duration limit: 10 NP 0 15:27:32 Stat_collector (EURUSD,H1) Nodes number: 15 RG 0 15:27:32 Stat_collector (EURUSD,H1) FH 0 15:27:32 Stat_collector (EURUSD,H1) =======List #3======= GN 0 15:27:32 Stat_collector (EURUSD,H1) Duration limit: 15 FG 0 15:27:32 Stat_collector (EURUSD,H1) Nodes number: 6 FR 0 15:27:32 Stat_collector (EURUSD,H1) CD 0 15:27:32 Stat_collector (EURUSD,H1) =======List #4======= PS 0 15:27:32 Stat_collector (EURUSD,H1) Nodes number: 20
Di conseguenza, ho ottenuto il seguente grafico (Fig. 19). Per comodità, la seconda linea di cornice verticale viene visualizzata in blu.
Fig. 19 Visualizzazione delle cornici (frames)
Curiosamente, l'ultimo frame si è formato durante l'ultima ora di venerdì 13 dicembre 2013. Rientrava nella seconda lista poiché era della durata di 6 ore.
4.2 Gestire il trading virtuale
Immagina di dover creare un Expert Advisor che implementi diverse strategie indipendenti rispetto a uno strumento in un flusso di tick. È chiaro che in realtà può essere attuata una sola strategia alla volta rispetto ad uno strumento. Tutte le altre strategie saranno di natura virtuale. Quindi, può essere implementato esclusivamente allo scopo di testare e ottimizzare un'idea di trading.
Qui, devo fare riferimento ad un articolo fondamentale che fornisce una descrizione dettagliata dei concetti base relativi al trading in generale e, in particolare, al terminale MetaTrader 5: Ordini, posizioni e deals(affari) in MetaTrader 5.
Quindi, se nel risolvere questo problema usiamo il concetto di trading, il sistema di gestione degli oggetti di trading e la metodologia di memorizzazione delle informazioni sugli oggetti di trading che sono consueti nell'ambiente MetaTrader 5, dovremmo probabilmente pensare di creare un database virtuale.
Ti ricordo che uno sviluppatore classifica tutti gli oggetti di trading in ordini, posizioni, accordi e ordini storici. Un occhio critico potrebbe notare che il termine 'oggetto di scambio' è stato qui utilizzato dall'autore stesso. Questo è vero...
Propongo di utilizzare un approccio simile nel trading virtuale e di ottenere i seguenti oggetti di trading virtuale: ordini virtuali, posizioni virtuali, offerte virtuali e ordini storici virtuali.
Questo argomento merita una trattazione approfondita e più dettagliata. Nel frattempo, tornando all'argomento dell'articolo, vorrei dire che i tipi di dati del contenitore, incluse le liste, possono semplificare la vita del programmatore durante l'implementazione di strategie virtuali.
Pensa a una nuova posizione virtuale che, naturalmente, non può essere dal lato del server di trading. Ciò significa che le informazioni a riguardo dovrebbero essere salvate sul lato terminale. Qui un database può essere rappresentato da una lista che a sua volta è composta da più liste, una delle quali conterrà i nodi della posizione virtuale.
Utilizzando l'approccio dello sviluppatore, ci saranno le seguenti classi di trading virtuale:
Classe/Gruppo | Descrizione |
COrdine Virtuale | Classe per lavorare con ordini in sospeso virtuali |
CVirtualHistoryOrder | Classe per lavorare con ordini di "storia" virtuale |
CVirtualPosition | Classe per lavorare con posizioni aperte virtuali |
CVirtualDeal | Classe per lavorare con le offerte di "storia" virtuale |
CVirtualTrade | Classe per eseguire operazioni di trading virtuale |
Tabella 1. Classi di trading virtuale
Non discuterò la composizione delle classi di trading virtuali. Ma probabilmente conterrà tutti o quasi tutti i metodi di una classe di trading standard. Voglio solo far notare che ciò che utilizza lo sviluppatore non è una classe di un determinato oggetto di trading, ma una classe delle sue proprietà.
Per utilizzare le liste nei tuoi algoritmi, avrai bisogno anche di nodi. Pertanto, abbiamo bisogno di concludere la classe di un oggetto di trading virtuale in un nodo.
Si supponga che il nodo di una posizione aperta virtuale sia di tipo CVirtualPositionNode. La definizione di questo tipo potrebbe inizialmente essere la seguente:
//+------------------------------------------------------------------+ //| Class CVirtualPositionNode | //+------------------------------------------------------------------+ class CVirtualPositionNode : public CObject { protected: CVirtualPositionNode *m_virt_position; // pointer to the virtual function public: void CVirtualPositionNode(void); // default constructor void ~CVirtualPositionNode(void); // destructor };
Ora, quando la posizione virtuale si apre, può essere aggiunta all'elenco delle posizioni virtuali.
Vorrei anche far notare che questo approccio al lavoro con oggetti di trading virtuale non richiede l'uso della memoria cache perché il database è archiviato nella memoria ad accesso casuale. Puoi, ovviamente, fare in modo che venga archiviato su altri supporti di memorizzazione.
Conclusione
In questo articolo ho cercato di dimostrare i vantaggi di un tipo di dati contenitore, le liste. Ma non potevo fare a meno di menzionare i suoi svantaggi. Ad ogni modo, spero che queste informazioni siano utili per chi studia l'OOP in generale, e in particolare, uno dei suoi principi fondamentali, il polimorfismo.
Posizione dei file:
A mio parere, sarebbe meglio creare e archiviare file nella cartella del progetto. Ad esempio, in questo modo: %MQL5\Projects\UserLists. Qui è dove ho salvato tutti i file del codice sorgente. Se si utilizzano directory predefinite, sarà necessario modificare il metodo di designazione dei file di inclusione (sostituire le virgolette con le parentesi angolari) nel codice di alcuni file.
# | File | La tua posizione | Descrizione |
---|---|---|---|
1 | CiSingleNode.mqh | %MQL5\Projects\UserLists | Classe di un nodo di lista concatenato singolarmente |
2 | CDoubleNode.mqh | %MQL5\Projects\UserLists | Classe di un nodo di lista doppiamente concatenato |
3 | CiUnrollDoubleNode.mqh | %MQL5\Projects\UserLists | Classe di un nodo di lista doppiamente concatenato srotolato |
4 | test_nodes.mq5 | %MQL5\Projects\UserLists | Script con esempi di lavoro con i nodi |
5 | CiSingleList.mqh | %MQL5\Projects\UserLists | Classe di una lista concatenata singolarmente |
6 | CDoubleList.mqh | %MQL5\Projects\UserLists | Classe di una lista doppiamente collegata |
7 | CiUnrollDoubleList.mqh | %MQL5\Projects\UserLists | Classe di una lista doppiamente collegata srotolata |
8 | CiCircleDoublList.mqh | %MQL5\Projects\UserLists | Classe di una lista circolare doppiamente concatenata |
9 | test_sList.mq5 | %MQL5\Projects\UserLists | Script con esempi di lavoro con un elenco collegato singolarmente |
10 | test_dList.mq5 | %MQL5\Projects\UserLists | Script con esempi di lavoro con una lista doppiamente collegata |
11 | test_UdList.mq5 | %MQL5\Projects\UserLists | Script con esempi di lavoro con un elenco srotolato a doppio collegamento |
12 | test_CdList.mq5 | %MQL5\Projects\UserLists | Script con esempi di lavoro con una lista circolare doppiamente collegata |
13 | test_MQL5_List.mq5 | %MQL5\Projects\UserLists | Script con esempi di lavoro con la classe CList |
14 | CNodeInt.mqh | %MQL5\Projects\UserLists | Classe del nodo di tipo intero |
15 | CIntList.mqh | %MQL5\Projects\UserLists | Elenca la classe per i nodi CNodeInt |
16 | CRandom.mqh | %MQL5\Projects\UserLists | Classe di un generatore di numeri casuali |
17 | CVertLineNode.mqh | %MQL5\Projects\UserLists | Classe nodo per la gestione del frame di linee verticali |
18 | Stat_collector.mq5 | %MQL5\Projects\UserLists | Script con un esempio di raccolta di statistiche |
Riferimenti:
- A. Friedman, L. Klander, M. Michaelis, H. Schildt. Archivi con annotazioni C/C++. McGraw-Hill Osborne Media, 1999. 1008 pagine.
- V.D. Daleka, A.S. Derevyanko, O.G. Kravets, L.E. Timanovskaya. Modelli e strutture di dati. Guida allo studio. Kharkov, KhGPU, 2000. 241 pagine (in russo).
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/709





- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso