Do básico ao intermediário: Classes (III)
Introdução
No artigo anterior Do básico ao intermediário: Classes (II), tentei da melhor forma possível explicar um dos temas, mais confusos e com quase toda a certeza, um dos mais difíceis que existe na programação orientada em objetos. Que é justamente o conceito e utilização básica de um destructor. Porém aquilo que foi visto ali, é apenas a parte fácil e agradável deste tema bastante espinhoso e bastante complicado de aprender. Isto porque, o que foi visto ali, se aplica a somente uma pequena parcela do que realmente acontece na prática.
Programação orientada em objetos, não é difícil, por conta da própria programação. Mas sim por conta de uma má definição de alguns conceitos necessários para se poder utilizar adequadamente a mesma. Muitos acadêmicos, acabam explicando de maneira, no mínimo bem superficial, isto quando não completamente inadequada. Certos conceitos que você, meu caro leitor precisa dominar. Isto para que de fato consiga entender e tirar todo proveito do que a programação orientada em objetos tem a nos oferecer. Lembrando que este material inicial, está sendo postado agora, apenas por que necessitamos dele para continuar a implementar o mecanismo de árvores. Que demos uma pequena parada na implementação, para que estes conceitos, da programação orientada em objetos fossem postos a luz do dia.
Muito provavelmente, acredito que este artigo irá encerra esta primeira etapa, do que precisa ser explicado. Nos permitindo assim continuar de onde paramos na implementação do mecanismo de árvores. Então sem mais delongas vamos iniciar o que será o tópico principal deste artigo.
Classes (III)
No artigo anterior implementamos dois tipos de códigos, um voltado a ser executado dentro de um script, e outro voltado a ser executado dentro de um indicador. Ambos os códigos contavam com um arquivo de cabeçalho comum a ambos. Permitindo assim que uma vez, cada código sendo executo no MetaTrader 5, pudesse ter um comportamento muito similar. Pelo menos no que diz respeito a utilização do código presente na classe, montada no arquivo cabeçalho.
Para o que será explicado aqui, possa de fato, vir a fazer sentido. É preciso que retomemos as coisas de onde elas pararam no artigo anterior. Então vamos fazer o seguinte: Vamos rever os códigos que estão no artigo anterior. Estes podem ser visualizados logo abaixo, na íntegra.
01. //+------------------------------------------------------------------+ 02. class C_Regression 03. { 04. private : 05. //+----------------+ 06. public : 07. //+----------------+ 08. C_Regression() 09. { 10. datetime dt0 = TimeCurrent(), 11. dt1[20]; 12. 13. CopyTime(NULL, NULL, dt0, dt1.Size(), dt1); 14. ObjectCreate(0, def_NameChannel, OBJ_REGRESSION, 0, dt1[0], 0, dt0, 0); 15. ChartRedraw(); 16. } 17. //+----------------+ 18. ~C_Regression() 19. { 20. ObjectDelete(0, def_NameChannel); 21. ChartRedraw(); 22. } 23. //+----------------+ 24. }; 25. //+------------------------------------------------------------------+
Código 01
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameChannel "Demo" 05. //+------------------------------------------------------------------+ 06. #include <Tutorial\File 01.mqh> 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. C_Regression channel; 11. 12. Sleep(2000); 13. } 14. //+------------------------------------------------------------------+
Código 02
01. //+------------------------------------------------------------------+ 01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Demo" 04. //+------------------------------------------------------------------+ 05. #define def_NameChannel "Demo" 06. //+------------------------------------------------------------------+ 07. #include <Tutorial\File 01.mqh> 08. //+------------------------------------------------------------------+ 09. C_Regression gl_Channel; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. return INIT_SUCCEEDED; 14. }; 15. //+------------------------------------------------------------------+ 16. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 17. { 18. return rates_total; 19. }; 20. //+------------------------------------------------------------------+ 21. void OnDeinit(const int reason) 22. { 23. }; 24. //+------------------------------------------------------------------+
Código 03
Ok, o código 01, é nosso arquivo de cabeçalho. Já o código 02, seria o nosso script e o código 03 o indicador. Tanto o script, quanto o indicador, tem o mesmo tipo de comportamento. Ambos irão adicionar um objeto que estamos definindo na linha 14 do código 01. Ou seja, uma linha de regressão. Mas apesar deste comportamento muito parecido. Ambos trabalham de uma maneira um pouco diferente. Isto por que, o script irá manter esta linha de regressão presente no gráfico pelo tempo indicado na linha 12 do código 02. Já o indicador, irá manter a linha de regressão no gráfico pelo tempo em que ele estiver presente no gráfico. Ou seja, quem controla quando a linha de regressão será removida do gráfico, neste caso será o próprio usuário. Pois no momento em que ele remover o indicador a linha irá ser removida junto.
Tudo isto é muito lindo e maravilhoso. Mas apesar de toda esta beleza existe um pequeno problema aqui. E este se deve justamente ao fato de que você, como programador, e utilizando as classes como está sendo feita em ambos os códigos, não tem o devido controle sobre elas.
E não me entenda mal, meu caro leitor, digo que você não tem o controle, justamente por conta de que você, não pode dizer, quando a classe deve iniciar seu funcionamento e quando ela deve terminar. Quem está fazendo este trabalho é o compilador. Você, apenas está dizendo, como a classe irá funcionar. Mas não quando e por quanto tempo. Sei que isto parece bastante estranho e difícil de acreditar. Já que aparentemente você se sente no controle do que o código está fazendo e de como ele está trabalhando. Porém, se você entendeu de maneira adequada o que foi explicado nos dois últimos artigos, sabe muito bem, que não é bem assim.
Quando declaramos a variável que irá utilizar a classe para referenciar os procedimentos ou funções presentes na mesma. Temos por parte do compilador, a chamada ao constructor mais adequado para que a classe seja inicializada. Assim como no momento em que a variável que estiver referenciando a classe, morrer. Teremos uma chamada ao destructor da classe. Finalizando assim as atividades da mesma da melhor forma possível. Ou da maneira que você, como programador planejou.
No entanto, apesar deste controle, ainda nos falta um. E este controle que nos falta, faz com que as coisas começam a se acumular de maneira muito rápida. Por isto, se você não entendeu o conteúdo dos artigos anteriores, peço gentilmente que pare, e retorne aos mesmos. Até que aquele conteúdo fique devidamente muito bem compreendido. Pois o que iremos ver agora irá bagunçar completamente o seu cérebro. Pois chegou a hora de aprender a utilizar dois novos operadores. Estes operadores já forma vistos, anteriormente. No entanto, aqui é onde a coisa realmente fica complicada. E estou falando dos operadores new e delete.
Ok, até onde eu sei, estes operadores servem para alocar memória. Certo? Sim meu caro leitor. Porém isto se aplica mais quando o assunto é C++. Aqui no MQL5, estes operadores são usados como uma forma de controlar a vida de uma variável que esteja referenciando uma classe. Até onde já experimentei o MQL5, estes operadores não podem ser usados como seriam se estivéssemos utilizando C++. O que de certa forma é uma coisa boa. Já que isto minimiza certos problemas, no que rege entendimento do código por parte de outros programadores.
Mas então, vamos voltar a nossa questão. Para tornar as coisas devidamente fáceis de entender. E assim não complicar de maneira descabida a própria didática. Vamos tomar como base apenas e tão somente o código do indicador e como ele está trabalhando. Então focaremos apenas no código 03 neste momento.
Como foi explicado no artigo anterior, quando a linha nove no código 03 é executada, o compilador irá criar uma chamada para o constructor, presente na linha oito do código 01. E em algum momento, que o indicador perceba que a variável da linha nove no código 03, já não será mais utilizada, ele irá criar uma chamada para a linha 18 vista no código 01. Simples assim. Mas podemos modificar a forma como isto estará acontecendo. Para isto, bastará mudar o código 03, para o que é visto logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Demo" 04. //+------------------------------------------------------------------+ 05. #define def_NameChannel "Demo" 06. //+------------------------------------------------------------------+ 07. #include <Tutorial\File 01.mqh> 08. //+------------------------------------------------------------------+ 09. C_Regression *gl_Channel; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. return INIT_SUCCEEDED; 14. }; 15. //+------------------------------------------------------------------+ 16. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 17. { 18. return rates_total; 19. }; 20. //+------------------------------------------------------------------+ 21. void OnDeinit(const int reason) 22. { 23. }; 24. //+------------------------------------------------------------------+
Código 04
Agora este código 04 não irá mais executar da mesma forma que o código 03. Hum, não estou vendo nenhuma mudança no código? Você tem certeza de que este código 04, funciona de maneira diferente do código 03? Pois para mim ambos são iguais. Não tem nada de diferente neles. Preste atenção, meu caro leitor. Existe uma diferença entre o código 03 e o código 04. Procure ler o código com um pouco mais de atenção.
E então conseguiu encontrar a diferença? Cara, ainda não estou vendo nenhuma diferença. A não ser um asterisco que você colocou neste código 04. Fora isto, tá tudo igual. De fato, parece estar tudo igual, mas lá no fundo não é a mesma coisa. O simples fato deste asterisco ter sido colocado no código, mudou completamente o comportamento dele. Tanto que agora, o constructor não será chamado pelo compilador de maneira implícita. Ou seja, não é somente declarar a variável que irá receber a classe, já torna possível de que a classe venha a ser construída. Agora, é preciso que digamos ao compilador:
Eu quero que você chame o constructor da classe neste exato momento.
Porém, ao fazermos isto, iremos também precisar dizer ao compilador quando chamar o destructor. Isto por que, agora o compilador, já não sabe quando ele deverá ser chamado. Cara, que complicação. Quando mais eu procuro ler seus artigos, mais complicado as coisas vão ficando. Acho que isto de programar, não é para mim. Desisto. Calma, meu caro leitor, ainda nem começamos a brincar de verdade. Volta aqui, pois a coisa a cada dia fica ainda mais divertida. Isto que estamos vendo neste momento, é brincadeira de criança, perto do que realmente podemos fazer. E você já está pensando em desistir? ( RISOS ).
Certo, para aqueles que não desistiram, vamos continuar. Resolveu voltar? Que bom, fico feliz com esta sua determinação. Então para comprovar que de fato, teremos o controle de quando e onde o constructor, assim como o destructor serão chamados. Vamos fazer uma pequena modificação no código presente no arquivo de cabeçalho. Estas mudanças podem ser vistas logo abaixo.
01. //+------------------------------------------------------------------+ 02. class C_Regression 03. { 04. private : 05. //+----------------+ 06. public : 07. //+----------------+ 08. C_Regression() 09. { 10. datetime dt0 = TimeCurrent(), 11. dt1[20]; 12. 13. CopyTime(NULL, NULL, dt0, dt1.Size(), dt1); 14. ObjectCreate(0, def_NameChannel, OBJ_REGRESSION, 0, dt1[0], 0, dt0, 0); 15. ChartRedraw(); 16. Print("Running ", __FUNCTION__, " in ", __FILE__, " in line ", __LINE__); 17. } 18. //+----------------+ 19. ~C_Regression() 20. { 21. ObjectDelete(0, def_NameChannel); 22. ChartRedraw(); 23. Print("Running ", __FUNCTION__, " in ", __FILE__, " in line ", __LINE__); 24. } 25. //+----------------+ 26. }; 27. //+------------------------------------------------------------------+
Código 05
Ok, agora podemos visualizar diretamente no terminal quando o constructor, assim como o destructor foram executados. Isto porque, nas linhas 16 e 23 irão nos mostrar isto. Porém, para tornar a coisa ainda bem mais divertida, vamos também promover uma mudança no código 04, que é justamente o código do indicador. Esta mudança pode ser vista logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Demo" 04. //+------------------------------------------------------------------+ 05. #define def_NameChannel "Demo" 06. //+------------------------------------------------------------------+ 07. #include <Tutorial\File 01.mqh> 08. //+------------------------------------------------------------------+ 09. C_Regression *gl_Channel; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. Print("Running ", __FUNCTION__, " in ", __FILE__, " in line ", __LINE__); 14. gl_Channel = new C_Regression; 15. 16. return INIT_SUCCEEDED; 17. }; 18. //+------------------------------------------------------------------+ 19. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 20. { 21. return rates_total; 22. }; 23. //+------------------------------------------------------------------+ 24. void OnDeinit(const int reason) 25. { 26. delete gl_Channel; 27. 28. Print("Running ", __FUNCTION__, " in ", __FILE__, " in line ", __LINE__); 29. }; 30. //+------------------------------------------------------------------+
Código 06
Agora preste muita, mas muita atenção meu caro leitor. De preferência largue tudo o que estiver fazendo, e apenas foque no que será explicado no artigo. Pois isto irá fazer uma enorme diferença daqui para frente.
Observe as linhas 14 e 26 deste código 06. Note que estamos fazendo algo que para a grande maioria não faz nenhum sentido. Mas que para você começará a fazer todo o sentido. Se o que eu afirmei a pouco, de que podemos controlar quando e onde uma classe será construída e quando e onde ela será destruída. As mensagens das linhas 13 e 28 irão correr antes e depois das mensagens colocadas lá no código 05. Já que neste meio tempo é que a classe estará disponível para ser utilizada. Porém se eu estiver errado. Estas mesmas mensagens das linhas 13 e 28 do código 06, serão colocadas depois das mensagens colocadas no código 05. Para verificar isto, precisamos executar este indicador em algum gráfico. E isto é feito na animação logo abaixo.

Animação 01
Talvez esta animação tenha ficado rápido demais. Então vamos capturar um momento específico da mesma. Este é mostrado logo abaixo.

Imagem 01
Agora observe atentamente as seguintes mensagens que estão nesta imagem 01. A primeira mensagem informa, que estamos executando OnInit dentro do Code 01.mq5 e na linha 13. Isto bate perfeitamente com o que seria esperado. Visto que o código 06 é exatamente o arquivo mencionado na mensagem. A segunda mensagem informa, que estamos executando o constructor dentro do arquivo File 01.mqh na linha 16. Novamente, a informação plotada corresponde ao esperado. Já que o File 01.mqh se trata justamente do código 05. Ou seja, com relação a questão sobre onde e quando queremos inicializar a classe, está tudo dentro do esperado.
Agora vamos a segunda etapa. A próxima mensagem informa que estamos executando o destructor no arquivo File 01.mqh na linha 23. Perfeito. Já a última mensagem nos informa que estamos executando OnDeinit no arquivo Code 01.mq5 na linha 28. Simplesmente fantástico. O código funcionou perfeitamente, nos dando a capacidade de dizer onde e quando a classe seria criada e destruída. Coisa que antes não acontecia. Se você não acredita, vamos fazer o seguinte: Vamos utilizar o código 03, que se trata do código original do indicador e voltar a compilar o mesmo. Mas com um pequeno detalhe. Iremos adicionar as mesmas mensagens que foram utilizadas aqui no código 06. Assim o código 03, irá ficar como mostrado logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Demo" 04. //+------------------------------------------------------------------+ 05. #define def_NameChannel "Demo" 06. //+------------------------------------------------------------------+ 07. #include <Tutorial\File 01.mqh> 08. //+------------------------------------------------------------------+ 09. C_Regression gl_Channel; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. Print("Running ", __FUNCTION__, " in ", __FILE__, " in line ", __LINE__); 14. 15. return INIT_SUCCEEDED; 16. }; 17. //+------------------------------------------------------------------+ 18. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 19. { 20. return rates_total; 21. }; 22. //+------------------------------------------------------------------+ 23. void OnDeinit(const int reason) 24. { 25. Print("Running ", __FUNCTION__, " in ", __FILE__, " in line ", __LINE__); 26. }; 27. //+------------------------------------------------------------------+ 28.
Código 07
Perceba que agora o código já não faz mais uso dos operadores new e delete, como acontecia no código 06. Porém, este código 07, tem o mesmo comportamento do código 03. Sendo assim, podemos verificar quando e onde a classe está sendo criada e também destruída. Para facilitar o entendimento, vamos ver a animação logo abaixo. Que demonstra a execução deste código 07.

Animação 02
Como muito provavelmente esta animação também deve ter ficado um tanto quanto rápida. Vamos dar uma olhada no momento final da mesma. Isto pode ser visto com calma na imagem logo abaixo.

Imagem 02
Agora observe que nesta imagem 02, podemos perceber claramente o fato de que o comportamento do código ser totalmente diferente do que pode ser visto na imagem 01. Neste caso, podemos notar que o constructor está sendo chamado ANTES do código presente em OnInit. Da mesma maneira que o destructor é chamado DEPOIS do código presente em OnDeinit. Comprovando assim, que um simples detalhe, no modo de implementar as coisas pode fazer toda a diferença no resultado final obtido.
Ok, meu caro com relação a esta questão, acho que estou conseguindo entender como as coisas trabalham, e como as devemos implementar. Mas e quanto a questão referente ao código do script? Digo isto, pelo fato de que, no indicador, a variável que referência à classe é do tipo global. Podendo ser utilizada em qualquer ponto do código. No entanto, no código 02, onde temos um script sendo implementado. A variável que referência à classe é do tipo local. Será que isto faz alguma diferença no final das contas. Ou é tudo a mesma coisa? De fato esta é uma dúvida bastante pertinente. Isto pelo simples fato, de que esta é uma questão um tanto quanto complicada a princípio de responder meu caro leitor. Pelo menos até este momento, acredito que não houve uma oportunidade de que ela fosse devidamente explicada. Isto por que, tudo vai depender de como você pretende trabalhar com o código da sua classe.
Vou tentar deixar isto um pouco mais palatável. Pois simplesmente dizendo que é ou não é igual, ao que vimos acontecer com o indicador. Não ajuda em quase nada. Muito pelo contrário, acaba gerando ainda mais dúvidas a este respeito. Porém para que você possa entender melhor isto, precisaremos adicionar algumas coisas ao código. E como pretendo colocar estes arquivos usados no artigo, todos disponíveis, para seja possível você os estudar com mais calma. Vamos criar um outro arquivo de cabeçalho, que a princípio será uma cópia quase exata do arquivo visto no código 01. Mas estou fazendo isto, justamente para evitar que você venha a ter dúvidas de porque as coisas estarem sendo implementadas desta ou daquela maneira. Assim fica mais simples estudar as diferenças no código. Assim sendo, o segundo arquivo de cabeçalho pode ser visto logo abaixo.
01. //+------------------------------------------------------------------+ 02. class C_Regression 03. { 04. private : 05. //+----------------+ 06. public : 07. //+----------------+ 08. C_Regression() 09. { 10. datetime dt0 = TimeCurrent(), 11. dt1[20]; 12. 13. CopyTime(NULL, NULL, dt0, dt1.Size(), dt1); 14. ObjectCreate(0, def_NameChannel, OBJ_REGRESSION, 0, dt1[0], 0, dt0, 0); 15. ChartRedraw(); 16. PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 17. } 18. //+----------------+ 19. ~C_Regression() 20. { 21. ObjectDelete(0, def_NameChannel); 22. ChartRedraw(); 23. PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 24. } 25. //+----------------+ 26. void PrintMsg(const string msg) 27. { 28. Print(msg); 29. } 30. //+----------------+ 31. }; 32. //+------------------------------------------------------------------+
Código 08
Note que neste código 08 existem pequenas diferenças nas linhas 16 e 23, frente ao que foi criado no código 05. Estas pequenas diferenças fazem com que venhamos a utilizar o procedimento da linha 26 deste mesmo código 08. Preste atenção a isto, pois no anexo, este mesmo arquivo 08 estará diferente, devido a algo que iremos fazer nele daqui a pouco.
Ok, então vamos agora partir para o que seria o arquivo contendo o código de script. Porém, vamos adicionar uma pequena mudança no código como pode ser visto logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameChannel "Demo" 05. //+------------------------------------------------------------------+ 06. #include <Tutorial\File 02.mqh> 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. C_Regression channel; 11. 12. channel.PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 13. 14. Sleep(2000); 15. } 16. //+------------------------------------------------------------------+
Código 09
Ok, note que agora na linha 12 do código 09, temos a possibilidade de enviar uma mensagem para dentro da classe, usando assim um método que acabamos de implementar. Até neste ponto, acredito que nenhum de vocês estejam com alguma dúvida com relação ao que será impresso no terminal. Porém de qualquer forma, você pode ver isto, ao olhar a imagem logo abaixo.

Imagem 03
Ok, agora vamos ao primeiro dos problemas a serem explicados como se resolve o mesmo. No artigo Do básico ao intermediário: Sobrecarga mencionei algumas coisas que podemos fazer para poder utilizar um mesmo procedimento ou função, no que se refere ao nome da mesma, porém com objetivos diferentes. Apesar de aquele artigo, aparentemente não ter tido muita atenção, é importante que você fique atento as coisas demonstradas ali. Pois agora iremos fazer algo muito parecido. Até acreditei que já tinha falado sobre o que iremos fazer em um outro artigo. Mas se o fiz, não me lembro, já que algumas informações foram colocadas no meu outro perfil de artigos. Bem, de qualquer forma, vamos ver como podemos proceder aqui.
No artigo Do básico ao intermediário: Passagem por valor ou por referência expliquei algumas coisas que serão complementadas neste artigo. Devido justamente a uma questão bem interessante e que merece ser devidamente explicada.
Agora pense na seguinte situação, meu caro leitor. Como você já deve saber, variáveis globais nunca devem ser a sua primeira escolha. O ideal é sempre começar utilizando variáveis locais. Expliquei um pouco sobre isto nos artigos sobre variáveis. Talvez este lhe ajude a entender melhor esta questão Do básico ao intermediário: Variáveis (II). Assim sendo, temos no código 09, uma variável local sendo definida, está nos permite referenciar a classe. Porém, suponhamos que você precise referenciar esta mesma classe em um outro ponto do código. Como por exemplo dentro de um outro procedimento.
Se você já tem a classe previamente sendo referenciada, como podemos ver na linha dez do código 09. O fato de criar uma outra instância que venha a referenciar. Não irá necessariamente vir a saber de todos os dados ou registros existentes na classe declarada na linha dez. Salvo o fato de que nenhum valor foi modificado ainda, o que normalmente não é o que de fato acontece, na prática. Então, começamos a ter alguns problemas. O primeiro é o fato de que temos que repassar referência feita na linha dez, para dentro do procedimento que precisa de acesso a classe. Isto pode ser feito, da forma como é mostrado logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameChannel "Demo" 05. //+------------------------------------------------------------------+ 06. #include <Tutorial\File 02.mqh> 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. C_Regression channel; 11. 12. channel.PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 13. 14. Checking(channel); 15. 16. Sleep(2000); 17. } 18. //+------------------------------------------------------------------+ 19. void Checking(C_Regression channel) 20. { 21. channel.PrintMsg("Demonstrating the passage from a class to a procedure."); 22. } 23. //+------------------------------------------------------------------+
Código 10
Agora atenção aos detalhes. Primeiro, se você tentar compilar este código 10, irá ver o compilador lhe enviar a seguinte mensagem:

Imagem 04
Esta mensagem informa, que precisamos utilizar passagem por referência aqui. Assim, para resolver esta questão, mudamos o código para que ele venha a ficar como mostrado logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameChannel "Demo" 05. //+------------------------------------------------------------------+ 06. #include <Tutorial\File 02.mqh> 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. C_Regression channel; 11. 12. channel.PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 13. 14. Checking(channel); 15. 16. Sleep(2000); 17. } 18. //+------------------------------------------------------------------+ 19. void Checking(C_Regression &channel) 20. { 21. channel.PrintMsg("Demonstrating the passage from a class to a procedure."); 22. } 23. //+------------------------------------------------------------------+
Código 11
Agora o código será compilado sem problema. Porém, toda via e, entretanto existe um problema em potencial aqui. Pois ao transferimos a referência à classe, para dentro de um procedimento ou função, corremos o risco de que algo seja modificado sem que notemos isto. E não queremos que o procedimento ou função, possa vir a fazer este tipo de modificação acidentalmente, seja por distração, seja por qualquer outro motivo. Desta forma precisamos considerar uma forma de travar as coisas. E isto foi explicado nos artigos anteriores. No entanto, não foi explicado um segundo passo que precisemos dar. Sendo assim, a primeira coisa a ser feita, é modificar novamente o script, de forma que ele fique como mostrado logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameChannel "Demo" 05. //+------------------------------------------------------------------+ 06. #include <Tutorial\File 02.mqh> 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. C_Regression channel; 11. 12. channel.PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 13. 14. Checking(channel); 15. 16. Sleep(2000); 17. } 18. //+------------------------------------------------------------------+ 19. void Checking(const C_Regression &channel) 20. { 21. channel.PrintMsg("Demonstrating the passage from a class to a procedure."); 22. } 23. //+------------------------------------------------------------------+
Código 12
Ok, fazendo esta modificação mostrada no código 12, garantimos que o procedimento ou função, não venha a modificar acidentalmente algum registro dentro da classe. Porém, ao fazermos isto, nos é gerado um outro erro. E este é visto logo abaixo.

Imagem 05
Este erro é bem curioso e engraçado aqui no MQL5. Porém quando o assunto é C++, a coisa pode realmente ficar muito perigosa, se o programador decidir utilizar certos meios para burlar este erro. Mas isto não vem ao caso. A questão aqui é que o compilador está nos dizendo que estamos tentando efetuar uma chamada, que pode modificar as coisas. Porém utilizando um valor constante. Perceba o seguinte detalhe nesta mensagem, meu caro leitor. Se a referência que estamos utilizando, não pode ser modificada, então qualquer valor dentro desta referência, também não pode ser modificada. No entanto, se a referência pode vir a ser modificada, a mesma coisa poderá ocorrer com os valores dentro desta referência.
Assim sendo, precisamos modificar isto, a fim de garantir que tudo continue funcionando perfeitamente bem. E é justamente aqui onde surge a necessidade de se saber como efetuar a sobrecarga. Neste caso específico, não precisaremos fazer a sobrecarga. Mesmo por que, fazer isto não traria grandes diferenças no final das contas. Sendo desta maneira, podemos simplesmente voltar ao arquivo de cabeçalho e o modificar como mostrado logo abaixo.
01. //+------------------------------------------------------------------+ 02. class C_Regression 03. { 04. private : 05. //+----------------+ 06. public : 07. //+----------------+ 08. C_Regression() 09. { 10. datetime dt0 = TimeCurrent(), 11. dt1[20]; 12. 13. CopyTime(NULL, NULL, dt0, dt1.Size(), dt1); 14. ObjectCreate(0, def_NameChannel, OBJ_REGRESSION, 0, dt1[0], 0, dt0, 0); 15. ChartRedraw(); 16. PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 17. } 18. //+----------------+ 19. ~C_Regression() 20. { 21. ObjectDelete(0, def_NameChannel); 22. ChartRedraw(); 23. PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 24. } 25. //+----------------+ 26. void PrintMsg(const string msg) const 27. { 28. Print(msg); 29. } 30. //+----------------+ 31. }; 32. //+------------------------------------------------------------------+
Código 13
Veja que tudo que precisamos fazer é converter o método, declarado na linha 26 em um método constante. Assim o compilador irá conseguir interpretar de maneira correta, o código que desejamos construir. Uma vez compilado o resultado pode ser visto na animação logo abaixo.

Animação 03
Muito bem, agora vamos fazer uma última coisa que também se encaixa perfeitamente no conteúdo deste artigo. E ao mesmo tempo irá ajudar a responder à questão de como devemos agir em certos tipos de situação. Nos artigos anteriores, vimos que podemos repassar valores para dentro do constructor. Isto a fim de termos a capacidade de controlar a inicialização do sistema. Ok, com base no que foi mostrado até este momento, você com toda a certeza, já sabe como agir na maior parte dos casos. Porém como você deve agir, caso necessite utilizar os operadores new e delete a fim de controlar quando e onde a classe será inicializada e finalizada? Esta é de fato uma pergunta da qual poucos se fazem, antes de enfrentar o problema de frente. Mas aqui vamos ver como resolver este tipo de problema, antes mesmo que ele venha a surgir.
Para começar note que no código 12 e no código 13, não existe a necessidade de repassar algum valor para dentro do constructor. Então vamos mudar isto, de maneira a precisarmos repassar algum valor. Só para saber como fazer isto, caso venhamos a ter de necessitar utilizar os operadores new e delete, no futuro.
01. //+------------------------------------------------------------------+ 02. class C_Regression 03. { 04. private : 05. //+----------------+ 06. public : 07. //+----------------+ 08. C_Regression(const string msg) 09. { 10. datetime dt0 = TimeCurrent(), 11. dt1[20]; 12. 13. CopyTime(NULL, NULL, dt0, dt1.Size(), dt1); 14. ObjectCreate(0, def_NameChannel, OBJ_REGRESSION, 0, dt1[0], 0, dt0, 0); 15. ChartRedraw(); 16. PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 17. PrintMsg("Message received: " + msg); 18. } 19. //+----------------+ 20. ~C_Regression() 21. { 22. ObjectDelete(0, def_NameChannel); 23. ChartRedraw(); 24. PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 25. } 26. //+----------------+ 27. void PrintMsg(const string msg) const 28. { 29. Print(msg); 30. } 31. //+----------------+ 32. }; 33. //+------------------------------------------------------------------+
Código 14
Neste código 14, podemos ver que agora precisamos repassar alguma informação para o constructor. E esta mesma informação será mostrada no terminal, devido justamente a linha 17. Agora vamos ver como fica o código do script. Este é mostrado logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameChannel "Demo" 05. //+------------------------------------------------------------------+ 06. #include <Tutorial\File 03.mqh> 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. C_Regression channel(StringFormat("Init in line %d", __LINE__)); 11. 12. channel.PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 13. 14. Checking(channel); 15. 16. Sleep(2000); 17. } 18. //+------------------------------------------------------------------+ 19. void Checking(const C_Regression &channel) 20. { 21. channel.PrintMsg("Demonstrating the passage from a class to a procedure."); 22. } 23. //+------------------------------------------------------------------+
Código 15
Observe atentamente a linha dez deste código 15. Pois quando este mesmo código vier a ser executado teremos como resultado, o que é mostrado logo abaixo.

Imagem 06
Simplesmente perfeito, mostrando que de fato está funcionando como era imaginado. Agora vamos modificar este script a fim de utilizar os operadores new e delete. No caso iremos fazer como é mostrado no código logo na sequência.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameChannel "Demo" 05. //+------------------------------------------------------------------+ 06. #include <Tutorial\File 03.mqh> 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. C_Regression *channel(StringFormat("Init in line %d", __LINE__)); 11. 12. channel.PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 13. 14. Checking(channel); 15. 16. Sleep(2000); 17. } 18. //+------------------------------------------------------------------+ 19. void Checking(const C_Regression &channel) 20. { 21. channel.PrintMsg("Demonstrating the passage from a class to a procedure."); 22. } 23. //+------------------------------------------------------------------+
Código 16
Se você tentar compilar este código 16, irá receber a seguinte resposta do compilador.

Imagem 07
Ou seja, a forma como a linha dez está sendo implementada está errada. Não pelo fato de que estamos fazendo algo parecido com o que foi feito no código 04. Mas sim pelo motivo de que NÃO É ASSIM que inicializaremos as coisas na classe. Perceba o seguinte: O código 04 será compilado, apesar de não apresentar nenhum resultado no gráfico. Já este código 16, NÃO SERÁ COMPILADO. Pois estamos tentando inicializar a classe da maneira errada.
Para que a classe, ou melhor dizendo, o constructor da classe, venha a receber algum argumento, quando estamos tentando fazer algo parecido com o que foi visto no código 04. Precisamos recorrer a uma metodologia um pouco diferente. Assim, este mesmo código 16, para poder ser compilado, precisa ser construído como mostrado logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameChannel "Demo" 05. //+------------------------------------------------------------------+ 06. #include <Tutorial\File 03.mqh> 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. C_Regression *channel = new C_Regression(StringFormat("Init in line %d", __LINE__)); 11. 12. (*channel).PrintMsg(StringFormat("Running %s in %s in line %d", __FUNCTION__, __FILE__, __LINE__)); 13. 14. Checking(channel); 15. 16. Sleep(2000); 17. 18. delete channel; 19. } 20. //+------------------------------------------------------------------+ 21. void Checking(const C_Regression &channel) 22. { 23. channel.PrintMsg("Demonstrating the passage from a class to a procedure."); 24. } 25. //+------------------------------------------------------------------+
Código 17
Normalmente esta linha dez vista no código 17, pode ser dividida em duas etapas. Porém como desejo inicializar a classe no mesmo ponto onde ela está sendo declarada. Fica mais fácil, modelar o código da maneira como ele pode ser visto acima. No entanto isto não muda o fato de que estamos fazendo algo muito parecido com o que foi feito no código 06. Onde usando os operadores new e delete, passamos a controlar onde e como a classe seria inicializada e destruída.
Portanto, diferente do que muitos poderiam ficar se questionando. A forma de repassar parâmetros de inicialização para o constructor, não muda. Apenas precisamos ajustar um pouco mais as coisas. E no final o resultado é o que podemos ver logo abaixo.

Imagem 08
Considerações finais
Neste artigo foi demonstrado como podemos controlar melhor o nosso código, ao utilizarmos a programação orientada em objetos. Apesar de que, ainda estamos apenas no início de todas as coisas que podem ser abordadas quando o assunto é programação orientada em objetos. Mas o que foi visto e explicado até este momento, já nos permite retornar a questão sobre a implementação das árvores. Assim sendo, procure praticar e estudar com muita calma e dedicação estes conceitos que apareceram nestes três últimos artigos, meu caro leitor. Pois estes mesmos conceitos, irão lhe acompanhar pelo resto de sua vida aqui no MQL5. Além é claro servir como uma rampa a fim de lhe ajudar, caso você venha a desejar, aprender a programar em C++.
No demais, me despeço por hora, e nos vemos no próximo artigo. Onde iremos voltar a falar sobre filas, listas e árvores.
| Arquivo MQ5 | Descrição |
|---|---|
| Indicador\Code 01 | Arquivo de demonstração |
| Indicador\Code 02 | Arquivo de demonstração |
| Script\Code 01 | Arquivo de demonstração |
| Script\Code 02 | Arquivo de demonstração |
| Script\Code 03 | Arquivo de demonstração |
| Script\Code 04 | Arquivo de demonstração |
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Simulação de mercado: Position View (XVI)
Criando um Painel de Administração de Trading em MQL5 (Parte VIII): Painel de Análises
Portfolio Risk Model using Kelly Criterion and Monte Carlo Simulation
MQL5 Trading Toolkit (Parte 4): Desenvolvendo uma Biblioteca EX5 de Gerenciamento de Histórico
- 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