Del básico al intermedio: Sobrecarga de operadores (IV)
Introducción
En el artículo anterior, Del básico al intermedio: Sobrecarga de operadores (III), se explicó y demostró cómo debíamos proceder cuando fuera necesario implementar la sobrecarga de operadores lógicos y relacionales. A pesar de toda la sencillez que intento dar a los artículos para que todos puedan entender cómo crear sus propias soluciones, este tema relacionado con la sobrecarga de operadores es uno de los que más confusión genera entre los programadores principiantes. Así que, mi querido lector, no pienses que ya sabes cómo hacer las cosas solo por haber leído el artículo anterior. Es muy importante que practiques y experimentes con distintas situaciones para asimilar correctamente ese tema.
Pues bien, si los operadores lógicos y relacionales ya pueden generar bastante confusión y problemas, lo que veremos aquí resulta todavía más complejo. Esto se debe a que trataremos otro operador que podemos sobrecargar. Me refiero al operador de subíndice y, como difícilmente aparece de forma aislada, también trataremos el operador de asignación. Así que ha llegado el momento de apartar todo aquello que pueda distraerte y centrarte en lo que veremos en este artículo. Como algunos suelen decir: ahora viene lo bueno.
Sobrecarga de operadores (IV)
Dentro de lo que MQL5 nos permite hacer, lo que veremos aquí cerrará el tema de la sobrecarga de operadores. Al menos en lo que respecta a la parte más sencilla, ya que existen otras posibilidades que convierten este tema en algo bastante más avanzado, capaz de confundir mucho a los principiantes. Esto lo iremos viendo a medida que se escriban y publiquen nuevos artículos. Aun así, en mi otro perfil trataré una forma bastante interesante de utilizar esta sobrecarga. Eso quedará para otro momento.
Bien, el detalle más importante del operador [], también conocido como operador de subíndice, es que normalmente no se implementa de forma aislada. Cuando sobrecargamos este operador, también solemos necesitar sobrecargar el operador de asignación. Esto se debe a que estaremos asignando un valor a un índice o leyendo un valor de un índice. En cualquier caso, no debemos descartar la posibilidad, e incluso la necesidad, de implementar dos operadores a la vez.
Como este operador de subíndice puede resultar bastante confuso, intentaré mostrar cómo se implementa su sobrecarga de manera que todos puedan entender lo que se está haciendo. Así que vamos a comenzar con algo realmente muy sencillo. Esto se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stList 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. int value; 10. //+----------------+ 11. public : 12. //+----------------+ 13. stList *operator[](const uchar arg) 14. { 15. Print("Value ", arg, " was passed as being an index to the operator []"); 16. 17. return GetPointer(this); 18. } 19. //+----------------+ 20. }; 21. //+------------------------------------------------------------------+ 22. void OnStart(void) 23. { 24. stList demo; 25. 26. demo[4] = 10; 27. } 28. //+------------------------------------------------------------------+
Código 01
Si intentas compilar este código 01, que no hace absolutamente nada, verás que el compilador indicará varios errores, como puede observarse en la siguiente imagen.

Imagen 01
En esta imagen 01 tenemos algunos problemas que es muy importante que entiendas bien, mi querido lector. El primero está relacionado con el error mostrado en la primera línea de la imagen 01. Este error se debe a que, en la línea 17 del código 01, estamos devolviendo una referencia a la clase declarada en la línea cuatro. Sin embargo, esta referencia quedará vinculada directamente a la variable declarada en la línea nueve. No es aconsejable devolver una referencia a una variable miembro privada de la clase. Hacerlo rompe el principio de encapsulamiento, lo que vuelve el código extremadamente peligroso y difícil de corregir en caso de fallos. Lo que debemos hacer es devolver un puntero que podamos utilizar posteriormente.
Pues bien, en los artículos anteriores vimos cómo podíamos resolver este tipo de error mostrado en la imagen 01. Sin embargo, aquí no lo haremos de esa forma. Seguiremos otro camino, algo más complicado al principio, pero que, a medida que practiques lo que veremos aquí, te resultará cada vez más sencillo.
Muy bien, los dos últimos errores de esta imagen 01 están directamente relacionados con el operador de asignación. Esto se debe a que el compilador no puede determinar cómo procesar la asignación en esta situación. Recuerda que aquí seguiremos un camino diferente y, por esta razón, es posible que al principio algunas cosas no tengan demasiado sentido. Así que vamos a modificar el código 01 para que quede como puedes ver a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stList 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. int value; 10. //+----------------+ 11. public : 12. //+----------------+ 13. stList *operator[](const uchar arg) 14. { 15. Print("Value ", arg, " was passed as being an index to the operator []"); 16. 17. return GetPointer(this); 18. } 19. //+----------------+ 20. void operator=(const int arg) 21. { 22. Print("Value ", arg," is being assigned to the internal variable of the class."); 23. } 24. //+----------------+ 25. }; 26. //+------------------------------------------------------------------+ 27. void OnStart(void) 28. { 29. stList demo; 30. 31. demo[4] = 10; 32. } 33. //+------------------------------------------------------------------+
Código 02
Ahora sí, el compilador podrá generar un ejecutable. Al ejecutarlo en el terminal de MetaTrader 5, obtendremos el resultado que se muestra a continuación.

Imagen 02
Bien, esto no parece gran cosa. Pero lo es. Y voy a explicarte el motivo para que puedas entender qué ocurrió aquí. Al observar la línea 31, te encuentras con algo muy habitual para quien ya sabe trabajar con arrays. Sin embargo, pese a esta aparente similitud, aquí estamos haciendo algo muy distinto de simplemente trabajar con arrays.
Por esta razón, es importante entender qué ocurre aquí, en el código 02, antes de dar el siguiente paso. Observa que, cuando se ejecuta el código 02, primero se ejecuta el código de la línea 13 y solo después el de la línea 20. Entender esto es importante para comprender la propia sobrecarga que se está implementando. Dependiendo de lo que intentes hacer, esta secuencia puede ser diferente. Así que presta atención a estos pequeños detalles.
Muy bien, pasemos al siguiente detalle. Al principio iremos muy despacio para que puedas entender cada pequeño detalle de lo que se está haciendo aquí. El siguiente paso es comprender qué hace realmente esta línea 31 del código 02. Esto puede observarse en el fragmento que se muestra a continuación.
. . . 26. //+------------------------------------------------------------------+ 27. void OnStart(void) 28. { 29. stList demo; 30. 31. demo.operator[](4).operator=(10); 32. } 33. //+------------------------------------------------------------------+
Fragmento 01
En este fragmento tenemos la forma en que el compilador interpretará el código incluido dentro del procedimiento OnStart, en el código 02. Comprender esto y tener clara esta idea es muy importante para entender por qué el código que veremos más adelante realmente funciona.
Bien, el siguiente paso se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stList 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. int value; 10. //+----------------+ 11. public : 12. //+----------------+ 13. stList *operator[](const uchar arg) 14. { 15. Print("Value ", arg, " was passed as being an index to the operator []"); 16. 17. return GetPointer(this); 18. } 19. //+----------------+ 20. stList *operator=(const int arg) 21. { 22. value = arg; 23. 24. return GetPointer(this); 25. } 26. //+----------------+ 27. void Debug(const int arg) 28. { 29. Print("Debugging line ", arg); 30. Print(value); 31. } 32. //+----------------+ 33. }; 34. //+------------------------------------------------------------------+ 35. void OnStart(void) 36. { 37. stList demo; 38. 39. (demo[4] = 10).Debug(__LINE__); 40. } 41. //+------------------------------------------------------------------+
Código 03
Y el resultado de la ejecución es el que podemos ver en la siguiente imagen.

Imagen 03
Perfecto, tenemos un código que funciona correctamente dentro de este enfoque sencillo. Antes de avanzar hacia algo más elaborado, quiero explicar algunos detalles que quizá marquen la diferencia para ti en el futuro, mi querido lector. Uno de ellos está relacionado precisamente con el operador de asignación. Para que puedas entender realmente lo que quiero explicar, necesitamos hacer que el operador de subíndice tenga alguna funcionalidad práctica, aunque sea muy sencilla. Por esta razón, vamos a modificar el código para que tenga una utilidad práctica. Así obtenemos el siguiente código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stDemo 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. union un_0 10. { 11. ulong u64; 12. uchar u8[8]; 13. }m_info; 14. //+----------------+ 15. public : 16. //+----------------+ 17. void operator=(const ulong arg) 18. { 19. m_info.u64 = arg; 20. } 21. //+----------------+ 22. void Debug(const int arg) 23. { 24. PrintFormat("Info is 0x%I64X", m_info.u64); 25. } 26. //+----------------+ 27. }; 28. //+------------------------------------------------------------------+ 29. void OnStart(void) 30. { 31. stDemo demo; 32. 33. demo = 0x1020304050607080; 34. 35. demo.Debug(__LINE__); 36. } 37. //+------------------------------------------------------------------+
Código 04
Al ejecutarse, este código 04 generará como resultado lo que se muestra a continuación.

Imagen 04
Observa que, en este caso, en el código 04, la sobrecarga del operador de asignación se implementó de tal forma que solo puede realizarse una asignación. Tampoco podremos llamar directamente al procedimiento de depuración en la misma línea en la que realizamos la asignación. Como puedes ver, es posible hacerlo así. Sin embargo, normalmente no es esto lo que hacemos en la práctica, mi querido lector. En la práctica, solemos devolver una referencia para poder realizar asignaciones en cascada. Llegaremos a ese punto dentro de poco.
Antes quiero que veas otra cosa. Observa que, en la línea nueve, estamos definiendo una unión y, dentro de ella, tenemos un array estático. Ahora presta atención a lo que vamos a hacer, porque esto nos servirá de apoyo para explicar algunos detalles interesantes sobre el uso del operador de subíndice junto con el operador de asignación. Así que modificamos una vez más el código, que queda como se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stDemo 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. union un_0 10. { 11. ulong u64; 12. uchar u8[8]; 13. }m_info; 14. uchar m_index; 15. //+----------------+ 16. public : 17. //+----------------+ 18. stDemo *operator[](const uchar arg) 19. { 20. m_index = arg; 21. return GetPointer(this); 22. } 23. //+----------------+ 24. void operator=(const ulong arg) 25. { 26. m_info.u64 = arg; 27. } 28. //+----------------+ 29. void Debug(const int arg) 30. { 31. PrintFormat("Info is 0x%I64X", m_info.u64); 32. } 33. //+----------------+ 34. }; 35. //+------------------------------------------------------------------+ 36. void OnStart(void) 37. { 38. stDemo demo; 39. 40. demo = 0x1020304050607080; 41. demo.Debug(__LINE__); 42. demo[1] = 0xFA; 43. demo.Debug(__LINE__); 44. } 45. //+------------------------------------------------------------------+
Código 05
Al ejecutar este código 05, el resultado es el que vemos a continuación.

Imagen 05
Obviamente, lo que vemos en la imagen 05 no es el resultado que queríamos obtener. Entonces, ¿por qué se produjo este resultado? El motivo es precisamente que no estamos sobrecargando nuevamente el operador de asignación. ¿Cómo es eso? Puede parecer un poco confuso. En realidad, es mucho más sencillo de lo que posiblemente estés imaginando, mi querido lector. Entiende lo siguiente: cuando el compilador intente utilizar el operador de asignación, solo verá lo que estamos definiendo en la línea 24. Al hacerlo, estará forzando el uso de la estructura de la forma que se muestra en la línea 40, y no como queremos hacerlo en la línea 42. Para cambiar esto, necesitamos que el código quede como se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stDemo 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. union un_0 10. { 11. ulong u64; 12. uchar u8[8]; 13. }m_info; 14. uchar m_index; 15. //+----------------+ 16. public : 17. //+----------------+ 18. stDemo *operator[](const uchar arg) 19. { 20. m_index = arg; 21. return GetPointer(this); 22. } 23. //+----------------+ 24. void operator=(const uchar arg) 25. { 26. m_info.u8[m_index] = arg; 27. } 28. //+----------------+ 29. void operator=(const ulong arg) 30. { 31. m_info.u64 = arg; 32. } 33. //+----------------+ 34. void Debug(const int arg) 35. { 36. PrintFormat("Info is 0x%I64X", m_info.u64); 37. } 38. //+----------------+ 39. }; 40. //+------------------------------------------------------------------+ 41. void OnStart(void) 42. { 43. stDemo demo; 44. 45. demo = 0x1020304050607080; 46. demo.Debug(__LINE__); 47. demo[1] = 0xFA; 48. demo.Debug(__LINE__); 49. } 50. //+------------------------------------------------------------------+
Código 06
Bien, observa ahora que estamos creando otra sobrecarga del mismo operador.
Para obtener más detalles sobre cómo funciona una sobrecarga y cómo puede implementarse, consulta el artículo Del básico al intermedio: Sobrecarga; allí se explicaron con detalle todos los principios y conceptos básicos implicados en la implementación de una sobrecarga. Bien, aparentemente el código parece correcto y, en principio, debería generar el resultado que queremos. Sin embargo, cuando intentes compilar este código 06, recibirás el siguiente mensaje del compilador.

Imagen 06
Pero ¿qué cosa tan extraña es esta? No entiendo por qué el compilador informa de este tipo de error, ya que aparentemente el código está perfectamente implementado. En efecto, mi querido lector, a nuestros ojos el código está correctamente implementado. El problema es que el compilador no lo interpreta así. La imagen 06 muestra el motivo. Observa que el compilador no puede determinar qué función o procedimiento debe invocar para poder compilar correctamente la línea 47. Esto se debe precisamente a que existen varias sobrecargas del operador de asignación.
Sé que, al principio, este tipo de situación resulta un tanto extraña, precisamente por lo explicado en el artículo en el que abordamos la sobrecarga de funciones y procedimientos. Sin embargo, corregir el error que vemos en la imagen 06 es muy sencillo. Lo único que necesitamos hacer es indicarle al compilador cuál de las sobrecargas debe utilizar. Para ello, modificamos la línea 47, como se muestra en el fragmento siguiente.
. . . 40. //+------------------------------------------------------------------+ 41. void OnStart(void) 42. { 43. stDemo demo; 44. 45. demo = 0x1020304050607080; 46. demo.Debug(__LINE__); 47. demo[1] = (uchar) 0xFA; 48. demo.Debug(__LINE__); 49. } 50. //+------------------------------------------------------------------+
Fragmento 02
Ahora sí, el compilador podrá crear el ejecutable. Así, cuando ejecutemos este ejecutable, obtendremos el resultado que se muestra en la siguiente imagen.

Imagen 07
Bien, ahora tenemos un punto de partida para entender otros aspectos relacionados con estos dos operadores. Pero quizá estés pensando que el índice es incorrecto. Si has llegado a esa conclusión, solo necesitas modificar el código como se muestra en el fragmento siguiente.
. . . 23. //+----------------+ 24. void operator=(const uchar arg) 25. { 26. m_info.u8[sizeof(m_info) - m_index - 1] = arg; 27. } 28. //+----------------+ . . .
Fragmento 03
Ahora, cuando volvamos a ejecutar el código, el resultado será el que podemos ver a continuación.

Imagen 08
Muy interesante, ¿no es cierto, mi querido lector? Sin embargo, lo que hemos visto aquí es solo la parte básica y menos interesante del uso de la sobrecarga de estos operadores. Así que, ¿qué tal si vemos algo un poco más entretenido, con lo que podamos ir algo más allá?
Pues bien, en el artículo Del básico al intermedio: Colas, Listas y Árboles (IV), se mostró cómo podíamos implementar una lista doblemente enlazada. En aquel código tenemos la posibilidad de hacer algo que, en ese momento, todavía no era posible. Esto se debe a que aún no contábamos con los mecanismos que hemos visto aquí, en este artículo. Y el mecanismo al que me refiero es precisamente la sobrecarga de los operadores de subíndice y de asignación. Sin embargo, hay un pequeño detalle que nos obliga a hacer algo un tanto diferente. Pero vayamos con calma para que todo tenga sentido y no se vuelva más complicado de lo necesario.
Así que vamos a tomar uno de los códigos vistos en el artículo mencionado anteriormente. Esto nos permitirá entender cómo utilizar la sobrecarga en este tipo de situación. El código que utilizaremos se muestra a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. template <typename T> class stList 005. { 006. private: 007. //+----------------+ 008. T info; 009. stList <T> *prev, 010. *start; 011. uint counter; 012. //+----------------+ 013. public: 014. //+----------------+ 015. stList(void) 016. :prev(NULL), 017. start(NULL), 018. counter(0) 019. {} 020. //+----------------+ 021. void Store(T arg, const uint index = 0xFFFFFFFF) 022. { 023. stList <T> *loc, 024. *ptr1 = start, 025. *ptr2 = NULL; 026. 027. for (uint c = 0; (ptr1 != NULL) && (c < index); ptr2 = ptr1, ptr1 = (*ptr1).start, c++); 028. 029. loc = new stList <T>; 030. (*loc).info = arg; 031. (*loc).start = (ptr2 != NULL ? (*ptr2).start : ptr1); 032. (*loc).prev = (ptr1 != NULL ? (*ptr1).prev : ptr2); 033. if (ptr2 != NULL) (*ptr2).start = loc; else start = loc; 034. if (ptr1 != NULL) (*ptr1).prev = loc; else prev = loc; 035. 036. counter++; 037. } 038. //+----------------+ 039. bool Restore(T &arg, const uint index = 0xFFFFFFFF) 040. { 041. if ((prev == NULL) || (start == NULL)) 042. return false; 043. 044. stList <T> *loc = (index < counter ? start : prev), 045. *ptr = NULL; 046. 047. for (uint c = 0; (loc != NULL) && (c < index) && (index < counter); ptr = loc, loc = (*loc).start, c++); 048. if (loc == NULL) return false; 049. 050. if (index == 0) 051. { 052. start = (*loc).start; 053. if (start != NULL) (*start).prev = NULL; 054. } else if (index >= (counter - 1)) 055. { 056. prev = (*loc).prev; 057. if (prev != NULL) (*prev).start = NULL; 058. } 059. else 060. { 061. (*ptr).start = (*loc).start; 062. (*loc).start.prev = ptr; 063. } 064. arg = (*loc).info; 065. delete loc; 066. counter--; 067. 068. return true; 069. } 070. //+----------------+ 071. bool Exclude(const uint index) 072. { 073. T tmp; 074. 075. return Restore(tmp, index); 076. } 077. //+----------------+ 078. void Debug(void) 079. { 080. Print("===== DEBUG ====="); 081. for (stList <T> *loc = start; loc != NULL; loc = (*loc).start) 082. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).start, loc, (*loc).prev, (*loc).info); 083. Print("================="); 084. } 085. //+----------------+ 086. }; 087. //+------------------------------------------------------------------+ 088. void OnStart(void) 089. { 090. stList <char> list; 091. 092. list.Store(10); 093. list.Store(84); 094. list.Store(-6); 095. list.Store(47, 0); 096. 097. list.Debug(); 098. 099. list.Exclude(3); 100. list.Store(35, 2); 101. 102. list.Debug(); 103. 104. for (char info; list.Restore(info, 0);) 105. Print(info); 106. }; 107. //+------------------------------------------------------------------+
Código 07
Al ejecutar este código 07, obtendremos como respuesta lo que se muestra en la siguiente imagen.

Imagen 09
Ahora presta atención, mi querido lector, porque este punto es muy importante. Este código 07 crea una lista, tal como vimos en el artículo mencionado anteriormente. Sin embargo, utilizar aquí directamente la sobrecarga de operadores tiene consecuencias si no modificas adecuadamente la forma en que se implementa la lista enlazada. Esto quizá parezca un tanto contradictorio, ya que el código 07 funciona correctamente. Sin embargo, tal como está, si intentas implementar la sobrecarga de operadores de cualquier manera, acabarás teniendo un gran problema entre manos o, como mínimo, algo que no resultará realmente útil en la práctica.
Implementar todos los cambios necesarios exige explicar otro detalle del que todavía no hemos hablado. Aun así, podemos hacer una implementación más básica de la sobrecarga vista en este artículo para que este código 07 sea, al menos, un poco más fácil de entender cuando volvamos a consultarlo. En principio, los cambios que tendríamos que implementar harían que las líneas 95 y 100 fueran algo más fáciles de comprender. Sin embargo, esto tiene un coste.
Por lo tanto, el resultado que veremos, al menos al principio, será un poco diferente del mostrado en la imagen 09. Pero, a medida que avancemos y explique otro detalle que también podemos utilizar aquí, el resultado final será exactamente el que vemos en la imagen 09. Sin embargo, el código del procedimiento OnStart será completamente diferente del que aparece en el código 07. Y, por supuesto, también cambiará gran parte del código de la clase stList. Esto se debe al uso mucho más intensivo de la sobrecarga de operadores.
Así que vamos a comenzar a realizar los cambios. El primero será eliminar la función Exclude, que aparece en la línea 71 de este código 07. Pero ¿por qué eliminar esta función ahora? Porque esta eliminación será solo temporal. Y, por extraño que pueda parecer, la función Exclude dificulta el acceso y la determinación del índice dentro del operador de subíndice, lo que hace que los resultados sean bastante más difíciles de comprender. Más adelante volveremos a incorporar esta función Exclude, aunque con una forma de uso algo diferente.
Bien. A continuación puedes ver el nuevo código. Se trata de un código de transición entre el código 07 y el código al que llegaremos más adelante.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. template <typename T> class stList 005. { 006. private: 007. //+----------------+ 008. T info; 009. stList <T> *prev, 010. *start; 011. uint counter; 012. //+----------------+ 013. public: 014. //+----------------+ 015. stList(void) 016. :prev(NULL), 017. start(NULL), 018. counter(0) 019. {} 020. //+----------------+ 021. void Store(T arg, const uint index = 0xFFFFFFFF) 022. { 023. stList <T> *loc, 024. *ptr1 = start, 025. *ptr2 = NULL; 026. 027. for (uint c = 0; (ptr1 != NULL) && (c < index); ptr2 = ptr1, ptr1 = (*ptr1).start, c++); 028. 029. loc = new stList <T>; 030. (*loc).info = arg; 031. (*loc).start = (ptr2 != NULL ? (*ptr2).start : ptr1); 032. (*loc).prev = (ptr1 != NULL ? (*ptr1).prev : ptr2); 033. if (ptr2 != NULL) (*ptr2).start = loc; else start = loc; 034. if (ptr1 != NULL) (*ptr1).prev = loc; else prev = loc; 035. 036. counter++; 037. } 038. //+----------------+ 039. bool Restore(T &arg, const uint index = 0xFFFFFFFF) 040. { 041. if ((prev == NULL) || (start == NULL)) 042. return false; 043. 044. stList <T> *loc = (index < counter ? start : prev), 045. *ptr = NULL; 046. 047. for (uint c = 0; (loc != NULL) && (c < index) && (index < counter); ptr = loc, loc = (*loc).start, c++); 048. if (loc == NULL) return false; 049. 050. if (index == 0) 051. { 052. start = (*loc).start; 053. if (start != NULL) (*start).prev = NULL; 054. } else if (index >= (counter - 1)) 055. { 056. prev = (*loc).prev; 057. if (prev != NULL) (*prev).start = NULL; 058. } 059. else 060. { 061. (*ptr).start = (*loc).start; 062. (*loc).start.prev = ptr; 063. } 064. arg = (*loc).info; 065. delete loc; 066. counter--; 067. 068. return true; 069. } 070. //+----------------+ 071. // bool Exclude(const uint index) 072. // { 073. // T tmp; 074. 075. // return Restore(tmp, index); 076. // } 077. //+----------------+ 078. stList <T> *operator[](const uint arg) 079. { 080. stList <T> *loc = start; 081. 082. return loc; 083. } 084. //+----------------+ 085. void operator=(const T arg) 086. { 087. info = arg; 088. } 089. //+----------------+ 090. void Debug(void) 091. { 092. Print("===== DEBUG ====="); 093. for (stList <T> *loc = start; loc != NULL; loc = (*loc).start) 094. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).start, loc, (*loc).prev, (*loc).info); 095. Print("================="); 096. } 097. //+----------------+ 098. }; 099. //+------------------------------------------------------------------+ 100. void OnStart(void) 101. { 102. stList <char> list; 103. 104. list.Store(10); 105. list.Store(84); 106. list.Store(-6); 107. list.Store(47, 0); 108. 109. list.Debug(); 110. 111. // list.Exclude(3); 112. list.Store(35, 2); 113. 114. list.Debug(); 115. 116. for (char info; list.Restore(info, 0);) 117. Print(info); 118. }; 119. //+------------------------------------------------------------------+
Código 08
Ahora, al ejecutar este código 08, obtenemos como resultado lo que se muestra a continuación.

Imagen 10
Sin embargo, presta atención a este código 08, porque en la línea 78 comenzamos a implementar la sobrecarga del operador de subíndice. Asimismo, en la línea 85 ya implementamos la primera sobrecarga del operador de asignación. A pesar de ello, el código del procedimiento OnStart continúa siendo básicamente el mismo. El objetivo de este código 08 es precisamente generar la imagen 10. Así podemos realizar la siguiente modificación, que hará que el procedimiento OnStart sea ligeramente diferente, como puedes observar a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. template <typename T> class stList 005. { 006. private: 007. //+----------------+ 008. T info; 009. stList <T> *prev, 010. *start; 011. uint counter; 012. //+----------------+ 013. public: 014. //+----------------+ 015. stList(void) 016. :prev(NULL), 017. start(NULL), 018. counter(0) 019. {} 020. //+----------------+ 021. void Store(T arg, const uint index = 0xFFFFFFFF) 022. { 023. stList <T> *loc, 024. *ptr1 = start, 025. *ptr2 = NULL; 026. 027. for (uint c = 0; (ptr1 != NULL) && (c < index); ptr2 = ptr1, ptr1 = (*ptr1).start, c++); 028. 029. loc = new stList <T>; 030. (*loc).info = arg; 031. (*loc).start = (ptr2 != NULL ? (*ptr2).start : ptr1); 032. (*loc).prev = (ptr1 != NULL ? (*ptr1).prev : ptr2); 033. if (ptr2 != NULL) (*ptr2).start = loc; else start = loc; 034. if (ptr1 != NULL) (*ptr1).prev = loc; else prev = loc; 035. 036. counter++; 037. } 038. //+----------------+ 039. bool Restore(T &arg, const uint index = 0xFFFFFFFF) 040. { 041. if ((prev == NULL) || (start == NULL)) 042. return false; 043. 044. stList <T> *loc = (index < counter ? start : prev), 045. *ptr = NULL; 046. 047. for (uint c = 0; (loc != NULL) && (c < index) && (index < counter); ptr = loc, loc = (*loc).start, c++); 048. if (loc == NULL) return false; 049. 050. if (index == 0) 051. { 052. start = (*loc).start; 053. if (start != NULL) (*start).prev = NULL; 054. } else if (index >= (counter - 1)) 055. { 056. prev = (*loc).prev; 057. if (prev != NULL) (*prev).start = NULL; 058. } 059. else 060. { 061. (*ptr).start = (*loc).start; 062. (*loc).start.prev = ptr; 063. } 064. arg = (*loc).info; 065. delete loc; 066. counter--; 067. 068. return true; 069. } 070. //+----------------+ 071. // bool Exclude(const uint index) 072. // { 073. // T tmp; 074. 075. // return Restore(tmp, index); 076. // } 077. //+----------------+ 078. stList <T> *operator[](const uint arg) 079. { 080. stList <T> *loc = start; 081. for (uint c = 0; (loc != NULL) && (c < arg); loc = (*loc).start, c++); 082. return loc; 083. } 084. //+----------------+ 085. void operator=(const T arg) 086. { 087. info = arg; 088. } 089. //+----------------+ 090. void Debug(void) 091. { 092. Print("===== DEBUG ====="); 093. for (stList <T> *loc = start; loc != NULL; loc = (*loc).start) 094. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).start, loc, (*loc).prev, (*loc).info); 095. Print("================="); 096. } 097. //+----------------+ 098. }; 099. //+------------------------------------------------------------------+ 100. void OnStart(void) 101. { 102. stList <char> list; 103. 104. list.Store(10); 105. list.Store(84); 106. list.Store(-6); 107. list.Debug(); 108. list[0] = 47; 109. list.Debug(); 110. list[2] = 35; 111. 112. list.Debug(); 113. 114. for (char info; list.Restore(info, 0);) 115. Print(info); 116. }; 117. //+------------------------------------------------------------------+
Código 09
Ahora, cuando ejecutes este código 09 en el terminal de MetaTrader 5, el resultado será el que se muestra a continuación.

Imagen 11
Vaya, qué cosa tan extraña. Pero veo que el resultado es diferente del que aparecía en las imágenes anteriores. ¿No habría alguna forma de conservar los resultados anteriores? Bien, no sé por qué querrías hacer algo así, mi querido lector, ya que acabaría haciendo todo mucho más difícil de entender. Sin embargo, antes de mostrar cómo podría hacerse, quiero que observes que ahora el propio operador de subíndice permite acceder a la lista como si fuera un array. Algo que antes no era posible.
Pero supongamos, y quiero dejar esto muy claro porque no aparecerá en el anexo, que deseas mantener el mismo comportamiento, o resultado, del código 08, pero utilizando el procedimiento OnStart del código 09. ¿Cómo podríamos hacerlo? Bien, para conseguirlo necesitamos modificar el código de una forma muy sutil, como se muestra a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. template <typename T> class stList 005. { 006. private: 007. //+----------------+ 008. T info; 009. stList <T> *prev, 010. *start; 011. uint counter; 012. //+----------------+ 013. public: 014. //+----------------+ 015. stList(void) 016. :prev(NULL), 017. start(NULL), 018. counter(0) 019. {} 020. //+----------------+ 021. void Store(T arg, const uint index = 0xFFFFFFFF) 022. { 023. this[index > counter ? counter : index] = arg; 024. } 025. //+----------------+ 026. bool Restore(T &arg, const uint index = 0xFFFFFFFF) 027. { 028. if ((prev == NULL) || (start == NULL)) 029. return false; 030. 031. stList <T> *loc = (index < counter ? start : prev), 032. *ptr = NULL; 033. 034. for (uint c = 0; (loc != NULL) && (c < index) && (index < counter); ptr = loc, loc = (*loc).start, c++); 035. if (loc == NULL) return false; 036. 037. if (index == 0) 038. { 039. start = (*loc).start; 040. if (start != NULL) (*start).prev = NULL; 041. } else if (index >= (counter - 1)) 042. { 043. prev = (*loc).prev; 044. if (prev != NULL) (*prev).start = NULL; 045. } 046. else 047. { 048. (*ptr).start = (*loc).start; 049. (*loc).start.prev = ptr; 050. } 051. arg = (*loc).info; 052. delete loc; 053. counter--; 054. 055. return true; 056. } 057. //+----------------+ 058. // bool Exclude(const uint index) 059. // { 060. // T tmp; 061. 062. // return Restore(tmp, index); 063. // } 064. //+----------------+ 065. stList <T> *operator[](const uint arg) 066. { 067. stList <T> *loc, 068. *ptr1 = start, 069. *ptr2 = NULL; 070. 071. for (uint c = 0; (ptr1 != NULL) && (c < arg); ptr2 = ptr1, ptr1 = (*ptr1).start, c++); 072. 073. loc = new stList <T>; 074. (*loc).start = (ptr2 != NULL ? (*ptr2).start : ptr1); 075. (*loc).prev = (ptr1 != NULL ? (*ptr1).prev : ptr2); 076. if (ptr2 != NULL) (*ptr2).start = loc; else start = loc; 077. if (ptr1 != NULL) (*ptr1).prev = loc; else prev = loc; 078. 079. counter++; 080. 081. return loc; 082. } 083. //+----------------+ 084. void operator=(const T arg) 085. { 086. info = arg; 087. } 088. //+----------------+ 089. void Debug(void) 090. { 091. Print("===== DEBUG ====="); 092. for (stList <T> *loc = start; loc != NULL; loc = (*loc).start) 093. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).start, loc, (*loc).prev, (*loc).info); 094. Print("================="); 095. } 096. //+----------------+ 097. }; 098. //+------------------------------------------------------------------+ 099. void OnStart(void) 100. { 101. stList <char> list; 102. 103. list.Store(10); 104. list.Store(84); 105. list.Store(-6); 106. list.Debug(); 107. list[0] = 47; 108. list.Debug(); 109. list[2] = 35; 110. 111. list.Debug(); 112. 113. for (char info; list.Restore(info, 0);) 114. Print(info); 115. }; 116. //+------------------------------------------------------------------+
Código 10
Al utilizar este código 10, recuerda que NO ESTARÁ EN EL ANEXO por motivos evidentes, podrás generar el resultado que se muestra en la imagen 12 a continuación.

Imagen 12
Observa que buena parte de esta imagen 12 se parece mucho a lo que podemos ver en la imagen 10. Esto demuestra que el código 10 puede generar el mismo resultado que el código 08, pero utilizando la sobrecarga de operadores. Ahora presta atención a lo siguiente en este código 10. Si observas el interior de la clase, verás que el contenido que antes estaba en el procedimiento Store ahora se encuentra en la implementación del operador de subíndice sobrecargado. Además, el procedimiento Store crea una referencia para acceder precisamente al operador sobrecargado. Al ver esto, podrías pensar lo siguiente: ya no necesitamos este procedimiento Store. Podemos utilizar directamente el código del fragmento siguiente y, aun así, la lista se crearía con el mismo resultado mostrado en la imagen 12.
. . . 098. //+------------------------------------------------------------------+ 099. void OnStart(void) 100. { 101. stList <char> list; 102. 103. list[0] = 10; 104. list[1] = 84; 105. list[2] = -6; 106. list.Debug(); 107. list[0] = 47; 108. list.Debug(); 109. list[2] = 35; 110. 111. list.Debug(); 112. 113. for (char info; list.Restore(info, 0);) 114. Print(info); 115. }; 116. //+------------------------------------------------------------------+
Fragmento 04
Sí, mi querido lector, podrías utilizar el código del fragmento 04. Sin embargo, quiero recordarte que el objetivo de la sobrecarga de operadores es precisamente hacer que el código sea más sencillo y legible. Pero NO ES ESO lo que está ocurriendo aquí. Al observar este fragmento 04, y teniendo en cuenta que muchas veces la clase estará realmente en un archivo de encabezado, puedes caer en el error de imaginar que dentro de la lista enlazada debería haber únicamente tres valores, y no cinco, como podemos comprobar al ejecutar el código.
Y lo que, a mi entender y también desde el punto de vista de muchos otros programadores, resulta todavía más confuso es lo siguiente. Si observas únicamente este fragmento 04, podrías imaginar que la lista comienza con el valor 10 y termina con el valor -6, y que, cuando se ejecuten las líneas 107 y 109, estos valores serán sustituidos por 47 y 35, respectivamente. Este es precisamente el tipo de problema con el que siempre debemos tener cuidado al implementar la sobrecarga de operadores.
Y es justamente por esta razón que este código 10 NO ESTARÁ DISPONIBLE EN EL ANEXO. Se incluye aquí únicamente como una curiosidad para este artículo.
Un último detalle antes de finalizar el artículo
Muy bien, todo parece perfecto. Todo es muy lindo y maravilloso. Sin embargo, existe una pequeña limitación que complica un poco las cosas cuando se trata de la sobrecarga del operador de subíndice en MQL5. Al menos hasta el momento en que escribo este artículo, no he encontrado una forma de evitar esta limitación.
Y esta limitación resulta un tanto desafortunada, si podemos expresarlo de esa manera. El hecho es que, en MQL5, NO PODEMOS utilizar punteros de la misma forma que en lenguajes como C o incluso C++. Esto dificulta algunos puntos clave cuando hablamos de sobrecarga de operadores. Sé que muchos quizá no entiendan lo que voy a explicar ahora, pero quienes llevan algo más de tiempo programando lo comprenderán perfectamente.
Cuando sobrecargamos un operador de subíndice, es decir, [], NO DEVOLVEMOS los valores de la forma en que lo hemos hecho aquí, en este artículo. Normalmente devolvemos una referencia a la posición de memoria donde se encuentra el valor que queremos modificar. Salvo en casos más complejos, en los que sí utilizamos algo muy parecido a lo que hemos visto aquí.
Naturalmente, cuando se mostró el código 03, muchos ya debieron de interesarse bastante. Lo mismo habrá ocurrido cuando se presentó el código 06. Pero, con toda seguridad, el entusiasmo habrá alcanzado su punto máximo con el código 09 o, quizá, con el código 10. Sin embargo, no sé si lo has notado, pero en todos los casos estamos utilizando la sobrecarga para escribir y en ningún momento para leer. ¿Por qué? El motivo es precisamente la limitación de MQL5 que mencioné anteriormente: no podemos utilizar punteros como en C o C++.
Para ejemplificarlo, voy a mostrar cómo sería un código muy sencillo en C que utilizara la sobrecarga. Puedes verlo a continuación.
01. //+------------------------------------------------------------------+ 02. #include <stdio.h> 03. //+------------------------------------------------------------------+ 04. template <typename T> class stDemo 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. T *u8; 10. //+----------------+ 11. public : 12. //+----------------+ 13. stDemo(const int arg) 14. { 15. u8 = new T[arg]; 16. } 17. //+----------------+ 18. T &operator[](const int arg) 19. { 20. return u8[arg]; 21. } 22. //+----------------+ 23. }; 24. //+------------------------------------------------------------------+ 25. int main(void) 26. { 27. stDemo <char> demo(8); 28. char v1; 29. 30. demo[3] = 10; 31. demo[1] = demo[3]; 32. v1 = demo[1]; 33. 34. printf("%d", v1); 35. 36. return 0; 37. } 38. //+------------------------------------------------------------------+
Código 11
Aquí, en este código 11, tenemos una sobrecarga completamente funcional. Sin embargo, este código NO ES MQL5. Es C++. Y quiero llamar tu atención precisamente sobre las líneas 31 y, sobre todo, sobre la línea 32. Observa que, en esta línea 32, estamos leyendo un valor y almacenándolo en una variable ajena a la clase. Sin embargo, en MQL5 no podemos hacer esto. Al menos no de esta manera.
En MQL5, necesitas decidir si quieres escribir o leer valores individuales mediante el operador de subíndice. Sin embargo, como vimos en este artículo, resulta más adecuado utilizarlo para escribir datos, ya que, en el caso de la lectura, tendríamos muchas más limitaciones. Esta limitación al leer valores individuales puede sortearse mediante una función o procedimiento dedicado exclusivamente a esa tarea cuando la sobrecarga del operador de subíndice esté orientada a la escritura de datos.
Vaya, sí que sabes cómo aguar la fiesta. Yo estaba tan feliz, contento y satisfecho, y vienes tú y acabas con mi alegría. Pero está bien. Como tampoco tengo tanta experiencia, no puedo quejarme de que estés dispuesto a explicar ciertos detalles. Eso sí, solo te perdonaré si me muestras cómo podríamos hacer en MQL5 algo parecido a lo que hace este código 11 escrito en C++.
Bien, el código en MQL5 que permite obtener el mismo resultado que este código C++ mostrado en el código 11 se presenta a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class stDemo 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. T u8[]; 10. int m_index; 11. //+----------------+ 12. public : 13. //+----------------+ 14. stDemo(const int arg) 15. { 16. m_index = -1; 17. ArrayResize(u8, arg); 18. } 19. //+----------------+ 20. stDemo <T> *operator[](const int arg) 21. { 22. m_index = arg; 23. return &this; 24. } 25. //+----------------+ 26. void operator=(const T arg) 27. { 28. u8[m_index - 1] = arg; 29. } 30. //+----------------+ 31. const T GetInfo(void) const 32. { 33. return u8[m_index - 1]; 34. } 35. //+----------------+ 36. }; 37. //+------------------------------------------------------------------+ 38. void OnStart(void) 39. { 40. stDemo <char> demo(8); 41. char v1; 42. 43. demo[3] = 10; 44. demo[1] = demo[3].GetInfo(); 45. v1 = demo[1].GetInfo(); 46. 47. Print(v1); 48. } 49. //+------------------------------------------------------------------+
Código 12
Ahora presta atención. En este código 12, escrito en MQL5, tenemos la sobrecarga del operador orientada precisamente a escribir datos en la estructura interna del objeto. Por esta razón, podemos tanto leer como escribir datos sin demasiadas complicaciones, siempre que, por supuesto, utilices la función que aparece en la línea 31. Sin embargo, observa que, aunque este código 12 produce el mismo resultado que el código 11 escrito en C++, es completamente diferente de este otro que podemos ver a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class stDemo 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. T u8[]; 10. //+----------------+ 11. public : 12. //+----------------+ 13. stDemo(const int arg) 14. { 15. ArrayResize(u8, arg); 16. } 17. //+----------------+ 18. stDemo <T> *Set(const int arg, const T info) 19. { 20. u8[arg - 1] = info; 21. return &this; 22. } 23. //+----------------+ 24. T operator[](const int arg) 25. { 26. return u8[arg - 1]; 27. } 28. //+----------------+ 29. }; 30. //+------------------------------------------------------------------+ 31. void OnStart(void) 32. { 33. stDemo <char> demo(8); 34. char v1; 35. 36. demo.Set(3, 10); 37. demo.Set(1, demo[3]); 38. v1 = demo[1]; 39. 40. Print(v1); 41. } 42. //+------------------------------------------------------------------+
Código 13
En el caso del código 13, también escrito en MQL5, tenemos la sobrecarga del operador de subíndice orientada precisamente a leer datos de la estructura interna del objeto. Observa que este sencillo cambio modifica por completo la forma en que trabajarás y deberás implementar el código. El resultado de estos tres códigos es exactamente el siguiente: en el terminal, ya sea en la consola, en el símbolo del sistema o incluso en MetaTrader 5, veremos impreso el valor diez. Y esto ocurre en todos los casos.
Sin embargo, quizá estés observando este código 12 o incluso el código 13 y pensando: ¿no habría alguna forma de combinar ambas cosas? O, mejor aún: ¿no podríamos crear algo parecido a lo que vemos en el código 11 escrito en C++? Bien, mi querido lector, lamentablemente la respuesta a esta pregunta es NO. Al menos, no he encontrado una forma de abordar directamente esta sobrecarga en MQL5. Existe otra manera, pero implica precisamente combinar código escrito en C++ con código escrito en MQL5. Sin embargo, a mi entender, este ya es un tema avanzado, algo que no abordaremos en esta serie de artículos. Quizá lo tratemos en una serie futura dedicada exclusivamente a contenidos avanzados.
Consideraciones finales
En este artículo hicimos una primera aproximación para mostrar cómo podríamos implementar la sobrecarga del operador de subíndice y también del operador de asignación, intentando ofrecer un enfoque práctico e interesante para todos. Sin embargo, lo que hemos visto aquí es solo una parte de lo que todavía pretendo mostrar y que está directamente relacionado con la sobrecarga de estos operadores. En este artículo apareció el ilustre código 07, pero no vimos cómo obtener el mismo resultado utilizando la sobrecarga de operadores. En el próximo artículo nos centraremos exclusivamente en este punto. Así podrás comprobar que, si planteamos bien la solución, podemos crear un código completo utilizando la sobrecarga de operadores sin demasiada dificultad. Y, al final, el código será mucho más legible. Al menos así lo veo yo, ya que me parece mucho más sencillo entender el código que veremos en el próximo artículo que el código 07 de este artículo.
En cualquier caso, intenta estudiar y practicar lo que hemos visto aquí. Y, si te interesa seguir aprendiendo, no lo olvides: pronto se publicará el próximo artículo. Hasta entonces.
| Archivo MQ5 | Descripció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 06 | 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/16951
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Simulación de mercado: La unión hace la fuerza (III)
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 8): Análisis de múltiples estrategias (2)
Motor de decision Multi-IA para MQL5 (Parte 5): backtest del motor en el simulador de estrategias y comparacion contra reglas simples.
Simulación de mercado: La unión hace la fuerza (II)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso