English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Os Fundamentos da programação orientada a objetos

Os Fundamentos da programação orientada a objetos

MetaTrader 5Exemplos | 21 janeiro 2014, 13:35
4 081 4
Dmitry Fedoseev
Dmitry Fedoseev

Introdução

Podemos supor que alguém que tentou começar a aprender programação orientada a objetos (OOP), encontrou pela primeira vez palavras como polimorfismo, encapsulamento, sobrecarga e herança. Talvez alguém procurou em algumas classes prontas e tentou descobrir o que polimorfismo ou encapsulamento realmente são... Muito provavelmente isso pode ser o fim do processo de aprendizagem OOP.

Na verdade, tudo é muito mais simples do que parece. Para usar OOP você não precisa saber o que estas palavras significam - você pode somente usar os recursos do OOP, sem nem ao menos saber como eles são chamados. Ainda assim espero que todos os que irão ler este artigo não só aprenderão a usar OOP, mas também entenderão os significados destas palavras.

Criação de bibliotecas de funções

A primeira e mais simples aplicação de OOP é criar suas próprias bibliotecas de funções usadas com freqüência. Claro, você pode simplesmente armazenar essas funções em um arquivo de inclusão (mqh). Quando você precisar de uma função, basta incluir um arquivo e chamar essa função. No entanto, se você programar tempo suficiente, você pode pode coletar uma grande quantidade de funções, de modo que seria difícil lembrar seus nomes e propósitos.

Você pode coletar funções em arquivos diferentes, dividindo-as em categorias baseadas na finalidade. Por exemplo, funções de trabalho com banco de dados, funções de trabalho com séries, funções de cálculo de ordens, etc. Na última frase, a palavra "categoria" pode ser substituída pela palavra "classes". O significado permanece o mesmo, mas vamos nos aproximar do tema - Programação orientada a objetos.

Assim, as funções podem ser divididas em classes: classe de funções para trabalhar com banco de dados, classe de funções para trabalhar com séries, classe de funções para contar ordens, etc. A palavra "classe" nos aproxima do assunto da OOP uma vez que é o seu conceito fundamental. Você pode pesquisar em vários livros de referência, dicionários e enciclopédias (por exemplo Wikipedia) o que significa "classe em programação".

Em programação orientada a objetos, uma classe é uma construção que é usada como um modelo para criar instâncias de si mesmo.

Talvez, a primeira impressão seria aproximadamente a mesma que a das palavras "polimorfismo", "encapsulamento", etc. Neste momento, para o nome "classe", vamos referir como um conjunto de funções e variáveis. No caso da utilização de classe para criar uma biblioteca - um conjunto de funções e variáveis agrupadas pelos tipos de dados processados ou pelos tipos de objetos processados: banco de dados, séries, ordens.

Um programa no programa

Haviam (e haverão) muitas perguntas semelhantes do fórum - como chamar um script de um Expert Advisor? Apesar de ficar longe do uso de ferramentas de terceiros, esta tarefa é realizada colocando-se o código de script no código Expert Advisor. Na verdade, não é uma tarefa difícil, mas um script pode usar os mesmos nomes de variáveis​ e funções como EA, então você precisará ajustar o código de script. As alterações não são complicadas, mas provavelmente significativas no volume.

Seria ótimo simplesmente chamar esse script como um programa separado e independente! Isso é possível se você programar o script como uma classe e em seguida usar essa classe. A quantidade de trabalho será aumentada apenas por algumas linhas de código. Neste caso, uma classe de funções não irá combinar com o tipo de dados processados, mas sim de acordo com a sua finalidade. Por exemplo: uma classe para excluir os pedidos pendentes, uma classe para abrir posição ou para colocar ordem, uma classe para trabalhar com objetos gráficos, etc.

Uma característica importante da classe é que ela se diferencia do espaço em que está localizada. A classe é como um programa executado num sistema operativo: múltiplos programas podem ser executados simultaneamente, mas por si próprios, independentemente uns dos outros. Portanto, classe pode ser chamada de "um programa no programa", tal como ela se diferencia do espaço em que está localizada.

Olhar e perceber uma classe

Criação de classe começa com a palavra classe, seguida pelo nome da classe e, em seguida, todo o código de classe é colocado em parênteses:

class CName 
  {
   // Here is the entire code of the class
  };
Atenção! Não se esqueça de colocar ponto e vírgula após o parêntese de encerramento.

Visível e oculto (encapsulamento)

Se você pegar qualquer programa, sabemos que inclui uma variedade de funções. Estas funções podem ser divididas em dois tipos: principais e auxiliares. As funções principais são funções de que um programa é realmente composto. Estas funções podem exigir muitas outras funções, as quais o usuário não precisa saber. Por exemplo, no terminal do cliente para abrir uma posição comercial precisa abrir o diálogo de Nova Ordem, entrar volume, valores de Parada de Perda e Retirada de Lucro e então clicar "Compra"ou "Venda".

Mas o que realmente acontece entre clicar no botão e abrir uma posição - apenas os desenvolvedores de terminais podem dizer com certeza. Podemos supor que o terminal faz uma série de ações: verifica a posição do volume, verifica os valores de Parada de Perda e Retirada de Lucro, verifica conexão de rede, etc. Muitos e muitos procedimentos estão escondidos ou, em outras palavras, encapsulados. Da mesma forma, em uma classe você pode dividir o código em partes (funções e variáveis) - algumas delas estarão disponíveis ao usar uma classe, e algumas delas estarão ocultas.

Níveis de encapsulamento são definidos usando as seguintes palavras-chave: privada, protegida e pública. A diferença entre protegida e privada será considerada um pouco mais tarde, mas primeiro vamos abordar as palavras-chave privada e pública. Assim, um modelo de classe simples tem a seguinte forma:

class CName 
  {
private:
   // Variables and functions available only inside the class
public:
   // Variables and functions available outside the class
  };
Isto é suficiente para tirar proveito do OOP. Em vez de escrever o código diretamente no Expert Advisor (Script ou Indicador), primeiro crie uma classe e, em seguida, escreva tudo nesta classe. Em seguida, vamos considerar a diferença entre as seções privada e pública em um exemplo prático.

Exemplo de criação de uma biblioteca

O modelo de classe apresentado acima pode ser utilizado para criar uma biblioteca de funções. Vamos criar uma classe para trabalhar com banco de dados. As tarefas mais comuns que podem surgir quando se utiliza um banco de dados - são adicionar um novo elemento ao banco de dados e adicionando um novo elemento, determinar que o elemento com dado valor não exista no banco de dados.

Vamos nomear a função que adiciona um elemento ao banco de dados como AddToEnd(), e a função que adiciona um elemento único ao banco de dados como AddToEndIfNotExists(). Na função AddToEndIfNotExists() primeiro precisamos verificar se o elemento adicionado já existe no banco de dados, senão use a função AddToEnd(). A função que checa se um elemento já existe no banco de dados será considerada como auxiliar, portanto, vamos colocá-la na seção privada e todas as outras funções na seção pública. Como resultado, teremos a seguinte classe:

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

Carregamento de classe

A fim de utilizar uma classe, ela deve ser carregada. Se a classe está localizada em um arquivo separado, você tem que incluir este arquivo

#include <OOP_CLibArray_1.mqh>

e então carregar esta classe. O carregamento de classe é semelhante à declaração de variáveis​​:

CLibArray ar;

Primeiro vem o nome da classe, depois o nome de um indicador para se referir a esta instância. Após carregar, a classe se torna um objeto. Para utilizar qualquer função de um objeto, escreva o nome do indicador, o ponto e em seguida, o nome da função. Após você digitar o ponto, uma lista suspensa de funções de classe irá abrir (Figura 1).

Figura 1. Lista de funções
Figura 1. Lista de funções

Graças à lista suspensa não há necessidade de lembrar os nomes das funções - você pode navegar a lista de nomes e lembrar o propósito da função. Essa é a maior vantagem do uso de classes para criar bibliotecas ao invés de simplesmente coletar funções em arquivos.

No caso da função de coleta, quando você digita algumas letras iniciais do nome da função, a lista suspensa mostrará todas as funções de todas as bibliotecas inclusas, e quando você usa as classes - apenas funções relacionadas com a classe especificada. Observe também que a função Find() não está listada - esta é a diferença entre as seções privada e pública. A função está escrita na seção privada e, portanto, não está disponível.

Fazendo uma biblioteca universal para diferentes tipos de dados (sobrecarga)

Neste ponto, nossa biblioteca inclui funções que trabalham somente com banco de dados do tipo int. Além dos bancos de dados do tipo int, talvez seja necessário aplicar funções da biblioteca para bancos de dados dos seguintes tipos: uint, long, ulong etc. Para banco de dados de outros tipos de dados temos que escrever suas próprias funções. Entretanto, você não precisa dar outros nomes a essas funções - a função correta será automaticamente selecionada dependendo do tipo de parâmetro transmitido ou conjunto de parâmetros (nesse exemplo, dependendo dos tipos de parâmetros). Vamos complementar a classe com funções de trabalho do banco de dados do tipo long:

class CLibArray 
  {
private:
   // Для int. Check if an element with required value exists in array
   int Find(int &aArray[],int aValue)
     {
      for(int i=0; i<ArraySize(aArray); i++) 
        {
         if(aArray[i]==aValue) 
           {
            return(i); // Element exists, return index of element
           }
        }
      return(-1); // No such element, return -1
     }
   // For long. Check if an element with required value exists in array
   int Find(long &aArray[],long aValue) 
     {
      for(int i=0; i<ArraySize(aArray); i++) 
        {
         if(aArray[i]==aValue) 
           {
            return(i); // Element exists, return index of element
           }
        }
      return(-1); // No such element, return -1
     }
public:
   // For int. Add to end of array
   void AddToEnd(int &aArray[],int aValue) 
     {
      int m_size=ArraySize(aArray);
      ArrayResize(aArray,m_size+1);
      aArray[m_size]=aValue;
     }
   // For long. Add to end of array
   void AddToEnd(long &aArray[],long aValue) 
     {
      int m_size=ArraySize(aArray);
      ArrayResize(aArray,m_size+1);
      aArray[m_size]=aValue;
     }
   // For int. Add to end of array if there is no such value in array already
   void AddToEndIfNotExistss(int &aArray[],int aValue) 
     {
      if(Find(aArray,aValue)==-1) 
        {
         AddToEnd(aArray,aValue);
        }
     }
   // For long. Add to end of array if there is no such value in array already
   void AddToEndIfNotExistss(long &aArray[],long aValue) 
     {
      if(Find(aArray,aValue)==-1) 
        {
         AddToEnd(aArray,aValue);
        }
     }
  };
Agora, usando o mesmo nome temos uma funcionalidade diferente. Estas funções são chamadas sobrecarregadas, conforme um nome é carregado com mais do que uma funcionalidade, ou seja sobrecarregado.

Você pode encontrar esse exemplo no arquivo OOP_CLibArray_1.mqh anexado a esses artigo.

Outra forma de notação de classe

Nos exemplos acima todas as funções foram escritas dentro da classe. Se você tem muitas funções e cada uma delas é muito grande, tal notação pode ser não muito confortável. Em tais casos você pode colocar funções fora da classe. Dentro da classe você escreve somente os nomes das funções com parâmetros e as funções são completamente descritas fora da classe. Além disso, você tem que indicar que a função pertence a uma classe específica: primeiro escreva o nome da classe, em seguida, coloque o dois pontos e o nome da função:

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

Com tal notação você pode obter uma imagem completa da composição de classe e dar uma olhada nas funções individuais, se necessário.

Você pode encontrar esse exemplo no arquivo OOP_CLibArray_2.mqh anexado a esse artigo.

Declarando variáveis na classe

Vamos continuar a considerar o exemplo mencionado anteriormente. Há uma diferença entre codificação diretamente no arquivo e dentro da classe. Diretamente no arquivo você pode atribuir variáveis com valores conforme você os declara:

int Var = 123;

Se você declarar uma variável em uma classe que não pode ser feito isso - os valores devem ser atribuídos ao executar alguma função de uma classe. Então, primeiro de tudo você precisa passar parâmetros para a classe (ou seja, preparar a classe para trabalhar). Vamos chamar essa função de Init().

Considere-a em um exemplo prático.

Exemplo de conversão de script em classe

Suponha que haja um script que exclui ordens pendentes (consulte o arquivo OOP_sDeleteOrders_1.mq5 em anexo).

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

// External parameters

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

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

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

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

O script tem parâmetros externos que permitem habilitar vários tipos de ordens e selecionar o símbolo para que as ordens sejam excluídas (todos os símbolos ou símbolo de gráfico no qual o script está sendo executado).

Converta esse script para classe chamada COrderDelete. No setor privado vamos declarar as mesmas variáveis que são declaradas no script como parâmetros externos, mas prefixando os nomes das variáveis com "m_" (da palavra "membro", ou seja, membro da classe). Adicionar o prefixo não é exigido, mas é muito conveniente porque ele permite distinguir facilmente as variáveis. Assim podemos ter certeza de que estamos lidando com variáveis limitadas pelo espaço da classe. Além disso, você não terá os avisos do compilador de que a declaração da variável esconde a variável declarada no alvo global.

Usando os mesmos nomes de variáveis ​num âmbito global da definição de classe, no corpo da função não é um erro, mas faz o programa difícil de compreender, de modo que é por isso que em tais casos o compilador faz uma advertência. Para atribuir variáveis com valores, escreva a função Init () com os parâmetros correspondentes a estas variáveis (e os parâmetros externos do script). Se você usar essa classe, primeiro você tem que chamar a função init() e passar parâmetros externos a ela. O resto do código de script permanece inalterado. A única exceção - em vez de usar diretamente os parâmetros externos, você deve usar as variáveis declaradas dentro da classe.

Então temos a seguinte classe:

#include <Trade/Trade.mqh> 

class COrderDelete 
  {

private:
   // Variables for parameters
   bool              m_AllSymbol;
   bool              m_BuyStop;
   bool              m_SellStop;
   bool              m_BuyLimit;
   bool              m_SellLimit;
   bool              m_BuyStopLimit;
   bool              m_SellStopLimit;
   // Load the CTrade class
   CTrade            m_Trade;
public:
   // Function to set parameters
   void Init(bool aAllSymbol,bool aBuyStop,bool aSellStop,bool aBuyLimit,bool aSellLimit,bool aBuyStopLimit,bool aSellStopLimit) 
     {
      // Set parameters
      m_AllSymbol    =aAllSymbol;
      m_BuyStop      =aBuyStop;
      m_SellStop     =aSellStop;
      m_BuyLimit     =aBuyLimit;
      m_SellLimit    =aSellLimit;
      m_BuyStopLimit =aBuyStopLimit;
      m_SellStopLimit=aSellStopLimit;
     }
   Main function to delete orders
   bool Delete() 
     {
      // Variable to check function result
      bool m_Ret=true;
      // Loop by all orders in terminal
      for(int i=0; i<OrdersTotal(); i++) 
        {
         // Select order and get its ticket
         ulong m_Ticket=OrderGetTicket(i);
         // Successfully selected
         if(m_Ticket>0) 
           {
            long m_Type=OrderGetInteger(ORDER_TYPE);
            // Check order type
            if(m_Type == ORDER_TYPE_BUY_STOP && !m_BuyStop) continue;
            if(m_Type == ORDER_TYPE_SELL_STOP && !m_SellStop) continue;
            if(m_Type == ORDER_TYPE_BUY_LIMIT && !m_BuyLimit) continue;
            if(m_Type == ORDER_TYPE_SELL_LIMIT && !m_SellLimit) continue;
            if(m_Type == ORDER_TYPE_BUY_STOP_LIMIT && !m_BuyStopLimit) continue;
            if(m_Type == ORDER_TYPE_SELL_STOP_LIMIT && !m_SellStopLimit) continue;
            // Check symbol/s61>
            if(!m_AllSymbol && Symbol()!=OrderGetString(ORDER_SYMBOL)) continue;
            // Delete
            if(!m_Trade.OrderDelete(m_Ticket)) 
              {
               m_Ret=false; // Failed to delete
              }
           }
         // Failed to select order, unknown result,
         // function ended up with error
         else 
           {
            m_Ret=false;
            Print("Error selecting order");
           }
        }
      // Return function result
      return(m_Ret);
     }
  };
Você pode encontrar exemplo dessa classe no arquivo OOP_CDeleteOrder_1.mqh em anexo a este artigo. O script usando essa classe é reduzido ao mínimo (parâmetros externos, classe de carga, chamada para o Init() e Delete() métodos):
// External parameters

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

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

// Include file with class
#include  

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

Você pode encontrar exemplo dessa classe no arquivo OOP_sDeleteOrders_2.mq5 em anexo a este artigo. A maior parte do script está processando os resultados da função Delete(), assim notificando os resultados de script.

Agora todas as funções básicas do script são projetadas como uma classe localizada em um arquivo separado, assim você pode usar essa classe a partir de qualquer outro programa (Expert Advisor ou Script), ou seja, chamar esse script de Expert Advisor.

Um pouco de automação (construtor e destruidor)

O funcionamento do programa pode ser dividido em três fases: o lançamento do programa, o processo de trabalho e a conclusão do seu trabalho. A importância desta separação é óbvia: quando o programa inicia, ele se prepara (por exemplo, carrega e define os parâmetros para trabalhar), quando o programa termina, ele deve fazer uma "limpeza" (por exemplo, remover os objetos gráficos da tabela).

Para separar esses estágios, Expert Advisors e indicadores tem funções especiais: OnInit() (executado durante a inicialização) e OnDeinit() (execução no desligamento). As classes têm características semelhantes: você pode adicionar funções que serão executadas automaticamente quando a classe é carregada e quando é descarregada. Essas funções são chamadas Construtor e Destruidor. Adicionar um construtor para a classe significa adicionar uma função com o nome exatamente igual ao nome da classe. Para adicionar um destruidor - faça tudo da mesma forma que para o construtor, mas o nome da função começa com um til "~".

Um script que demonstra o construtor e o destruidor:

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

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

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

Esta classe, na verdade, tem apenas uma função Sleep() que faz uma pausa durante 3 segundos. Ao executar o script, uma janela de alerta com a mensagem "Construtor" aparece, depois de uma pausa de três segundos, uma janela de alerta com a mensagem "Destruidor" é exibida. Isto é apesar do fato de que as funções CName() e ~ CName() nunca foram chamadas explicitamente.

Você pode encontrar esse exemplo no arquivo OOP_sConstDestr_1.mq5 anexado a esse artigo.

Passando parâmetros para o construtor

No exemplo em que convertemos o script na classe, você ainda pode reduzir a quantidade de código por uma linha - livrar-se de chamar a função init(). Parâmetros podem ser passados para o construtor ao carregar a classe. Adicionar construtor para a classe:

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

A função Init() permanece como era, mas ela é chamada do construtor. Todos os parâmetros no construtor são opcionais, sendo assim, essa classe pode ser usada como antes: carregar a classe sem parâmetros e chamar a função init().

Depois de criar um construtor não há outra maneira de usar essa classe. Quando a classe é carregada você pode passar parâmetros para ela sem a necessidade de chamar a função init():

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

A função Init() foi deixada na seção pública para permitir a reinicialização da classe. Quando você usa o programa (Expert Advisor), em um caso pode ser necessário remover apenas as ordens de Parada, em outro - apenas as ordens Limitadas. Para fazer isso, você pode chamar a função init() com diferentes parâmetros, então a função Delete() irá apagar um conjunto diferente de ordens.

Você pode encontrar este exemplo nos arquivos OOP_CDeleteOrder_2.mqh e OOP_sDeleteOrders_3.mq5 em anexo a este artigo.

Utilizando várias instâncias de uma classe

Como foi mencionado na seção anterior, a mesma classe pode executar ações diferentes, dependendo dos parâmetros a serem definidos durante a inicialização. Se você sabe para que fim a sua classe será usada, você pode omitir a classe de reinicialização. Para fazer isso, você deve carregar alguns exemplos de classe com parâmetros diferentes.

Por exemplo, sabe-se que quando a nossa EA está funcionando, em alguns casos precisamos apagar as ordens BuyStop e BuyLimit, enquanto que em outros casos - SellStop e ordens SellLimit. Neste caso, você pode carregar duas instâncias da classe.

Para excluir as ordens BuyStop e BuyLimit:

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

Para excluir as ordens SellStop e SellLimit:

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

Agora, quando você quer excluir as ordens de Compra pendentes, use uma instância de uma classe:

DeleteBuy.Delete();

Quando você quer excluir as ordens pendentes de Venda - outra instância.

DeleteSell.Delete();

Banco de dados de objetos

Você não pode sempre saber com certeza quantas instâncias de classe que você vai precisar quando o programa está sendo executado. Neste caso você pode criar um banco de dados de instâncias de classe (objetos). Vamos ter um olhar neste exemplo de classe com construtor e destruidor. Alterando ligeiramente a classe, vamos passar parâmetro para o construtor para que possamos monitorar cada instância da classe:

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

public:
   // Constructor
   CName(int aArg) 
     {
      m_arg=aArg;
      Alert("Constructor "+IntegerToString(m_arg));
     }
   // Destructor
  ~CName() 
     { 
      Alert("Destructor "+IntegerToString(m_arg)); 
     }
   //---
   void Sleep() 
     { 
      Sleep(3000); 
     }
  };
Vamos usar essa classe. Você pode declarar um banco de dados de um determinado tamanho, por exemplo, dez elementos:
CName* cname[10];

Veja uma diferença em relação ao habitual banco de dados de declaração de variáveis ​​- um asterisco "*". Um asterisco indica que o indicador dinâmico é usado em contraste ao indicador automático usado anteriormente.

Você pode usar um banco de dados dinâmico (sem tamanho pré-distribuído, não confunda banco de dados dinâmico com o indicador dinâmico):

CName* cname[];

Neste caso será necessária a escala (realizada dentro de qualquer função, em scripts - dentro da função OnStart()):

ArrayResize(cname,10);

Agora vamos percorrer todos os elementos do banco de dados a carregar a instância de classe dentro de cada um deles. Para fazer isso, use a palavra-chave nova:

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

Verifique o script. Execute-o e veja se há dez construtores, mas não destruidores. Se você usar indicadores dinâmicos, as classes não são descarregadas automaticamente quando o programa é encerrado. Além disso, na guia "Especialistas", você pode ver as mensagens sobre vazamentos de memória. Você deve excluir objetos manualmente:

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

Agora, no final do script, há dez destruidores executando sem mensagens de erro.

Você pode encontrar esse exemplo no arquivo OOP_sConstDestr_2.mq5 anexado a esse artigo.

Usando OOP para mudar a lógica do programa (funções virtuais, polimorfismo)

Polimorfismo - talvez esta seja a característica mais interessante e significativa do OOP, que lhe permite controlar a lógica do seu programa. Ele faz uso de uma classe base com funções virtuais e múltiplas classes filhas. Uma classe pode assumir várias formas definidas por classes filhas.

Veja um exemplo simples - comparação de dois valores. Pode haver cinco versões de comparação: maior que (>), menor que (<), maior que ou igual a (> =), menor que ou igual a (<=), igual a (==).

Criar uma classe base com a função virtual. A função virtual - é exatamente a mesma função normal, mas a sua declaração começa com a palavra virtual:

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

A função virtual não tem código. É uma espécie de um conector que vai aceitar vários dispositivos. Dependendo do tipo de dispositivo irá executar ações diferentes.

Criar cinco classes filhas:

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

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

Antes de usar esta classe ela deve ser carregada. Se você sabe que classe filha deve ser utilizada, você pode declarar um indicador com o modelo desta filha. Por exemplo, se você quiser conferir a ">" condição:

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

Se, como no nosso caso, nós não conhecemos de antemão o tipo de filha, um indicador para a classe é declarado com o modelo de classe de base. Mas neste caso o indicador dinâmico é usado.

CCheckVariant* var;

Esta filha deve ser carregada usando a nova palavra-chave. Carregue filha dependendo da variante selecionada:

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

Verificar condições:

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

O resultado da comparação dos dois valores dependerão da classe filha, embora o código que verifica as condições é idêntico para todos os casos.

Você pode encontrar esse exemplo no arquivo OOP_sVariant_1.mq5 anexado a esse artigo.

Mais sobre o encapsulamento (privado, protegido, público)

Para esse momento é muito claro com o seção pública - em conter funções e variáveis ​que devem ser visíveis para o usuário de classe (por usuário queremos dizer um programador escrevendo programas usando uma classe pronta). Da perspectiva do usuário de classe, não há diferença entre as seções protegida e privada - funções e variáveis nestas seções não estão disponíveis para o usuário:

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

CName1 c1; // Load class with the protected keyword
CName2 c2; // Load class with the private keyword
Nesse exemplo há duas classes: CName1 e CName2. Cada classe tem duas funções: uma está localizada na seção pública, e a outra na seção protegida (para a classe CName1) ou na seção privada (para a classe CName2). Ambas as classes têm apenas uma função da seção pública na lista suspensa de funções (Figuras 2 e 3).

Figura 2. Funções da classe CName1
Figura 2. Funções da classe CName1

Figura 3. Funções da classe CName2
Figura 3. Funções da classe CName2

Você pode encontrar esse exemplo no arquivo OOP_sProtPriv_1.mq5 anexado a esse artigo.

As seções privada e protegida determinam a visibilidade da função de classe base para as suas classes filhas:

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

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

Neste exemplo, nós temos classe base chamada CBase e classe filha chamada Classe. Tente chamar a função da classe base localizada nas seções protegida e privada da classe filha. Se você chamar a função da seção protegida, tudo compila e executa. Se você chamar a função da seção privada, é exibido um erro do compilador (não pode chamar a função membro privado). Isto é, a função da seção privada não é visível para classes filhas.

A seção protegida protege funções somente de usuários de classe, e a função privada protege também as funções de classes filhas. A visibilidade das funções da classe base (localizada em seções diferentes) da classe filha é mostrada na Figura 4.

Figura 4. Visibilidade das funções da classe base da classe filha
Figura 4. Visibilidade das funções da classe base de classe filha
Setas azuis - funções estão disponíveis, cinza - não disponíveis.

Você pode encontrar esse exemplo no arquivo OOP_sProtPriv_2.mq5 anexado a esse artigo.

Função virtual padrão e herança

Nem todas as funções virtuais na classe base deve ter as funções correspondentes em classes filhas. Se a classe filha tem o mesmo nome da função - ela vai usar essa mesma função, se não - ela vai executar o código de função virtual da classe base. Considere-a no exemplo.

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

  };

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

Apesar do fato de que a classe Class2 não tem funções, ainda é possível chamar a função Function() a partir dela. Isto executará a função da classe CBase. A classe Class1 irá executar a sua própria função:

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

Do ponto de vista de classe do usuário, ao utilizar uma classe filha todas as funções da classe base da seção pública estarão disponíveis. Isso é chamado herança. Se a função da classe base é declarada como virtual, ela será substituída pela função da classe filha se uma classe filha tem uma função com esse nome (Figura 5).

Figura 5. Acessando funções por usuários de classe
Figura 5. Acessando funções por usuários de classe

Exceto o caso quando a classe filha não tem funções correspondentes às funções virtuais da classe base, a classe filha pode ter funções "extras" (funções sem funções virtuais com o mesmo nome dentro da classe base). Se você carregar a classe usando indicador para o tipo classe filha, estas funções estarão disponíveis. Se você carregar a classe usando indicador para o tipo classe base, estas funções não estarão disponíveis.

Figura 6. Visibilidade de funções
Figura 6. A visibilidade da função "extra" (seta vermelha) é determinada
pelo tipo de indicador usado para carregar a classe.

Você pode encontrar esse exemplo no arquivo OOP_sDefaultVirtual_1.mq5 anexado a esse artigo.

Um pouco mais sobre carregamento de classe

Quando você usar as funções virtuais e consequentemente, classe base e classes filhas, se você sabe qual classe filha deve ser utilizada, você pode usar um indicador que corresponde à classe filha:

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

Se não se sabe qual classe filha deve ser utilizada, então use um indicador dinâmico para o tipo de classe base e carregamento da classe usando a palavra-chavenova:

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

Se você usar o indicador automático de classe base

CBase c; // Automatic pointer

a classe base será utilizada como é. Quando você chamar suas funções virtuais, ela vai executar o código localizado dentro dessas funções. As funções virtuais são convertidas em funções normais.

Objetos de processamento na função

O título desta seção é autossuficiente. Indicadores para objetos podem ser passados​para funções, portanto, dentro da função você pode chamar funções de objetos. Parâmetro de função pode ser declarada com o tipo de classe base. Isto torna a função universal. Um indicador para uma classe pode ser passado para função somente por referência (indicado pelo sinal &):

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

Class1 c1; // Load class 1
Class2 c2; // Load class 2
//+------------------------------------------------------------------+
//|   Function to process objects                                    |
//+------------------------------------------------------------------+
void Function(CBase  &c)
  {
   Alert(c.Function());
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
// Process objects using one function.
   Function(c1);
   Function(c2);
  }
Você pode encontrar esse exemplo no arquivo OOP_sFunc_1.mq5 anexado a esse artigo.

Funções e métodos, variáveis e propriedades

Até agora neste artigo usamos a palavra "função". Mas em OOP, em vez da palavra "função", programadores costumam usar a palavra "método". Se você olhar para dentro da classe do ponto de vista de um programador escrevendo uma classe, todas as funções permanecem funções. Se você olhar para a classe do ponto de vista de um programador usando uma classe já pronta, então as funções de interface de classe localizadas na seção pública (disponível na lista suspensa após digitar um ponto) são chamadas métodos.

Além de métodos, a classe interface pode incluir as propriedades da classe. A seção pública pode incluir não apenas funções, mas também variáveis (incluindo banco de dados).

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

Estas variáveis serão chamados propriedades de classe e também estarão disponíveis na lista suspensa (Figura 7).

Figura 7. Métodos e propriedades da classe em uma lista
Figura 7. Métodos e propriedades da classe em uma lista

Você pode usar essas propriedades da mesma maneira que as variáveis:

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

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

Você pode encontrar esse exemplo no arquivo OOP_sMethodsAndProperties.mq5 anexado a esse artigo.

Estrutura de dados

Estruturas de dados são semelhantes às classes, apenas um pouco mais fácil. Embora você possa colocar dessa maneira: as classes são como estruturas de dados, mas um pouco mais complicado. A diferença é que as estruturas de dados podem incluir apenas variáveis. A este respeito, não há necessidade de as dividir nas secções pública, privada e protegida. Todos os conteúdos da estrutura já estão localizados na secção pública. Estrutura de dados começa com a palavra struct, seguida pelo nome da estrutura, dentro das chaves você declarar variáveis.

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

Para utilizar uma estrutura ela deve ser declarado como uma variável, mas em vez do tipo de variável, usar o nome de estrutura.

Str1 s1;

Você também pode declarar um banco de dados de estruturas:

Str1 sar1[];

Estruturas podem incluir não apenas variáveis e banco de dados, mas também outras estruturas:

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

Neste caso, para chamar variável de uma estrutura que é parte da estrutura 2, você deve usar dois pontos:

s2.Str.IntVar=1;

Você pode encontrar esse exemplo no arquivo OOP_Struct.mq5 anexado a esse artigo.

Classes podem incluir não apenas variáveis, mas também estruturas.

Conclusão

Vamos rever os principais pontos da programação orientada a objetos e momentos importantes para manter em mente:

1. A classe é criada usando a palavra-chave classe, seguida do nome da classe, e então dentro de chaves, vem o código de classe escrito em três seções.

class CName 
  {
private:

protected:

public:
  };

2. As funções e variáveis podem ser localizadas em uma das três seções: privada, protegida e pública. As funções e variáveis​ da seção privada estão disponíveis apenas dentro da classe. As funções e variáveis​ da seção protegida estão disponíveis dentro da classe e de classes filhas. As funções e variáveis​ da seção pública estão disponíveis para todos.

3. As funções de classe podem estar localizadas dentro ou fora da classe. Se você colocar funções fora da classe, você deve identificar qual a classe a que pertencem, colocando o nome da classe e dois-pontos antes de cada nome da função:

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

4. A classe pode ser carregada usando ambos indicadores automático e dinâmico. Ao usar indicador dinâmico, a classe deve ser carregada usando a palavra-chavenova. Neste caso, você tem que deletar um objeto usando a palavra-chave delete quando encerrar seu programa.

5. Para dizer que a classe filha pertence à classe base, você tem que adicionar o nome da classe base após o nome da classe filha.

class Class : public CBase { ... }

6. Você não pode atribuir variáveis com valores durante a inicialização da classe. Você pode atribuir valores durante a execução de alguma função mais frequentemente - o construtor.

7. Funções virtuais são declaradas usando a palavra-chave virtual. Se a classe filha tem um função com o mesmo nome, ela executa esta própria função, caso contrário - executa a função virtual da classe base.

8. Indicadores para classes podem ser passados​para funções. Você pode declarar os parâmetros da função com o tipo de classe base, de modo que você pode passar um indicador para qualquer classe filha em função.

9. A seção pública não tem apenas funções (métodos), mas também variáveis (propriedades).

10. Estruturas podem incluir banco de dados e outras estruturas.

Lista de arquivos anexos

  • OOP_CLibArray_1.mqh - arquivo incluído, deve ser colocado na pasta MQL5/Include. Exemplo de uso de classe para criar biblioteca. As palavras-chaves protegida e privada. Sobrecarga.
  • OOP_CLibArray_2.mqh - arquivo incluso, deve ser colocado na pasta MQL5/Include. Exemplo de colocação de funções de classe além da classe.
  • OOP_sDeleteOrders_1.mq5 - script, deve ser colocado na pasta MQL5/Scripts. Script simples para eliminar as ordens pendentes.
  • OOP_CDeleteOrder_1.mqh - arquivo incluso, deve ser colocado na pasta MQL5/Include. Exemplo de conversão de OOP_sDeleteOrders_1 script em classe
  • OOP_sDeleteOrders_2.mq5 - arquivo incluso, deve ser colocado na pasta MQL5/Scripts. Exemplo de uso de classe para deletar ordens. Retirado do arquivo OOP_CDeleteOrder_1.mqh (definição de parâmetros via função Init ()).
  • OOP_sConstDestr_1.mq5 - script, deve ser colocado na pasta MQL5/Scripts. Demonstração de construtor e destruidor
  • OOP_CDeleteOrder_2.mqh - arquivo incluso, deve ser colocado na pasta MQL5/Include. Classe que exclui as ordens com construtor e passagem de parâmetros via construtor.
  • OOP_sDeleteOrders_3.mq5 - script, deve ser colocado na pasta MQL5/Scripts. Exemplo de uso de classe para deletar ordens. Retirado do arquivo OOP_CDeleteOrder_2.mqh (parametrização via construtor).
  • OOP_sConstDestr_2.mq5 - script, deve ser colocado na pasta MQL5/Scripts. Exemplo de carregamento de classes no banco de dados.
  • OOP_sVariant_1.mq5 - script, deve ser colocado na pasta MQL5/Scripts. Exemplo de classe base com filhas. Função virtual, polimorfismo.
  • OOP_sProtPriv_1.mq5 - script, deve ser colocado na pasta MQL5/Scripts. Um exemplo de identidade das palavras-chave protegida e privada quando se utiliza uma classe.
  • OOP_sProtPriv_2.mq5 - script, deve ser colocado na pasta MQL5/Scripts. Exemplo de afetar as palavras-chave protegida e privada em classe filha.
  • OOP_sDefaultVirtual_1.mq5 - script, deve ser colocado na pasta MQL5/Scripts. Exemplo de classe filha que não tem função que corresponde à função virtual da classe base.
  • OOP_sFunc_1.mq5 - script, deve ser colocado na pasta MQL5/Scripts. Exemplo de uso de objetos em uma função.
  • OOP_sMethodsAndProperties.mq5 - script, deve ser colocado na pasta MQL5/Scripts. Exemplo de propriedades.
  • OOP_Struct.mq5 - script, deve ser colocado na pasta MQL5/Scripts. Exemplo de estruturas.

Após experimentar com esses arquivos, você pode excluir todos eles, exceto OOP_CDeleteOrder_2.mqh e OOP_sDeleteOrders_3.mq5. Os arquivos OOP_CDeleteOrder_2.mqh e OOP_sDeleteOrders_3.mq5 podem ser úteis na prática de programação.

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

Arquivos anexados |
files_en.zip (11.04 KB)
Últimos Comentários | Ir para discussão (4)
Joao Luiz Sa Marchioro
Joao Luiz Sa Marchioro | 24 fev 2019 em 03:33
Thanks for the article, it has helped me a lot. I am very weak in the use and understanding of CLASSES. Thank you very much, Hugs.
জচেলিনো
জচেলিনো | 24 fev 2019 em 15:38
Joao Luiz Sa Marchioro:
Thanks for the article, it has helped me a lot. I am very weak in the use and understanding of CLASSES. Thank you very much, Hugs.

Olha o homem rasgando no inglês... hehehe

[ ]'s

Joao Luiz Sa Marchioro
Joao Luiz Sa Marchioro | 6 mar 2019 em 22:36
Joscelino Celso de Oliveira:

Olha o homem rasgando no inglês... hehehe

[ ]'s

Você quer dizer o Google rasgando no inglês. KKKKKK

albertpess
albertpess | 13 mai 2021 em 00:40
Excelente... Muito obrigado....
Abordagem orientada a objetos para construção de painéis de múltiplos períodos de tempo e múltiplas moedas Abordagem orientada a objetos para construção de painéis de múltiplos períodos de tempo e múltiplas moedas
Este artigo descreve como a programação orientada a objetos pode ser usada para criar painéis de múltiplos períodos de tempo múltiplas moedas para o MetaTrader 5. O principal objetivo é construir um painel universal, que pode ser utilizado para exibição de diversos tipos diferentes de dados, tal como preços, variação de preços, valores de indicador ou condições personalizadas de compra/venda, sem a necessidade de modificar o código do painel em si.
Crie seus próprios painéis gráficos no MQL5 Crie seus próprios painéis gráficos no MQL5
A usabilidade do programa MQL5 é determinada tanto por sua rica funcionalidade como pela interface de usuário gráfica elaborada. A percepção visual, algumas vezes, é mais importante do que uma operação rápida e estável. Aqui está um guia passo-a-passo para você mesmo criar painéis de exibição com base nas classes da Biblioteca padrão.
Sistema de negociação simples com o uso de indicadores semáforo Sistema de negociação simples com o uso de indicadores semáforo
Se examinarmos por completo qualquer sistema de negócio complexo, veremos que é baseado em um conjunto simples de sinais de negócio. Consequentemente, não há necessidade para que novos desenvolvedores comecem imediatamente a escrever algoritmos complexos. Este artigo fornece um exemplo de um sistema de negócio que utiliza indicadores semáforo para realizar negócios.
Controles gráficos personalizados. Parte 3. Formas Controles gráficos personalizados. Parte 3. Formas
Este é o último dos três artigos dedicados a controles gráficos. Ele cobre a criação do principal componente da interface gráfica - a forma - e seu uso em combinação com outros controles. Além das classes de forma, as classes CFrame, CButton, CLabel foram adicionadas à biblioteca de controle.