Português
preview
Del básico al intermedio: Sobrecarga de operadores (V)

Del básico al intermedio: Sobrecarga de operadores (V)

MetaTrader 5Ejemplos |
22 0
CODE X
CODE X

Introducción

En el artículo anterior, Del básico al intermedio: Sobrecarga de operadores (IV), tuvimos nuestro bautismo de fuego con la sobrecarga de operadores. En aquel artículo se demostró que la sobrecarga de operadores presenta ciertas limitaciones. Aun así, aunque esas limitaciones existen, en ningún caso impiden sobrecargar operadores para que el código sea más legible o más fácil de comprender para cualquier persona.

Queda una última cuestión que quiero explicar en este artículo, posiblemente el último de esta serie sobre la sobrecarga de operadores en su nivel más básico y sencillo.

Quiero abordar el uso conjunto de diversos operadores sobrecargados para implementar una aplicación de forma bastante simbólica y, al mismo tiempo, mucho más fácil de comprender. Al menos, desde mi punto de vista.

Como el tema que abordamos aquí es relativamente extenso y, al mismo tiempo, bastante profundo, entremos directamente en el tema principal. Ha llegado el momento de dejar de lado cualquier cosa que pueda distraerte y concentrarte por completo en el contenido de este artículo.


Sobrecarga de operadores (V)

Hay muchos aspectos de la programación que solo el tiempo, el estudio y la práctica podrán enseñarte realmente, mi querido lector. Aun así, quiero ayudarte a comprender ciertas cuestiones con algo más de rapidez y darte un pequeño impulso en una dirección concreta, para que entiendas que no todo es como normalmente imaginamos. Por esta razón amo programar. Esta es, sin duda, una pasión que tal vez ni siquiera la muerte pueda arrebatarme.

En el artículo anterior, presenté una primera aproximación para crear una lista enlazada con sobrecarga de operadores. No obstante, aquella propuesta resulta bastante aburrida comparada con la de este artículo. Antes de volver al tema de la lista enlazada, necesito mostrarte otro detalle que, aunque pueda parecer una tontería, te ayuda a entender los cambios que aplicamos al código de la lista enlazada.

Bien, en el artículo Del básico al intermedio: Colas, listas y árboles (II), expliqué cómo podíamos transformar una cola implementada dentro de un array en una cola que utilizara punteros para enlazar sus propios elementos. Esta transformación abría la posibilidad de que surgiera una nueva estructura de datos: las listas. Sin embargo, ese no es exactamente el punto central. El detalle importante es cómo implementar el mismo tipo de cola mostrado en el artículo mencionado anteriormente mediante sobrecarga de operadores.

Implementar esta variante es muy divertido y le da una nueva vida al código, haciéndolo mucho más interesante.

Como punto de partida, utilizamos uno de los códigos vistos en aquel artículo. A continuación se muestra.

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

Al ejecutar este código, se obtiene el resultado que aparece a continuación.

Imagen 01

La idea es recrear este mismo código 01 utilizando sobrecarga de operadores y mantener el resultado de la imagen 01. En este momento quizá estés pensando: Vamos, esto es una locura. ¿Cómo lograrlo sin convertir el código en una tremenda confusión? Bien, mi querido lector, pronto entenderás el procedimiento. Antes, demos a este código 01 el aspecto de un código real. En la práctica, es mucho más habitual dividir el código en varios archivos que mantenerlo todo en uno solo, como ocurre en el código 01. Así que ahora tenemos dos archivos. Como puedes ver, esta división no cambia el funcionamiento, aunque hace el código bastante más interesante.

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

Ahora sí, la estructura resulta mucho más atractiva. Aunque en el archivo adjunto estos archivos son diferentes, porque los modificamos a lo largo del artículo y allí aparece el resultado final. Así que no te apresures. Veamos paso a paso cómo estos códigos llegan al estado que encontrarás en el archivo adjunto.

Perfecto. Ahora centrémonos en la sobrecarga. Quien estudie C++ se encontrará con dos operadores que, en principio, resultan un tanto exóticos. Básicamente, estos operadores se conocen como Stdin y Stdout. Permiten acceder de forma sencilla y práctica a la entrada o salida estándar. No obstante, también pueden utilizarse en otras situaciones bastante interesantes.

Muy bien, MQL5 no cuenta, o al menos no utiliza, el mismo concepto de Stdin y Stdout. Aun así, nada nos impide implementar nuestros códigos para utilizar ese mismo concepto.

La implementación que veremos aquí es relativamente sencilla. En mi otro perfil, dedicado a explicar técnicas que pueden utilizarse en MQL5, mostraré una forma mucho más avanzada de aplicar este mismo concepto. No voy a arruinar la sorpresa de lo que haremos allí. La técnica resulta muy interesante y vuelve el código mucho más fácil de comprender.

Volviendo al tema, el concepto de Stdin y Stdout aparece en el siguiente código.

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

No necesitas entender C++ para comprender este código, ya que lo visto en MQL5 basta para formarte una idea general de su funcionamiento. Identifica el siguiente patrón en este código 04. En algunas líneas aparece lo siguiente: std::cout seguido de un operador. También encontramos en otras líneas otro elemento: std::cin, igualmente seguido de un operador. Ahora veamos qué es esta cosa extraña que aparece en el código.

En primer lugar, std::cout representa la salida estándar. Todos los datos que escribimos se envían a esta salida estándar. Por su parte, std::cin representa la entrada estándar y funciona de una forma muy parecida a std::cout. La diferencia es que std::cin corresponde a la entrada estándar. Así, cualquier dato presente en la entrada estándar se dirige a alguna variable u otro elemento dentro del código.

Como muchas veces std::cout y std::cin por sí solos no resultan demasiado claros, utilizamos un operador que nos ayuda a entender cómo se dirigen los datos. En una situación especial, como la que aparece en la línea 14 de este código 04, este mismo operador se repite varias veces, mostrando así cómo debe interpretar las expresiones el compilador de C++.

Muy bien, aquí termina la parte de C++ que vemos en este artículo. Ahora replicamos precisamente el operador que aparece en el código 04, pero en MQL5. Con ello, nuestro código MQL5 adopta un formato algo más exótico. No obstante, si la sobrecarga de operadores está bien diseñada, el código puede ser mucho más legible. Llevaré esta idea un poco más lejos en otro artículo que publicaré próximamente en mi otro perfil. Aquí nos quedamos solo con lo más básico, ya que aprendemos a utilizar la sobrecarga de operadores.

Bien, modifiquemos el archivo de cabecera que aparece en el código 03. Ahora tiene el aspecto que aparece íntegramente a continuación.

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

Revisa este código 05 y compáralo con el código 03. Fíjate en que el cambio ha sido muy sutil. Aun así, estas sencillas modificaciones son más que suficientes para hacerle un nudo en el cerebro a más de uno que dice ser programador de MQL5. Ahora el código realmente se vuelve mucho más divertido. Así que presta mucha atención al siguiente paso. Antes, vuelve al código 02. ¿Entiendes ese código? Bien, ahora revisa el código 06 que aparece a continuación.

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, ¿qué locura aparece en este código 06? Vamos, debo admitirlo: estás completamente loco. Si me hubieras mostrado este código 06 al principio, creo que habría dejado de leer estos artículos. Pero, por lo que haces con el código, creo que para ti este enfoque no es más que pura diversión. Te gusta jugar con el código y con el lenguaje. Pues no sabía que se podía programar de esta manera en MQL5.

Pues bien, mi querido lector, este ejemplo es solo una pequeña muestra de lo que realmente podemos hacer. Y el resultado de ejecutar este código 06 es exactamente el mismo que el del código 02. De hecho, dividí deliberadamente el archivo en dos, uno principal y otro de cabecera, precisamente para mostrarte con claridad la implementación realizada aquí. Intentar explicar directamente esta implementación, sin más, sería prácticamente imposible.

La diversión apenas comienza. Si estudiaste lo visto en los artículos sobre colas, listas y árboles, sabrás que la salida de este código 06, al igual que la del código 02, representa una pila. Ahora bien, basta un pequeño cambio en el código, esta vez únicamente en el archivo de cabecera, para obtener una implementación FIFO. El archivo de cabecera que permite hacerlo aparece a continuación.

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 archivo 07 tiene otro nombre en el adjunto, ya que pretendo facilitar al máximo las pruebas para que puedas experimentar con este contenido. Así, el código 06 queda como aparece a continuación.

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

Ahora observa lo siguiente, mi querido lector. Si la línea cuatro está habilitada, obtenemos la implementación de una pila. Si, en cambio, está habilitada la línea cinco, obtenemos una cola FIFO. NO debes habilitar ambas líneas, salvo que modifiques los archivos de cabecera incluidos en el adjunto.

Bien, si ejecutamos el código tal como se muestra anteriormente, el resultado aparece en la siguiente imagen.

Imagen 02

En resumen, la implementación FIFO mediante sobrecarga de operadores también funciona, de la misma forma que la implementación de la pila. Para crear una cola circular, debes hacer algunos ajustes en el código 07. Pero esos ajustes no deberían suponerte ningún problema.

Perfecto. Todavía no hemos terminado. Sigue con el resto del artículo, porque necesito explicar otro aspecto de este tema. Ahora afrontamos un desafío algo mayor. Aunque tampoco es un gran desafío: implementar una lista enlazada que utilice exclusivamente la sobrecarga de operadores para crear, modificar y mantener su contenido. En el artículo anterior ya comenzamos a hacerlo. Sin embargo, aquella implementación, desde mi punto de vista, resulta bastante aburrida y poco entretenida. Ha llegado el momento de implementar un código más divertido, que utilice la sobrecarga de operadores con mayor intensidad. Como quiero que primero comprendas muy bien este tema y no mezcles los conceptos, reservamos esa implementación para otro apartado. Por tanto, primero comprende bien este tema y solo después aborda el contenido que sigue.


Una lista enlazada repleta de diversión (I)

Antes de comenzar, quiero recordarte el siguiente concepto, mi querido lector. Una lista enlazada busca comportarse de forma muy similar a un array dinámico. Precisamente por esta razón, en el artículo anterior fue posible implementarla de aquella manera. Sin embargo, este es el punto importante: NO DEBES CONFUNDIR UNA LISTA ENLAZADA CON UN ARRAY. Ambas estructuras tienen propósitos completamente diferentes.

Aclarada esta diferencia, podemos comenzar. No comenzamos copiando el código visto en el artículo anterior, porque aquí incorporamos cambios que pueden volver la explicación algo confusa. Por tanto, modificamos los archivos de cabecera vistos en el tema anterior para implementar la lista enlazada.

Si tienes alguna duda sobre por qué debemos implementar inicialmente la lista como la presento, revisa los artículos que explican el concepto y cómo implementar una lista enlazada. Aquí daré por supuesto que ya sabes cómo hacerlo.

Así pues, basándonos en los principios y conceptos necesarios para implementar esta lista, el código inicial que utilizamos aparece íntegramente a continuación.

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 probar este código, utilizamos el código 08. Claro, con una pequeña modificación. Así que, como no quiero complicarte demasiado la explicación, mi querido lector, el código que utilizamos aparece a continuación.

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

Qué extraño, este código 10 parece ser el código 06. En realidad, no solo lo parece, sino que es el código 06, mi querido lector. La única diferencia es que utilizamos otro archivo en la línea cuatro. Ahora presta atención a este detalle. Si revisas el código 09, en las líneas 68 y 69 hay dos líneas comentadas. Esas líneas están comentadas porque quiero que pruebes ambas situaciones. Según cuál utilicemos, obtenemos un resultado ligeramente diferente al ejecutar el código 10. No voy a mostrar ese resultado aquí, ya que considero que no es necesario.

Prueba a ejecutar el código 10 utilizando primero la línea 68 del código 09 y después la línea 69. Los resultados se parecen mucho a lo mostrado en las imágenes del tema anterior. Esta similitud no sorprende, ya que ese comportamiento se mostró anteriormente. Bien, si no sabes o no tienes la menor idea de qué estoy hablando, revisa los artículos sobre colas, listas y árboles.

Como ahora este código 09 implementa una lista enlazada, podemos elaborar un poco más el código de la lista hasta volverlo completamente funcional. Para ello, añadimos ahora el operador de subíndice, tal como vimos en el artículo anterior. Con este cambio, el nuevo código aparece a continuación.

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 probar este código 11, utilizamos el código que aparece a continuación.

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 implementación ya resulta algo más interesante que la vista en el artículo anterior, porque aquí profundizamos mucho más en la sobrecarga de operadores. Antes de mostrar el siguiente paso, quiero que veas el resultado de ejecutar este código 12 junto con lo implementado en el código 11. El resultado aparece en la siguiente imagen.

Imagen 03

Ahora observa con atención la imagen 03, mi querido lector. Fíjate en que, aunque nuestra intención principal es añadir los valores 47 y 35 a la lista, en realidad estos valores sustituyen los que ya existen en ella. Dependiendo de tu implementación, este resultado puede ajustarse perfectamente a lo que buscas.

Sin embargo, NO QUEREMOS SUSTITUIR los valores existentes. Queremos añadir nuevos valores a la lista. Así que ahora presento mi propuesta para resolver este problema. No te asustes al ver el código, mi querido lector. A continuación explico cómo y por qué funciona, ya que comprender su funcionamiento es más importante que el propio código.

Para comenzar, veamos cómo quedó el archivo de cabecera. Se muestra íntegramente a continuación.

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

El código presenta varias diferencias respecto de la versión anterior. Y estas diferencias tienen todo el sentido, como muestra la explicación. El código que utiliza este archivo de cabecera para crear la lista enlazada aparece a continuación.

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

Por el amor de DIOS y por todo lo más sagrado. ¿No tienes compasión de nosotros, pobres lectores? ¿Qué clase de código tan disparatado y absurdo me muestras? Vamos, nunca he visto algo tan complicado en mi vida. Ahora sí estoy seguro: estás LOCO. Tranquilo, mi querido lector. Como dije, explicaré qué ocurre aquí. Antes, ¿qué tal si vemos el resultado de ejecutar este código 14? Bien, aparece en la siguiente imagen.

Imagen 04

Fíjate en que ahora sí tenemos lo que queremos. Todavía no hemos terminado. Antes de continuar, entendamos cómo funcionan este código 13 y el código 14. Obviamente, si analizas primero el código 14, ya puedes tener cierta idea de lo que ocurre gracias a lo explicado en el tema anterior. Sin embargo, las líneas 14, 16 y 18 de este código 14 pueden dejarte algo desconcertado, ya que parecen una auténtica locura. Como dije antes, esta es mi propuesta para resolver el problema. Y, como muestra la imagen 04, la solución realmente funciona. La pregunta es: ¿CÓMO funciona? Bien, para entender el mecanismo, debemos analizar ahora el código 13.

Este código 13 recibió una modificación algo más amplia para hacerlo más compacto. Al mismo tiempo, configuré la salida en modo FIFO. Ahora, el funcionamiento es un poco diferente del anterior. La diferencia se debe a un detalle utilizado en este código 13 que todavía no había comentado. Sin embargo, es muy importante comprenderlo correctamente para entender por qué funciona el código 14.

El código solo contiene dos bucles: uno en la línea 43 y otro en la línea 66. ¿Y por qué son importantes estos bucles? Porque, si no entiendes su función, NO ENTENDERÁS cómo funciona la sobrecarga del operador que aparece en la línea 52. Es más, si no comprendes adecuadamente lo que hicimos en el tema anterior, mi explicación te resultará mucho más confusa de lo que realmente es en la práctica. Empecemos por el principio.

En la línea 18 tenemos la sobrecarga del operador que añade nuevos elementos a la lista doblemente enlazada. La forma en que añadimos nuevos elementos aquí es muy parecida a lo que ocurre cuando añadimos elementos a una cola. Para garantizar que la lista sea doblemente enlazada, utilizamos la línea 20 para establecer el puntero que enlaza con el elemento anterior ya presente en la lista o para crear el primer elemento si la lista está vacía. Esta parte evita que tengamos que recorrer la lista mediante un bucle for, como hacíamos antes. No obstante, esta decisión tiene consecuencias en el funcionamiento del código. En cualquier caso, los elementos se apilan en la lista a medida que se añaden, siempre en la parte superior de la lista.

Bien, añadir los elementos uno tras otro a la lista no significa que trabajemos en modo FIFO o en modo pila. El modo de lectura lo determina precisamente el operador que implementamos en la línea 29. En la línea 31 ajustamos ligeramente la búsqueda del elemento situado en la base de la lista. Es decir, el comportamiento en sí no queda definido hasta que necesitas utilizar los elementos. Ese ajuste garantiza que la lista se lea en modo FIFO.

¿Por qué necesitamos ese ajuste que mencionaste? ¿Los elementos no se añaden en el mismo orden en que los insertamos en la lista? Sí, mi querido lector, en principio los elementos siempre se colocan en la parte superior de la lista. Sin embargo, implementamos una lista, no una cola. Por eso podemos añadir o eliminar cualquier elemento en cualquier posición. Bien, hay un detalle al respecto. Vayamos paso a paso.

Bien, hasta este punto creo que ha sido fácil entender lo que hacemos. Ahora viene la parte complicada, y precisamente por eso necesitamos el ajuste que realizamos. Así que intenta mantener la atención en mi explicación. De lo contrario, acabarás completamente perdido en medio de ella.

En los artículos anteriores, mostré cómo el operador de subíndice podía combinarse con el operador de asignación para modificar el valor de un determinado elemento mediante un índice que selecciona cuál debe modificarse. Sin embargo, lo visto en aquellos artículos es solo una parte del conjunto y, dicho sea de paso, esa es la parte fácil. Cuando se ejecuta el bucle de la línea 43 del código 13, la línea 44 devuelve, en realidad, un puntero a una determinada posición de memoria. Precisamente por esta razón, el operador de asignación que se implementa en la línea 47 solo necesita la línea 49 para funcionar y modificar efectivamente el contenido de un determinado elemento.

Ahora detente y reflexiona un poco sobre este mecanismo, mi querido lector. Si la sobrecarga del operador de subíndice, implementada en la línea 40, devuelve un puntero a una determinada posición de memoria y, en el procedimiento de la línea 18, usamos punteros para añadir un nuevo elemento a la lista, entonces, si combinamos ese mismo principio con la asignación de un valor mediante el operador de asignación, ¿qué obtenemos? Bien, precisamente la implementación que comienza en la línea 52.

Hum, en cierto modo, este mecanismo me parece bastante ingenioso. Debo admitir que, ahora que lo mencionas, aquel código 14 ya no parece tan complicado. Me gusta la idea. Al volver a analizar el código 13, veo que el código utilizado para añadir nuevos elementos no se parece tanto al implementado en la línea 52. Incluso pensé que podrían ser iguales, pero observo que presentan algunas diferencias. ¿Podrías explicar mejor esas diferencias? Sí, mi querido lector. Veamos entonces por qué ambos códigos difieren.

Para empezar, el código implementado en la línea 18 sabe que SIEMPRE, ABSOLUTAMENTE SIEMPRE, el elemento se añade en la parte superior de la lista. En cambio, el de la línea 52 no sabe exactamente dónde se añade el elemento, ya que puede insertarse en cualquier punto de la lista. Ahora bien, aquí está la parte interesante: para este código de la línea 52 el elemento siempre se añade, en cierto modo, en la base de la lista.

Espera un momento. Ahora la explicación se ha vuelto muy confusa. Dijiste que este código de la línea 52 NO SABE dónde se coloca el elemento. Pero, casi inmediatamente después, dices que el elemento siempre se añade en la base de la lista. Vamos, tienes que ser más claro.

No me refiero a eso, mi querido lector. Tú interpretas una cuestión distinta de la que estoy explicando. Para aclarar la diferencia, revisa el código que aparece a continuación.

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

Al ejecutar este código 15, obtenemos el siguiente resultado:

Imagen 05

Presta atención a lo que ocurrió. Al ejecutar la línea 17, el código generó una salida bastante extraña. Sin embargo, hasta la línea 15 funcionaba perfectamente, precisamente por la forma en que el código generaba la lista. ¿Por qué esta línea 17 hizo que la lista se volviera completamente extraña, hasta el punto de dejar de conservarse correctamente? Bien, para entender la causa, necesitamos modificar el código 13 añadiendo lo que aparece en el siguiente fragmento.

                   .
                   .
                   .
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

Ahora volvemos a compilar el código 15 y, como resultado, obtenemos lo que aparece en la siguiente imagen.

Imagen 06

Bien, ahora puedo intentar explicar cómo funciona este código de la línea 51 y por qué siempre apunta al inicio de la lista local. Y, más importante aún, ahora puedes entender por qué necesitamos utilizar el operador de subíndice antes de intentar usar este operador de la línea 51.

En las tres primeras líneas de la imagen 06, siempre apuntamos a algún punto dentro de la lista ya construida. Esta referencia nos permite añadir cualquier elemento nuevo entre los elementos que ya existen. Obviamente, en el caso de los valores 47 y 110, los colocamos justo al principio de la lista ya construida. En cambio, fíjate en lo que ocurre cuando intentamos añadir el valor 51 a la lista. En este caso tenemos un problema. Tanto es así que, cuando depuramos la lista, se genera una secuencia completamente extraña. Esa secuencia rompe la lista que nuestro código mantenía.

Bien, pero todavía no entiendo del todo esta cuestión, mi querido autor. Para mí, todo parece absolutamente normal. De acuerdo, veamos si consigo dejarla un poco más clara para ti, mi amigo lector. Para ello, basta con observar la imagen 04. Fíjate en que el valor hexadecimal más bajo que aparece allí es 0x200000. La razón es que el valor 0x100000 NO PUEDE UTILIZARSE. Esta restricción se explicó en los artículos anteriores. Revísalos para obtener más detalles. Así pues, cuando se intenta acceder al elemento con valor 51 debido a la línea 17 del código 15, se indica un intento de utilizar precisamente la dirección 0x100000 como la siguiente dirección de la lista, lo cual es completamente inválido. Por esta razón, la lista termina rompiéndose.

Volvamos a otra cuestión para entender por qué digo que la lista se construye parcialmente cuando añadimos algún elemento en medio de ella. Para comprenderlo, necesitamos modificar el código tal como se muestra en el siguiente fragmento.

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

Aquí nos interesa precisamente la ejecución de la línea 69. En el adjunto tienes este archivo de cabecera completo. Así que no tienes que preocuparte, ya que es una modificación del código 13. Este cambio fue necesario para preservar el valor de m_prev, porque cuando la línea 69 ejecutaba el código original para depurar la lista, terminaba alterando el valor del puntero m_prev. Enseguida queda claro el motivo. Así que, para demostrar esta construcción parcial de la lista, utilizamos el código que aparece a continuación.

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

El resultado es realmente interesante, mi querido lector. Al ejecutar este código 16, se obtiene lo que aparece a continuación.

Imagen 07

Esta imagen 07 prácticamente explica cómo y por qué funciona todo el código visto en este tema. Sobre todo, deja claro lo que mencioné anteriormente: al añadir elementos en una posición arbitraria, solo se toma como referencia una parte de la lista.


Consideraciones finales

Muy bien, este artículo ya es mucho más extenso de lo que realmente pretendía. Sin embargo, fue necesario extenderme un poco más para explicar con claridad cómo implementar estas ideas. Un detalle: no sé si prestaste la debida atención a todo el contenido. Si seguiste atentamente la explicación y conoces los elementos necesarios para implementar una lista enlazada, puedes notar que todavía falta implementar la función, o procedimiento, para eliminar un elemento de la lista. Además, por supuesto, quedan algunos otros pequeños detalles antes de poder decir: SÍ, tenemos una implementación de una lista enlazada que utiliza plenamente la sobrecarga de operadores, sin necesidad de que el código externo a la lista llame prácticamente a ninguna otra función o procedimiento.

Eliminar elementos de la lista mediante sobrecarga de operadores resulta más complicado. No porque sea difícil de implementar, sino porque la mayoría de ustedes, lectores, tiene muy poca experiencia; implementar el código de eliminación sin explicar adecuadamente por qué funciona no sería justo. Al menos, así me sentiría yo si estuviera en tu lugar y alguien simplemente me soltara un código y te dejara solo intentando averiguar cómo funciona.

No es así como quiero ni me gusta trabajar. Quiero que todos aprendan y aprovechen el contenido que publico. Para ello, necesitamos un artículo que explique adecuadamente por qué funciona esta implementación. Así que, en el próximo artículo, creo que finalizaremos esta parte básica sobre sobrecarga de operadores. Estudia con calma todo este contenido, porque lo necesitarás para entender el próximo artículo.

Archivo MQ5Descripción
Code 01 Demostración básica
Code 02 Demostración básica
Code 03 Demostración básica
Code 04 Demostración básica
Code 05  Demostración básica
Code.cpp  Demostración básica

Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/16977

Archivos adjuntos |
Anexo.zip (5.46 KB)
Repetición y simulación de mercado: Gran final Repetición y simulación de mercado: Gran final
Sé que muchos podrían haber imaginado que publicaría más artículos para explicar otros aspectos del sistema. Los elementos que faltan son sencillos de implementar. Aun así, su desarrollo te permitirá comprobar hasta qué punto estás realmente preparado.
Motor de decision Multi-IA para MQL5 (Parte 5): backtest del motor en el simulador de estrategias y comparacion contra reglas simples. Motor de decision Multi-IA para MQL5 (Parte 5): backtest del motor en el simulador de estrategias y comparacion contra reglas simples.
En esta última parte se pone a prueba el motor multi-IA de la serie. Se graban los votos reales de los modelos barra por barra en XAUUSD H1 y se reproducen en el Simulador de estrategias, para compararlo con reglas simples bajo el mismo riesgo y los mismos costos, dentro y fuera de muestra. Se reportan los resultados y su causa, y se incluye un arnés de grabación y reproducción y una prueba de diagnóstico aplicable a cualquier asesor con IA.
Automatización de estrategias de trading en MQL5 (Parte 20): Estrategia multisímbolo con CCI y AO Automatización de estrategias de trading en MQL5 (Parte 20): Estrategia multisímbolo con CCI y AO
En este artículo, creamos una estrategia de trading multisimbolo utilizando los indicadores CCI y AO para captar reversiones de tendencia. Analizamos su diseño, la implementación de MQL5 y el proceso de backtesting. El artículo concluye con consejos para mejorar el rendimiento.
Simulación de mercado: La unión hace la fuerza (III) Simulación de mercado: La unión hace la fuerza (III)
En este artículo presentaré nuestro sistema para simular operaciones a mercado. Aunque está prácticamente terminado, todavía quedan algunas cosas por implementar y unos pocos cambios por realizar. Sin embargo, confieso que, después de todo lo que ya hemos desarrollado, estoy cansado de seguir atrapado en la implementación de este sistema.