English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Usando os Ponteiros de Objeto no MQL5

Usando os Ponteiros de Objeto no MQL5

MetaTrader 5Exemplos | 23 dezembro 2013, 16:33
2 215 0
MetaQuotes
MetaQuotes

Introdução

No MQL5, você pode criar a sua própria classe para uso futuro de variáveis do tipo classes em seu código. Como já sabemos do artigo A Ordem de Criação e Destruição de Objetos em MQL5, as estruturas e classes podem ser criadas de duas maneiras - automaticamente e dinamicamente.

Para criar um objeto automaticamente, simplesmente declare uma variável de tipo classe - o sistema irá criá-lo e iniciá-lo automaticamente. Para criar um objeto dinâmico é necessário apontar o operador novo ao ponteiro do objeto de maneira explícita.

Entretanto, qual a diferença entre os objetos criados automaticamente e dinamicamente? Quando se faz necessário o uso do ponteiro para objeto e quando que a criação de objetos automaticamente já é suficiente? Estes tópicos serão os assuntos abordados neste artigo. Primeiramente, vamos discutir algumas possíveis armadilhas quando se trabalha com objetos e considerar maneiras de resolvê-los.

Um Erro Crítico ao Acessar um Ponteiro Inválido

Primeiramente você deve se lembrar que ao usar os ponteiros de objetos,é obrigatório a inicialização do objeto antes de seu uso. Quando você acessar um ponteiro inválido, o programa em MQL5 se encerrará com um erro crítico, assim, o programa é removido. Como exemplo, vamos considerar um Expert Advisor simples, com a classe CHello declarada logo abaixo. O ponteiro para a instância de classe é declarado em nível global.

//+------------------------------------------------------------------+
//|                                             GetCriticalError.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Uma Classe simples                                               |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Iniciando...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- chamando um método para mostrar seu status
   Print(pstatus.GetMessage());
//--- Imprimindo uma mensagem se o Expert Advisor foi inicializado corretamente
   Print(__FUNCTION__," A função OnInit() foi concluída");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Função tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

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

A variável pstatus é o ponteiro do objeto, mas nós "esquecemos" intencionalmente de criar o próprio objeto usando o operador new. Se você tentar executar este Expert Advisor no gráfico EURUSD, você verá o resultado natural - o Expert Advisor será imediatamente descarregado no estágio de execução da função OnInit(). As mensagens que aparecem no Jornal Experts são as seguintes:

14:46:17 Expert GetCriticalError (EURUSD, H1) carregamento bem sucedido
14:46:18 Initializing of GetCriticalError (EURUSD, H1) falhou
14:46:18 Expert GetCriticalError (EURUSD, H1) removido

Este exemplo é muito simples, o erro capturado é simples. Entretanto, se o seu programa MQL5 contém centenas ou até milhares de linhas de códigos, a captura desses erros pode ser bem complicada. É importante, especialmente para os casos em que as condições de situações de emergência no comportamento do programa depende de fatores imprevisíveis - por exemplo, na estrutura de mercado em particular.

Verificação do Ponteiro Antes do seu Uso

Será que é possível evitar o encerramento crítico do programa? Sim, é claro! Basta inserir a verificação do ponteiro do objeto antes do seu uso. Vamos modificar este exemplo adicionando a função PrintStatus:

//+------------------------------------------------------------------+
//| Imprime uma mensagem usando um método de CHello do tipo objeto   |
//+------------------------------------------------------------------+
void PrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," a variável 'objeto' não foi inicializada!");
   else Print(pobject.GetMessage());
  }

Agora esta função chama o método GetMessage(), o ponteiro do objeto do tipo CHello é passado pela função. Primeiramente, ela verifica o ponteiro usando a função CheckPointer(). Vamos adicionar um parâmetro externo e salvar o código do Expert Advisor no arquivo GetCriticalError_OnDemand.mq5.

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

input bool GetStop=false;// para obter um erro crítico
//+------------------------------------------------------------------+
//| Uma Classe simples                                               |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Iniciando...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| Imprime uma mensagem usando um método de CHello do tipo objeto   |
//+------------------------------------------------------------------+
void PrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," a variável 'objeto' não foi inicializada!");
   else Print(pobject.GetMessage());
  }
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- chamando um método para mostrar seu status
   if(GetStop)
      pstatus.GetMessage();
   else
      PrintStatus(pstatus);
//--- Imprimindo uma mensagem se o Expert Advisor foi inicializado corretamente
   Print(__FUNCTION__," A função OnInit() foi concluída");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Função tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

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

Agora podemos executar o Expert Advisor de duas maneiras:

  1. Com um erro crítico (GetStop = true)
  2. Sem um erro, mas com a mensagem sobre o ponteiro inválido (GetStop = false)

Por padrão, a execução do Expert Advisor será bem sucedida e a seguinte mensagem aparecerá no Jornal "Experts":

GetCriticalError_OnDemand (EURUSD, H1) 15:01:57 PrintStatus a variável 'objeto' não foi inicializada!
GetCriticalError_OnDemand (EURUSD, H1) 15:01:57 OnInit function OnInit () is completed

Assim, a verificação do ponteiro antes do seu uso permite evitar os erros críticos.

Sempre verifique a exatidão do ponteiro antes do seu uso na função

Passando Objetos Não Inicializados por Referência

O que acontece se você passar objetos não inicializados como um parâmetro de entrada da função? (o próprio objeto por referência, não o ponteiro do objeto). Os objetos complexos, tais como classes e estruturas são passados por referência com um ampersand (|&). Vamos reescrever alguns códigos do GetCriticalError_OnDemand.mq5. Vamos renomear a função PrintStatus() e reescrever o seu código de maneira diferente.

//+------------------------------------------------------------------+
//| Imprime uma mensagem usando um método de CHello do tipo objeto   |
//+------------------------------------------------------------------+
void UnsafePrintStatus(CHello &object)
  {
   DebugBreak();
   if(CheckPointer(GetPointer(object))==POINTER_INVALID)
      Print(__FUNCTION__," a variável 'objeto' não foi inicializada!");
   else Print(object.GetMessage());
  }

Agora, a diferença é que a própria variável deste tipo é passada por referência como um parâmetro de entrada em vez do ponteiro do objeto do tipo CClassHello. Vamos salvar a versão nova do Expert Advisor como GetCriticalError_Unsafe.mq5.

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

input bool GetStop=false;// para obter um erro crítico
//+------------------------------------------------------------------+
//| Uma Classe simples                                               |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Iniciando...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| Imprime uma mensagem usando um método de CHello do tipo objeto   |
//+------------------------------------------------------------------+
void UnsafePrintStatus(CHello &object)
  {
   DebugBreak();
   if(CheckPointer(GetPointer(object))==POINTER_INVALID)
      Print(__FUNCTION__," a variável 'objeto' não foi inicializada!");
   else Print(object.GetMessage());
  }
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- chamando um método para mostrar seu status
   if(GetStop)
      pstatus.GetMessage();
   else
      UnsafePrintStatus(pstatus);
//--- Imprimindo uma mensagem se o Expert Advisor foi inicializado corretamente
   Print(__FUNCTION__," A função OnInit() foi concluída");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Função tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

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

Pode-se então ver a diferença entre os Expert Advisors GetCriticalError_OnDemand.mq5 e GetCriticalError_Unsafe.mq5 sobre a maneira em que o parâmetro é passado pela função. Para o primeiro caso o ponteiro do objeto é passado para a função e para o segundo caso o próprio objeto é passado por referência. Em ambos os casos, antes do uso do objeto e seu ponteiro, a função verifica se o ponteiro está correto.

Isso significa que os Expert Advisors trabalham da mesma maneira? Não, não! Vamos executar o Expert Advisor com o parâmetro GetStop=false e novamente teremos um erro crítico. O fato é que se um objeto é passado por referência o erro crítico ocorrerá no estágio de chamada da função, devido ao objeto não iniciado ter sido passado como um parâmetro. Você pode verificá-lo, se você executar o script em modo de depuração diretamente do MetaEditor5, usando o botão F5

Para evitar os pontos de interrupção manuais, vamos modificar a função adicionando o ponto de interrupção DebugBreak () dentro da função.

//+------------------------------------------------------------------+
//| Imprime uma mensagem usando um método de CHello do tipo objeto   |
//+------------------------------------------------------------------+
void UnsafePrintStatus(CHello &object)
  {
   DebugBreak();
   if(CheckPointer(GetPointer(object))==POINTER_INVALID)
      Print(__FUNCTION__," a variável 'objeto' não foi inicializada!");
   else Print(object.GetMessage());
  }

No modo de depuração, o Expert Advisor será descarregado antes da chamada da função DebugBreak() . Como escrever um código seguro para o caso se um objeto não iniciado for passado por referência? A resposta é simples - usar a sobrecarga de funções.

Se um ponteiro de um objeto não iniciado for passado por referência como um parâmetro da função, ele levará a um erro crítico e irá encerrar o programa mql5.

Usando a sobrecarga de funções para um código mais seguro

Seria muito inconveniente, se o desenvolvedor, que usa uma biblioteca externa, forçasse executar a verificação dos objetos de entrada para a exatidão. É muito melhor executar todas as verificações necessárias dentro da biblioteca, isto pode ser implementado usando a sobrecarga de funções.

Vamos implementar o método UnsafePrintStatus() usando a sobrecarga de funções e escrever duas versões desta função - a primeira, que usa o ponteiro do objeto passado em vez do próprio objeto, a segunda, que usa a passagem do objeto por referência. Ambas as funções terão o mesmo nome "PrintStatus", mas esta implementação não será mais potencialmente perigosa.

//+--------------------------------------------------------------------------------------+
//| A impressão de modo seguro de uma mensagem usando o ponteiro do objeto de CHello     |
//+--------------------------------------------------------------------------------------+
void SafePrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," a variável 'objeto' não foi inicializada!");
   else Print(pobject.GetMessage());
  }
//+-------------------------------------------------------------------------+
//| Imprimindo uma mensagem usando o objeto CHello, passado por referência  |
//+-------------------------------------------------------------------------+
void SafePrintStatus(CHello &pobject)
  {
   DebugBreak();
   SafePrintStatus(GetPointer(pobject));
  }

Agora para o caso de a função ser chamada passando pelo ponteiro do objeto, a verificação de sua exatidão será executada e o erro crítico não ocorrerá. Se você chamar a sobrecarga de função na passagem de um objeto por referência, primeiramente, conseguiremos o ponteiro do objeto usando a função GetPointer e então chamamos o código mais seguro, que usa a passagem de um objeto pelo ponteiro.

Podemos reescrever uma versão segura da função ainda mais curta, ao invés de

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

vamos escrever a versão compacta:

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

Ambas as versões são iguais. Vamos salvar a segunda versão com o nome GetCriticalError_Safe.mq5.

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

input bool GetStop=false;// para obter um erro crítico
//+--------------------------------------------------------------------+
//| Uma Classe simples                                                 |
//+--------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Iniciando...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+--------------------------------------------------------------------------------------+
//| A impressão de modo seguro de uma mensagem usando o ponteiro do objeto de CHello     |
//+--------------------------------------------------------------------------------------+
void SafePrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," a variável 'objeto' não foi inicializada!");
   else Print(pobject.GetMessage());
  }
//+-------------------------------------------------------------------------+
//| Imprimindo uma mensagem usando o objeto CHello, passado por referência  |
//+-------------------------------------------------------------------------+
void SafePrintStatus(CHello &pobject)
  {
   DebugBreak();
   SafePrintStatus(GetPointer(pobject));
  }
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- chamando um método para mostrar seu status
   if(GetStop)
      pstatus.GetMessage();
   else
      SafePrintStatus(pstatus);
//--- Imprimindo uma mensagem se o Expert Advisor foi inicializado corretamente
   Print(__FUNCTION__," A função OnInit() foi concluída");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Função tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

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

Se você usar duas funções com implementações diferentes (passando por referência e passando pelo ponteiro do objeto), isto lhe permite assegurar um trabalho seguro da sobrecarga de funções.

Finalmente, aprendemos como usar estes objetos, passados pela função como parâmetros, agora é hora de aprender:

Quando Você Precisa de Ponteiros?

Os ponteiros de objeto permitem executar um gerenciamento flexível no processo de criação e destruição de objetos e permite que você crie objetos abstratos mais complexos. Deixando o programa mais flexível.

Lista Encadeada

Em alguns casos uma maneira diferente de organização de dados é necessária, a lista encadeada é uma delas. A classe de uma lista encadeada CList está disponível na Biblioteca Padrão, aqui apresentaremos os nossos próprios exemplos. Uma lista encadeada significa que cada item da lista está conectado com o próximo item (next) e o item anterior (prev), se eles existirem. Para organizar tais ligações, é conveniente usar os ponteiros de objeto dos itens da lista (ListItem).

Vamos criar uma classe que representará um item da lista, como este:

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

A própria lista será organizada em uma classe separada:

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

A classe CList contém um ponteiro do primeiro item da lista m_first , o acesso aos outros itens da lista estará sempre disponível através das funções next() e prev() da classe CListItem(). A classe CList tem duas funções interessantes. A primeira é a função para adicionar um item novo na lista.

//+------------------------------------------------------------------+
//| Adicionando um ítem à lista                                      |
//+------------------------------------------------------------------+
CList::addItem(CListItem *item)
  {
//--- verificação de um ponteiro, ele deve estar correto
   if(CheckPointer(item)==POINTER_INVALID) return;
//--- aumentando o número de itens da lista
   m_counter++;
//--- Verificando se não há elementos na lista
   if(CheckPointer(m_first)!=POINTER_DYNAMIC)
     {
      m_first=item;
     }
   else
     {
      //--- Definindo para primeiro o ponteiro do intem anterior
      m_first.prev(item);
      //--- salvando um ponteiro do primeiro item atual
      CListItem *p=m_first;
      //--- colocando o item de entrada no lugar do primeiro elemento
      m_first=item;
      //--- definindo um ponteiro para o próximo item  no primeiro item na lista
      m_first.next(p);
     }
  }

Para cada novo item, adicionado à lista, ele se torna o primeiro item, o ponteiro do primeiro item anterior é gravado no campo m_next. Então, um item, adicionado primeiro à lista estará no final da lista. O último item adicionado será o primeiro e o seu ponteiro estará gravado na variável m_first. A segunda função interessante é o destruidor ~CList(). Ele é chamado quando o objeto é destruído, ele deve assegurar a destruição correta dos objetos da lista. Ele pode ser feito de maneira muito simples:

//+------------------------------------------------------------------+
//| Destruidor da lista                                              |
//+------------------------------------------------------------------+
CList::~CList(void)
  {
   int ID=m_first.getID();
   if(CheckPointer(m_first)==POINTER_DYNAMIC) delete(m_first);
   Print(__FUNCTION__," o primeiro item com ID =", ID, "é destruído");
  }

Pode-se ver que, se m_first contém o ponteiro correto do item da lista, somente o primeiro item da lista será removido. Todos os outros itens da lista são removidos em avalanche, pois o destruidor de classe CListItem() em funcionamento produz a desinicialização correta do objeto.

//+------------------------------------------------------------------+
//| Destruidor de itens                                              |
//+------------------------------------------------------------------+
CListItem::~CListItem(void)
  {
   if(CheckPointer(m_next)==POINTER_DYNAMIC)
     {
      delete(m_next);
      Print(__FUNCTION__," removendo um item com ID =", m_ID);
     }
   else
      Print(__FUNCTION__," o próximo item não foi definido para o item com o ID =", m_ID);

  }

A exatidão de um ponteiro é verificada dentro do destruidor, se ele for especificado, o objeto com o ponteiro m_next será destruído usando o operador delete(). Antes da destruição, o primeiro item da lista chama o destruidor, ele irá excluir o segundo item, então ele causará a exclusão do terceiro item e assim por diante até o fim da cadeia.

O trabalho da lista é exibido no script SampleList.mq5. A declaração de uma lista (uma variável de CListType) está na função OnStart(). Esta lista será criada e inicializada automaticamente. O preenchimento da lista é executado dentro de uma lista e primeiramente, cada item da lista é criado dinamicamente usando o operador new e então adicionado à lista. 

void OnStart()
  {
//---
   CList list;
   for(int i=0;i<7;i++)
     {
      CListItem *item=new CListItem;
      item.setID(i);
      list.addItem(item);
     }
     Print("Há ",list.size()," itens na lista");
  }

Execute o script e você verá as seguintes mensagens no jornal "Experts".

2010.03.18 11:22:05 SampleList (EURUSD, H1) CList:: ~ CList O primeiro item com ID=6 será destruído
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removendo um item com ID = 6
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removendo um item com ID = 5
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removendo um item com ID = 4
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removendo um item com ID = 3
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removendo um item com ID = 2
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removendo um item com ID = 1
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem O próximo item não foi definido com o itemID=0
2010.03.18 11:22:05 SampleList (EURUSD, H1) Há 7 itens na lista

Se você estudou o código cuidadosamente, não será uma surpresa que o item com ID=0 não tenha o próximo item seguinte. Entretanto, nós consideramos somente uma razão, quando o uso dos ponteiros faz com que a escrita dos programas seja conveniente e legível. Há uma segunda razão e é chamada de

Polimorfismo

É frequentemente necessário implementar a mesma funcionalidade para objetos diferentes, pertencentes ao mesmo tipo. Por exemplo, há objetos simples como linha, triângulo, retângulo e círculo. Apesar de eles parecerem diferentes eles têm uma característica em comum - eles podem ser desenhados. Vamos criar uma classe base CShape e usá-la para a criação de seu descendentes para cada tipo de formas geométricas.


Para fins educacionais, a classe de base e seus descendentes tem uma funcionalidade mínima.

//+------------------------------------------------------------------+
//| A classe base para a forma do objeto                             |
//+------------------------------------------------------------------+
class CShape
  {
private:
   int               m_type;
public:
                     CShape(){m_type=0;}
   void              Draw();
   string            getTypeName(){return("Forma");}
  };
//+------------------------------------------------------------------+
//| A classe  do objeto linha                                        |
//+------------------------------------------------------------------+
class CLine:public CShape
  {
private:
   int               m_type;
public:
                     CLine(){m_type=1;}
   void              Draw();
   string            getTypeName(){return("Linha");}
  };
//+------------------------------------------------------------------+
//| A classe do objeto triângulo                                     |
//+------------------------------------------------------------------+
class CTriangle:public CShape
  {
private:
   int               m_type;
public:
                     CTriangle(){m_type=2;}
   void              Draw();
   string            getTypeName(){return("Triangle");}
  };
//+------------------------------------------------------------------+
//| A classe do objeto retângulo                                     |
//+------------------------------------------------------------------+
class CRectangle:public CShape
  {
private:
   int               m_type;
public:
                     CRectangle(){m_type=3;}
   void              Draw();
   string            getTypeName(){return("Retângulo");}
  };
//+------------------------------------------------------------------+
//| A classe do objeto círculo                                       |
//+------------------------------------------------------------------+
class CCircle:public CShape
  {
private:
   int               m_type;
public:
                     CCircle(){m_type=4;}
   void              Draw();
   string            getTypeName(){return("Círculo");}
  };

A classe principal CShape contém duas funções, que são sobreescritas manualmente em seus descendentes - a Draw() e getTypeName(). A função Draw() se destina a desenhar uma forma e a função getTypeName() retorna uma descrição em texto da forma.

Vamos criar um banco de dados *shapes[], que contenha ponteiros do tipo base CShape e especificar os valores de um ponteiro para as classes diferentes.

//+------------------------------------------------------------------+
//| Função início do Script                                          |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- um array de ponteiros de objetos do tipo CShape
   CShape *shapes[];
//--- redimensionamento de um array 
   ArrayResize(shapes,5);
//--- Preenchendo os ponteiros do array
   shapes[0]=new CShape;
   shapes[1]=new CLine;
   shapes[2]=new CTriangle;
   shapes[3]=new CRectangle;
   shapes[4]=new CCircle;
//--- imprimindo o tipo de cada elemento do array
   for(int i=0;i<5;i++)
     {
      Print(i,shapes[i].getTypeName());
     }
//--- excluindo todos os objetos do array
   for(int i=0;i<5;i++) delete(shapes[i]);
  }

A classe principal CShape contém duas funções, que são sobreescritas manualmente em seus descendentes - a Draw() e getTypeName(). A função Draw() se destina a desenhar uma forma e a função getTypeName() retorna uma descrição em texto da forma.

2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 4 Forma
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 3 Forma
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 2 Forma
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 1 Forma
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 0 Forma

A explicação deste fato é a seguinte: o banco de dados *shapes[] é declarado como um banco de dados de ponteiros do tipo CShape e então cada objeto do banco de dados chama o método getTypeName() de uma classe base, mesmo que um descendente tenha uma implementação diferente. Para chamar a função getTypeName() correspondente com o tipo de objeto real (descendente) no momento da execução do programa, é necessário declarar esta função em uma classe base como uma função virtual.

Vamos adicionar a palavra-chave virtual à função getTypeName() na declaração da classe principal CShape.

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

e executar o script novamente. Agora os resultados são consistentes com aqueles esperados:

2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 4 Círculo
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 3 Retângulo
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 2 Triângulo
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 1 Linha
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 0 Forma

Então, a declaração de uma função virtual em uma classe base permitiu a chamada da mesma função de um descendente durante a execução do programa. Agora podemos implementar a função Draw() com recurso total para cada uma das classes derivadas.

Um exemplo deste trabalho pode ser encontrado no script anexado DrawManyObjects.mq5, que mostram formas aleatórias no gráfico.

Conclusão

Então, é hora de resumir. Em MQL5, a criação e destruição dos objetos é executada automaticamente, então você deve usar os ponteiros somente se eles forem realmente necessários e se você souber como trabalhar com eles.

Entretanto, se você não puder fazer sem a utilização de ponteiros, certifique-se de verificar a exatidão do ponteiro antes do seu uso, usando o CheckPointer() - foi adicionado especialmente para esses casos.

Uma última coisa: no MQL5 os ponteiros não são na verdade ponteiros de memória, como os usados em C++, então você não deve passá-los para DLL como parâmetros de entrada.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/36

Indicadores personalizados no MQL5 para novatos Indicadores personalizados no MQL5 para novatos
Qualquer assunto novo parece complicado e difícil de aprender para um principiante. Os assuntos que conhecemos parecem muito simples e claros para nós. Mas, simplesmente não lembramos, que todos têm que estudar algo desde o início, até a nossa língua materna. O mesmo é com a linguagem de programação MQL5 que oferece amplas possibilidades para desenvolver estratégias próprias de negociação - você pode aprender a partir de noções básicas e dos exemplos mais simples. A interação de um indicador técnico com o terminal de cliente MetaTrader 5 é considerada neste artigo sobre o exemplo de um indicador personalizado simples SMA.
Introdução ao MQL5: Como escrever Expert Advisor e Custom Indicator simples Introdução ao MQL5: Como escrever Expert Advisor e Custom Indicator simples
MetaQuotes Programming Language 5 (MQL5), incluído no Terminal Cliente do MetaTrader 5, tem muitas novas possibilidades e um maior desempenho, em comparação com MQL4. Este artigo irá ajudá-lo a se familiarizar com esta nova linguagem de programação. Os exemplos simples de como escrever um Expert Advisor e indicador personalizado são apresentados neste artigo. Vamos também considerar alguns detalhes da linguagem MQL5 que são necessários para entender estes exemplos.
Processamento de eventos trade no Expert Advisor usando a função OnTrade() Processamento de eventos trade no Expert Advisor usando a função OnTrade()
O MQL5 apresentou uma variedade de inovações, incluindo trabalhos com eventos de vários tipos (eventos de tempo, eventos de negócio, eventos de personalização, etc.). A habilidade de manipular eventos permite que você crie tipos completamente novos de programas para negociação automática e semi-automática. Neste artigo, consideraremos os eventos de negócio e escreveremos alguns códigos para a função OnTrade(), que irá processar o evento Trade.
MQL5 para Novatos: Guia para o Uso de Indicadores Técnicos em Expert Advisors MQL5 para Novatos: Guia para o Uso de Indicadores Técnicos em Expert Advisors
Para obter valores de um indicador interno ou personalizado em um Expert Advisor, primeiro seu manipulador deve ser criado usando a função correspondente. Exemplos no artigo mostram como usar este ou aquele indicador técnico durante a criação de seus próprios programas. O artigo descreve os indicadores que são construídos utilizando a linguagem MQL5. Ele é destinado para aqueles que não têm muita experiência no desenvolvimento de estratégias de negociação, oferecendo maneiras simples e claras de trabalhar com indicadores utilizando a biblioteca de funções oferecida.