preview
Do básico ao intermediário: Template e Typename (III)

Do básico ao intermediário: Template e Typename (III)

MetaTrader 5Exemplos | 22 janeiro 2025, 09:54
106 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: Template e Typename (II), foi explicado como poderíamos lidar com algumas situações muito específicas no nosso dia a dia como programadores. Seja como uma atividade esporádica, seja como programador profissional. Aquele tipo de utilização de templates de funções e procedimentos, pode ajudar bastante em determinados momentos. Apesar de não ser algo muito comum e aplicável a todo momento aqui no MQL5. Mas é bom que você saiba que tal conceito pode ser aplicado e que ele tem seus momentos. Necessitando desta maneira que seja adequadamente compreendido, a fim de que você não venha a ficar doido. Isto quanto vier tentar mexer em um código que por ventura utiliza tal modelagem.

Porém templates não se aplicam apenas a funções e procedimentos. Eles na verdade, tem uma ampla e vasta faixa de aplicações práticas. Sendo, mais ou menos utilizados, dependendo do tipo de aplicação que você, meu caro leitor, venha a querer desenvolver. Vale lembrar, e ressaltar mais uma vez. De que podemos implementar o mesmo tipo de aplicação, sem utilização de templates. Porém, fazer uso de tal ferramenta e recurso presente no MQL5, torna, tanto a fase de implementação mais simples e prazerosa. Como também evita alguns tipos de falhas chatas e difíceis de serem detectadas.


Templates em variáveis locais

Nos dois artigos anteriores, onde falamos apenas sobre templates em funções ou procedimentos. A coisa toda era bem simples e até mesmo curiosa. Mas acredito, que devido as explicações que foram dadas deve ter ficado claro o seguinte: Templates de funções ou procedimentos, são uma forma bem agradável de jogar o trabalho para o compilador. Isto a ponto de criar a sobrecarga de uma função ou procedimento. Porém, aqueles exemplos que estavam sendo mostrado era algo muito simples. Já que o tipo de dados a ser utilizado era lançado apenas no que ser refere aos parâmetros de entrada. Seja da função, seja de um procedimento. Porém, toda via e, entretanto, a coisa pode ser levada a um nível um pouco maior.

Para entender isto, vamos começar com um exemplo bem simples e bacana. O cálculo de médias. Sim, apesar de ser algo simples, podemos ver como aplicar templates a este tipo de situação. Entretanto, quero lembrar a você, meu caro leitor, que os códigos aqui apresentados visam apenas a didática. Eles não representam necessariamente código, que seriam utilizados em uma aplicação real.

Como acredito, que você, já deve ter compreendido de maneira adequada, como uma função ou procedimento sobrecarregado funciona. Poderemos focar apenas nas partes que realmente merecem algum destaque. Eliminando assim muito do que é desnecessário colocar no código. Assim, temos em primeira mão, o código que é visto logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define PrintX(X) Print(#X, " :: ", X)
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     const double d_values[] = {2.5, 3.4, 1.25, 2.48, .85, 3.75};
09.     const long i_values[] = {8, 3, 4, 6, 4, 5, 7};
10.     
11.     PrintX(Averange(d_values));
12.     PrintX(Averange(i_values));
13. }
14. //+------------------------------------------------------------------+
15. double Averange(const double &arg[])
16. {
17.     double local = 0;
18. 
19.     for (uint c = 0; c < arg.Size(); c++)
20.         local += arg[c];
21. 
22.     return local / arg.Size();
23. }
24. //+------------------------------------------------------------------+
25. long Averange(const long &arg[])
26. {
27.     long local = 0;
28. 
29.     for (uint c = 0; c < arg.Size(); c++)
30.         local += arg[c];
31. 
32.     return local / arg.Size();
33. }
34. //+------------------------------------------------------------------+

Código 01

Aqui temos um código, bem comum, cujo objetivo é calcular a média de um conjunto de dados. Para quem não entende, o cálculo da média, se baseia, em se somar todos os valores, e depois dividir o resultado desta soma pelo número de elementos presentes no cálculo. Este tipo de cálculo é bem simples, mas apresenta um pequeno problema é que é o fato de se os valores forem positivos e também negativos. Podemos ter um valor médio distorcido, que não venha de fato a representar o que estamos realmente interessados em saber. Mas isto, é um simples detalhe, que não cabe aqui. Apenas o estou mencionado, para que você saiba que nem sempre um código, realmente nos dá a informação que era desejada, mas que foi criado por uma outra pessoa. E o fato de outro programador ter criando um dado código, irá fazer com que ele responda ao que aquele programador específico estava procurando. E não ao que você possa estar procurando.

No entanto, acredito, que não será preciso explicar como este código 01 funciona. Já que ele faz uso da sobrecarga, a fim de que a fatoração seja feita da maneira mais adequada. Assim, o resultado, do mesmo quando executado pode ser observado na imagem vista na sequência, logo abaixo.

Imagem 01

Ou seja, algo trivial e simples. Mas quero que você observe com atenção a função implementada na linha 15 e na linha 25. O que elas têm em comum? Bem, você pode dizer que são praticamente idênticas. Exceto por uma parte muito específica. Esta parte, é justamente o tipo de dado que está sendo utilizado ali. Fora isto, são a mesma coisa. Sem nenhuma diferença aparente. E de fato, se você conseguiu notar isto, deve estar imaginando uma coisa: Podemos tornar estas duas funções, vistas no código 01, em um template. Isto tornaria as coisas bem mais simples. No entanto, estou com uma dúvida. Em ambas funções, temos uma variável local. Que pode ser vista na linha 17 e a outra na linha 27. Sei como declarar e implementar a chamada da função como template. Isto por que já vi como fazer isto nos artigos anteriores. Mas esta variável local está me incomodando. Pois não sei como lidar com ela. Talvez eu possa fazer algo parecido com o código visto logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define PrintX(X) Print(#X, " :: ", X)
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     const double d_values[] = {2.5, 3.4, 1.25, 2.48, .85, 3.75};
09.     const long i_values[] = {8, 3, 4, 6, 4, 5, 7};
10.     
11.     PrintX(Averange(d_values));
12.     PrintX(Averange(i_values));
13. }
14. //+------------------------------------------------------------------+
15. template <typename T>
16. T Averange(const T &arg[])
17. {
18.     double local = 0;
19. 
20.     for (uint c = 0; c < arg.Size(); c++)
21.         local += arg[c];
22. 
23.     return (T)(local / arg.Size());
24. }
25. //+------------------------------------------------------------------+

Código 02

De fato, você poderia fazer isto, que é mostrado no código 02, meu caro leitor. Porém, ao tentar compilar este código 02, você receberia um alerta do compilador, como é mostrado logo abaixo.

Imagem 02

Note que NÃO É UMA MENSAGEM DE ERRO. Mas sim um aleta. Isto por conta de que na linha 21, onde estamos somando os valores do array. Existirá um momento, em que estaremos somando um valor de ponto flutuante com um valor inteiro. E isto, pode causar distorções no resultado final. Mas observe que ainda assim o resultado final, quando você executar este código 02. Será o mesmo que podemos ver na imagem 01. No entanto, este alerta do compilador, pode ser um tanto quanto incomodo para alguns programadores. Sendo assim, como corrigir este detalhe? Bem, para resolver isto, precisaremos fazer uma conversão de tipo. Da mesma forma que estamos fazendo na linha 23. Só que aplicando a conversão, aqui na linha 21. Porém atenção. Você NÃO deve usar o que é mostrado logo abaixo.

local += (T)arg[c];

Pois isto NÃO IRÁ RESOLVER O ALERTA. Já que precisamos fazer a conversão, para que o valor presente no array, venha a se compatível com o valor da variável da linha 18. Isto parece um tanto quanto complicado. Mas é bem simples e direto. Tudo que precisamos fazer é basicamente, modificar a linha 21 do código 02, para a linha que podemos ver logo abaixo, na sequência.

local += (double)arg[c];

Agora sim, resolvemos o nosso problema. Temos a função Averange implementada em forma de template. Sendo perfeitamente funcional. Mas existe uma outra maneira de lidar com esta mesmas questões. Isto para que não precisemos mexer tanto no código. Então vamos voltar novamente ao código 01. Observe que de fato o código 02, representa o que é implementado no código 01. Mas se, e somente se, a variável local, é que está nos trazendo transtornos como pode ser visto acima. Por que não, fazer algo um pouco mais elaborado? Isto de modo que o próprio compilador, lide com este tipo de inconveniente para nós. Parece algo ousado de ser feito, não é mesmo?

Entretanto, se você entendeu de fato, como o código 01 foi traduzido para o código 02. E como fizemos para lidar com o alerta, que o compilador estava disparando. Deve ter pensando, em uma outra solução. Uma que torne possível mudar o tipo da variável que está sendo declarada na linha 18 do código 02. Isto sim seria uma solução de fato interessante. E é justamente isto que podemos ver, sendo implementado logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define PrintX(X) Print(#X, " :: ", X)
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     const double d_values[] = {2.5, 3.4, 1.25, 2.48, .85, 3.75};
09.     const long i_values[] = {8, 3, 4, 6, 4, 5, 7};
10.     
11.     PrintX(Averange(d_values));
12.     PrintX(Averange(i_values));
13. }
14. //+------------------------------------------------------------------+
15. template <typename T>
16. T Averange(const T &arg[])
17. {
18.     T local = 0;
19. 
20.     for (uint c = 0; c < arg.Size(); c++)
21.         local += arg[c];
22. 
23.     return local / arg.Size();
24. }
25. //+------------------------------------------------------------------+

Código 03

Este código 03, nos dá o mesmo resultado visto na imagem 01. E além disto, não gera nenhum alerta por parte do compilador. Por que? O motivo, é que quando o compilador vier notar, que estamos precisando de um tipo inteiro, ele irá adequar o tipo da variável ao tipo inteiro. Quando o compilador notar que precisamos de um tipo de dado ponto flutuante. Ele irá fazer com que a variável declarada na linha 18, seja do tipo adequado, para fazer uso de ponto flutuante.

E assim, temos um código, bem mais simples que o código 01. Porém muito parecido com o código 02. Mas sem precisar fazer todos aqueles ajustes. Isto para que a conversão de tipo venha a ocorrer e não tenhamos alertas do compilador sendo disparados. Legal não é mesmo? Acredito, que se você já tinha visto um código parecido com este. Deve ter ficado imaginando como aquilo dali deveria funcionar. Mas entendendo os conceitos básico e vendo como cada coisa vai se encaixando. Códigos que antes pareciam complicados, passam a ser cada vez mais simples e interessantes.

Muito bem, esta foi a parte gostosa. Agora vamos ver a parte realmente divertida. Pois tendo em vista, estas possibilidades de uso, que podemos ver no código 03. Logo vem o desejo de experimentar isto com outros tipos de dados.

Como até o momento apenas foi mostrado o tipo de dado, que utiliza a união. Podemos tentar fazer uso desta mesma técnica vista no código 03, a fim de criar um template ainda mais amplo. Mas para separar adequadamente as coisas. Vamos ver isto em um outro tópico.


Usando as coisas em uniões

O que vamos fazer aqui, será primeiramente ver o que é mostrado no código 03. Só que de uma maneira um pouco mais simples, pelo menos neste começo. De fato, não vamos mexer novamente no código 03. Mas vamos utilizar aquele conceito, mostrado no código, a fim de criar uma região de memória que seja dinâmica, dentro de uma união. Ou melhor dizendo, vamos criar um template para gerar uma união entre um array e um tipo de dado qualquer.

Quando estudamos e vimos como uma união seria definida, vimos que a mesma é constituída de um bloco com tamanho fixo. Este que é definido em termos de bytes, tem como valor, a quantidade de bytes, pertencentes ao maior tipo presente dentro da união. Pois bem, mas podemos fazer com que isto, não seja uma verdade absoluta. Ou melhor dizendo. Podemos fazer com que uma determinada união possa ser configurada de maneira dinâmica pelo compilador. Nos permitindo assim, criar uma união, que a princípio, terá um bloco de tamanho dinâmico. Já que a largura em termos de bytes será definida no momento em que formos compilar o nosso código. Passando, daquele momento em diante a ter um tamanho fixo.

Neste momento, você pode estar pensando: Cara, mais isto parece algo muito, mais muito complicado de ser feito. Já que, à primeira vista, não sei como iremos dizer ao compilador, como trabalhar com este tipo de abordagem ou implementação. Sinceramente precisamos ver isto agora? Bem, meu caro leitor, de certa maneira, alguns podem achar que este conceito que será explicado, é algo avançado. Mas ao meu ver isto ainda sim é um conceito básico e que todo iniciante deveria conhecer. Já que facilita muito, a criação de diversos tipos de códigos com um objetivo mais amplo.

Sei que muitos, podem estar achando que estamos indo rápido demais. Porém, estou indo o mais devagar possível. Sem atropelar nenhum conceito, ou explicação. Já que se esta base de conhecimento, for bem construída, e bem assimilada. Tudo que será visto depois, se tornará muito mais natural e fácil de compreender. Porém, como sei que o que será visto, pode ser um tanto quanto confuso no começo. Iremos começar com um código bem mais simples. Assim será mais agradável e palatável, estudar este novo conceito. Pensando desta forma, vamos começar com o código mostrado logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const ulong info = 0xA1B2C3D4E5F6789A;
07. 
08.     PrintFormat("Before modification: 0x%I64X", info);
09.     PrintFormat("After modification : 0x%I64X", Swap(info));
10. }
11. //+------------------------------------------------------------------+
12. ulong Swap(ulong arg)
13. {
14.     union un_01
15.     {
16.         ulong   value;
17.         uchar   u8_bits[sizeof(ulong)];
18.     }info;
19. 
20.     info.value = arg;
21. 
22.     PrintFormat("The region is composed of %d bytes", sizeof(info));
23. 
24.     for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--)
25.     {
26.         tmp = info.u8_bits[i];
27.         info.u8_bits[i] = info.u8_bits[j];
28.         info.u8_bits[j] = tmp;
29.     }
30. 
31.     return info.value;
32. }
33. //+------------------------------------------------------------------+

Código 04

Este código ainda não está utilizando o recurso template. Porém, como ele já foi explicado em outro artigo, quando introduzimos o assunto união. Não vejo necessidade de explicar como o mesmo funciona. Já que isto foi feito anteriormente. No entanto, quando este código 04 foi apresentado. Não estávamos interessados em expandir o mesmo. Sendo assim, não existia de fato esta função que é vista na linha 12. Tudo era feito dentro de OnStart. Porém agora, devido justamente ao fato de que iremos explicar templates em uniões. Fica adequado separar as coisas em um bloco distinto. Isto para facilitar este primeiro contato com templates de união. Depois iremos ver que podemos colocar tudo em um único bloco de código. Assim como era feito originalmente, quando este código 04 foi apresentado.

Ok, quando você compilar e executar este código 04, irá ter como resposta no terminal do MetaTrader 5, o que pode ser visto logo na sequência.

Imagem 03

Ou seja, trocamos os bits de lugar. Simples assim. Agora, como faríamos para poder trocar bits de outros tipos de dados? Isto por que se temos um tipo, como por exemplo ushort, onde existem 2 bytes, se enviarmos este tipo, para a função da linha 12, teremos um resultado incorreto. Ou no mínimo um tanto quanto estranho. Para verificar isto, basta que mudemos a linha seis do código 04, para a linha mostrada logo abaixo.

const ushort info = 0xCADA;

Agora quando executarmos este código 04, com esta linha mostrada acima. O resultado é o que podemos ver sendo impresso logo abaixo. Isto no terminal do MetaTrader 5.

Imagem 04

Agora preste atenção a uma coisa nesta imagem 04. Esta região que estou marcando em vermelho, são dados indesejados que foram adicionados ao valor do tipo ushort. Mesmo que você, note que a rotação de fato aconteceu. Estes bytes marcados em vermelho, são um problema. Ainda mais se você precisar de fato que o tipo seja mantido, como sendo ushort. É justamente aqui, onde entra o template.

Como você viu no tópico anterior, conseguimos de uma maneira muito simples, prática e segura, trabalhar com valores de diferentes tipos. Isto para conseguir efetuar um cálculo de média de valores. Tanto que a nossa aplicação, conseguia de fato trabalhar com valores de tipo inteiro e do tipo ponto flutuante sem nenhum problema. Mas como poderíamos fazer este mesmo tipo de coisa aqui? Onde estamos fazendo uso, e precisamos de uma união, para tornar o código mais simples? Bem, esta é a parte interessante e divertida.

Primeiro precisamos transformar o código 04 em um código com capacidade de template. Com base no que já foi mostrado até aqui, fazer isto, é algo muito simples e direto. Ainda mais se você de fato está conseguindo compreender os conceitos que estão sendo explicados. Então sem muito problema, você cria o código mostrado logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const ushort info = 0xCADA;
07. 
08.     PrintFormat("Before modification: 0x%I64X", info);
09.     PrintFormat("After modification : 0x%I64X", Swap(info));
10. }
11. //+------------------------------------------------------------------+
12. template <typename T>
13. T Swap(T arg)
14. {
15.     union un_01
16.     {
17.         ulong   value;
18.         uchar   u8_bits[sizeof(ulong)];
19.     }info;
20. 
21.     info.value = arg;
22. 
23.     PrintFormat("The region is composed of %d bytes", sizeof(info));
24. 
25.     for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--)
26.     {
27.         tmp = info.u8_bits[i];
28.         info.u8_bits[i] = info.u8_bits[j];
29.         info.u8_bits[j] = tmp;
30.     }
31. 
32.     return info.value;
33. }
34. //+------------------------------------------------------------------+

Código 05

Porém ao tentar compilar este código 05, você recebe um alerta do compilador. Este alerta pode ser observado logo na imagem abaixo.

Imagem 05

Agora pense comigo. Você está vendo este alerta, que diz que o valor na linha 32 não está sendo compatível com o valor de retorno. Já que são de tipos diferentes. Onde você já viu isto acontecer antes? Bem, se você voltar um pouco, neste mesmo artigo, irá ver algo semelhante sendo mostrado na imagem 02. E com base nisto, você já tem meios de solucionar este mesmo problema. Você pode seguir por um caminho ou por outro. Porém, como você deve ter notado no tópico anterior. O melhor caminho, e que nos força a fazer menos modificações no código é o que joga a variável de tipo errado para um mesmo tipo esperado como retorno. Ou seja, precisamos mexer, NÃO NA LINHA 32. Mas sim na linha 15 onde está sendo declarada a união de valores.

Perceba o seguinte fato. Por que motivo, não estou falando para mexermos na linha 17? Pois é ali onde está a variável que estamos usando na linha 32 para retornar o valor. O motivo para isto, é que, não iremos mexer apenas naquela linha 17 especificamente. Precisamos também mexer no array que se encontra na linha 18. Isto por que, se mexermos apenas na linha 17, corrigimos parte do problema. Porém, toda via e, entretanto, o problema irá mudar de um ponto para outro. Já que não iremos estar modificando a largura da união. Por isto é importante, ficar atendo e prestar atenção ao que se está programando. Se você tomar os devidos cuidados, não haverá problemas. Já que seu código, passará a ter uma utilidade maior, cobrindo mais casos, e com muito menos risco e esforço.

Assim, dada esta pequena introdução do que precisa ser feito. Podemos modificar o código 05, para este visto logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const ushort info = 0xCADA;
07. 
08.     PrintFormat("Before modification: 0x%I64X", info);
09.     PrintFormat("After modification : 0x%I64X", Swap(info));
10. }
11. //+------------------------------------------------------------------+
12. template <typename T>
13. T Swap(T arg)
14. {
15.     union un_01
16.     {
17.         T     value;
18.         uchar u8_bits[sizeof(T)];  
19.     }info;
20. 
21.     info.value = arg;
22. 
23.     PrintFormat("The region is composed of %d bytes", sizeof(info));
24. 
25.     for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--)
26.     {
27.         tmp = info.u8_bits[i];
28.         info.u8_bits[i] = info.u8_bits[j];
29.         info.u8_bits[j] = tmp;
30.     }
31. 
32.     return info.value;
33. }
34. //+------------------------------------------------------------------+

Código 06

Ao executarmos este código 06, que nada mais é que uma modificação do código 05. Temos como resposta o que pode ser observado na imagem logo abaixo.

Imagem 06

Simplesmente perfeito e maravilhoso. Agora sim, temos o que seria a resposta correta. E mais, acabamos de transformar aquele mesmo código 04, em um código muito melhor. Já que agora podemos fazer com que o compilador, crie e gere funções, ou procedimentos sobrecarregados, para nós. Isto a fim de que venhamos a poder utilizar qualquer tipo de dado, sem nenhum tipo de preocupação. Já que o compilador, irá com base no template, que podemos ver neste código 06. Criar os procedimentos adequados para que a rotina trabalhe de maneira correta. Nos dados sempre resultados corretos e coerentes com o que esperamos obter.

Muito bem, devido ao fato de que já criamos um modelo bem mais interessante. Você pode estar pensando: Ok, então é isto. Agora já sei como criar templates de uniões. Certo, meu caro leitor, você já sabe como fazer em um caso muito simples. Porém, isto que mostrei aqui, NÃO É UM TEMPLATE DE UNIÃO. Isto é apenas a adequação de uma união a fim de utilizar um template, que está sendo feito, em uma função ou procedimento. Para atingir o que seria um template de união, o buraco é um pouco mais embaixo, como alguns dizem. Porém, para separar de maneira adequada os assuntos, vamos ver como fazer isto e um novo tópico. Assim, você não correrá o risco de confundir uma coisa com a outra.


Definindo um template de união

Bem, agora vamos cavar um pouco mais fundo na questão sobre como trabalhar com templates. Isto porque diferente do que foi visto até este momento. Quando definirmos um template para uma união, assim como para outros tipos, que veremos futuramente. Precisamos tomar algumas decisões que muitos programadores iniciantes ficam de cabelo em pé, ao ver como o código precisa ser implementado. Isto por que, parte do código que estaremos construindo, pode acabar tendo uma aparência um tanto quanto exótica, por assim dizer.

Se você por ventura vier a ter esta sensação, de que o código está ficando muito complicado e confuso. Pare, relaxe, e dê um passo para trás, meu caro leitor. O ideal e correto, é que você procure estudar e praticar o que já foi mostrado até aqui, até compreender de maneira adequada os princípios e conceitos adotados. Somente depois, volte e comece de onde havia parado antes de ter dado uma pausa.

Não tente atropelar as coisas, ou achar que já entendeu algo, apenas por que viu um código que venha a utilizar um certo recurso. Já que não é bem assim que as coisas funcionam. Entenda o conceito de maneira adequada e correta, que tudo irá se tornar muito mais simples e fácil de ser entendido. 

Pois bem, o que iremos fazer agora de fato é bem confuso, neste primeiro momento. Apesar de que a confusão mesmo irá ficar para o próximo artigo. Mas de qualquer forma, o que será visto aqui, precisa ser feito, e você precisa entender o que se passa, quando encontrar algo parecido. Ou que você precise implementar algo, que venha a necessitar ser feito da maneira como será mostrado.

Como de praxe, vamos começar com algo bem simples e didático. Algo parecido com o código 06, visto no tópico anterior será o nosso objetivo. Porém, NÃO USAREMOS UMA FUNÇÃO OU PROCEDIMENTO. Não ainda, pois isto será visto depois. Por ser um tanto mais complicado de se entender. Então neste primeiro momento faremos tudo dentro da chamada OnStart. Sendo assim vamos do princípio. Primeiro o código que é visto logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06. //+----------------+
07. #define macro_Swap(X)   for (uchar i = 0, j = sizeof(X) - 1, tmp; i < j; i++, j--)  \
08.                         {                                                           \
09.                             tmp = X.u8_bits[i];                                     \
10.                             X.u8_bits[i] = X.u8_bits[j];                            \
11.                             X.u8_bits[j] = tmp;                                     \
12.                         }
13. //+----------------+
14.     union un_01
15.     {
16.         ulong value;
17.         uchar u8_bits[sizeof(ulong)];
18.     };
19. 
20.     {
21.         un_01 info;
22. 
23.         info.value = 0xA1B2C3D4E5F6789A;
24.         PrintFormat("The region is composed of %d bytes", sizeof(info));
25.         PrintFormat("Before modification: 0x%I64X", info.value);
26.         macro_Swap(info);
27.         PrintFormat("After modification : 0x%I64X", info.value);
28.     }
29.     
30.     {
31.         un_01 info;
32. 
33.         info.value = 0xCADA;
34.         PrintFormat("The region is composed of %d bytes", sizeof(info));
35.         PrintFormat("Before modification: 0x%I64X", info.value);
36.         macro_Swap(info);
37.         PrintFormat("After modification : 0x%I64X", info.value);
38.     }
39. }
40. //+------------------------------------------------------------------+

Código 07

Quando executado, este código 07, irá produzir um resultado muito parecido como o código 04. Porém, aqui estamos entrando com dois valores ao mesmo tempo. visando mostrar as coisas de uma só vez. Desta forma o resultado real da execução deste código 07, é o que podemos ver logo abaixo.

Imagem 07

Da mesma maneira que marcamos na imagem 04, que haveria valores impróprios sendo colocados no resultado final. Aqui na imagem 07, vemos estes mesmos valores aparecendo novamente. Simplificando: Temos um valor que seria de uma variável ushort, sofrendo interferência de outros valores indevidos. Sei que a princípio este código possa parecer correto. Podendo até mesmo estar correto, dependendo do que esteja sendo implementado. No entanto, o objeto de estudo aqui, é justamente promover a criação de um template, para que consigamos utilizar as coisas, de forma a gerar um resultado parecido como o que foi visto e obtido no tópico anterior.

Porém se fazer uso de nenhuma função ou procedimento. Note uma outra coisa. Justamente para evitar utilizar, neste momento, uma função ou procedimento, estamos lançando a parte comum do código para dentro de uma macro. Esta pode ser vista, sendo implementada a partir da linha sete. Como já foi explicado, como o código de macros funcionam. Não irei entrar com mais detalhes sobre isto. Já que não vejo esta necessidade neste caso específico.

Ok, então vamos a questão principal aqui. Diferente do que foi feito no tópico anterior, agora estamos lidando com um tipo de situação diferente. Mas como um objetivo parecido. Note que agora, aquele conceito que antes podia ser aplicado, já não poderá ser aplicado mais. Isto por que, não temos uma função ou procedimento aqui no código 07, para podermos aplicar um template no mesmo. Porém o conceito base, a ser aplicado aqui é muito similar ao que tem sido aplicado. Só que um tanto quanto confuso no começo.

Preste atenção meu caro leitor. Nosso objetivo é criar um código que consiga lidar com qualquer tipo de dado, assim como conseguimos fazer, ao construir o que seria o código 06. No entanto, para atingir este objetivo, precisamos recorrer a um tipo de sobrecarga um pouco diferente. Até o momento, a sobrecarga que estava sendo feita era em cima das funções ou procedimentos. Porém, agora precisamos de uma sobrecarga de tipo. E isto pode ser um tanto quanto confuso. Mas o conceito base, é o mesmo de quando usamos sobrecarga para funções ou procedimentos.

Então observe o seguinte detalhe: Precisamos que a união declarada na linha 14 do código 07, trabalhe da mesma forma que a união presente no código 06. Mas como fazer isto? Simples. Basta dizermos que esta união da linha 14 é um template. Hã? Não entendi. Como faremos isto? Bem, como eu disse, isto é um pouco confuso neste primeiro contato que você está tendo com este tipo de modelagem. Para começar, esta união que vemos na linha 14, é de âmbito local. E todo template, DEVE SER DE ÂMBITO GLOBAL. Sendo assim a primeira coisa a ser feita, é remover esta declaração de dentro do procedimento OnStart. Fazendo isto, tornaremos a declaração um modelo global. Depois disto, precisamos simplesmente adicionar o que já estamos acostumados em ver. Que é a transformação da união em um template. Assim como seria feito para uma função ou procedimento. Desta maneira, surge o que é visto no código 08 mostrado logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename T>
05. union un_01
06. {
07.     T value;
08.     uchar u8_bits[sizeof(T)];
09. };
10. //+------------------------------------------------------------------+
11. void OnStart(void)
12. {
13. //+----------------+
14. #define macro_Swap(X)   for (uchar i = 0, j = sizeof(X) - 1, tmp; i < j; i++, j--)  \
15.                         {                                                           \
16.                             tmp = X.u8_bits[i];                                     \
17.                             X.u8_bits[i] = X.u8_bits[j];                            \
18.                             X.u8_bits[j] = tmp;                                     \
19.                         }
20. //+----------------+
21. 
22.     {
23.         un_01 info;
24. 
25.         info.value = 0xA1B2C3D4E5F6789A;
26.         PrintFormat("The region is composed of %d bytes", sizeof(info));
27.         PrintFormat("Before modification: 0x%I64X", info.value);
28.         macro_Swap(info);
29.         PrintFormat("After modification : 0x%I64X", info.value);
30.     }
31.     
32.     {
33.         un_01 info;
34. 
35.         info.value = 0xCADA;
36.         PrintFormat("The region is composed of %d bytes", sizeof(info));
37.         PrintFormat("Before modification: 0x%I64X", info.value);
38.         macro_Swap(info);
39.         PrintFormat("After modification : 0x%I64X", info.value);
40.     }
41. }
42. //+------------------------------------------------------------------+

Código 08

Ok, agora até este ponto está sendo tudo muito fácil e simples. Já que estamos lidando exatamente com algo que já sabemos como trabalhar. Porém, e é aqui onde as coisas começam a complicar. Se você tentar compilar este código 08, não irá conseguir. Já que pelo ponto de vista do compilador este código contém um tipo de erro, que é difícil de ser entendido por iniciantes. O tipo de informação que o compilador irá reportar quando tentarmos compilar este código é mostrado logo abaixo.

Imagem 08

Definitivamente, parece que não estamos no caminho certo. Já que esta quantidade de erros, é um tanto quanto desanimadora. Mas, é aqui onde mora a questão realmente confusa para muitos iniciantes. Pois, o que está gerando todos estes erros, que você pode observar na imagem 08, é o fato do compilador NÃO SABER qual o tipo de dado a ser utilizado. Como já foi dado muita explicação a respeito de diversas coisas neste artigo. E NÃO QUERO confundir você meu caro leitor, mostrando como solucionar esta questão que é mostrada na imagem 08. Irei dar uma pausa neste momento. Deixando esta questão para ser explicada no próximo artigo.


Considerações finais

Neste artigo, começamos a explorar um tema muito complicado a princípio. Isto se você não estiver de fato praticando o que vem sendo mostrado. Porém, como quero que você meu caro e estimado leitor, aprenda da maneira correta. Ficou decido dar uma parada no último tópico deste artigo. Já que explicar de maneira adequada como dizer ao compilador como agir, é algo que demanda tempo e não quero tornar confuso um tema que por si só já é bastante complicado de entender.

Sendo assim, procure praticar o que está sendo mostrado neste artigo. Mas principalmente, procure imaginar como você poderia dizer ao compilador que tipo de dado deverá ser utilizado, a fim de que o código 08, visto no final deste artigo, possa de fato funcionar. Pense a este respeito com calma. Pois no próximo artigo iremos ver como realmente fazer este tipo de coisa. Assim como também como utilizar isto ao nosso favor.

Arquivos anexados |
Anexo.zip (4.04 KB)
Simulação de mercado (Parte 04): Iniciando a classe C_Orders (I) Simulação de mercado (Parte 04): Iniciando a classe C_Orders (I)
Neste artigo vamos começar a montar a classe C_Orders, para poder enviar pedidos ao servidor de negociação. Vamos fazer isto aos pouco. Já que o intuito será explicar o mais detalhadamente possível como isto será feito, via sistema de mensagens.
Aprendendo MQL5 do iniciante ao profissional (Parte IV): Sobre Arrays, Funções e Variáveis Globais do Terminal Aprendendo MQL5 do iniciante ao profissional (Parte IV): Sobre Arrays, Funções e Variáveis Globais do Terminal
Este artigo é uma continuação do ciclo para iniciantes. Ele descreve em detalhes arrays de dados, a interação entre dados e funções, bem como variáveis globais do terminal que permitem a troca de dados entre diferentes programas MQL5.
Redes neurais em trading: Resultados práticos do método TEMPO Redes neurais em trading: Resultados práticos do método TEMPO
Damos continuidade à exploração do método TEMPO. Neste artigo, avaliaremos a eficácia prática das abordagens propostas com base em dados históricos reais.
Redes neurais em trading: Usando modelos de linguagem para previsão de séries temporais Redes neurais em trading: Usando modelos de linguagem para previsão de séries temporais
Continuamos a analisar modelos de previsão de séries temporais. Neste artigo, proponho a apresentação de um algoritmo complexo baseado no uso de um modelo de linguagem previamente treinado.