Do básico ao intermediário: Sobrecarga de operadores (V)
Introdução
No artigo anterior Do básico ao intermediário: Sobrecarga de operadores (IV), foi feita o que posso chamar de a batismo de fogo no que diz respeito a sobrecarga de operadores. Já que naquele artigo foi demonstrado que existem certas limitações no que diz respeito a sobrecarga de operadores. Porém apesar de tais limitações de fato existirem. Isto não inviabiliza de forma alguma a prática de se sobrecarregar operadores, com o objetivo de tornar o código mais legível. Ou mais simples de ser compreendido por qualquer pessoa.
Porém, existe uma última questão, na qual ainda desejo explicar, neste que possivelmente será o último artigo referente a sobrecarga de operadores, no seu patamar mais básico e fácil. Pelo menos dentro desta sequência de artigos.
A questão que pretendo abordar se refere ao uso conjunto de diversos operadores sobrecarregados, de tal modo que possamos implementar uma aplicação de maneira bem simbólica. Porém muito mais fácil de se compreender. Pelo menos isto ao meu entender.
Como o assunto a ser abordado aqui é relativamente longo, e ao mesmo tempo, bastante profundo. Vamos iniciar logo o que será o tópico principal. Então, chegou a hora de dar um tempo nas coisas que possam vir a lhe distrair, e focar inteira e integralmente no tema que será visto neste artigo.
Sobrecarga de operadores (V)
Existem diversas coisas referentes a programação, que somente o tempo, estudo e a prática, poderão realmente lhe ensinar, meu caro leitor. Porém, quero pelo menos lhe dar a oportunidade de tentar entender certas coisas de maneira um pouco mais rápida. Lhe dando assim algum impulso em uma determinada direção, a ponto de que você consiga compreender, que nem tudo, é como normalmente imaginamos. Por conta disto que amo programar. Está com toda a certeza é uma paixão que talvez nem mesmo a morte poderá arrancar de mim.
No artigo anterior, mostrei o que seria uma primeira abordagem para a confecção de uma lista encadeada, onde faríamos uso da sobrecarga de operadores. No entanto, aquilo que foi mostrado é algo muito chato, perto do que irei mostrar neste artigo. Mas antes de voltarmos a questão da lista encadeada. Preciso mostrar uma outra coisa. Que apesar de ser algo aparentemente bobo, irá lhe ajudar a entender o que será feito quando formos mexer no código da lista encadeada.
Ok, no artigo Do básico ao intermediário: Filas, Listas e Árvores (II), expliquei como poderíamos transformar o que seria um fila dentro de um array, em uma fila que usaria ponteiros a fim de ligar os elementos na própria fila. Abrindo assim, espaço para que uma nova estrutura de dados pudesse vir a surgir. Que seria justamente as listas. Bem, mas esta não é exatamente a questão aqui. O grande detalhe, e é isto que importa para nós aqui. É como poderíamos implementar aquele mesmo tipo de fila, mostrada no artigo acima mencionado. Mas fazendo uso da sobrecarga de operadores.
Fazer isto é algo muito divertido e que dá ao código uma vida nova. O tornando bem mais interessante.
Para termos um ponto de partida, vamos usar um dos códigos vistos naquele artigo. Este é mostrado logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class C_Demo 05. { 06. private: 07. //+----------------+ 08. T info; 09. C_Demo <T> *prev; 10. //+----------------+ 11. public: 12. //+----------------+ 13. C_Demo(void) 14. :prev(NULL) 15. {} 16. //+----------------+ 17. void Push(T arg) 18. { 19. C_Demo <T> *loc; 20. 21. loc = new C_Demo <T>; 22. (*loc).info = arg; 23. (*loc).prev = prev; 24. prev = loc; 25. } 26. //+----------------+ 27. bool Pop(T &arg) 28. { 29. C_Demo <T> *loc; 30. 31. if (prev == NULL) 32. return false; 33. 34. loc = prev; 35. arg = (*loc).info; 36. prev = (*loc).prev; 37. 38. delete loc; 39. 40. return true; 41. } 42. //+----------------+ 43. }; 44. //+------------------------------------------------------------------+ 45. void OnStart(void) 46. { 47. C_Demo <char> demo; 48. 49. demo.Push(10); 50. demo.Push(84); 51. demo.Push(-6); 52. 53. for (char info; demo.Pop(info);) 54. Print(info); 55. }; 56. //+------------------------------------------------------------------+
Código 01
Quando este código é executado, ele irá gerar o resultado visto logo abaixo.

Imagem 01
A ideia aqui é tentar criar este mesmo código 01, usando sobrecarga de operadores. Porém de forma que este resultado visto na imagem 01, se mantenha. Você pode estar imaginando neste momento: Cara isto é loucura. Como poderíamos fazer isto, sem tornar o código uma tremenda de uma confusão? Bem, meu caro leitor, você logo irá entender, como faremos isto. Mas antes, vamos fazer com que este código 01, venha a se parecer com o que seria de fato um código real. Pois na prática, é muito mais comum dividir as coisas em diversos arquivos, do que as fazer em um único, como mostrado no código 01. Sendo assim, agora teremos dois arquivos. Mas você irá ver que isto não muda as coisas. Mas as tornará bem mais divertidas.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Include\C_Demo_01.mqh" 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. C_Demo <char> demo; 09. 10. demo.Push(10); 11. demo.Push(84); 12. demo.Push(-6); 13. 14. for (char info; demo.Pop(info);) 15. Print(info); 16. }; 17. //+------------------------------------------------------------------+
Código 02
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class C_Demo 05. { 06. private: 07. //+----------------+ 08. T info; 09. C_Demo <T> *prev; 10. //+----------------+ 11. public: 12. //+----------------+ 13. C_Demo(void) 14. :prev(NULL) 15. {} 16. //+----------------+ 17. void Push(T arg) 18. { 19. C_Demo <T> *loc; 20. 21. loc = new C_Demo <T>; 22. (*loc).info = arg; 23. (*loc).prev = prev; 24. prev = loc; 25. } 26. //+----------------+ 27. bool Pop(T &arg) 28. { 29. C_Demo <T> *loc; 30. 31. if (prev == NULL) 32. return false; 33. 34. loc = prev; 35. arg = (*loc).info; 36. prev = (*loc).prev; 37. 38. delete loc; 39. 40. return true; 41. } 42. //+----------------+ 43. }; 44. //+------------------------------------------------------------------+
Código 03
Agora sim, temos as coisas de uma forma bem mais atraente. Se bem que no anexo, você irá ver que estes arquivos estão diferentes. Já que iremos os modificar aqui no artigo, sendo que o resultado final irá ser colocado no anexo. Então não seja afoito. Vamos ver o passo a passo até que estes códigos cheguem no ponto que estará no anexo.
Legal. Agora vamos a questão da sobrecarga. Quem vir a estudar C++ irá se deparar com dois operadores que a princípio são um tanto quanto exóticos. Estes operadores são conhecidos basicamente com sendo Stdin e Stdout. O objetivo de tais operadores é o de promover uma forma simples e prática de termos acesso a entrada ou saída padrão. No entanto eles podem ser usados em outros tipos de situação bem interessantes.
Muito bem, o MQL5, não conta, ou pelo menos, não utiliza o mesmo conceito de Stdin e Stdout. Porém isto não nos impede de implementarmos nossos códigos a fim de utilizar este mesmo conceito.
O que iremos fazer aqui é algo relativamente simples. Porém, no meu outro perfil, voltado a explicar técnicas que podem ser usadas aqui no MQL5. Irei mostrar uma forma de utilizar este mesmo conceito que será visto aqui, de uma maneira bem mais avançada. Mas não vou estragar a surpresa do que será feito ali. Mas realmente é algo bem interessante e que torna o código muito mais simples de ser compreendido.
Mas voltando ao que iremos fazer aqui. O conceito de Stdin e Stdout, é visto no código logo abaixo.
01. //+------------------------------------------------------------------+ 02. #include <iostream> 03. //+------------------------------------------------------------------+ 04. int main() 05. { 06. int i1, i2, sum; 07. 08. std::cout << "Summing two numbers.\n"; 09. std::cout << "Enter the first number: "; 10. std::cin >> i1; 11. std::cout << "Enter the second number: "; 12. std::cin >> i2; 13. sum = i1 + i2; 14. std::cout << "The result is: " << sum << "\n"; 15. } 16. //+------------------------------------------------------------------+
Código 04
Você não precisa entender C++ para que consiga entender o que este código estará fazendo. Já que com base no que foi visto aqui em MQL5, você deve conseguir ter uma vaga noção do que estará ocorrendo aqui. Porém, quero que você note o seguinte padrão neste código 04. Existe em algumas das linhas a seguinte informação std::cout seguido por um operador. Assim como também temos uma outra informação sendo vista em outras linhas. Um tal de std::cin seguido também por um operador. Agora entender o que seria está coisa estranha que está surgindo no código.
Primeiro, este tal std::cout seria a saída padrão, toda e qualquer informação que venhamos a escrever será repassada para esta saída padrão. Já std::cin seria a entrada padrão. Servindo de maneira muito parecida com std::cout. Só que diferente de std::cout, std::cin seria a entrada padrão. Assim qualquer coisa que estiver na entra padrão será direcionada para alguma variável, ou outra coisa dentro do seu código.
Como muitas das vezes std::cout e std::cin não fariam muito sentido usamos um operador para nós ajudar a entender como os dados estão sendo direcionado. Em uma situação especial, como o que acontece na linha 14 deste código 04, temos este mesmo operador sendo repetido algumas vezes. Mostrando assim como as coisas deverão ser interpretadas pelo compilador C++.
Muito bem, a parte sobre C++ a ser vista neste artigo, termina aqui. O que iremos fazer aqui, será justamente replicar este operador que podemos ver no código 04. Isto aqui dentro do MQL5. Ao fazermos isto, o nosso código em MQL5, passará a ter um formato um pouco mais exótico. No entanto, ele pode, se bem planejado a sobrecarga de operadores, se tornar extremamente mais legível. Irei levar isto a um extremo um pouco maior em um outro artigo, que será postado em breve no meu outro perfil. Porém aqui iremos ficar apenas e somente no mais básico. Já que o objetivo aqui é aprender como usar a sobrecarga de operador.
Ok, então vamos modificar o arquivo cabeçalho que é visto no código 03. Ele agora irá ter a seguinte aparência, que pode ser vista logo abaixo na íntegra.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class C_Demo 05. { 06. private: 07. //+----------------+ 08. T info; 09. C_Demo <T> *prev; 10. //+----------------+ 11. public: 12. //+----------------+ 13. C_Demo(void) 14. :prev(NULL) 15. {} 16. //+----------------+ 17. void operator<<(const T arg) 18. { 19. C_Demo <T> *loc; 20. 21. loc = new C_Demo <T>; 22. (*loc).info = arg; 23. (*loc).prev = prev; 24. prev = loc; 25. } 26. //+----------------+ 27. bool operator>>(T &arg) 28. { 29. C_Demo <T> *loc; 30. 31. if (prev == NULL) 32. return false; 33. 34. loc = prev; 35. arg = (*loc).info; 36. prev = (*loc).prev; 37. 38. delete loc; 39. 40. return true; 41. } 42. //+----------------+ 43. }; 44. //+------------------------------------------------------------------+
Código 05
Agora olhe este código 05 e o compare como o código 03. Note que a mudança foi muito sutil. Porém estas simples mudanças que foram feitas aqui, são mais do que o suficiente para dar um nó no cérebro de muito marmanjo que diz ser programador de MQL5. Visto que agora a coisa realmente irá ficar muito mais divertida e engraçada. Então preste muita atenção o que iremos fazer agora. Mas antes, olhe mais uma vez para o código 02. Você consegue entender aquele código? Bem, agora veja este código 06 logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Include\C_Demo_01.mqh" 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. C_Demo <char> demo; 09. 10. demo << 10; 11. demo << 84; 12. demo << -6; 13. 14. for (char info; demo >> info;) 15. Print(info); 16. }; 17. //+------------------------------------------------------------------+
Código 06
Caramba, que coisa mais maluca é esta que estou vendo neste código 06? Cara, devo confessar, você é muito doido. Se você me mostrasse este código 06, lá no começo, acho que eu teria desistido de ler estes artigos. Mas vendo o que você consegue fazer. Acho que isto daqui não passa de pura diversão para você. Você gosta de brincar com o código e com a linguagem. Pois não sabia que dava para fazer isto aqui no MQL5.
Pois bem, meu caro leitor, isto daqui é apenas uma pequena amostra do que podemos realmente fazer. E o resultado da execução deste código 06, é exatamente o mesmo do código 02. De fato, fiz questão de dividir o arquivo em dois, sendo um arquivo principal e outro de cabeçalho. Justamente para que você conseguisse notar que tipo de coisa foi feita aqui. Pois tentar explicar isto, assim sem mais nem menos seria praticamente impossível.
No entanto, a diversão aqui, está apenas começando. Isto por que, se você estudou o que foi visto nos artigos sobre filas, listas e árvores, sabe que a saída deste código 06, assim como do código 02 é a representação de uma pilha. No entanto, podemos fazer uma pequena mudança no código. E desta vez apenas no arquivo de cabeçalho, e assim teremos uma implementação do tipo FIFO. O arquivo de cabeçalho que faz isto, pode ser visto logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> struct C_Demo 05. { 06. private: 07. //+----------------+ 08. T info[]; 09. //+----------------+ 10. public: 11. //+----------------+ 12. void operator<<(const T arg) 13. { 14. T local[1]; 15. local[0] = arg; 16. ArrayInsert(info, local, info.Size()); 17. } 18. //+----------------+ 19. bool operator>>(T &arg) 20. { 21. arg = NULL; 22. 23. if (info.Size() > 0) 24. { 25. arg = info[0]; 26. ArrayRemove(info, 0, 1); 27. return true; 28. } 29. return false; 30. } 31. //+----------------+ 32. }; 33. //+------------------------------------------------------------------+
Código 07
Obviamente este arquivo 07, terá um outro nome dentro do anexo. Já que pretendo deixar as coisas o mais fácil possível para que você possa experimentar o que estamos vendo aqui. Assim o código 06, fica como mostrado logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. // #include "Include\C_Demo_01.mqh" 05. #include "Include\C_Demo_02.mqh" 06. //+------------------------------------------------------------------+ 07. void OnStart(void) 08. { 09. C_Demo <char> demo; 10. 11. demo << 10; 12. demo << 84; 13. demo << -6; 14. 15. for (char info; demo >> info;) 16. Print(info); 17. }; 18. //+------------------------------------------------------------------+
Código 08
Agora veja o seguinte meu caro leitor. Se a linha quatro estiver habilitada, teremos a implementação de uma pilha. Já se a linha cinco estiver habilitada teremos uma fila do tipo FIFO sendo implementada. Você NÃO deve habilitar ambas as linhas. Salvo se vier a modificar os arquivos de cabeçalho que estarão no anexo.
Ok, no caso de executarmos o código como é visto acima, o resultado é o que podemos ver na imagem logo abaixo.

Imagem 02
Resumindo, a implementação FIFO usando sobrecarga de operador, também funciona. Da mesma maneira que a implementação da pilha. Para se criar uma fila circular, você precisa fazer alguns ajustes no código 07. Mas isto não deve ser problema para você.
Legal. Mas ainda não terminamos. Acompanhe o restante do artigo, pois preciso explicar uma outra coisa a respeito do que foi visto neste tópico. Agora chegou a hora de partirmos para um desafio um pouco maior. Se bem que não é assim, um grande desafio. Que é justamente o de implementar uma lista encadeada que utilizará exclusivamente a sobrecarga de operadores para criar, modificar e manter o conteúdo da lista. Se bem que no artigo anterior, começamos a fazer isto. Mas aquilo que foi visto lá, é algo, que ao meu ver, é muito chato e sem graça. Chegou a hora de implementarmos um código mais divertido. Que venha de fato a fazer uso mais intensivo da sobrecarga de operadores. Mas como quero que você primeiro entenda muito bem o que foi visto aqui, e que não venha a misturar as coisas. Vamos fazer esta implementação em um outro tópico. Assim para entender o que será visto no próximo tópico, primeiro procure entender este. Somente depois tente entender o que será visto a partir de agora.
Uma lista encadeada repleta de diversão (I)
Antes de iniciarmos, quero lembrar a você do seguinte conceito, meu caro leitor. Uma lista encadeada, tem como objetivo, ter um comportamento muito próximo ao que seria um array dinâmico. Justamente por conta disto é que no artigo anterior, foi possível implementar as coisas daquela maneira. Porém, é este é o ponto importante, você NÃO DEVE CONFUDIR UMA LISTA ENCADEADA COM UM ARRAY. Ambas as coisas tem propósitos e objetivos que são completamente diferentes entre si.
Dito isto, podemos começar. No entanto, não faremos isto, copiando o código visto no artigo anterior. Isto por que, aqui iremos implementar coisas que tornaria o entendimento um tanto quanto confuso. Sendo assim, vamos modificar os arquivos de cabeçalho vistos no tópico anterior, para que venhamos a implementar a tal lista encadeada.
Se você ficar com alguma dúvida a respeito de por que a implementação precisa ser feita da forma como irei apresentar a mesma inicialmente. Procure ver os artigos, onde foi explicado, tanto o conceito quanto a forma de implementar uma lista encadeada. Aqui irei pressupor que você já saiba como isto deve ser feito.
Assim sendo, e com base nos princípios e conceitos necessários para implementar a tal lista. O código inicial que iremos utilizar, pode ser visto na íntegra logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class C_Demo 05. { 06. private: 07. //+----------------+ 08. uint m_counter; 09. T m_info; 10. C_Demo <T> *m_prev, 11. *m_next; 12. //+----------------+ 13. void Store(T arg1, const uint arg2 = UINT_MAX) 14. { 15. C_Demo <T> *loc = new C_Demo <T>, 16. *ptr1 = m_next, 17. *ptr2 = NULL; 18. for (uint c = 0; (ptr1 != NULL) && (c < arg2); ptr2 = ptr1, ptr1 = (*ptr1).m_next, c++); 19. 20. (*loc).m_info = arg1; 21. (*loc).m_next = (ptr2 != NULL ? (*ptr2).m_next : ptr1); 22. (*loc).m_prev = (ptr1 != NULL ? (*ptr1).m_prev : ptr2); 23. if (ptr1 != NULL) (*ptr1).m_prev = loc; else m_prev = loc; 24. if (ptr2 != NULL) (*ptr2).m_next = loc; else m_next = loc; 25. m_counter++; 26. } 27. //+----------------+ 28. bool Restore(T &arg1, const uint arg2 = UINT_MAX) 29. { 30. if ((m_prev == NULL) && (m_next == NULL)) return false; 31. C_Demo <T> *loc = (arg2 < m_counter ? m_next : m_prev), 32. *ptr = NULL; 33. 34. for (uint c = 0; (loc != NULL) && (c < arg2) && (arg2 < m_counter); ptr = loc, loc = (*loc).m_next, c++); 35. if (loc == NULL) return false; 36. if (arg2 == 0) 37. { 38. m_next = (*loc).m_next; 39. if (m_next != NULL) (*m_next).m_prev = NULL; 40. }else if (arg2 >= (m_counter - 1)) 41. { 42. m_prev = (*loc).m_prev; 43. if (m_prev != NULL) (*m_prev).m_next = NULL; 44. }else 45. { 46. (*ptr).m_next = (*loc).m_next; 47. (*loc).m_next.m_prev = ptr; 48. } 49. arg1 = (*loc).m_info; 50. delete loc; 51. m_counter--; 52. m_prev = (m_counter ? m_prev : NULL); 53. 54. return true; 55. } 56. //+----------------+ 57. public: 58. //+----------------+ 59. C_Demo() : m_counter(0), m_next(NULL), m_prev(NULL) {} 60. //+----------------+ 61. void operator<<(const T arg) 62. { 63. Store(arg); 64. } 65. //+----------------+ 66. bool operator>>(T &arg) 67. { 68. return Restore(arg); // Stack mode 69. // return Restore(arg, 0); // FIFO mode 70. } 71. //+----------------+ 72. }; 73. //+------------------------------------------------------------------+
Código 09
Para testar este código iremos usar o código 08. Claro que com uma pequena modificação. Assim, como não quero deixar as coisas muito confusas para você, meu caro leitor. O código a ser utilizado é este que vemos logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Include\C_Demo_03.mqh" 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. C_Demo <char> demo; 09. 10. demo << 10; 11. demo << 84; 12. demo << -6; 13. 14. for (char info; demo >> info;) 15. Print(info); 16. }; 17. //+------------------------------------------------------------------+
Código 10
Estranho, este código 10, parece ser o código 06. Na verdade ele não só parece, com na verdade é o código 06, meu caro leitor. Só que com a diferença de estarmos usando um outro arquivo na linha quatro. Agora preste atenção ao seguinte fato. Se você olhar o código 09, irá perceber que nas linhas 68 e 69 temos dois comentários sendo feitos. Isto por que, quero que você experimente ambas situações. Pois dependendo de qual destas linhas estejamos fazendo uso. Teremos um resultado ligeiramente diferente, quando o código 10 for executado. Não irei mostrar isto aqui, já que acho ser desnecessário.
Mas experimente executar o código 10, usando primeiro a linha 68 no código 09. E depois usando a linha 69. Você irá ver que as respostas ser parecerão em muito com o que foi mostrado nas imagens vistas no tópico anterior. Mas isto não deve de fato lhe surpreender. Isto por que tal coisa foi mostrada antes. Ok, se você não sabe, ou não faz a mínima ideia do que estou falando. Veja os artigos sobre filas, listas e árvores.
Como agora este código 09 está implementando uma lista encadeada, podemos começar a tornar este código da lista, um pouco mais elaborada. Isto a fim de tornar a lista completamente funcional. Para tal, agora iremos adicionar o operador subscrito, assim como foi visto no artigo anterior. Ao fazermos isto, o novo código será este mostrado logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class C_Demo 05. { 06. private: 07. //+----------------+ 08. uint m_counter; 09. T m_info; 10. C_Demo <T> *m_prev, 11. *m_next; 12. //+----------------+ 13. void Store(T arg1, const uint arg2 = UINT_MAX) 14. { 15. C_Demo <T> *loc = new C_Demo <T>, 16. *ptr1 = m_next, 17. *ptr2 = NULL; 18. for (uint c = 0; (ptr1 != NULL) && (c < arg2); ptr2 = ptr1, ptr1 = (*ptr1).m_next, c++); 19. 20. (*loc).m_info = arg1; 21. (*loc).m_next = (ptr2 != NULL ? (*ptr2).m_next : ptr1); 22. (*loc).m_prev = (ptr1 != NULL ? (*ptr1).m_prev : ptr2); 23. if (ptr1 != NULL) (*ptr1).m_prev = loc; else m_prev = loc; 24. if (ptr2 != NULL) (*ptr2).m_next = loc; else m_next = loc; 25. m_counter++; 26. } 27. //+----------------+ 28. bool Restore(T &arg1, const uint arg2 = UINT_MAX) 29. { 30. if ((m_prev == NULL) && (m_next == NULL)) return false; 31. C_Demo <T> *loc = (arg2 < m_counter ? m_next : m_prev), 32. *ptr = NULL; 33. 34. for (uint c = 0; (loc != NULL) && (c < arg2) && (arg2 < m_counter); ptr = loc, loc = (*loc).m_next, c++); 35. if (loc == NULL) return false; 36. if (arg2 == 0) 37. { 38. m_next = (*loc).m_next; 39. if (m_next != NULL) (*m_next).m_prev = NULL; 40. }else if (arg2 >= (m_counter - 1)) 41. { 42. m_prev = (*loc).m_prev; 43. if (m_prev != NULL) (*m_prev).m_next = NULL; 44. }else 45. { 46. (*ptr).m_next = (*loc).m_next; 47. (*loc).m_next.m_prev = ptr; 48. } 49. arg1 = (*loc).m_info; 50. delete loc; 51. m_counter--; 52. m_prev = (m_counter ? m_prev : NULL); 53. 54. return true; 55. } 56. //+----------------+ 57. public: 58. //+----------------+ 59. C_Demo() : m_counter(0), m_next(NULL), m_prev(NULL) {} 60. //+----------------+ 61. void operator<<(const T arg) 62. { 63. Store(arg); 64. } 65. //+----------------+ 66. bool operator>>(T &arg) 67. { 68. return Restore(arg); // Stack mode 69. // return Restore(arg, 0); // FIFO mode 70. } 71. //+----------------+ 72. C_Demo <T> *operator[](const uint arg) 73. { 74. C_Demo <T> *loc = m_next; 75. for (uint c = 0; (loc != NULL) && (c < arg); loc = (*loc).m_next, c++); 76. 77. return loc; 78. } 79. //+----------------+ 80. void operator=(const T arg) 81. { 82. m_info = arg; 83. } 84. //+----------------+ 85. void Debug(void) 86. { 87. Print("===== DEBUG ====="); 88. for (C_Demo <T> *loc = m_next; loc != NULL; loc = (*loc).m_next) 89. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).m_next, loc, (*loc).m_prev, (*loc).m_info); 90. Print("================="); 91. } 92. //+----------------+ 93. }; 94. //+------------------------------------------------------------------+
Código 11
Para testar este código 11, iremos utilizar o código visto logo a seguir.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Include\C_Demo_03.mqh" 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. C_Demo <char> demo; 09. 10. demo << 10; 11. demo << 84; 12. demo << -6; 13. demo.Debug(); 14. demo[0] = 47; 15. demo.Debug(); 16. demo[2] = 35; 17. demo.Debug(); 18. 19. for (char info; demo >> info;) 20. Print(info); 21. }; 22. //+------------------------------------------------------------------+
Código 12
Esta implementação já é um pouco mais interessante do que a que foi vista no artigo anterior. Isto por que, aqui estamos fazendo um uso bem mais profundo da sobrecarga de operadores. Mas antes de mostrar o que iremos fazer a seguir, quero que você veja o resultado da execução deste código 12, em conjunto como o que foi implementado no código 11. O resultado é o que podemos ver na imagem logo abaixo.

Imagem 03
Agora observe com atenção o que temos nesta imagem 03, meu caro leitor. Perceba, que apesar de nossa intenção principal, venha a ser a de adicionar os valores 47 e 35 a lista. Estes valores estão na verdade substituído os valores que já existem ali na lista. Dependendo do tipo de coisa que você esteja implementado, este tipo de resultado mostrado na imagem 03, já será bastante adequado ao objetivo proposto.
Porém, toda via e, entretanto, NÃO É ISTO que queremos de fato fazer. Queremos sim adicionar novos valores, e não substituir o que já existem na lista. Então o que iremos ver agora é a minha proposta para se resolver este problema. Então, não precisa ficar apavorado ao olhar o código, meu caro leitor. Irei dar algumas explicações sobre como e por que o mesmo funciona. Já que entender isto, é mais importante do que o código em si.
Para começar, vamos ver como ficou o arquivo de cabeçalho. Este pode ser visto na íntegra logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class C_Demo 05. { 06. private: 07. //+----------------+ 08. T m_info; 09. C_Demo <T> *m_prev, 10. *m_next; 11. //+----------------+ 12. #define def_Adjust(x) (m_prev = (m_prev == NULL ? x : ((*m_prev).m_prev != NULL ? (*m_prev).m_prev : m_prev))) 13. //+----------------+ 14. public: 15. //+----------------+ 16. C_Demo() : m_next(NULL), m_prev(NULL) {} 17. //+----------------+ 18. void operator<<(const T arg) 19. { 20. C_Demo <T> *loc = new C_Demo <T>; 21. 22. (*loc).m_info = arg; 23. (*loc).m_prev = m_next; 24. if (m_next != NULL) (*m_next).m_next = loc; 25. m_next = loc; 26. m_prev = def_Adjust(loc); 27. } 28. //+----------------+ 29. bool operator>>(T &arg) 30. { 31. C_Demo <T> *loc = def_Adjust(m_prev); 32. if (loc == NULL) return false; 33. arg = (*loc).m_info; 34. if ((m_prev = (*loc).m_next) != NULL) (*m_prev).m_prev = NULL; 35. delete loc; 36. 37. return true; 38. } 39. //+----------------+ 40. C_Demo <T> *operator[](const uint arg) 41. { 42. C_Demo <T> *loc = def_Adjust(m_prev); 43. for (uint c = 0; (loc != NULL) && (c < arg); loc = (*loc).m_next, c++); 44. return loc; 45. } 46. //+----------------+ 47. void operator=(const T arg) 48. { 49. m_info = arg; 50. } 51. //+----------------+ 52. void operator<<=(const T arg) 53. { 54. C_Demo <T> *loc = new C_Demo <T>; 55. 56. (*loc).m_info = arg; 57. (*loc).m_next = GetPointer(this); 58. (*loc).m_prev = m_prev; 59. if (m_prev != NULL) (*m_prev).m_next = loc; 60. m_prev = loc; 61. } 62. //+----------------+ 63. void Debug(uint line) 64. { 65. Print("===== DEBUG [", line, "]====="); 66. for (C_Demo <T> *loc = def_Adjust(m_prev); (loc != NULL); loc = (*loc).m_next) 67. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).m_prev, loc, (*loc).m_next, (*loc).m_info); 68. Print("================="); 69. } 70. //+----------------+ 71. #undef def_Adjust 72. //+----------------+ 73. }; 74. //+------------------------------------------------------------------+
Código 13
Note que diversas partes do código estão diferentes do que foi visto antes. E estas diferenças fazem todo o sentido, conforme você irá ver durante a explicação. Já o código que irá fazer uso deste arquivo de cabeçalho. Isto a fim de criar a lista encadeada, é visto logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Include\C_Demo_04.mqh" 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. C_Demo <char> demo; 09. 10. demo << 10; 11. demo << 84; 12. demo << -6; 13. demo.Debug(__LINE__); 14. demo[2] <<= 35; 15. demo.Debug(__LINE__); 16. demo[0] <<= 47; 17. demo.Debug(__LINE__); 18. demo[0] <<= 110; 19. demo.Debug(__LINE__); 20. 21. for (char info; demo >> info;) 22. Print(info); 23. }; 24. //+------------------------------------------------------------------+
Código 14
Pelo amor de DEUS e por tudo que é mais sagrado. Você não tem dó de nós pobres leitores? Que código mais maluco e sem noção é este que você está me apresentando? Cara nunca vi algo tão complicado assim em minha vida. Agora tenho certeza: Você é MALUCO. Calma meu caro leitor. Como eu disse, irei explicar o que está acontecendo aqui. Mas antes disto, que tal ver o resultado da execução deste código 14? Bem, isto pode ser observado ao olhar a imagem logo abaixo.

Imagem 04
Perceba que agora sim, temos o que queremos. Mas nem tudo está terminado. Porém antes disto, vamos entender como este código 13 e o código 14 funcionam. Obviamente, olhando primeiro para o código 14, você já deve ter uma certa noção do que está ocorrendo, justamente devido ao que foi explicado no tópico anterior. Porém existe a questão das linhas 14, 16 e 18, neste código 14, que ao olhar as mesmas, você pode ficar um tanto quanto atordoado. Já que isto parece pura insanidade. Mas como foi dito antes. Esta é a minha proposta para resolver o problema. E como você pode ver na imagem 04. A coisa realmente funciona. A pergunta é: COMO isto é possível? Bem, para entender, precisamos olhar agora para o código 13.
Este código 13, passou por uma mudança um pouco mais generosa para que ficasse mais enxuto. Ao mesmo tempo configurei a saída para ser feita no modo FIFO. Agora perceba que a coisa aqui, é um pouco diferente do que estava sendo feito anteriormente. Isto por que existe um detalhe que está sendo utilizado neste código 13, do qual eu ainda não a via comentado. Porém este é muito importante de ser corretamente compreendido, para que você consiga entender por que o código 14 funciona.
Note que temos somente dois laços em todo o código. Sendo um visto na linha 43 e o outro na linha 66. E por que isto é importante para nós? O motivo é que sem entender isto, você NÃO CONSEGUIRÁ ENTENDER como a sobrecarga do operador visto na linha 52 funciona. E mais do que isto. Sem entender de maneira adequada o que foi feito no tópico anterior. O que será explicado aqui, se tornará muito mais confuso, do que realmente é na prática.
Mas vamos começar pelo início. Na linha 18 temos a sobrecarga do operador, cujo objetivo é justamente o de adicionar novos elementos a lista duplamente encadeada. A forma como novos elementos serão adicionados aqui é muito parecido com o que acontece quando estamos adicionando elementos em uma fila. Porém, para garantir que a lista seja duplamente encadeada, usamos a linha 20 a fim de estabelecer o ponteiro que irá se ligar ao elemento anterior já presente na lista. Ou criar um primeiro elemento, caso a lista esteja vazia. Esta parte garante que não venhamos a precisar varrer a lista com um laço for, como era feito antes. Porém isto tem consequências no como o código irá trabalhar. De qualquer maneira os elementos serão empilhados na lista, conforme forem sendo adicionados, e sempre no topo da lista.
Ok, o fato de adicionarmos os elementos um a pós o outro na lista, não indica que estaremos trabalhando em modo FIFO ou modo pilha. Quem irá dizer isto, é justamente este operador que estamos implementando na linha 29. Aqui na linha 31, fazemos um pequeno ajuste a fim de procuramos o elemento presente na base da lista. Ou seja, a coisa em si não está definida, até que você as precise utilizar. E ao fazermos este pequeno ajuste, passamos a garantir que a lista será lida em modo FIFO.
Mas por que precisamos fazer este ajuste que você mencionou? Os elementos não serão adicionados na ordem em que os inserimos na lista? Sim meu caro leitor, os elementos, em tese serão colocados sempre no topo da lista. Porém o que estamos implementando, não é uma fila, mas sim uma lista. E sendo desta forma, podemos adicionar ou remover qualquer elemento em qualquer posição. Bem, existe um detalhe a este respeito. Mas nós vamos chegar lá. Uma coisa de cada vez.
Ok, até este ponto acredito que tenha sido fácil de entender o que estamos fazendo. Porém agora vem a parte complicada. E daí o motivo pelo qual este ajuste que estará sendo feito vir a ser necessário. Sendo assim, tente manter o foco no que será explicado. Caso contrário você irá ficar completamente perdido no meio da explicação.
Nos artigos anteriores demonstrei como o operador subscrito, poderia trabalhar em conjunto com o operador de atribuição. Isto a fim de que consigamos modificar o valor de um dado elemento, vindo a utilizar um índice como forma de selecionar qual elemento deveria ser modificado. Porém, aquilo que foi visto naqueles artigos, é apenas parte de um todo, e diga-se de passagem, aquilo é a parte fácil. Já que quando o laço da linha 43 do código 13 é executado. O que retornamos na linha 44, é na verdade um ponteiro para uma dada posição de memória. Justamente por conta disto, que o operador de atribuição, que está sendo implementado na linha 47, necessita apenas e tão somente da linha 49 para poder trabalhar. E assim conseguir efetivamente modificar o conteúdo de um dado elemento.
Agora pare e reflita um pouco a este respeito, meu caro leitor. Se a sobrecarga do o operador subscrito, que é feita na linha 40, está retornando um ponteiro para uma dada posição de memória. E no procedimento que usamos para adicionar novos elementos na lista, que pode ser visto na linha 18, usamos ponteiros para fazer a tal adição a fim de que um novo elemento venha a surgir na lista. Então se unirmos aquele mesmo princípio de adicionar um elemento, com a ideia de atribuir um valor, usando o operador de atribuição, o que teremos? Bem, teremos justamente o que está sendo implementado a partir da linha 52.
Hum, isto de certa forma me parece algo que foi bem bolado. Devo admitir que agora que você mencionou isto, aquele código 14, não parece assim tão complicado. Gostei da ideia. Mas agora que você mencionou isto, voltei a dar uma nova olhada no código 13 e percebo que o código usando para adicionar novos elementos, não se parece tanto com este código implementado na linha 52. Até pensei que poderiam vir a ser a mesma coisa, mas noto que eles têm algumas diferenças. Poderia explicar isto melhor? Sim meu caro leitor. Então vamos entender por que ambos os códigos tem algumas diferenças entre si.
Para começar o código implementado na linha 18, sabe que SEMPRE E SEMPRE o elemento será adicionado no topo da lista. Já este da linha 52, não sabe exatamente onde o elemento será adicionado. Já que ele pode estar sendo adicionado em qualquer ponto da lista. No entanto, e é aqui que mora a parte interessante. Para este código da linha 52, o elemento sempre será de certa forma adicionado na base da lista.
Espera um pouco aí. Agora a coisa ficou muito confusa. Você disse que este código da linha 52, NÃO SABE onde o elemento será colocado. Mas quase que imediatamente você volta e diz que o elemento será adicionado sempre na base da lista? Cara você precisa ser mais objetivo.
Não é isto meu caro leitor. Você está pensando uma coisa quando na verdade estou dizendo outra. Para tornar isto um pouco mais claro, vamos fazer o seguinte: Veja o código logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Include\C_Demo_04.mqh" 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. C_Demo <char> demo; 09. 10. demo << 10; 11. demo << 84; 12. demo << -6; 13. demo[2] <<= 35; 14. demo[0] <<= 47; 15. demo[0] <<= 110; 16. demo.Debug(__LINE__); 17. demo <<= 51; 18. demo.Debug(__LINE__); 19. 20. for (char info; demo >> info;) 21. Print(info); 22. }; 23. //+------------------------------------------------------------------+
Código 15
Quando este código 15 for executado, iremos ter o seguinte resultado:

Imagem 05
Preste atenção no que aconteceu. Ao executar a linha 17, o código gerou uma coisa bem bizarra na saída. No entanto, até a linha 15 ele estava funcionando perfeitamente bem. Justamente por conta da forma como o código estava gerando a lista. Mas por que esta linha 17 fez com que a lista se tornasse completamente bizarra, a ponto de ela não mais ser mantida? Bem para entender isto precisamos modificar o código 13, adicionando o que é visto no fragmento logo abaixo.
. . . 51. //+----------------+ 52. void operator<<=(const T arg) 53. { 54. C_Demo <T> *loc = new C_Demo <T>; 55. 56. (*loc).m_info = arg; 57. (*loc).m_next = GetPointer(this); 58. (*loc).m_prev = m_prev; 59. if (m_prev != NULL) (*m_prev).m_next = loc; 60. m_prev = loc; 61. PrintFormat("%s %d :: 0x%06X ->> 0x%06X:[%d] <<- 0x%06X ?? (%d)", __FUNCTION__, __LINE__, m_prev, GetPointer(this), m_info, m_next, arg); 62. } 63. //+----------------+ . . .
Fragmento 01
Agora compilamos novamente o código 15 e como resultado iremos ver o que é mostrado na imagem logo abaixo.

Imagem 06
Ok, agora posso tentar explicar como este código da linha 51 funciona. E por que ele está sempre olhando para o início da lista local. E mais do que isto. Agora você irá entender por que precisamos usar o operador subscrito antes de tentar usar este operador da linha 51.
Note que nas três primeiras linhas da imagem 06, estamos sempre olhando para algum ponto dentro da lista já construída. Isto nos permite adicionar qualquer novo elemento em meio aos elementos já existente. Obviamente no caso dos valores 47 e 110, os estamos colocando bem no início da lista já construída. No entanto, repare nos valores quando tentamos adicionar o valor 51 a lista. Neste caso temos um problema. Tanto que quando vamos depurar a lista, temos uma sequência completamente estranha sendo criada. E isto quebra a lista que estava sendo mantida pelo nosso código.
Ok, mas ainda não entendi bem esta questão, meu caro autor. Para mim tudo parece absolutamente normal. Certo, vamos ver que consigo deixar isto um pouco mais claro para você, meu amigo leitor. Para isto, basta você olhar a imagem 04. Repare que o valor mais baixo em hexadecimal ali é o de 0x200000. Isto por que o valor 0x100000 NÃO PODE SER UTILIZADO. Este tipo de coisa foi explicado nos artigos anteriores. Confira os mesmo para maiores detalhes. Sendo assim, quando elemento de valor 51 está tentando ser acionado, graças a linha 17 do código 15. Ele irá reportar uma tentativa de utilizar justamente o endereço 0x100000 como sendo o próximo endereço na lista. O que é algo completamente inválido. Por conta disto a lista acaba quebrando.
Mas vamos voltar a uma outra questão, para entender por que digo que a lista é montada de maneira parcial, ao adicionarmos algum elemento no meio dela. Para conseguir entender isto, precisamos modificar o código da forma como é mostrado no fragmento logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class C_Demo 05. { 06. private: 07. //+----------------+ 08. T m_info; 09. C_Demo <T> *m_prev, 10. *m_next; 11. //+----------------+ 12. #define def_Adjust(x) (m_prev = (m_prev == NULL ? x : ((*m_prev).m_prev != NULL ? (*m_prev).m_prev : m_prev))) 13. //+----------------+ 14. void Debug_Private(string fn, uint line) 15. { 16. Print("===== DEBUG [", fn, " :: ", line, "]====="); 17. for (C_Demo <T> *loc = m_prev; (loc != NULL); loc = (*loc).m_next) 18. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).m_prev, loc, (*loc).m_next, (*loc).m_info); 19. Print("================="); 20. } 21. //+----------------+ . . . 59. //+----------------+ 60. void operator<<=(const T arg) 61. { 62. C_Demo <T> *loc = new C_Demo <T>; 63. 64. (*loc).m_info = arg; 65. (*loc).m_next = GetPointer(this); 66. (*loc).m_prev = m_prev; 67. if (m_prev != NULL) (*m_prev).m_next = loc; 68. m_prev = loc; 69. Debug_Private(__FUNCTION__, __LINE__); 70. } 71. //+----------------+ 72. void Debug(string fn, uint line) 73. { 74. m_prev = def_Adjust(m_prev); 75. Debug_Private(fn, line); 76. } 77. //+----------------+ 78. #undef def_Adjust 79. //+----------------+ 80. }; 81. //+------------------------------------------------------------------+
Fragmento 02
O que nos interessa aqui é justamente a execução da linha 69. No anexo, você terá este arquivo de cabeçalho completo. Então não precisa ficar preocupado. Já que ele é uma modificação do código 13. Porém esta mudança foi necessária para preservar o valor de m_prev. Já que quando esta linha 69, chamava o código original para depurar a lista, ela acabava por danificar o valor do ponteiro de m_prev. Mas você logo irá tender o motivo. Então para demonstrar esta montagem parcial da lista, utilizaremos o código logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Include\C_Demo_04.mqh" 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. C_Demo <char> demo; 09. 10. demo << 10; 11. demo << 84; 12. demo << -6; 13. demo[2] <<= 35; 14. demo[0] <<= 47; 15. demo[4] <<= 110; 16. demo.Debug(__FUNCTION__, __LINE__); 17. 18. for (char info; demo >> info;) 19. Print(info); 20. }; 21. //+------------------------------------------------------------------+
Código 16
Isto daqui é muito legal, meu caro leitor. Isto por que, quando executarmos este código 16, o resultado será o que é visto logo abaixo.

Imagem 07
Esta imagem 07, praticamente explica como e por que todo este código visto neste tópico funciona. Mas principalmente, esta imagem deixa claro, aquilo que mencionei antes. Que é justamente o fato de que a lista será sempre observada de maneira parcial, quando estivermos adicionando elementos em alguma posição arbitrária dentro dela.
Considerações finais
Muito bem, este artigo já está bem mais longo do que eu realmente pretendia criar. No entanto, foi necessário me estender um pouco além, justamente para explicar e deixar claro como as coisas poderiam ser implementadas. Detalhe, não sei se você prestou a devida atenção a tudo que foi visto aqui. Mas se de fato conseguiu manter o foco, dentro do que foi explicado aqui, e tendo o conhecimento sobre que tipo de coisas precisamos implementar em uma lista encadeada. Deve ter notado que a função, ou procedimento para excluir algum elemento da lista não foi ainda implementado. Além é claro alguns outros pequenos detalhes que ainda precisam ser construídos para que possamos dizer: SIM, temos uma implementação de lista encadeada, que faz uso total e completo da sobrecarga de operadores. Não necessitando de praticamente nenhuma outra função, ou procedimento sendo chamado pelo código fora da lista.
Porém esta parte sobre como excluir elementos dentro da lista, fazendo uso da sobrecarga de operadores, é um tanto quanto complicada. Não que seja difícil de implementar. Mas como a maior parte de vocês leitores, tem muita pouca experiência. Implementar o código de exclusão, sem dar a devida explicação do porquê de ele funcionar. Não é algo justo. Pelo menos é assim que eu me sentiria se estivesse no seu lugar. Onde alguém simplesmente despeja um código e você que se vire para tentar entender como ele funciona.
Isto não é a forma como quero e gosto de fazer as coisas. Quero que todos aprendam e consigam com isto tirar algum tipo de proveito naquilo que estou postando. E para isto, se faz necessário criar um artigo onde teremos uma explicação adequada de por que aquilo funciona. Então no próximo artigo iremos, creio eu, finalizar esta parte básica sobre sobrecarga de operadores. Então estude tudo o que foi visto aqui com calma. Pois isto será necessário para que você consiga entender o que será visto no próximo artigo.
| Arquivo MQ5 | Descrição |
|---|---|
| Code 01 | Demonstração básica |
| Code 02 | Demonstração básica |
| Code 03 | Demonstração básica |
| Code 04 | Demonstração básica |
| Code 05 | Demonstração básica |
| Code.cpp | Demonstração básica |
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.
Redes Adversariais Generativas (GANs) para Dados Sintéticos em Modelagem Financeira (Parte 2): Criação de Símbolo Sintético para Testes
Replay e Simulação de mercado: Gran Finale
Desenvolvimento do Kit de Ferramentas de Análise de Price Action (Parte 10): Fluxo Externo (II) VWAP
Redes neurais em trading: Previsão de séries temporais com o auxílio da decomposição modal adaptativa (Conclusão)
- 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