
Os Fundamentos da programação orientada a objetos
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".
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
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 keywordNesse 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 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 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
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. 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 passadospara 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
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 passadospara 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





- 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
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
Olha o homem rasgando no inglês... hehehe
[ ]'s
Você quer dizer o Google rasgando no inglês. KKKKKK