preview
Do básico ao intermediário: Passagem por valor ou por referência

Do básico ao intermediário: Passagem por valor ou por referência

MetaTrader 5Exemplos | 16 agosto 2024, 13:00
13 0
CODE X
CODE X

Introdução

O conteúdo exposto aqui, visa e tem como objetivo, pura e simplesmente a didática. De modo algum deve ser encarado como uma aplicação final, onde o objetivo não seja o estudo dos conceitos aqui mostrados.

No artigo anterior Do básico ao intermediário: Operadores, foi explicado um pouco sobre operações aritméticas e lógicas. Apesar de ter sido algo bem superficial, já é o bastante para que possamos entrar em outros assuntos. Com o decorrer do tempo, e conforme os artigos forem sendo escritos. Iremos nos aprofundar aos poucos nos assuntos relacionados neste primeiro momento.

Então tenha paciência e estude com calma o material que está sendo postado. Pois os resultados não aparecem da noite para o dia. Eles surgem com o tempo. E se estudar o material desde agora, você nem sentirá que a carga está sendo aumentada. Pois bem, como mencionado no artigo anterior, aqui iriamos começar a falar de operadores de controle. Porém, antes de falarmos sobre isto. É interessante falar sobre uma outra coisa. Assim será mais interessante e divertido, ver o que se pode fazer com os operadores de controle.

Para o perfeito e completo entendimento do que será explicando e apresentado neste artigo, existe o pré-requisito: O mesmo é entender e compreender a diferença entre uma variável e uma constante. Se você não entende, ou desconhece esta diferença, veja o artigo Do básico ao intermediário: Variáveis (I)

Uma das coisas que mais gera dúvidas e erros nos programas de pessoas que estão começando. É saber, quando usar valores ou referência em suas funções e procedimentos. Isto porque, dependendo do caso, é mais prático, a passagem por referência. No entanto, a passagem por valor, em muitos casos é mais segura. Mas então quando usar uma ou outra? Isto depende, meu caro leitor. Não existe de fato uma regra clara e definitiva para tal prática. Isto por que, em alguns casos, de fato a passagem por referência é utilizada. Enquanto em outros a passagem de valor será a requerida.

Normalmente o compilador faz a escolha de maneira a tornar o código executável o mais eficiente possível. Porém, você precisa entender o que cada situação pode requerer. A fim de conseguir construir um código seguro e eficiente.


Passagem por valor

Para começarmos a entender isto na prática, o mais adequado é utilizar um código que seja o mais simples quanto for possível de ser implementado. Então vamos começar vendo um primeiro modelo de utilização. Este será o de passagem por valor. Sendo aplicado no código que é visto logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     Procedure(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. void Procedure(int arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16.     arg1 = arg1 + 3;
17.     Print(__FUNCTION__, " : #2 => ", arg1);
18. }
19. //+------------------------------------------------------------------+

Código 01

Quando este código 01 for executado. Você verá no terminal do MetaTrader 5, algo como o que é mostrado na imagem 01, logo abaixo.


Imagem 01

Sei que para muitos, isto que está sendo mostrado na imagem 01, parece ser muito complexo. Mas como você, está disposto a aprender da maneira correta, como programar em MQL5. Vamos entender o que está imagem está nos dizendo. Para isto será preciso que você acompanhe tanto o que é visto no código 01, quanto o que será visto na imagem 01.

Com base no que foi explicado nos artigos anteriores, você já deve saber que na linha seis estamos definindo uma variável, com um dado valor. Já na linha oito, estamos imprimindo esta mesma variável. Porém ao observar a imagem, você nota que existem outras informações sendo impressas. Que no caso é o nome do procedimento, ou função, em que se encontra a linha oito.

Agora preste atenção meu caro leitor. Durante a fase de compilação do código 01, o compilador, assim que encontrar a macro predefinida __FUNCTION__, irá procurar qual é o nome da atual rotina. No caso o nome está sendo definido na linha quatro. Ou seja, OnStart. Ao encontrar este nome, o compilador irá substituir a palavra __FUNCTION__ pela palavra OnStart, criando assim uma nova string para ser impressa no terminal. Isto por que Print, está direcionada a saída padrão. Que no caso é o terminal com você pode notar na imagem 01. Por conta disto, temos as informações tanto da rotina, quanto do valor da variável declarada na linha seis. Existem outras macros predefinidas. E cada uma é bastante útil em cada caso particular, estude elas, pois as mesmas facilitam bastante o trabalho de acompanhar o que seu código estará fazendo. Da mesma maneira que __FUNCTION__ dentro da rotina OnStart, irá ser substituída por este nome. __FUNCTION__ dentro da rotina Procedure, também será substituída. Assim está finalmente explicado o que são aquelas informações que precedem os valores numéricos.

Mas vamos voltar a questão de entender sobre usar passagem por valor. Por conta de que estamos usando um sistema de passagem por valor. Ao efetuarmos a chamada da linha nove, o valor que estiver a variável definida na linha seis, será repassado para o procedimento da linha 13. Uma observação importante, deve ser dada neste momento. Apesar de eu estar falando que estamos passando, ou melhor dizendo, copiando o valor da variável info, para dentro da variável arg1. Isto nem sempre acontece de fato. Muitas vezes o compilador, de maneira bastante inteligente, faz que que arg1, aponte para a variável info. Porém, toda via e, entretanto, e é aqui onde a coisa fica realmente bastante interessante. A variável arg1 não compartilha a mesma memória da variável info. O que acontece é que o compilador, aponta arg1 para info, de forma que arg1 pode observar e utilizar o que info tem na memória. Seria como se você pudesse olhar através de uma janela de vidro. No entanto, justamente por conta do vidro, você não pode tocar no objeto observado. A mesma coisa acontece aqui. Já que para arg1, info seria vista como sendo uma constante.

Por conta disto, que na linha 15, pedimos para imprimir o valor que arg1 contém. Ao fazemos isto, podemos observar que a segunda linha na imagem 01. Mostra exatamente o mesmo valor, tanto para arg1 quanto para info. Porém, na linha 16, mudamos o valor de arg1. Neste momento, durante a compilação, e sabendo que isto irá ocorrer na execução do código, o compilador aloca espaço para comportar a mesma quantidade de bytes que a variável info contém. No entanto, isto não muda o fato de que arg1 continua a observar a variável info. Porém quando a linhas 16 for executada. O que acontece é que, arg1 usará o valor presente na variável info como se info fosse não uma variável e sim uma constante. Fazendo assim com que arg1 crie um valor local para ela. Deste momento em diante, arg1 ficará completamente desvinculada da variável info, passando assim a ter vida própria. Por conta disto, que no momento da execução da linha 17, temos a terceira linha sendo impressa no terminal. Mostrando que de fato o valor foi modificado.

No entanto, quando o procedimento retorna, temos a execução da linha 10. Fazendo com que a quarta linha da imagem 01 seja impressa, mostrando de fato que info, continua com o valor intacto.

Isto meu caro leitor, é basicamente o mecanismo utilizado como passagem de valor. Claro que a forma exata como isto é feito, depende de como o compilador foi construído. Mas basicamente é assim que funciona.

Muito bem, isto foi a parte fácil. Mas mesmo este mecanismo de passagem de valor, pode ser visto em um código de maneira um pouco diferente. Não irei entrar em detalhes sobre isto, neste exato momento. Isto por que, antes de mencionar outras formas de se usar este mecanismo de passagem por valor. Será preciso explicar outras coisas sobre operadores e tipos de dados. Coisas estas que se forem mencionadas aqui, irá mais complicar do que esclarecer. Então vamos com calma.

No entanto, mesmo este mecanismo visto acima, pode ter uma modelagem um pouco diferente. Isto para evitar justamente o que aconteceu no código acima. Para que possamos explorar isto da maneira adequada, vamos fazer uma pequena suposição. Por qualquer motivo, você não deseja que a variável arg1, tenha o seu valor alterado. Você quer que ela apenas olhe para a variável info, e sempre que arg1 seja usada, você de fato quer que o valor presente na variável info seja utilizado.

Bem, como fazer isto? Existem diversas forma e maneira. Sendo uma delas tomar todo o cuidado, para não modificar a variável arg1, durante todo o bloco de código presente em Procedure. Porém fazer isto, apesar de parecer simples, não é. Muitas vezes, sem nos darmos conta, acabamos mudando o valor de uma variável. E só percebemos isto, devido a algum comportamento estranho da aplicação quando ela é executada. Este tipo de coisa, faz com que venhamos a perder muito tempo tentando corrigir o problema. Porém existe uma outra solução. Fazer com que o compilador trabalhe para nós. Nos dizendo que estamos tentando fazer algo que não deveria ser feito.

Para que isto fique claro. Vamos usar o código logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     Procedure(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. void Procedure(const int arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16.     arg1 = arg1 + 3;
17.     Print(__FUNCTION__, " : #2 => ", arg1);
18. }
19. //+------------------------------------------------------------------+

Código 02

Ao tentar compilar este código 02, a seguinte mensagem de erro irá lhe ser apresentada.


Imagem 02

Claramente você nota, que o compilador está dizendo que o erro se encontra na linha 16. Isto por que estamos tentando mudar o valor da variável arg1. Mas espere um pouco aí. Agora arg1 já não é mais uma variável. Ela é uma constante. Sendo assim, durante todo o código presente em Procedure, você já não poderá mais modificar o valor de arg1. Isto garante que aquele risco que tínhamos antes já não tem mais sentido. Já que o próprio compilador irá se assegurar de não permitir a modificação de arg1. Ou seja, saber o que se está fazendo, e ter os conceitos adequados, é uma mão na roda. Agiliza bastante o trabalho de programação.

Mas então, será que não podemos modificar o valor que poderá ser impresso na linha 17 do código 02? Sim, meu caro leitor, podemos modificar o valor. Desde que estejamos atribuindo-o a uma outra variável, mesmo que esta variável não esteja implícita no código. Assim para conseguir o mesmo resultado visto na imagem 01. Mas usando algo parecido com o código 02. Podemos usar um código muito próximo do que é visto logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     Procedure(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. void Procedure(const int arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16.     Print(__FUNCTION__, " : #2 => ", arg1 + 3);
17. }
18. //+------------------------------------------------------------------+

Código 03

Ao fazemos as mudanças, de forma a termos o código 03, continuamos com arg1, sendo uma constante. Porém na linha 16 deste código 03, estamos atribuindo o valor da soma de uma constante, que no caso é arg1, com uma outra constante, que é o valor três, a uma outra variável. Esta será criada pelo compilador, a fim de permitir que Print, consiga mostrar o valor correto. Mas você poderia criar uma variável somente para este propósito. No entanto, não vejo necessidade de se fazer isto. Pelo menos não neste tipo de código que está sendo mostrado.


Passagem por referência

A outra maneira de passar valores entre rotinas, é por meio de referência. Neste caso, precisamos tomar alguns cuidados extras. Mas antes de continuarmos quero dar uma pequena pausa, e explicar algo, que é muito importante neste momento.

Você meu caro leitor, deve evitar ao máximo utilizar passagem por referência, a menos que seja realmente e extremamente necessário fazer isto. Uma das coisas, que mais gera erros de difícil solução é o uso inadequado e inadvertido da passagem por referência. Em alguns casos, chega a ser quase um vício de alguns programadores. O que torna, corrigir e melhor os códigos criados um verdadeiro tormento. Então evite ao máximo utilizar passagem por referência. Ainda mais se, o objetivo for o de modificar somente um único valor. Irei mostrar um exemplo disto daqui a pouco. Mas antes vamos ver um caso onde existe a passagem por referência e o que isto faz com a aplicação. Para tal fato, vamos utilizar o código logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     Procedure(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. void Procedure(int & arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16.     arg1 = arg1 + 3;
17.     Print(__FUNCTION__, " : #2 => ", arg1);
18. }
19. //+------------------------------------------------------------------+

Código 04

Agora preste muita, mas muita atenção ao que irei mencionar nesta parte. Pois a coisa aqui, pode ficar tensa. Isto quando você estiver trabalhando em códigos realmente complexos. Ao executar este código você irá ver no terminal do MetaTrader 5, a seguinte saída mostrada na imagem abaixo.


Imagem 03

Este tipo de coisa, quando acontece de maneira não intencional, faz você perder horas, dias e até meses tentando entender por que o código não está funcionando como seria esperado. Observe que o resultado é totalmente diferente do que é mostrado na imagem 01. Justamente no que tange a quarta linha na figura 03. Mas por que isto ocorreu? Já que aparentemente o código 04 é idêntico ao código 01. Não faz o mínimo sentido, a quarta linha da imagem 03 ser diferente da quarta linha da imagem 01.

Bem meu caro leitor, apesar de parecer idêntico, os códigos 01 e 04 tem uma pequena, porém crucial diferença. Está diferença está justamente na linha 13. Fiz questão de deixar bem evidenciado para que você consiga entender. Observe a presença de um símbolo aparentemente inocente, que existe no código 04, no entanto não existe no código 01. Este símbolo < & > um E COMERCIAL, como é conhecido. É a causa da diferença entre a imagem 03 e a imagem 01. Normalmente você irá ver este mesmo símbolo sendo usados em operações de lógica bit a bit, com o objetivo de se fazer uma operação AND, isto quando o assunto é MQL5. Pois quando se trata de C e C++, além de servir para fazer uma operação AND, ele ainda server para referência variáveis em memória.

Opá. Agora o bicho pegou de vez. Pois se já é complicado entender o que um simples símbolo pode significar. Pensa só ele poder ter dois significados completamente diferentes dentro de um mesmo contexto de código. É literalmente coisa de maluco. Por isto programar em C e C++ é tão complicado, e demanda muito tempo para que se torne um programador realmente bom. Mas aqui no MQL5, a coisa é um pouco mais simples. Pelo menos não temos toda aquela complicação existente em C e C++. Justamente por conta que o MQL5 não utiliza diretamente um recurso, muito presente na linguagem C e C++, que é justamente os ponteiros.

Para que você, meu caro leitor entenda de maneira correta, por que a presença deste símbolo na linha 13 do código 04, influencia o resultado final. É preciso entender o que este símbolo na verdade significa. Para isto, irei explicar o conceito de ponteiro, presente em C / C++, sem entrar nas questões complicadas presentes no próprio conceito de ponteiros.

Um ponteiro, seria como se fosse uma variável. Mas não uma variável qualquer. Ela é uma variável que, como o nome já indica, aponta para algo. No caso, um ponteiro, aponta para uma posição de memória em que de fato existe uma variável. Sei que isto parece confuso. Usar uma variável para apontar para outra variável, é um conceito muito mais complicado do que parece. Porém este conceito é muito utilizado em C / C++, para produzir o mais variado estilo de código. Sendo um dos motivos que C e C++ ser uma das linguagens mais rápidas que existe. Ficando lado a lado com a linguagem Assembly em termos de velocidade de processamento. Mas sem entrar em detalhes sobre estes tais ponteiros. Já que isto iria lhe confundir completamente. Quando arg1 está sendo declarada, da forma como está sendo feita no código 04. NÃO ESTAMOS usando uma forma convencional de instanciar a variável info. Na verdade, arg1 é vista pelo compilador do MQL5, como sendo um ponteiro, para a variável info.

Por este motivo, quando fazemos algo, que no caso é a soma presente na linha 16, o que seria o ponteiro para a variável info. NÃO, e repito, NÀO ESTAMOS SOMANDO a variável arg1. Estamos de fato somando a variável info. Isto porque, tanto info quanto arg1 compartilha o mesmo espaço de memória. Ou seja, arg1 é info e info é arg1. Isto dentro da rotina Procedure.

Ficou confuso? Pois de fato isto é apenas a parte simples e agradável de utilizar ponteiros. Como o MQL5 NÃO USA, ou melhor, não nos permite usar, ponteiros como acontece no C / C++. A explicação de como arg1 consegue modificar info termina aqui. Você então deve pensar que arg1 e info são a mesma entidade. Mesmo estando em locais diferentes. E aparentemente sem nenhum vínculo entre elas. Em C / C++ este tipo de coisa é muito, mas muito mais complicado. E como não quero criar confusão, na sua cabeça, meu caro leitor. A coisa toda se resumi ao que foi dito até aqui.

Mas agora vem uma questão: Será que não existe uma forma de bloquear esta modificação? Isto para que arg1, mesmo quando modificada pela linha 16. Não venha a modificar junto a variável info. Sim meu caro leitor, existe sim, formas de impedir que arg1 modifique info. Uma delas é usando a passagem por valor como foi explicado no tópico anterior. Outra maneira é modificando o código 04 para o que é visto logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     Procedure(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. void Procedure(const int & arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16.     arg1 = arg1 + 3;
17.     Print(__FUNCTION__, " : #2 => ", arg1);
18. }
19. //+------------------------------------------------------------------+

Código 05

Porém ao fazermos o que é mostrado no código 05, iremos cair na mesma questão vista no código 02. Ou seja, agora arg1 será tratada como sendo uma constante. Por conta disto, a tentativa de compilar este código 05, será a mesma de tentar compilar o código 02. Ou seja, a imagem 02. Mesmo que arg1 esteja apontando para info, que é uma variável. Apesar de parecer ser um erro, isto não é de fato um erro. Muitas vezes, somos obrigados, ou melhor dizendo, forçados a fazer algo como visto no código 05. Porém ele não irá ser compilado, justamente por que a linha 16 está tentando modificar arg1. Assim a solução final para este problema será justamente adotar algo parecido com o código 03. Nascendo desta forma o código visto logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     Procedure(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. void Procedure(const int & arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16.     Print(__FUNCTION__, " : #2 => ", arg1 + 3);
17. }
18. //+------------------------------------------------------------------+

Código 06

Maravilha, com isto temos um código que funciona e se parece muito com o código 03. Porém, diferente do código 03 que utiliza passagem por valor, aqui estamos utilizando passagem por referência. O resultado da execução deste código 06 será o mesmo que é mostrado na imagem 01.

Neste ponto, podemos voltar a questão falada no começo deste tópico. Que era justamente o de evitar usar passagem de referência a fim de modificar o valor de uma e somente uma variável.

Quando você, por qualquer motivo, necessitar de que uma rotina modifique o valor de uma variável. Principalmente uma do tipo básico. Você deve dar preferência a utilizar uma função ao invés de um procedimento. Por isto as funções existem nas linguagens de programação. Elas não foram criadas para embelezar um código. Mas sim, para evitar problemas no mesmo.

Agora supondo, que de fato você queira que a variável info, seja modificada. Não pelo código onde ela se encontra. Mas sim por uma rotina qualquer que você tenha criado. A forma correta, ou melhor dizendo, a forma mais adequada de fazer isto é, usando um código parecido como o que é visto logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     info = Function(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. int Function(const int & arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16. 
17.     return arg1 + 3;
18. }
19. //+------------------------------------------------------------------+

Código 07

Observe que neste código 07, ainda assim, estou utilizando a passagem por referência. Porém aqui não temos coisas indesejadas acontecendo. Isto por que, olhando a linha 09, notamos que de fato o valor de info será modificada. De forma e maneira completamente controlada pelo nosso código. Dificilmente um código escrito desta maneira, utilizando funções irá lhe dar problemas. E o resultado da execução deste código 07, pode ser observado logo abaixo.


Imagem 04

Veja quem em nenhum momento, ficamos na dúvida do que está ocorrendo. Pois basta olhar o código para que consigamos compreender o motivo pelo qual o valor de info foi modificado dentro do bloco OnStart. Agora tome cuidado para não fazer algo do tipo mostrado logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     info = Function(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. int Function(int & arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16. 
17.     return (arg1 = arg1 + 3) + 3;
18. }
19. //+------------------------------------------------------------------+

Código 08

É muito comum, alguns programadores desejarem fazer algo, tentando um objetivo e acabarem fazendo uma tremenda de uma confusão. Este código 08 é um exemplo disto. Sei que não é um exemplo adequado o suficiente. Já que quando este tipo de coisa ocorre. Ocorre justamente em operações bem complexas e envolvendo muitas variáveis e passos. Mas isto daqui é apenas para exemplificar um tipo de condição.

Agora lhe pergunto meu caro leitor. Qual é o valor de info a ser impresso na linha 10 do código 08? Bem, talvez você não tenha notado, mas arg1 agora não é mais uma constante. Assim sendo qual o valor de info a ser impresso na linha 10? Talvez você tenha ficado um tanto quanto confuso, isto por conta da linha 17 e o fato de que tanto o valor retornado, quanto o valor atribuído a variável info está sendo modificado ao mesmo tempo. Mas antes de explicar por que não é assim tão confuso. Vamos ver o resultado impresso no terminal. Este pode ser observado na imagem 05 logo abaixo.


Imagem 05

Note que o valor não é 13, mas sim 16. Por que? O motivo é que apesar de arg1 ser um ponteiro para info. E na linha 17 estamos atribuindo o valor 13 para ser usado em arg1 e por consequência para info. O valor de retorno irá sobrepor o valor 13, já que ele estará somando outros 3 ao valor atribuído na linha 17. Sendo assim o resultado impresso é de fato 16. Justamente por conta de que o retorno da função está sendo atribuído a variável info.

No entanto, se ao invés de atribuir o valor a info na linha nove, você simplesmente ignorasse o valor que está sendo retornado pela função. Você fatalmente iria acabar caindo na mesma condição vista nos códigos anteriores. Onde o valor de info seria modificado, porém você tentando entender o código, e assim corrigir o código. Iria gastar um belo tempo apenas para encontrar a falha. E lembre-se do seguinte fato, normalmente códigos que contem este tipo de falha, costuma ter diversos arquivos com centenas de linhas em cada arquivo. Então dá-lhe trabalho para encontrar onde está o erro.

E quanto ao valor de retorno, não é raro que eles muitas das vezes são simplesmente ignorados. Já que não somos obrigados a atribuir, ou mesmo utilizar o valor de retorno. Sendo assim tome cuidado ao fazer este tipo de construção em seus códigos. Apesar da grande facilidade que a passagem por referência pode vir a nos oferecer. Não é difícil e tão pouco improvável que você em algum momento venha a ter problemas com este tipo de abordagem. Ainda mais quando tem pouco experiência em programação.

Bem, como último ponto a ser falado aqui. Existe um outro problema, como a passagem por referência. E muitas vezes, ele acontece devido a uma falha no momento de repassar os argumentos para a rotina que os irá processar. Quando estamos trabalhando com passagem por valor, a possibilidade de falhas devido a troca acidental de um argumento por outro. Tem pouco efeito no código em geral. Muitas vezes durante o processo de compilação acabamos recebendo algum alerta do compilador a este respeito. Desde que os tipos de dados esperados sejam diferentes. Mas quando usamos passagem por referência, as vezes o compilador nos alerta de que existe uma falha ali. Porém, como os tipos estão errados e não nos atentamos ao fato de que é a ordem dos argumentos que está errada. Acabamos por cometer um erro, que torna ainda mais difícil encontrar a falha de uma aplicação. Por exemplo, supondo que você queira fazer uma função onde você informa uma data e uma dada hora. A função soma a quantidade de horas que você informou, e na variável referente ao número de horas, você quer que esta mesma função converta o tempo para segundo. Ok, isto parece ser uma tarefa bem simples e prática de fazer. Podendo ser feita com o código mostrado logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     uint        info = 10;
07.     datetime    dt = D'17.07.2024';
08. 
09.     Print(__FUNCTION__, " : #1 => ", info, " :: ", dt);
10.     dt = Function(dt, info);
11.     Print(__FUNCTION__, " : #2 => ", info, " :: ", dt);
12. }
13. //+------------------------------------------------------------------+
14. datetime Function(ulong &arg1, datetime arg2)
15. {
16.     Print(__FUNCTION__, " : #1 => ", arg1, " :: ", arg2);
17. 
18.     return arg2 + (arg1 = arg1 * 3600);
19. }
20. //+------------------------------------------------------------------+

Código 09

Porém ao pedir para o compilador criar o executável, o compilador dispara um alerta como mostrado abaixo. Este tipo de alerta não impede de o código ser compilado.


Imagem 06

Porém como você é um programador que sempre fica em alerta a cada mensagem enviada pelo compilador. Você logo vai até a linha que o compilador está mencionado e faz a devida correção. No caso é simplesmente deixar explicito o fato de que o valor do tipo ulong deverá ser convertido para o tipo datetime. Um detalhe importante: Ambos valores tem a mesma largura de bits. Então este alerta muitas vezes é ignorado por muita gente. Assim aquela linha 18 do código 09, é modificada de forma a ficar como mostrado logo abaixo.

return arg2 + (datetime)(arg1 = arg1 * 3600);

Agora aquele alerta do compilador já não é mais disparado. Porém ao executar o código, para a sua surpresa e total espanto, o resultado é o que se vê logo abaixo.


Imagem 07

Neste momento, você logo fica todo desconcertado. Já que seu código aparentemente não funciona. E é aqui onde mora o grande detalhe: Seu código não está de fato errado. Ele está apenas com um pequeno erro. E este tipo de erro muitas vezes é difícil de descobrir. Aqui é fácil pois o código é: Primeiro simples e bem curto. Segundo: Aqui estamos apenas demonstrando algumas funcionalidades básicas. Na prática, dificilmente você ou qualquer outro programador, irá ficar digitando um pequeno trecho de código e testando o mesmo. Normalmente o que acontece na prática, é que você cria uma série de coisas e as coloca para interagir entre si. Em um dado momento, você vai testar para verificar se está tudo funcionando. Algumas vezes irá notar alguma falha e em outras não. O que mais dificulta encontrar uma falha em um código mais complexo, é que muitas das vezes, a rotina que está causando a falha. Em algumas linhas do código, causa a falha, enquanto em outras parece funcionar sem problemas. Neste tipo de cenário, a coisa realmente fica bastante complicada de ser resolvida.

Para encerrar, vamos entender onde está a falha aqui. Ela está justamente na linha dez. Neste momento você pode estar olhando e pensando: Cara, mas como assim, a falha está na linha 10? A falha com toda a certeza está dentro da rotina da linha 14. Muito provavelmente na linha 18. Ela com toda a certeza não está na linha dez.

Mas é aí que você se engana meu caro leitor. Preste atenção e pense comigo. Você quer que a variável info tenha o valor final dado em segundo. E quer que a variável dt, inicie na data declarada na linha sete, porém com o valor ajustado em horas, com base na variável info. Este ajuste será feito pela rotina da linha 14. Justamente na linha 18. Até aí tudo certo. No entanto existe um pequeno erro aqui no código. Como o tipo datetime é de 8 bytes, assim como o tipo ulong também é de 8 bytes. A rotina não irá reclamar do diferente tipo de dado que está entrando. Mas veja que info é do tipo uint. Você logo pensa é aqui que está o erro. Mas não é aqui. Como 24 hora contem 86.400 segundos. O valor do tipo uint que é de 4 bytes é o mínimo que precisamos para comportar o valor correto. Você poderia usar um outro tipo menor. Porém iria correr o risco de ter um erro no valor retornado.

Agora como arg1 é um ponteiro, sendo assim ele irá estar usando passagem por referência. Já arg2 estará usando passagem por valor. Então o erro está de fato na linha dez. Já que o primeiro argumento deveria ser a variável info. Já que ela é que será modificada. Já a variável dt, somente será modificada pelo valor que a função irá retornar. Tendo isto em mente, você troca a linha dez, pelo código visto logo abaixo.

    dt = Function(info, dt);

Ao tentar compilar o código, o compilador irá reclamar, mostrando o seguinte erro:


Imagem 08

Então você desesperado, querendo fazer o código funcionar de qualquer maneira. E sabendo que não precisa de um valor de 8 bytes e que um valor de 4 bytes já lhe basta. Vai na linha 14 e a modifica como mostrado abaixo.

datetime Function(uint &arg1, datetime arg2)

E finalmente, o código é compilado, e agora você volta a executar o mesmo. E como milagre ao fazer isto, o tão esperado resultado lhe será apresentado de maneira correta. Como você pode ver na imagem a seguir.


Imagem 09


Considerações finais

Neste artigo foi explicado as nuances presentes em uma programação real. Claro que aqui, todos os exemplos são simples e voltados a didática. Mas mesmo assim, não deixa de ser algo extremamente divertido e prazeroso explicar como as coisas funcionam de verdade. Sei que muitos consideram este tipo de material pouco interessante. Já que existem muitos que já tem um bom tempo criando e desenvolvendo programas. Mas pergunto: Quanto tempo você perdeu ou precisou para aprender o que este singelo artigo mostrou de maneira bastante simples? Eu mesmo demorei um bom tempo para compreender por que meus códigos em C / C++ as vezes faziam coisas malucas e sem sentido. Até que finalmente consegui compreender cada detalhe envolvido.

Uma vez tendo compreendido aquilo. Todo o resto ficou muito mais simples e direto. Hoje me divirto programando em diversas linguagens diferentes e fazendo graça a cada problema que elas me apresentam. Isto por que, consegui criar uma base solida e bastante confiável vinda do C / C++. MQL5 é uma linguagem muito mais agradável, simples e fácil de explicar do que toda aquela insanidade presente em C / C++. Porém se você meu caro leitor compreender o que foi explicado e demonstrado neste artigo. Conseguirá aprender de maneira muito mais rápida como criar excelentes aplicações em MQL5. Então no próximo artigo, finalmente poderemos começar com um material bem mais divertido. Então nos vemos lá.


Arquivos anexados |
Anexo.zip (3.12 KB)
Hibridização de algoritmos populacionais. Estruturas sequenciais e paralelas Hibridização de algoritmos populacionais. Estruturas sequenciais e paralelas
Aqui, vamos mergulhar no mundo da hibridização de algoritmos de otimização, analisando três tipos principais: mistura de estratégias, hibridização sequencial e paralela. Realizaremos uma série de experimentos combinando e testando algoritmos de otimização relevantes.
DoEasy. Funções de serviço (Parte 2): Padrão "Barra Interna" DoEasy. Funções de serviço (Parte 2): Padrão "Barra Interna"
Neste artigo, continuaremos a explorar os padrões de preço na biblioteca DoEasy. Vamos desenvolver a classe do padrão "Barra Interna" das formações Price Action.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Desenvolvendo um sistema de Replay (Parte 60): Dando play no serviço (I) Desenvolvendo um sistema de Replay (Parte 60): Dando play no serviço (I)
Já faz um bom tempo que estamos mexendo apenas no indicadores. Mas agora chegou a hora de fazer o serviço voltar a executar o seu trabalho, a fim de que possamos ver o gráfico sendo construído com os dados informados. Mas como nem tudo é tão simples, será preciso ver para entender o que nos espera.