Fundamentos básicos da Programação MQL5: Lista
Introdução
A nova versão da linguagem MQL proporcionou aos desenvolvedores de sistemas automatizados de negociação ferramentas eficazes para a implementação de tarefas complexas. Não se pode negar o fato de que as funcionalidades de programação da linguagem foram ampliadas consideravelmente. As características de POO em MQL5 por si só já valem muito. Além disso, não se deve deixar de mencionar a Biblioteca Padrão. A julgar pelo código de erro de número 359, os templates de classe serão suportados em breve.
Neste artigo, eu gostaria de conduzir algo que pode de alguma forma ser uma expansão ou continuação dos temas que descrevem os tipos de dados e seus conjuntos. Aqui, eu gostaria de fazer referência a um artigo publicado no site da Comunidade MQL5. Uma descrição muito detalhada dos princípios e da lógica de como trabalhar com arrays foi fornecido por Dmitry Fedoseev (Integer) em seu artigo "Fundamentos básicos da programação MQL5: Arrays".
Assim, hoje me proponho em recorrer as listas, e, mais precisamente, nas listas lineares encadeadas ou ligadas. Vamos começar com a estrutura da lista, seu significado e sua lógica. Depois disso, vamos estudar as ferramentas relacionadas que já estão disponíveis na Biblioteca Padrão. Em suma, eu vou fornecer exemplos de como as listas podem ser utilizados quando se trabalha com MQL5.
- Conceitos de Lista e Nó: Teoria
- 1.1 Nó em uma lista encadeada simples
- 1.2 Nó em uma lista duplamente encadeada
- 1.3 Nó em uma lista circular duplamente encadeada
- 1.4 Principais Operações de uma lista
- Conceitos de Lista e Nó: Programação
- 2.1 Nó em uma lista encadeada simples
- 2.2 Nó em uma lista duplamente encadeada
- 2.3 Nó em uma lista vetorial duplamente encadeada
- 2.4 Lista encadeada simples
- 2.5 Lista duplamente ligada
- 2.6 Lista vetorial duplamente encadeada
- 2.7 Lista circular duplamente encadeada
- Listas na biblioteca padrão MQL5
- Exemplos de utilização das listas em MQL5
1. Conceitos de Lista e Nó: Teoria
Então, o que é uma lista para um desenvolvedor e como procedemos sobre isso? Eu vou fazer referência à uma fonte pública de informação, a Wikipédia, por uma definição genérica deste termo:
Em ciência da computação, uma lista é um tipo de dados abstrato que implementa uma coleção finita e ordenada de valores, onde o mesmo valor pode ocorrer mais de uma vez. Uma instância de uma lista é uma representação computacional do conceito matemático de uma sequência finita - uma tupla. Cada valor de uma instância na lista é geralmente chamado de um item, de entrada, ou elemento da lista; se o mesmo valor ocorre várias vezes, cada ocorrência é considerada um produto distinto.
O termo 'lista' também é utilizado por várias estruturas de dados concretas que pode ser utilizado para implementar listas abstratas, especialmente as listas encadeadas.
Eu acredito que você irá concordar comigo que esta definição é de certa maneira acadêmica demais.
Para os fins deste artigo, estamos mais interessados na última frase desta definição. Então, vamos debruçar sobre ele:
Em ciência da computação, uma lista encadeada é uma estrutura de dados básica e dinâmica consistido de nós, onde cada nó é composto de dados e uma ou duas referências ('ligadas ou encadeadas') para o nó seguinte e / ou anterior da lista.[1] O principal benefício de uma lista encadeada sobre um array convencional é sua flexibilidade estrutural: a seqüência dos itens da lista encadeada não precisa coincidir com a seqüência de elementos de dados na memória do computador, enquanto que as ligações internas dos itens da lista são sempre mantidos para a lista de travessia.
Vamos tentar olhar para ela passo a passo.
Em ciência da computação, uma lista em si, é algum tipo de dados. Nós estabelecemos isso. Ela é preferivelmente um tipo de dados sintético, uma vez que inclui outros tipos de dados. Uma lista é um pouco semelhante a um array. Se um array de um tipo de dados nunca foi classificado como um novo tipo de dados, então ele seria uma lista. Mas não por completo.
A principal vantagem de uma lista é que ela permite a inserção ou a remoção dos nós em qualquer ponto na lista, como requerido. Aqui, a lista é semelhante a um array dinâmico, exceto para aquela lista em que você não precisa usar a função ArrayResize() o tempo todo.
Falando em termos de ordem de elementos de memória, os nós da lista não está armazenado e não precisam ser armazenados da mesma forma que os elementos do array são armazenados em áreas de memória adjacentes.
É mais ou menos isso. Indo mais para baixo na lista.
1.1 Nó em uma lista encadeada simples
As listas permitem que você armazene os nós, em vez de itens. Nó é um tipo de dados que consiste de duas partes.
A primeira parte é um campo de dados, e a segunda parte é usada para ligar (encadear) com outro nó(s) (Fig. 1). O primeiro nó da lista é chamado de 'cabeça', e o último nó da lista é chamado de "cauda". O campo de ligação da cauda contém uma referência NULL. Ela é usada basicamente para indicar a ausência de outros nós na lista. Outras fontes especializados se referem ao resto da lista, após a cabeça, como "cauda".
Fig. 1 Nós em uma lista encadeada simples
Além dos nós da lista encadeada simples, há outros tipos de nós. Um nó em uma lista duplamente encadeada é, talvez, a mais comum.
1.2 Nó em uma lista duplamente encadeada
Precisaremos também de um nó que irá atender às necessidades de uma lista duplamente encadeada. Ela é diferente do tipo da lista anterior uma vez que ela contém uma outra ligação que aponta para o nó anterior. E, naturalmente, o nó da cabeça da lista conterá uma referência NULL. No diagrama que mostra a estrutura da lista que contém tais nós (Fig. 2), as ligações que apontam para os nós anteriores são apresentadas como flechas vermelhas.
Fig. 2 Nós em uma lista duplamente encadeada
Assim, as capacidades de um nó de uma lista duplamente encadeada serão semelhantes às de um nó de uma lista encadeada simples Você só vai precisar lidar com mais uma ligação, que será para o nó anterior.
1.3 Nó em uma lista circular duplamente encadeada
Há casos em que os nós acima também podem ser utilizados em listas não-lineares. E, apesar do artigo descrever principalmente as listas lineares, vou dar um exemplo de uma lista circular também.
Fig. 3 Nós em um lista circular duplamente encadeada
O diagrama de uma lista circular duplamente encadeada (Fig. 3) mostra que os nós com dois campos de ligação são simplesmente um encadeamento circular. Isso é feito usando as flechas laranja e verde. Assim, o nó principal irá ser ligada à cauda (como o elemento anterior). E o campo de ligação do nó da cauda não estará vazio, uma vez que ele irá apontar para a cabeça.
1.4 Principais Operações de uma lista
Conforme estabelecido na literatura especializada, todas as operações da lista podem ser divididas em três grupos base:
- Inserção (de um novo nó na lista);
- Remoção (de um nó da lista);
- Verificação (dos dados de um nó).
Os métodos de inserção incluem:
- inserir um novo nó no início da lista;
- inserir um novo nó no fim da lista;
- inserir um nó em uma posição específica da lista;
- inserir um nó em uma lista vazia;
- parametrização do construtor.
Quanto às operações de remoção, elas se assemelham praticamente com as operações correspondentes do grupo de inserção:
- remover o nó principal;
- remover o nó da cauda;
- remover um nó a partir de uma posição específica da lista;
- destrutor.
Aqui, eu gostaria de tomar nota que destrutor serve não só para completar e encerrar a operação de lista corretamente, mas também para eliminar corretamente todos os seus elementos.
O terceiro grupo, que são as operações de verificação, fornece acesso ao nós ou aos valores do nó da lista:
- busca de um determinado valor;
- verifica se a lista esta vazia;
- obtém o valor do n-ésimo nó da lista;
- obtém o ponteiro para o n-ésimo nó da lista;
- obtém o tamanho da lista;
- imprimi os valores dos elementos da lista.
Além dos grupos de base, eu gostaria também de separar o quarto, o grupo de serviço. Ele presta serviços de manutenção aos grupos anteriores:
- operador de atribuição;
- construtor de cópia;
- trabalhar com ponteiro dinâmico;
- copiar a lista por valores;
- ordenação.
Bem, isso é tudo. O desenvolvedor pode, claro, expandir a funcionalidade e as características de uma classe lista a qualquer momento, conforme for sua necessidade.
2. Conceitos de Lista e Nó: Programação
Nesta parte, sugiro que devemos prosseguir diretamente para a programação dos nós e listas. Será fornecido Ilustrações para o código, se necessário.
2.1 Nó em uma lista encadeada simples
Vamos criar uma base para a classe nó (Fig. 4) e atender às necessidades de uma lista encadeada simples. Você pode se familiarizar com a notação de diagramas de classe (modelo) no artigo intitulado "Como Desenvolver um Expert Advisor usando ferramentas de UML" (Ver fig. 5. Modelo UML da classe CTradeExpert).
Fig. 4 Modelo da classe CiSingleNode
Vamos agora tentar trabalhar com o código. Ele baseia-se no exemplo apresentado no livro de Art Friedman e outros autores. "C/C++ Annotated Archives".
//+------------------------------------------------------------------+ //| Classe CiSingleNode | //+------------------------------------------------------------------+ class CiSingleNode { protected: int m_val; // dados CiSingleNode *m_next; // aponta para o próximo nó public: void CiSingleNode(void); // construtor padrão void CiSingleNode(int _node_val); // construtor parametrizado void ~CiSingleNode(void); // destrutor void SetVal(int _node_val); // método que define os dados void SetNextNode(CiSingleNode *_ptr_next); // método que define o próximo nó virtual void SetPrevNode(CiSingleNode *_ptr_prev){}; // método que define o nó anterior virtual CiSingleNode *GetPrevNode(void) const {return NULL;}; // método que obtém o nó anterior CiSingleNode *GetNextNode(void) const; // método que obtém o próximo nó int GetVal(void){TRACE_CALL(_t_flag) return m_val;} // método que obtém os dados };
Eu não vou explicar cada método da classe CiSingleNode. Você será capaz de dar uma olhada neles no arquivo anexado CiSingleNode.mqh. No entanto, eu gostaria de chamar sua atenção para uma nuance interessante. A classe contém métodos virtuais que trabalham com os nós anteriores. Eles são, na verdade, manequins e sua presença serve para fins de polimorfismo para futuros descendentes.
O código usa a diretiva de pré-processamento TRACE_CALL(f) necessária para rastrear as chamadas de cada método utilizado.
#define TRACE_CALL(f) if(f) Print("Calling: "+__FUNCSIG__);
Com apenas a classe CiSingleNode disponível, você já consegue criar uma lista encadeada simples. Deixe-me dar um código de exemplo.
/ / =========== Exemplo 1 (processamento do tipo CiSingleNode) CiSingleNode *p_sNodes[3]; // #1 p_sNodes[0]=NULL; srand(GetTickCount()); // Inicializa um gerador de números aleatórios //--- cria os nós for(int i=0;i<ArraySize(p_sNodes);i++) p_sNodes[i]=new CiSingleNode(rand()); // #2 //--- ligações for(int j=0;j<(ArraySize(p_sNodes)-1);j++) p_sNodes[j].SetNextNode(p_sNodes[j+1]); // #3 //--- verifica os valores 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)); } //--- verifica os próximos nós 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)); } //--- remove os nós for(int i=0;i<ArraySize(p_sNodes);i++) delete p_sNodes[i]; // #12
No comentário #1, declaramos um array de ponteiros para objetos do tipo CiSingleNode. No comentário #2, o array é preenchido com os ponteiros criados. Para os dados de cada nó, nós obtemos um inteiro pseudo randômico na faixa de 0 a 32767 usando a função rand(). Os nós são ligados ao próximo ponteiro no comentário #3. Nos cometários #4 e #5, verificamos os valores dos nós, e nos cometários de #9 a #11 vamos verificar o desempenho das ligações. Os ponteiros são removidos no cometário #12.
Isto é o que foi impresso no log.
DH 0 23:23:10 test_nodes (EURUSD,H4) Nó #1 valor = 3335 KP 0 23:23:10 test_nodes (EURUSD,H4) Nó #2 valor = 21584 GI 0 23:23:10 test_nodes (EURUSD,H4) Nó #3 valor = 917 HQ 0 23:23:10 test_nodes (EURUSD,H4) Próximo Nó #1 valor = 21584 HI 0 23:23:10 test_nodes (EURUSD,H4) Próximo Nó #2 valor = 917
A estrutura resultante do nó pode ser esquematicamente mostrada como segue na (Fig. 5).
Fig. 5 Ligações entre os nós no array CiSingleNode *p_sNodes[3]
Vamos agora proceder para os nós em uma lista duplamente encadeada.
2.2 Nó em uma lista duplamente encadeada
Primeiro, precisamos retocar ao fato de que um nó em uma lista duplamente encadeada é diferente devido a existência de dois ponteiros: o ponteiro do próximo nó e o ponteiro do nó anterior. Ou seja, além da ligação ao nó seguinte, você precisa adicionar um ponteiro para o nó anterior ao nó da lista encadeada simples.
Ao fazer isso, proponho usar a herança de classes como uma relação de classe. Em seguida, o modelo de classe para um nó em uma lista duplamente encadeada deve parecer como se segue (Fig. 6).
Fig. 6 Modelo da classe CDoubleNode
Agora, é hora de dar uma olhada no código.
//+------------------------------------------------------------------+ //| Classe CDoubleNode | //+------------------------------------------------------------------+ class CDoubleNode : public CiSingleNode { protected: CiSingleNode *m_prev; // aponta para o nó anterior public: void CDoubleNode(void); // construtor padrão void CDoubleNode(int node_val); // construtor parametrizado void ~CDoubleNode(void){TRACE_CALL(_t_flag)};// destrutor virtual void SetPrevNode(CiSingleNode *_ptr_prev); // método para definir o nó anterior virtual CiSingleNode *GetPrevNode(void) const; // método para obter o nó anterior CDoubleNode };
Há poucos métodos adicionais - eles são virtuais e estão relacionados em trabalhar com o nó anterior. A descrição completa da classe é fornecida em CDoubleNode.mqh.
Vamos tentar criar uma lista duplamente encadeada com base na classe CDoubleNode. Deixe-me dar um código de exemplo.
//=========== Exemplo 2 (processamento do tipo CDoubleNode) CiSingleNode *p_dNodes[3]; // #1 p_dNodes[0]=NULL; srand(GetTickCount()); // Inicializa um gerador de números aleatórios //--- Criar nós for(int i=0;i<ArraySize(p_dNodes);i++) p_dNodes[i]=new CDoubleNode(rand()); // #2 //--- Ligações 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 } //--- verifica os valores 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)); } //--- verifica os próximos nós 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)); } //--- verifica os nós anteriores 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)); } //--- remove os nós for(int i=0;i<ArraySize(p_dNodes);i++) delete p_dNodes[i]; // #15
A princípio, isto é semelhante à criação de uma lista encadeada simples, mas ainda há algumas peculiaridades. Observe como o array de ponteiros p_dNodes[] é declarado no comentário #1. O tipo de ponteiros pode ser definido de forma idêntica a classe base. O princípio do polimorfismo no comentário #2 irá ajudar-nos a reconhecê-los no futuro. Os Nós anteriores são verificadas nos comentários de #12 a #14.
As informações a seguir foram impressas no log.
GJ 0 16:28:12 test_nodes (EURUSD,H4) Nó #1 valor = 17543 IQ 0 16:28:12 test_nodes (EURUSD,H4) Nó #2 valor = 1185 KK 0 16:28:12 test_nodes (EURUSD,H4) Nó #3 valor = 23216 DS 0 16:28:12 test_nodes (EURUSD,H4) Próximo Nó #1 valor = 1185 NH 0 16:28:12 test_nodes (EURUSD,H4) Próximo Nó #2 valor = 23216 FR 0 16:28:12 test_nodes (EURUSD,H4) Nó anterior #2 valor = 17543 LI 0 16:28:12 test_nodes (EURUSD,H4) Nó anterior #3 valor = 1185
A estrutura resultante do nó pode ser esquematicamente mostrada como segue na (Figura 7):
Fig. 7 Ligações entre os nós no array CDoubleNode *p_sNodes[3]
Agora eu sugiro que nós consideremos um nó que será necessário na criação de uma lista vetorial duplamente encadeada.
2.3 Nó em uma lista vetorial duplamente encadeada
Pense em um nó que contém um membro de dados que, em vez de um valor único, é atribuível a todo array, isto é, ele contém e descreve todo o array. Tal nó pode ser utilizado para criar uma lista vetorial. Eu decidi não fornecer qualquer ilustração aqui já que este nó é exatamente o mesmo que um nó padrão em uma lista duplamente encadeada. A única diferença é que o atributo de dados encapsula todo o array.
Vou usar a herança de classe novamente. A classe CDoubleNode servirá como a classe base para um nó em uma lista vetorial duplamente encadeada. E o modelo de classe para um nó em uma lista vetorial duplamente encadeada ficará da seguinte forma (Fig. 8).
Fig. 8 Modelo da classe CiUnrollDoubleNode
A classe CiUnrollDoubleNode pode ser definida usando o seguinte código:
//+------------------------------------------------------------------+ //| Classe CiUnrollDoubleNode | //+------------------------------------------------------------------+ class CiUnrollDoubleNode : public CDoubleNode { private: int m_arr_val[]; // data array public: void CiUnrollDoubleNode(void); // construtor padrão void CiUnrollDoubleNode(int &_node_arr[]); // construtor parametrizado void ~CiUnrollDoubleNode(void); // destrutor bool GetArrVal(int &_dest_arr_val[])const; // método para obter o array de dados bool SetArrVal(const int &_node_arr_val[]); // método para definir o array de dados };
É possível olhar para cada método com mais detalhe em CiUnrollDoubleNode.mqh.
Vamos considerar um construtor parametrizado como exemplo.
//+------------------------------------------------------------------+ //| Construtor parametrizado | //+------------------------------------------------------------------+ void CiUnrollDoubleNode::CiUnrollDoubleNode(int &_node_arr[]) : CDoubleNode(ArraySize(_node_arr)) { ArrayCopy(this.m_arr_val,_node_arr); TRACE_CALL(_t_flag) }
Aqui, usando a lista de inicialização, nós colocamos o tamanho do array unidimensional no membro de dados this.m_val.
Depois disso, criamos "manualmente" uma lista vetorial duplamente encadeada e verificamos as ligações dentro dele.
//=========== Exemplo 3 (processamento do tipo CiUnrollDoubleNode) //--- arrays de dados int arr1[],arr2[],arr3[]; // #1 int arr_size=15; ArrayResize(arr1,arr_size); ArrayResize(arr2,arr_size); ArrayResize(arr3,arr_size); srand(GetTickCount()); // Inicializa um gerador de números aleatórios //--- Preenche os arrays com números inteiros pseudo aleatórios for(int i=0;i<arr_size;i++) { arr1[i]=rand(); // #2 arr2[i]=rand(); arr3[i]=rand(); } //--- criar nós CiUnrollDoubleNode *p_udNodes[3]; // #3 p_udNodes[0]=new CiUnrollDoubleNode(arr1); p_udNodes[1]=new CiUnrollDoubleNode(arr2); p_udNodes[2]=new CiUnrollDoubleNode(arr3); //--- ligações 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 } //--- verifica os valores 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)); } //--- verifica os valores dos arrays 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); } } //--- verifica o próximos nós 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); } } //--- verifica os nós anteriores 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); } } //--- remove os nós for(int i=0;i<ArraySize(p_udNodes);i++) delete p_udNodes[i]; // #12 }
A quantidade de código tornou-se um pouco maior. Isso tem a ver com o fato de que precisamos criar e preencher um array para cada nó.
O trabalho com arrays de dados começa no comentário #1. Ele é semelhante ao que tivemos nos nós considerados anteriormente. É exatamente isso que precisamos para imprimir os valores dos dados de cada nó para toda o array (por exemplo, comentário #9).
Isto foi o que eu recebi:
IN 0 00:09:13 test_nodes (EURUSD.m,H4) Nó #1 valor = 15 NF 0 00:09:13 test_nodes (EURUSD.m,H4) Nó #2 valor = 15 CI 0 00:09:13 test_nodes (EURUSD.m,H4) Nó #3 valor = 15 FQ 0 00:09:13 test_nodes (EURUSD.m,H4) Nó #1 valores do array = 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) Nó #2 valores do array = 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) Nó #3 valores do array = 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) Próximo Nó #1 valores do array = 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) Próximo Nó #2 valores do array = 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) Nó anterior #2 valores do array = 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) Nó anterior #3 valores do array = 1809, 18553, 23224, 20208, 10191, 4833, 25959, 2761, 7291, 23254, 29865, 23938, 7585, 20880, 25756
Eu sugiro que devemos traçar uma linha sob trabalhar com nós e proceder diretamente para as definições de classe de listas diferentes. Os exemplos 1-3 podem ser encontrados no script test_nodes.mq5.
2.4 Lista encadeada simples
Já está na hora de criarmos um modelo de classe de uma lista encadeada simples fora dos principais grupos de operação da lista (Fig. 9).
Fig. 9 Modelo da classe CiSingleList
É fácil de ver que a classe CiSingleList usa o nó do tipo CiSingleNode. Falando nos tipos de relações entre as classes, podemos dizer que:
- a classe CiSingleList contém a classe CiSingleNode (composição);
- a classe CiSingleList usa os métodos da classe CiSingleNode (dependência).
A ilustração das relações acima é fornecido na figura. 10.
Fig. 10 Tipos de relações entre a classe CiSingleList e a classe CiSingleNode
Vamos criar uma nova classe - CiSingleList. Olhando em frente, todas as outras classes de lista usadas neste artigo, serão baseadas nesta classe. É por isso que ela é tão "rica".
//+------------------------------------------------------------------+ //| Classe CiSingleList | //+------------------------------------------------------------------+ class CiSingleList { protected: CiSingleNode *m_head; // cabeça CiSingleNode *m_tail; // cauda uint m_size; // número de nós na lista public: //--- construtor e destrutor void CiSingleList(); // construtor padrão void CiSingleList(int _node_val); // construtor parametrizado void ~CiSingleList(); // destrutor //--- inserir nós void AddFront(int _node_val); // insere um novo nó no inicio da lista void AddRear(int _node_val); // insere um novo nó no fim da lista virtual void AddFront(int &_node_arr[]){TRACE_CALL(_t_flag)}; // insere um novo nó no inicio da lista virtual void AddRear(int &_node_arr[]){TRACE_CALL(_t_flag)}; // insere um novo nó no fim da lista //--- remove nós int RemoveFront(void); // remove o nó cabeça int RemoveRear(void); // remove o nó do fim da lista void DeleteNodeByIndex(const uint _idx); // remove o n-ésimo nó da lista //--- verificação virtual bool Find(const int _node_val) const; // busca o valor solicitado bool IsEmpty(void) const; // verifica se a lista esta vazia virtual int GetValByIndex(const uint _idx) const; // valor do n-ésimo nó da lista virtual CiSingleNode *GetNodeByIndex(const uint _idx) const; // obtém o n-ésimo nó da lista virtual bool SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insere o n-ésimo novo nó na lista CiSingleNode *GetHeadNode(void) const; // obtém o nó cabeça CiSingleNode *GetTailNode(void) const; // obtém o nó cauda virtual uint Size(void) const; // list size //--- serviço virtual void PrintList(string _caption=NULL); // imprime a lista virtual bool CopyByValue(const CiSingleList &_sList); // copia a lista pelos valores virtual void BubbleSort(void); // Ordenação BubbleSort //---templates template<typename dPointer> bool CheckDynamicPointer(dPointer &_p); // template para verificar o ponteiro dinâmico template<typename dPointer> bool DeleteDynamicPointer(dPointer &_p); // template para remover o ponteiro dinâmico protected: void operator=(const CiSingleList &_sList) const; // operador de atribuição void CiSingleList(const CiSingleList &_sList); // copia o construtor virtual bool AddToEmpty(int _node_val); // insere um novo nó a uma lista vazia virtual void addFront(int _node_val); // insere um novo nó "nativo" no começo da lista virtual void addRear(int _node_val); // insere um novo nó "nativo" no fim da lista virtual int removeFront(void); // remove o nó cabeça "nativo" virtual int removeRear(void); // remove o nó "nativo" do fim da lista virtual void deleteNodeByIndex(const uint _idx); // remove o n-ésimo nó "nativo" da lista virtual CiSingleNode *newNode(int _val); // novo nó "nativo" virtual void CalcSize(void) const; // calcula o tamanho da lista };
A definição completa dos métodos da classe é fornecida em CiSingleList.mqh.
Quando eu comecei a desenvolver esta classe, havia apenas 3 membros de dados e apenas alguns métodos. Mas uma vez que esta classe serviu de base para outras classes, eu tive que adicionar várias funções virtuais. Eu não vou descrever estes métodos em detalhes. Um exemplo do uso dessa classe de lista encadeada simples pode ser encontrado no script test_sList.mq5.
Se ela é executada sem o flag de rastreamento, as seguintes entradas aparecerão no log:
KG 0 12:58:32 test_sList (EURUSD,H1) =======Lista #1======= PF 0 12:58:32 test_sList (EURUSD,H1) Nó #1, val=14 RL 0 12:58:32 test_sList (EURUSD,H1) Nó #2, val=666 MD 0 12:58:32 test_sList (EURUSD,H1) Nó #3, val=13 DM 0 12:58:32 test_sList (EURUSD,H1) Nó #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) =======Lista #2======= RE 0 12:58:32 test_sList (EURUSD,H1) Nó #1, val=14 DQ 0 12:58:32 test_sList (EURUSD,H1) Nó #2, val=666 GK 0 12:58:32 test_sList (EURUSD,H1) Nó #3, val=13 FP 0 12:58:32 test_sList (EURUSD,H1) Nó #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) =======Lista renovada #2======= GK 0 12:58:32 test_sList (EURUSD,H1) Nó #1, val=11 JP 0 12:58:32 test_sList (EURUSD,H1) Nó #2, val=13 JI 0 12:58:32 test_sList (EURUSD,H1) Nó #3, val=14 CF 0 12:58:32 test_sList (EURUSD,H1) Nó #4, val=34 QL 0 12:58:32 test_sList (EURUSD,H1) Nó #5, val=35 OE 0 12:58:32 test_sList (EURUSD,H1) Nó #6, val=36 MR 0 12:58:32 test_sList (EURUSD,H1) Nó #7, val=37 KK 0 12:58:32 test_sList (EURUSD,H1) Nó #8, val=38 MS 0 12:58:32 test_sList (EURUSD,H1) Nó #9, val=666 OF 0 12:58:32 test_sList (EURUSD,H1) QK 0 12:58:32 test_sList (EURUSD,H1)
O script preencheu 2 listas encadeadas simples e depois aumentou e ordenou na segunda lista.
2,5 Lista duplamente encadeada
Vamos tentar criar agora uma lista duplamente encadeada com base na lista do tipo anterior. A ilustração do modelo da classe de uma lista duplamente encadeada é fornecido na figura. 11:
Fig. 11 Modelo da classe CDoubleList
A classe descendente contém menos métodos, enquanto que os membros de dados estão completamente ausentes. Abaixo está a definição da classe CDoubleList.
//+------------------------------------------------------------------+ //| Classe CDoubleList | //+------------------------------------------------------------------+ class CDoubleList : public CiSingleList { public: void CDoubleList(void); // construtor padrão void CDoubleList(int _node_val); // construtor parametrizado void ~CDoubleList(void){}; // destrutor virtual bool SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insere o n-ésimo novo nó na lista protected: virtual bool AddToEmpty(int _node_val); // insere um nó a uma lista vazia virtual void addFront(int _node_val); // insere um novo nó "nativo" no começo da lista virtual void addRear(int _node_val); // insere um novo nó "nativo" no fim da lista virtual int removeFront(void); // remove o nó cabeça "nativo" virtual int removeRear(void); // remove o nó cauda "nativo" virtual void deleteNodeByIndex(const uint _idx); // remove o n-ésimo nó "nativo" da lista virtual CiSingleNode *newNode(int _node_val); // novo nó "nativo" };
A descrição completa dos métodos de classe CDoubleList é fornecido em CDoubleList.mqh.
De um modo geral, as funções virtuais são usados aqui apenas para servir as necessidades do apontador para o nó anterior, que não existe nas listas encadeadas simples.
Um exemplo do uso da lista do tipo CDoubleList pode ser encontrado no script test_dList.mq5. Ele demonstra todas as operações da lista comuns que são pertinentes para este tipo de lista. O código do script contém uma seqüência peculiar:
CiSingleNode *_new_node=new CDoubleNode(666); // Cria um novo nó do tipo CDoubleNode
Não há nenhum erro porque essa construção é bastante aceitável nos casos em que o ponteiro da classe base descreve um objeto da classe descendente. Esta é uma das vantagens da herança de classe.
Em MQL5, bem como em С++, o ponteiro para a classe base pode apontar para o objeto da subclasse derivada dessa classe base. Mas o inverso não é válido.
Se você escrever a linha a seguir:
CDoubleNode*_new_node=new CiSingleNode(666);
o compilador não irá relatar um erro ou um aviso, mas o programa será executado até que ele atinja esta linha. Neste caso, você verá uma mensagem sobre o lance errado dos tipos de referência pelos ponteiros. Uma vez que o mecanismo de ligação tardia só vem em ação quando o programa está sendo executado, é preciso considerar cuidadosamente a hierarquia das relações entre as classes.
Depois de executar o script, o registro conterá as seguintes entradas:
DN 0 13:10:57 test_dList (EURUSD,H1) =======Lista #1======= GO 0 13:10:57 test_dList (EURUSD,H1) Nó #1, val=14 IE 0 13:10:57 test_dList (EURUSD,H1) Nó #2, val=666 FM 0 13:10:57 test_dList (EURUSD,H1) Nó #3, val=13 KD 0 13:10:57 test_dList (EURUSD,H1) Nó #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) =======Lista #2======= IL 0 13:10:57 test_dList (EURUSD,H1) Nó #1, val=14 KH 0 13:10:57 test_dList (EURUSD,H1) Nó #2, val=666 PR 0 13:10:57 test_dList (EURUSD,H1) Nó #3, val=13 MI 0 13:10:57 test_dList (EURUSD,H1) Nó #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) =======Lista renovada #2======= PR 0 13:10:57 test_dList (EURUSD,H1) Nó #1, val=11 QI 0 13:10:57 test_dList (EURUSD,H1) Nó #2, val=13 QP 0 13:10:57 test_dList (EURUSD,H1) Nó #3, val=14 LO 0 13:10:57 test_dList (EURUSD,H1) Nó #4, val=34 JE 0 13:10:57 test_dList (EURUSD,H1) Nó #5, val=35 HL 0 13:10:57 test_dList (EURUSD,H1) Nó #6, val=36 FK 0 13:10:57 test_dList (EURUSD,H1) Nó #7, val=37 DR 0 13:10:57 test_dList (EURUSD,H1) Nó #8, val=38 FJ 0 13:10:57 test_dList (EURUSD,H1) Nó #9, val=666 HO 0 13:10:57 test_dList (EURUSD,H1) JR 0 13:10:57 test_dList (EURUSD,H1)
Como no caso da lista encadeada simples, o script tem preenchido a primeira lista (duplamente ligada), o copiou e depois passou para a segunda lista. Em seguida, ele aumentou o número de nós na segunda lista, ordenou e imprimiu a lista.
2.6 Lista vetorial duplamente encadeada
Esse tipo de lista é conveniente na medida em que lhe permite armazenar não apenas um valor, mas um array inteiro.
Vamos criar uma base para a lista do tipo CiUnrollDoubleList (fig. 12).
Fig. 12 Modelo da classe CiUnrollDoubleList
Já que aqui nós estamos indo lidar com um array de dados, nós teremos que redefinir os métodos definidos na classe base indireta CiSingleList.
Abaixo está a definição da classe CiUnrollDoubleList.
//+------------------------------------------------------------------+ //| Classe CiUnrollDoubleList | //+------------------------------------------------------------------+ class CiUnrollDoubleList : public CDoubleList { public: void CiUnrollDoubleList(void); // construtor padrão void CiUnrollDoubleList(int &_node_arr[]); // construtor parametrizado void ~CiUnrollDoubleList(void){TRACE_CALL(_t_flag)}; // destrutor //--- virtual void AddFront(int &_node_arr[]); // insere um novo nó no começo da lista virtual void AddRear(int &_node_arr[]); // insere um novo nó no fim da lista virtual bool CopyByValue(const CiSingleList &_udList); // copia pelos valores virtual void PrintList(string _caption=NULL); // imprime a lista virtual void BubbleSort(void); // Ordenação do tipo BubbleSort protected: virtual bool AddToEmpty(int &_node_arr[]); // insere um nó a uma lista vazia virtual void addFront(int &_node_arr[]); // insere um novo nó "nativo" para o início da lista virtual void addRear(int &_node_arr[]); // insere um novo nó "nativo" para o fim da lista virtual int removeFront(void); // remove o nó "nativo" do início da lista virtual int removeRear(void); // remove o nó "nativo" do fim da lista virtual void deleteNodeByIndex(const uint _idx); // remove o n-ésimo nó "nativo" da lista virtual CiSingleNode *newNode(int &_node_arr[]); // novo nó "nativo" };
A definição completa dos métodos da classe é fornecida em CiUnrollDoubleList.mqh.
Vamos executar o script test_UdList.mq5 para verificar o funcionamento dos métodos da classe. Aqui, as operações com o nó são semelhantes as utilizadas nos scripts anteriores. Talvez devêssemos dizer algumas palavras sobre a ordenação e os métodos de impressão. O método de ordenação classifica os nós pelo número de elementos que contém no array do nó, assim, o array de menor tamanho estará na cabeça da lista.
O método de impressão imprime uma série de valores do array contidos em um determinado nó.
Após a execução do script, o registro terá as seguintes entradas:
II 0 13:22:23 test_UdList (EURUSD,H1) =======Lista #1======= FN 0 13:22:23 test_UdList (EURUSD,H1) Lista Nó #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) Lista Nó #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) =======Lista #2 antes da ordenação======= JO 0 13:22:23 test_UdList (EURUSD,H1) List Nó #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) Lista Nó #2, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 CF 0 13:22:23 test_UdList (EURUSD,H1) Lista Nó #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) =======Lista #2 após a ordenação======= FN 0 13:22:23 test_UdList (EURUSD,H1) Lista Nó #1, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 CJ 0 13:22:23 test_UdList (EURUSD,H1) Lista Nó #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) Lista Nó #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)
Como você pode ver, depois de ser ordenada, a lista udList2 foi impressa a partir do nó com o menor array para o nó que contém o maior array.
2.7 Lista circular duplamente encadeada
Embora as listas não-lineares não são considerados neste artigo, sugiro que nós trabalhemos com elas também. Como você pode ligar os nós de forma circular já foi mostrado acima (Fig. 3).
Vamos criar um modelo da classe CiCircleDoubleList (Fig. 13). Esta classe será uma classe descendente da classe CDoubleList.
Fig. 13 Modelo da classe CiCircleDoubleList
Devido ao fato de que os nós nesta lista são de caráter específico (a cabeça e a cauda são ligadas), quase todos os métodos da base de origem da classe CiSingleList terá de ser feito virtualmente.
//+------------------------------------------------------------------+ //| Classe CiCircleDoubleList | //+------------------------------------------------------------------+ class CiCircleDoubleList : public CDoubleList { public: void CiCircleDoubleList(void); // construtor padrão void CiCircleDoubleList(int _node_val); // construtor parametrizado void ~CiCircleDoubleList(void){TRACE_CALL(_t_flag)}; // destrutor //--- virtual uint Size(void) const; // tamanho da lista virtual bool SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // inserir o n-ésimo novo nó na lista virtual int GetValByIndex(const uint _idx) const; // valor do n-ésimo nó da lista virtual CiSingleNode *GetNodeByIndex(const uint _idx) const; // obter o n-ésimo nó da lista virtual bool Find(const int _node_val) const; // buscar o valor solicitado virtual bool CopyByValue(const CiSingleList &_sList); // copiar a lista pelos valores protected: virtual void addFront(int _node_val); // inserir um novo nó "nativo" para o início da lista virtual void addRear(int _node_val); // inserir um novo nó "nativo" para o fim da lista virtual int removeFront(void); // remover o nó cabeça "nativo" virtual int removeRear(void); // remover o nó cauda "nativo" virtual void deleteNodeByIndex(const uint _idx); // remover o n-ésimo nó "nativo" da lista protected: void CalcSize(void) const; // calcula o tamanho da lista void LinkHeadTail(void); // liga a cabeça a cauda };
A descrição completa da classe é fornecida em CiCircleDoubleList.mqh.
Vamos considerar alguns métodos da classe. O método CiCircleDoubleList::LinkHeadTail() liga o nó da cauda para o nó cabeça. Ele deve ser chamado quando não há nem uma nova cauda ou cabeça e a ligação anterior foi perdida.
//+------------------------------------------------------------------+ //| Ligando cabeça à cauda | //+------------------------------------------------------------------+ void CiCircleDoubleList::LinkHeadTail(void) { TRACE_CALL(_t_flag) this.m_head.SetPrevNode(this.m_tail); // liga cabeça à cauda this.m_tail.SetNextNode(this.m_head); // liga cauda à cabeça }
Pense que este método seria como se estivéssemos lidando com uma lista circular encadeada simples.
Considere, por exemplo, o método CiCircleDoubleList::addFront().
//+------------------------------------------------------------------+ //| Novo nó "nativo" para o início da lista | //+------------------------------------------------------------------+ void CiCircleDoubleList::addFront(int _node_val) { TRACE_CALL(_t_flag) CDoubleList::addFront(_node_val); // Chama um método semelhante da classe base this.LinkHeadTail(); // liga cabeça à cauda }
Você pode ver no corpo do método que um método semelhante da classe base CDoubleList é chamado. Neste ponto, podemos concluir a operação do método (o método como tal, basicamente, não é necessário aqui), se não fosse por uma coisa. A ligação entre a cabeça e a cauda está perdida e que a lista não pode ser circularmente ligada sem ele. É por isso que nós temos que chamar o método de ligar a cabeça e a cauda.
Podemos trabalhar com a lista circular duplamente encadeada no script test_UdList.mq5.
Em termos de funções e objetivos, os outros métodos utilizados são os mesmos que nos exemplos anteriores.
Como resultado, o log contém as seguintes entradas:
PR 0 13:34:29 test_CdList (EURUSD,H1) =======Lista #1======= QS 0 13:34:29 test_CdList (EURUSD,H1) Nó #1, val=14 QI 0 13:34:29 test_CdList (EURUSD,H1) Nó #2, val=666 LQ 0 13:34:29 test_CdList (EURUSD,H1) Nó #3, val=13 OH 0 13:34:29 test_CdList (EURUSD,H1) Nó #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) =======Lista #2 antes da ordenação======= MS 0 13:34:29 test_CdList (EURUSD,H1) Nó #1, val=38 IJ 0 13:34:29 test_CdList (EURUSD,H1) Nó #2, val=37 IQ 0 13:34:29 test_CdList (EURUSD,H1) Nó #3, val=36 EH 0 13:34:29 test_CdList (EURUSD,H1) Nó #4, val=35 EO 0 13:34:29 test_CdList (EURUSD,H1) Nó #5, val=34 FF 0 13:34:29 test_CdList (EURUSD,H1) Nó #6, val=14 DN 0 13:34:29 test_CdList (EURUSD,H1) Nó #7, val=666 GD 0 13:34:29 test_CdList (EURUSD,H1) Nó #8, val=13 JK 0 13:34:29 test_CdList (EURUSD,H1) Nó #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) =======Lista #2 depois da ordenação======= LE 0 13:34:29 test_CdList (EURUSD,H1) Nó #1, val=11 KL 0 13:34:29 test_CdList (EURUSD,H1) Nó #2, val=13 QS 0 13:34:29 test_CdList (EURUSD,H1) Nó #3, val=14 NJ 0 13:34:29 test_CdList (EURUSD,H1) Nó #4, val=34 NQ 0 13:34:29 test_CdList (EURUSD,H1) Nó #5, val=35 NH 0 13:34:29 test_CdList (EURUSD,H1) Nó #6, val=36 NO 0 13:34:29 test_CdList (EURUSD,H1) Nó #7, val=37 NF 0 13:34:29 test_CdList (EURUSD,H1) Nó #8, val=38 JN 0 13:34:29 test_CdList (EURUSD,H1) Nó #9, val=666 RJ 0 13:34:29 test_CdList (EURUSD,H1) RE 0 13:34:29 test_CdList (EURUSD,H1)
Assim, o diagrama final de herança entre as classes das listas introduzidas se encontra a seguir (fig. 14).
Eu não tenho certeza se todas as classes precisam ser relacionados por herança, mas eu decidi deixar tudo como está.
Fig. 14 Herança entre as classes da lista
Completando esta seção do artigo, que foram mostrados as listas personalizadas, eu gostaria de salientar que praticamente não toquei no grupo das listas não-lineares, multi listas, etc. Como eu estou acumulando informações relevantes e ganhando experiência trabalhando com este tipos de estruturas dinâmicas, vou tentar escrever um outro artigo.
3. Listas na biblioteca padrão MQL5
Vamos dar uma olhada na classe lista disponível na biblioteca padrão (Fig. 15).
Ele pertence as Classes de dados.
Fig. 15 Modelo da classe CList
Curiosamente, a CList é um descendente da classe CObject. Ou seja, a lista herda dados e métodos da classe, que é um nó.
A classe de lista contém um impressionante conjunto de métodos. Para ser honesto, eu não esperava encontrar uma grande classe dessa na biblioteca padrão.
A classe CList tem 8 membros de dados. Eu gostaria de destacar algumas coisas. Os atributos da classe contêm o índice do nó atual (intm_curr_idx) e o ponteiro para o nó atual (CObject* m_curr_node). Pode-se dizer que a lista é "inteligente" - pode indicar o local onde o controle está localizado. Além disso, ela possui um mecanismo de gerenciamento de memória (que pode eliminar fisicamente um nó ou simplesmente excluí-lo da lista), flag de lista ordenada e modo de ordenação.
Falando de métodos, todos os métodos da classe CList são divididos nos seguintes grupos:
- Atributos;
- Criar métodos;
- Adicionar métodos;
- Remover métodos;
- Navegação;
- Métodos de ordenação;
- Métodos de comparação;
- Métodos de busca;
- Entrada / Saída.
Como de costume, há um construtor padrão e destrutor.
O primeiro esvazia (NULL) todos os ponteiros. O estado do flag de gerenciamento de memória está definido para remover. A nova lista não será ordenada.
Em seu corpo, o destrutor só chama o método Clear() para esvaziar a lista de nós. O fim da existência da lista não implica necessariamente a "morte" de seus elementos (nós). Assim, o flag de gerenciamento de memória define quando excluir os elementos da lista transforma a relação da classe de composição para agregação.
Podemos lidar com este flag usando os métodos set- , get- e FreeMode().
Existem dois métodos na classe que lhe permitem aumentar a lista: Add() e Insert(). O primeiro é semelhante ao método AddRear() utilizado na primeira parte do artigo. O segundo método é semelhante ao método SetNodeByIndex().
Vamos começar com um pequeno exemplo. Precisamos primeiro criar uma classe de nó CNodeInt, um descendente da classe de interface CObject. Ele irá armazenar o valor do tipo inteiro.
//+------------------------------------------------------------------+ //| Classe CNodeInt | //+------------------------------------------------------------------+ class CNodeInt : public CObject { private: int m_val; // dados do nó public: void CNodeInt(void){this.m_val=WRONG_VALUE;}; // construtor padrão void CNodeInt(int _val); // construtor parametrizado void ~CNodeInt(void){}; // destrutor int GetVal(void){return this.m_val;}; // método para obter os dados do nó void SetVal(int _val){this.m_val=_val;}; // método para definir os dados do nó }; //+------------------------------------------------------------------+ //| Construtor parametrizado| //+------------------------------------------------------------------+ void CNodeInt::CNodeInt(int _val):m_val(_val) { };
Vamos trabalhar com a lista CList no script test_MQL5_List.mq5.
Exemplo 1 demonstra uma criação dinâmica da lista e dos nós. A lista é então preenchida com nós e o valor do primeiro nó é verificado antes e depois de eliminar a lista.
//--- Exemplo 1 (testando o gerenciamento de memória) CList *myList=new CList; // myList.FreeMode(false); // redefine o flag bool _free_mode=myList.FreeMode(); PrintFormat("\nList \"myList\" - flag de gerenciamento de memória: %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("Lista \"myList\" tem como muitos nós como: %d",myList.Total()); Print("=======Antes de Remover \"myList\"======="); PrintFormat("O 1º valor do nó é: %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("=======Após Remover \"myList\"======="); PrintFormat("O 1º valor do nó é: %d",val_to_check);
Se a seqüência de redefinir o flag permanece comentada (inativo), teremos as seguintes entradas no log:
GS 0 14:00:16 test_MQL5_List (EURUSD,H1) EO 0 14:00:16 test_MQL5_List (EURUSD,H1) Lista "myList" - flag de gerenciamento de memória: 1 FR 0 14:00:16 test_MQL5_List (EURUSD,H1) Lista "myList" tem como muitos nós como: 10 JH 0 14:00:16 test_MQL5_List (EURUSD,H1) =======Antes de remover "myList"======= DO 0 14:00:16 test_MQL5_List (EURUSD,H1) O 1º valor do nó é: 7189 KJ 0 14:00:16 test_MQL5_List (EURUSD,H1) =======Após remover "myList"======= QK 0 14:00:16 test_MQL5_List (EURUSD,H1) O 1º valor do nó é: -1
Por favor, note que após a exclusão dinâmica da lista myList, todos os nós que também estavam lá foram apagados da memória.
No entanto, se descomentamos a linha do flag de redefinição:
// myList.FreeMode(false); // redefine o flag
a saída para o log será a seguinte:
NS 0 14:02:11 test_MQL5_List (EURUSD,H1) CN 0 14:02:11 test_MQL5_List (EURUSD,H1) Lista "myList" - flag de gerenciamento de memória: 0 CS 0 14:02:11 test_MQL5_List (EURUSD,H1) Lista "myList" tem como muitos nós como: 10 KH 0 14:02:11 test_MQL5_List (EURUSD,H1) =======Antes de remover "myList"======= NL 0 14:02:11 test_MQL5_List (EURUSD,H1) O 1º valor do nó é: 20411 HJ 0 14:02:11 test_MQL5_List (EURUSD,H1) =======Após remover "myList"======= LI 0 14:02:11 test_MQL5_List (EURUSD,H1) o 1º valor do nó é: 20411 QQ 1 14:02:11 test_MQL5_List (EURUSD,H1) 10 objetos que faltam remover DD 1 14:02:11 test_MQL5_List (EURUSD,H1) 10 objetos que faltam do tipo CNodeInt DL 1 14:02:11 test_MQL5_List (EURUSD,H1) 400 bytes de memória vazia
É fácil perceber que o nó principal conserva o seu valor tanto antes quanto depois da lista ser excluída. Neste caso, haverá também objetos restantes não removidos se o script não conter o código para excluí-los corretamente.
Vamos agora tentar trabalhar com o método de ordenação.
//--- Exemplo 2 (ordenação) 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\" tem como muitos nós como: %d",myList.Total()); Print("=======Lista \"myList\" antes da ordenação======="); for(int i=0;i<myList.Total();i++) { CNodeInt *p_node_int=myList.GetNodeAtIndex(i); int node_val=p_node_int.GetVal(); PrintFormat("Nó #%d é igual a: %d",i+1,node_val); } myList.Sort(0); Print("\n=======Lista \"myList\" depois da ordenação======="); for(int i=0;i<myList.Total();i++) { CNodeInt *p_node_int=myList.GetNodeAtIndex(i); int node_val=p_node_int.GetVal(); PrintFormat("Nó #%d é igual a: %d",i+1,node_val); } delete myList;
Como resultado, o log contém as seguintes entradas:
OR 0 22:47:01 test_MQL5_List (EURUSD,H1) FN 0 22:47:01 test_MQL5_List (EURUSD,H1) Lista "myList" tem como muitos nós como: 10 FH 0 22:47:01 test_MQL5_List (EURUSD,H1) =======Lista "myList" antes da ordenação======= LG 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #1 é igual a: 30511 CO 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #2 é igual a: 17404 GF 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #3 é igual a: 12215 KQ 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #4 é igual a: 31574 NJ 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #5 é igual a: 7285 HP 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #6 é igual a: 23509 IH 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #7 é igual a: 26991 NS 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #8 é igual a: 414 MK 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #9 é igual a: 18824 DR 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #10 é igual a: 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) Nó #1 é igual a: 26991 RE 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #2 é igual a: 23509 ML 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #3 é igual a: 18824 DD 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #4 é igual a: 414 LL 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #5 é igual a: 1560 IG 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #6 é igual a: 17404 PN 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #7 é igual a: 30511 II 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #8 é igual a: 31574 OQ 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #9 é igual a: 12215 JH 0 22:47:01 test_MQL5_List (EURUSD,H1) Nó #10 é igual a: 7285
Mesmo que qualquer ordenação foi feita em tudo, a técnica de ordenação permanece um mistério para mim. Vou explicar o porquê. Sem entrar em muitos detalhes sobre a ordem de chamada, o metodo CList::Sort() chama o método virtual CObject::Compare() que não é implementado na classe de base de qualquer forma. Assim, o programador tem de lidar com a implementação de um método de classificação por conta própria.
E agora, algumas palavras sobre o método Total(). Ele retorna o número de elementos (nós) para o qual o membro de dados m_data_total é responsável. É um método conciso e muito curto. A contagem do elemento nesta implementação será muito mais rápido do que a que eu propus anteriormente. Na verdade, em vez de passar pela lista e fazer a contagem dos nós, o número exato pode ser definido na hora de adicionar ou remover os nós.
O exemplo 3 compara a velocidade de preenchimento das listas do tipo CList e CiSingleList e calcula o tempo para obter o tamanho de cada lista.
//--- Exemplo 3 (número de nós) int iterations=1e7; // 10 milhões de iterações //--- A nova CList CList *p_mql_List=new CList; uint start=GetTickCount(); // Valor inicial 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; // Tempo gasto, ms Print("\n=======A lista do tipo CList======="); PrintFormat("O Prenchendo da lista de %.3e nós levou %d ms",iterations,time); //--- obtém o tamanho start=GetTickCount(); int list_size=p_mql_List.Total(); time=GetTickCount()-start; PrintFormat("Para obter o tamanho da lista levou-se %d ms",time); delete p_mql_List; //--- A nova CiSingleList CiSingleList *p_sList=new CiSingleList; start=GetTickCount(); // valor inicial for(int i=0;i<iterations;i++) p_sList.AddRear(rand()); time=GetTickCount()-start; // tempo gasto, ms Print("\n=======A lista do tipo CiSingleList======="); PrintFormat("O preenchimento da lista de %.3e nós levou %d msec",iterations,time); //--- obter o tamanho start=GetTickCount(); list_size=(int)p_sList.Size(); time=GetTickCount()-start; PrintFormat("Para obter o tamanho da lista levou-se %d ms",time); delete p_sList;
Isto é o que eu tenho no log:
KO 0 22:48:24 test_MQL5_List (EURUSD,H1) CK 0 22:48:24 test_MQL5_List (EURUSD,H1) =======A lista do tipo CList======= JL 0 22:48:24 test_MQL5_List (EURUSD,H1) O preenchimento da lista de 1.000e+007 nós levou 2606 ms RO 0 22:48:24 test_MQL5_List (EURUSD,H1) Para obter o tamanho da lista levou-se 0 ms LF 0 22:48:29 test_MQL5_List (EURUSD,H1) EL 0 22:48:29 test_MQL5_List (EURUSD,H1) =======A lista do tipo CiSingleList======= KK 0 22:48:29 test_MQL5_List (EURUSD,H1) O preenchimento da lista de 1.000e+007 nós levou 2356 ms NF 0 22:48:29 test_MQL5_List (EURUSD,H1) Para obter o tamanho da lista levou-se 359 ms
O método para obter o tamanho funciona instantaneamente na lista CList. Por sinal, a adição de nós para a lista também é bem rápido.
No próximo bloco (Exemplo 4), sugiro que preste atenção a uma das principais desvantagens da lista como um contêiner de dados - a velocidade de acesso aos elementos. O fato é que os elementos da lista são acessados de forma linear. Na classe CList, eles são acessados de forma binária, o que diminui ligeiramente a complexidade do algoritmo.
Ao procurar linearmente, a complexidade é de O(N) iterações. A busca implementada de forma binária resulta em uma complexidade de log2(N) iterações.
Este é um exemplo do código ao acessar os elementos de um conjunto de dados:
//--- Exemplo 4 (velocidade de acesso do nó) 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]; // Número de iterações uint randArr[]; // Array de números aleatórios uint idxArr[]; // Array de índices //--- Define o tamanho do array ArrayResize(randArr,cur_iterations); ArrayResize(idxArr,cur_iterations); CRandom myRand; // Gerador de números aleatórios //--- Preenche o array com números aleatórios for(uint t=0;t<cur_iterations;t++) randArr[t]=myRand.int32(); //--- Preenche os índices do array com números aleatórios (de 0 a 10 milhões) int iter_log10=(int)log10(cur_iterations); for(uint r=0;r<cur_iterations;r++) { uint rand_val=myRand.int32(); // Valor aleatório (de 0 a 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); } //--- Verificar o limite if(rand_val>=cur_iterations) { Alert("Erro no valor aleatório!"); return; } idxArr[r]=rand_val; } //--- Tempo gasto para o array uint start=GetTickCount(); //--- Acessar os elementos do array for(uint p=0;p<cur_iterations;p++) uint random_val=randArr[idxArr[p]]; uint time=GetTickCount()-start; // Tempo gasto, ms Print("\n=======array do tipo uint======="); PrintFormat("Acesso aleatório do array de elementos %.1e levou %d ms",cur_iterations,time); //--- A lista do tipo CList CList *p_mql_List=new CList; //--- preencher a lista 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(); //--- Acessando os nós da lista for(uint w=0;w<cur_iterations;w++) CNodeInt *p_node_int=p_mql_List.GetNodeAtIndex(idxArr[w]); time=GetTickCount()-start; // tempo gasto, ms Print("\n=======A lista do tipo CList======="); PrintFormat("Acesso aleatório da lista %.1e de nós levou %d ms",cur_iterations,time); //--- Libera a memória ArrayFree(randArr); ArrayFree(idxArr); delete p_mql_List; }
Com base nos resultados de operação do bloco, as seguintes entradas foram impressos para o log:
MR 0 22:51:22 test_MQL5_List (EURUSD,H1) QL 0 22:51:22 test_MQL5_List (EURUSD,H1) =======O array do tipo uint======= IG 0 22:51:22 test_MQL5_List (EURUSD,H1) Acesso aleatório do array de elementos 1.0e+003 levou 0 ms QF 0 22:51:22 test_MQL5_List (EURUSD,H1) IQ 0 22:51:22 test_MQL5_List (EURUSD,H1) =======A lista do tipo CList======= JK 0 22:51:22 test_MQL5_List (EURUSD,H1) Acesso aleatório da lista de nós 1.0e+003 levou 0 ms MJ 0 22:51:22 test_MQL5_List (EURUSD,H1) QD 0 22:51:22 test_MQL5_List (EURUSD,H1) =======O array do tipo uint======= GO 0 22:51:22 test_MQL5_List (EURUSD,H1) Acesso aleatório do array de elementos 3.0e+003 levou 0 ms QN 0 22:51:22 test_MQL5_List (EURUSD,H1) II 0 22:51:22 test_MQL5_List (EURUSD,H1) =======A lista do tipo CList======= EP 0 22:51:22 test_MQL5_List (EURUSD,H1) Acesso aleatório da lista de nós 3.0e+003 levou 16 ms OR 0 22:51:22 test_MQL5_List (EURUSD,H1) OL 0 22:51:22 test_MQL5_List (EURUSD,H1) =======O array do tipo uint======= FG 0 22:51:22 test_MQL5_List (EURUSD,H1) Acesso aleatório do array de elementos 6.0e+003 levou 0 ms CF 0 22:51:22 test_MQL5_List (EURUSD,H1) GQ 0 22:51:22 test_MQL5_List (EURUSD,H1) =======A lista do tipo CList======= CH 0 22:51:22 test_MQL5_List (EURUSD,H1) Acesso aleatório da lista de nós 6.0e+003 levou 31 ms QJ 0 22:51:22 test_MQL5_List (EURUSD,H1) MD 0 22:51:22 test_MQL5_List (EURUSD,H1) =======O array do tipo uint======= MO 0 22:51:22 test_MQL5_List (EURUSD,H1) Acesso aleatório do array de elementos 9.0e+003 levou 0 ms EN 0 22:51:22 test_MQL5_List (EURUSD,H1) MJ 0 22:51:22 test_MQL5_List (EURUSD,H1) =======A lista do tipo CList======= CP 0 22:51:22 test_MQL5_List (EURUSD,H1) Acesso aleatório da lista de nós 9.0e+003 levou 47 ms CR 0 22:51:22 test_MQL5_List (EURUSD,H1) KL 0 22:51:22 test_MQL5_List (EURUSD,H1) =======O array do tipo uint======= JG 0 22:51:22 test_MQL5_List (EURUSD,H1) Acesso aleatório do array de elementos 1.0e+004 levou 0 ms GF 0 22:51:22 test_MQL5_List (EURUSD,H1) KR 0 22:51:22 test_MQL5_List (EURUSD,H1) =======A lista do tipo CList======= MK 0 22:51:22 test_MQL5_List (EURUSD,H1) Acesso aleatório da lista de nós 1.0e+004 levou 343 ms GJ 0 22:51:22 test_MQL5_List (EURUSD,H1) GG 0 22:51:22 test_MQL5_List (EURUSD,H1) =======O array do tipo uint======= LO 0 22:51:22 test_MQL5_List (EURUSD,H1) Acesso aleatório do array de elementos 3.0e+004 levou 0 ms QO 0 22:51:24 test_MQL5_List (EURUSD,H1) MJ 0 22:51:24 test_MQL5_List (EURUSD,H1) =======A lista do tipo CList======= NP 0 22:51:24 test_MQL5_List (EURUSD,H1) Acesso aleatório da lista de nós 3.0e+004 levou 1217 ms OS 0 22:51:24 test_MQL5_List (EURUSD,H1) KO 0 22:51:24 test_MQL5_List (EURUSD,H1) =======O array do tipo uint======= CP 0 22:51:24 test_MQL5_List (EURUSD,H1) Acesso aleatório do array de elementos 6.0e+004 levou 0 ms MG 0 22:51:26 test_MQL5_List (EURUSD,H1) ER 0 22:51:26 test_MQL5_List (EURUSD,H1) =======A lista do tipo CList======= PG 0 22:51:26 test_MQL5_List (EURUSD,H1) Acesso aleatório da lista de nós 6.0e+004 levou 2387 ms GK 0 22:51:26 test_MQL5_List (EURUSD,H1) OG 0 22:51:26 test_MQL5_List (EURUSD,H1) =======O array do tipo uint======= NH 0 22:51:26 test_MQL5_List (EURUSD,H1) Acesso aleatório do array de elementos 9.0e+004 levou 0 ms JO 0 22:51:30 test_MQL5_List (EURUSD,H1) NK 0 22:51:30 test_MQL5_List (EURUSD,H1) =======A lista do tipo CList======= KO 0 22:51:30 test_MQL5_List (EURUSD,H1) Acesso aleatório da lista de nós 9.0e+004 levou 3619 ms HS 0 22:51:30 test_MQL5_List (EURUSD,H1) DN 0 22:51:30 test_MQL5_List (EURUSD,H1) =======O array do tipo uint======= RP 0 22:51:30 test_MQL5_List (EURUSD,H1) Acesso aleatório do array de elementos 1.0e+005 levou 0 ms OD 0 22:52:05 test_MQL5_List (EURUSD,H1) GS 0 22:52:05 test_MQL5_List (EURUSD,H1) =======A lista do tipo CList======= DE 0 22:52:05 test_MQL5_List (EURUSD,H1) Acesso aleatório da lista de nós 1.0e+005 levou 35631 ms NH 0 22:52:06 test_MQL5_List (EURUSD,H1) RF 0 22:52:06 test_MQL5_List (EURUSD,H1) =======O array do tipo uint======= FI 0 22:52:06 test_MQL5_List (EURUSD,H1) Acesso aleatório do array de elementos 3.0e+005 levou 0 ms HL 0 22:54:20 test_MQL5_List (EURUSD,H1) PD 0 22:54:20 test_MQL5_List (EURUSD,H1) =======A lista do tipo CList======= FN 0 22:54:20 test_MQL5_List (EURUSD,H1) Acesso aleatório da lista de nós 3.0e+005 levou 134379 ms RQ 0 22:54:20 test_MQL5_List (EURUSD,H1) JI 0 22:54:20 test_MQL5_List (EURUSD,H1) =======O array do tipo uint======= MR 0 22:54:20 test_MQL5_List (EURUSD,H1) Acesso aleatório do array de elementos 6.0e+005 levou 15 ms NE 0 22:58:48 test_MQL5_List (EURUSD,H1) FL 0 22:58:48 test_MQL5_List (EURUSD,H1) =======A lista do tipo CList======= GE 0 22:58:48 test_MQL5_List (EURUSD,H1) Acesso aleatório da lista de nós 6.0e+005 levou 267589 ms
Você pode ver que o acesso aleatório a lista de elementos leva mais tempo quando o tamanho da lista fica maior (Fig. 16).
Figo. 16 Tempo gasto para o acesso aleatório ao array e para a lista de elementos
Vamos agora considerar métodos para salvar e carregar os dados.
A classe base da lista CList contém tais métodos, mas eles são virtuais. Assim, a fim de testar as suas operação através de um exemplo, nós precisamos fazer algumas preparações.
Devemos herdar as capacidades da classe CList usando a classe descendente CIntList. Este último terá apenas um método para criar um novo elemento CIntList::CreateElement().
//+------------------------------------------------------------------+ //| Classe CIntList | //+------------------------------------------------------------------+ class CIntList : public CList { public: virtual CObject *CreateElement(void); }; //+-------------------------------------------------------------------+ //| Novo elemento da lista | //+------------------------------------------------------------------+ CObject *CIntList::CreateElement(void) { CObject *new_node=new CNodeInt(); return new_node; }
Também será necessário adicionar os métodos virtuais CNodeInt::Save() e CNodeInt::Load() para o nó derivado do tipo CNodeInt. Eles serão chamados de funções membro CList::Save() e CList::Load(), respectivamente.
Como resultado, o exemplo esta a seguir (Exemplo 5):
//--- Exemplo 5 (salvando os dados na lista) //--- A lista do tipo CIntList CList *p_int_List=new CIntList; int randArr[1000]; // Array de números aleatórios ArrayInitialize(randArr,0); //--- Preencher o array de números aleatórios for(int t=0;t<1000;t++) randArr[t]=(int)myRand.int32(); //--- Preencher a lista for(uint q=0;q<1000;q++) { CNodeInt *p_node_int=new CNodeInt(randArr[q]); p_int_List.Add(p_node_int); } //--- Salvar a lista para o arquivo 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(); //--- Carregar a lista a partir do arquivo 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("Nós carregado a partir do arquivo: %d",Loaded_List_size); //--- Livre da memória delete p_int_List;
Depois de executar o script no gráfico, a seguinte entrada será adicionada ao Log:
ND 0 11:59:35 test_MQL5_List (EURUSD,H1) Nós carregados a partir do arquivo: 1000.
Assim, vimos a implementação de métodos de entrada / saída para um membro de dados do nó do tipo CNodeInt.
Na próxima seção, vamos ver exemplos de como as listas podem ser usadas para resolver os problemas quando se trabalha com MQL5.
4. Exemplos de utilização das listas em MQL5
Na seção anterior, eu dei alguns exemplos considerando os métodos da biblioteca padrão da classe CList.
Agora, eu vou considerar casos em que a lista é utilizada para resolver um problema específico. E aqui não posso deixar de ressaltar mais uma vez o benefício da lista como um recipiente de tipo de dados. Nós podemos trabalhar com o código de forma mais eficiente, aproveitando a flexibilidade de uma lista.
4.1 Manipulação de objetos gráficos
Imagine que nós precisamos criar programaticamente objetos gráficos no gráfico. Estes podem ser diferentes objetos que podem aparecer no gráfico devido a várias razões.
Lembro-me uma vez como que a lista me ajudou a resolver uma situação com objetos gráficos. E eu gostaria de compartilhar essa lembrança com você.
Eu tinha uma tarefa para criar linhas verticais por uma condição especificada. De acordo com a condição, as linhas verticais serviam como limites para um determinado intervalo de tempo que variaram em comprimento de um caso para outro. Dito isso, o intervalo não foi sempre completamente formado.
Eu estava estudando o comportamento de EMA21 e para efeito tive que coletar estatísticas.
Eu estava particularmente interessado no comprimento da inclinação da média móvel. Por exemplo, em um movimento de baixa o ponto de partida foi identificado por registrar um movimento negativo da média móvel (ou seja diminuição de valor), pelo qual uma linha vertical foi desenhada. Fig. 17 mostra tal ponto que foi identificado para o EURUSD, em H1 no dia 5 de setembro de 2013 as 16:00, após a abertura de uma vela.
Fig. 17 Primeiro ponto do intervalo descendente
O segundo ponto que sugere o fim do movimento de queda foi identificado com base no princípio inverso - ao registrar um movimento positivo da média móvel, ou seja, aumento de valor (Fig. 18).
Fig. 18 Segundo ponto do intervalo descendente
Assim, o intervalo alvo foi a partir das 16:00 do dia 05 de setembro de 2013 até 17:00 do dia 6 de setembro de 2013.
O sistema para a identificação dos intervalos diferentes podem ser mais complexo ou mais simples. Este não é o ponto. O que é importante é o fato de que esta técnica para trabalhar com objetos gráficos e ao mesmo tempo coletar dados estatísticos envolve uma das principais vantagens da lista - a flexibilidade de composição.
Quanto ao exemplo atual, primeiro eu criei um nó do tipo CVertLineNode o qual é responsável por 2 objetos gráficos de "linha vertical".
A definição da classe é o seguinte:
//+------------------------------------------------------------------+ //| Classe CVertLineNode | //+------------------------------------------------------------------+ class CVertLineNode : public CObject { private: SVertLineProperties m_vert_lines[2]; // Array de estruturas com propriedades de linha verticais uint m_duration; // duração do frame bool m_IsFrameFormed; // flag formação do frame public: void CVertLineNode(void); void ~CVertLineNode(void){}; //--- métodos set 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;}; //--- métodos get void GetLine(SVertLineProperties &_vert_line_out,bool IsFirst=true) const; uint GetDuration(void) const; bool GetFrameFlag(void) const; //--- desenha a linha bool DrawLine(bool IsFirst=true) const; };
Basicamente, esta classe nó descreve um frame (aqui interpretado como um certo número de velas confinadas dentro de duas linhas verticais). Os limites do frame são representados por um par de estruturas com propriedades de linhas verticais, duração e formação do flag.
Além do construtor padrão e destrutor, a classe tem vários métodos get e set, assim como o método para desenhar a linha no gráfico.
Deixe-me lembrá-lo que o nó de linhas verticais (frame) no meu exemplo pode ser considerado formado quando há uma primeira linha vertical que indica o início do movimento de baixa e a segunda linha vertical que indica o início do movimento de alta.
Usando o script Stat_collector.mq5, Eu exibi todos os quadros no gráfico e contei quantos nós (frames) correspondem a um determinado limite de duração ao longo das últimos 2.000 barras.
Para ilustração, eu criei quatro listas que podem conter qualquer frame. A primeira lista continha frames com o número de até 5 velas, o segundo - até 10, o terceiro - até 15 e o quarto - número ilimitado.
NS 0 15:27:32 Stat_collector (EURUSD,H1) =======Lista #1======= RF 0 15:27:32 Stat_collector (EURUSD,H1) Limite de duração: 5 ML 0 15:27:32 Stat_collector (EURUSD,H1) Número de nós: 65 HK 0 15:27:32 Stat_collector (EURUSD,H1) OO 0 15:27:32 Stat_collector (EURUSD,H1) =======Lista #2======= RI 0 15:27:32 Stat_collector (EURUSD,H1) Limite de duração: 10 NP 0 15:27:32 Stat_collector (EURUSD,H1) Número de nós: 15 RG 0 15:27:32 Stat_collector (EURUSD,H1) FH 0 15:27:32 Stat_collector (EURUSD,H1) =======Lista #3======= GN 0 15:27:32 Stat_collector (EURUSD,H1) Limite de duração: 15 FG 0 15:27:32 Stat_collector (EURUSD,H1) Número de nós: 6 FR 0 15:27:32 Stat_collector (EURUSD,H1) CD 0 15:27:32 Stat_collector (EURUSD,H1) =======Lista #4======= PS 0 15:27:32 Stat_collector (EURUSD,H1) Número de nós: 20
Como resultado, eu tenho o gráfico a seguir (Fig. 19). Para maior comodidade, o frame da segunda linha vertical é exibido em azul.
Fig. 19 Exibindo os frames
Curiosamente, o último frame foi formado durante a última hora de sexta-feira, dia 13 de dezembro de 2013. Ele caiu sob a segunda lista, uma vez que foi de 6 horas de duração.
4.2 Lidar com negociação virtual
Imagine que você precisa criar um Expert Advisor que irá implementar várias estratégias independentes no que diz respeito a um instrumento em um fluxo de ticks. É claro que, na realidade, apenas uma estratégia pode ser aplicada a um tempo no que diz respeito a um instrumento. Todas as outras estratégias serão de natureza virtual. Assim, ele pode ser implementado apenas para fins de teste e otimização de uma idéia de negociação.
Aqui, eu tenho que fazer uma referência a um artigo fundamental que fornece uma descrição detalhada sobre os conceitos básicos relacionados à negociação em geral e, particularmente, para o terminal MetaTrader 5: "Ordens, posições e negócios no MetaTrader 5".
Assim, se na resolução deste problema, usamos o conceito de negociação, o sistema de gerenciamento de objetos de negociação e a metodologia de armazenamento de informações sobre objetos de negociação, que são habituais no ambiente MetaTrader 5, nós devemos provavelmente pensar em criar um banco de dados virtual.
Deixe-me lembrá-lo que um desenvolvedor classifica todos os objetos de troca em ordens, posições, negociações e histórico de ordens. Um olhar crítico pode notar que o termo "objeto de negociação 'foi colocado em uso aqui pelo próprio autor. Isto é verdade ...
Eu proponho usar uma abordagem similar na negociação virtual e obter os seguintes objetos virtuais de negociação: ordens virtuais, posições virtuais, negociações virtuais e histórico de ordens virtuais.
Acredito que este assunto merece uma discussão mais profunda e detalhada. Entretanto, voltando ao assunto do artigo, eu gostaria de dizer que o recipiente dos tipos de dados, incluindo listas, pode tornar a vida do programador mais fácil quando for implementar estratégias virtuais.
Pense em uma nova posição virtual que, naturalmente, não pode ser do lado do servidor de negociação. Isso significa que as informações sobre ele deve ser salvo do lado do terminal. Aqui, uma base de dados pode ser representada por uma lista, que por sua vez é constituída de várias listas, sendo que uma delas contêm os nós da posições virtuais.
Usando a abordagem do desenvolvedor, haverá as seguintes classes de negociação virtual:
Classe / Grupo |
Descrição |
CVirtualOrder |
Classe para trabalhar com ordens pendentes virtuais |
CVirtualHistoryOrder |
Classe para trabalhar com histórico de ordens virtuais |
CVirtualPosition |
Classe para trabalhar com posições abertas virtualmente |
CVirtualDeal |
Classe para trabalhar com histórico virtual de negócios |
CVirtualTrade |
Classe para a realização de operações de negociação virtual |
Tabela 1. Classes de negociação virtuais
Não vou discutir a composição de qualquer uma das classes de negociação virtuais. Mas ela provavelmente irá conter todos ou quase todos os métodos de uma classe de negociação padrão. Eu só quero ressaltar que o que o desenvolvedor usa não é uma classe de um determinado objeto de negociação em si, mas uma classe de suas propriedades.
Para utilizar as listas em seus algoritmos, você também vai precisar de nós. Portanto, precisamos encerrar a classe de um objeto de negócio virtual em um nó.
Suponha que o nó de uma posição virtual aberta é do tipo CVirtualPositionNode. A definição deste tipo pode, inicialmente, ser como se segue:
//+------------------------------------------------------------------+ //| Classe CVirtualPositionNode | //+------------------------------------------------------------------+ class CVirtualPositionNode : public CObject { protected: CVirtualPositionNode *m_virt_position; // ponteiro para a posição virtual public: void CVirtualPositionNode(void); // construtor padrão void ~CVirtualPositionNode(void); // destrutor };
Agora, quando a posição virtual abrir, ela pode ser adicionada à lista de posições virtuais.
Eu também gostaria de notar que esta abordagem para trabalhar com objetos de negociação virtual não requerem o uso de memória cache, porque o banco de dados é armazenado na memória de acesso aleatório. Você pode, é claro, providenciar para que sejam armazenadas em outros meios de armazenamento.
Conclusão
Neste artigo, eu tentei demonstrar as vantagens de um recipiente de tipo de dados, como a lista. Mas eu não podia ir sem mencionar seus inconvenientes. Enfim, espero que esta informação seja útil para aqueles que estudam POO em geral e, particularmente, um dos seus princípios fundamentais, o polimorfismo.
Localização dos arquivos:
Na minha opinião, seria melhor criar e armazenar os arquivos na pasta do projeto. Por exemplo, assim: %MQL5\Projects\UserLists. Aqui é onde eu salvo todos os arquivos de código fonte. Se você usa os diretórios padrão, você vai precisar mudar o método de designação incluir arquivo (substitua as aspas com colchetes) no código de alguns arquivos.
# | Arquivo | Localização | Descrição |
---|---|---|---|
1 | CiSingleNode.mqh | %MQL5\Projects\UserLists | Classe de um nó de lista encadeada simples |
2 | CDoubleNode.mqh | %MQL5\Projects\UserLists | Classe de um nó de lista duplamente encadeada |
3 | CiUnrollDoubleNode.mqh | %MQL5\Projects\UserLists | Classe de um nó de lista vetorial duplamente encadeada |
4 | test_nodes.mq5 | %MQL5\Projects\UserLists | Script com exemplos de como trabalhar com os nós |
5 | CiSingleList.mqh | %MQL5\Projects\UserLists | Classe de uma lista encadeada simples |
6 | CDoubleList.mqh | %MQL5\Projects\UserLists | Classe de uma lista duplamente encadeada |
7 | CiUnrollDoubleList.mqh | %MQL5\Projects\UserLists | Classe de uma lista vetorial duplamente encadeada |
8 | CiCircleDoublList.mqh | %MQL5\Projects\UserLists | Classe de uma lista circular duplamente encadeada |
9 | test_sList.mq5 | %MQL5\Projects\UserLists | Script com exemplos de como trabalhar com uma lista encadeada simples |
10 | test_dList.mq5 | %MQL5\Projects\UserLists | Script com exemplos de como trabalhar com uma lista duplamente encadeada |
11 | test_UdList.mq5 | %MQL5\Projects\UserLists | Script com exemplos de como trabalhar com uma lista vetorial duplamente encadeada |
12 | test_CdList.mq5 | %MQL5\Projects\UserLists | Script com exemplos de como trabalhar com uma lista circular duplamente encadeada |
13 | test_MQL5_List.mq5 | %MQL5\Projects\UserLists | Script com exemplos de como trabalhar com a classe CList |
14 | CNodeInt.mqh | %MQL5\Projects\UserLists | Classe do nó do tipo inteiro |
15 | CIntList.mqh | %MQL5\Projects\UserLists | Classe da lista para os nós CNodeInt |
16 | CRandom.mqh | %MQL5\Projects\UserLists | Classe de um gerador de números aleatórios |
17 | CVertLineNode.mqh | %MQL5\Projects\UserLists | Classe nó para lidar com a estrutura de linhas verticais |
18 | Stat_collector.mq5 | %MQL5\Projects\UserLists | Script com um exemplo de coleta de dados estatísticos |
Referências:
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/709
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso