Del básico al intermedio: Sobrecarga de operadores (VI)
Introducción
En el artículo anterior Del básico al intermedio: Sobrecarga de operadores (V), que, a mi parecer, fue uno de esos artículos que muchos principiantes necesitarán estudiar con bastante calma y atención, vimos la primera parte de la implementación de una lista doblemente enlazada utilizando la sobrecarga de operadores de una forma bastante pura. Todo ello dentro de las posibilidades que ofrece MQL5. Durante la redacción y la explicación, quedó claro que había que sacrificar algo para evitar que el artículo resultara demasiado confuso o extenso, así que decidí dividir el contenido en dos artículos.
Pero, mientras se redactaba y se explicaba el artículo, se observó que había que prescindir de algo para que el artículo no resultara demasiado confuso o demasiado extenso. Por lo tanto, si no has leído o no has comprendido adecuadamente lo explicado en el artículo anterior, te pido que vuelvas a él antes de comenzar a leer este. Sin entender lo que vimos allí, comprender lo que veremos aquí se convierte en una tarea prácticamente imposible, dado el tipo de implementación que abordaremos.
Gran parte de las técnicas y conceptos que veremos en este artículo provienen de mi experiencia en programación en C y C++. Así que procura estudiarlo con bastante calma y atención, porque aquí veremos contenidos que, en mi opinión, un programador de nivel intermedio debe dominar. Como tenemos mucho de qué hablar, pasemos directamente al tema principal del artículo.
Una lista enlazada llena de diversión (II)
Con lo que hicimos en el artículo anterior, ya disponemos de mecanismos para añadir y leer elementos de una lista enlazada. Sin embargo, lo implementado allí NO es suficiente para disponer de una lista completamente funcional. En primer lugar, porque todavía no tenemos un mecanismo para eliminar elementos. En segundo lugar, cuando leemos la lista, esta se destruye, algo que necesitamos corregir. En tercer lugar, no podemos acceder a sus elementos de forma aleatoria, ya que ese mecanismo todavía no se ha implementado.
En relación con este tercer punto, en el artículo Del básico al intermedio: Sobrecarga de operadores (IV) se explicó un pequeño detalle que debemos tener en cuenta. Sin embargo, existe una forma de sortear ese pequeño inconveniente. Para ello utilizaremos otra técnica, aunque no funciona de la manera que quizá estés imaginando. Recuerda que lo explicado en el artículo mencionado anteriormente sigue siendo válido. Aun así, podemos estructurar el código de cierta manera para poder implementar este tercer punto.
Bien, entonces empecemos por lo más sencillo y avancemos hacia lo más complicado. Como el segundo y el tercer punto están estrechamente relacionados, en cierto modo podemos considerarlos un único desafío. Aunque en realidad ni siquiera es un desafío, y enseguida entenderás por qué. Será algo más sencillo de comprender que la parte relacionada con eliminar elementos de la lista mediante sobrecarga de operadores.
Así que retomemos lo que ya tenemos implementado, que será nuestro punto de partida.
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. 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 01
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. //+----------------+ 22. public: 23. //+----------------+ 24. C_Demo() : m_next(NULL), m_prev(NULL) {} 25. //+----------------+ 26. void operator<<(const T arg) 27. { 28. C_Demo <T> *loc = new C_Demo <T>; 29. 30. (*loc).m_info = arg; 31. (*loc).m_prev = m_next; 32. if (m_next != NULL) (*m_next).m_next = loc; 33. m_next = loc; 34. m_prev = def_Adjust(loc); 35. } 36. //+----------------+ 37. bool operator>>(T &arg) 38. { 39. C_Demo <T> *loc = def_Adjust(m_prev); 40. if (loc == NULL) return false; 41. arg = (*loc).m_info; 42. if ((m_prev = (*loc).m_next) != NULL) (*m_prev).m_prev = NULL; 43. delete loc; 44. 45. return true; 46. } 47. //+----------------+ 48. C_Demo <T> *operator[](const uint arg) 49. { 50. C_Demo <T> *loc = def_Adjust(m_prev); 51. for (uint c = 0; (loc != NULL) && (c < arg); loc = (*loc).m_next, c++); 52. return loc; 53. } 54. //+----------------+ 55. void operator=(const T arg) 56. { 57. m_info = arg; 58. } 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. } 70. //+----------------+ 71. void Debug(string fn, uint line) 72. { 73. m_prev = def_Adjust(m_prev); 74. Debug_Private(fn, line); 75. } 76. //+----------------+ 77. #undef def_Adjust 78. //+----------------+ 79. }; 80. //+------------------------------------------------------------------+
Código 02
Muy bien, cuando ejecutamos este código 01, obtenemos el resultado que se muestra a continuación.

Imagen 01
Perfecto. Ahora podemos empezar. En el artículo Del básico al intermedio: Clases (III) se explicaron algunos detalles sobre el tiempo de vida de una clase. Ahora piensa en lo siguiente: como en muchos casos no leeremos toda la lista desde nuestro código principal para destruirla y liberar la memoria asignada, necesitamos implementar un destructor en la clase que vimos en el código 02. Esto evitará que MetaTrader 5 dispare alertas cuando se destruya la variable que contiene la lista sin haber liberado previamente la memoria asignada. Hacerlo es muy sencillo y directo. Solo necesitamos añadir al código 02 el fragmento que se muestra a continuación.
. . . 25. //+----------------+ 26. ~C_Demo() 27. { 28. for (T loc; this >> loc;); 29. } 30. //+----------------+ . . .
Fragmento 01
Con esto, ya no tenemos que preocuparnos por errores provocados por no liberar la memoria asignada. Muy bien, ahora podemos centrarnos en el acceso aleatorio a los elementos de la lista, ya que la lectura secuencial puede ir eliminando los elementos a medida que se leen. En cambio, el acceso aleatorio no destruirá la lista, lo que nos permitirá tener un mayor control sobre las operaciones que realicemos con ella. De esta forma, la lista será mucho más funcional.
Como quiero que pruebes cada cambio que hagamos aquí para entenderlo paso a paso, siempre que sea posible utilizaré archivos diferentes. De este modo, en los archivos adjuntos tendrás acceso a todos los códigos que considere necesario incluir. En cualquier caso, procura probar todo lo que veremos en el artículo para comprender mejor qué efecto tendrá cada cambio en el resultado final.
Muy bien, pasemos entonces a la primera prueba. Para ello utilizaremos el código que se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Include\C_Demo_02.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. demo[3] = 18; 18. demo.Debug(__FUNCTION__, __LINE__); 19. }; 20. //+------------------------------------------------------------------+
Código 03
Cuando se ejecuta, este código genera el resultado que se muestra a continuación.

Imagen 02
Es decir, no tenemos ningún problema para modificar un valor dentro de la lista enlazada. También podemos comprobar que el destructor funciona perfectamente. Ahora bien, si quisiéramos leer el contenido de un elemento determinado, tendríamos que añadir al archivo de cabecera lo que se muestra en el siguiente fragmento.
. . . 75. //+----------------+ 76. void GetElement(T &arg) 77. { 78. arg = m_info; 79. } 80. //+----------------+ . . .
Fragmento 02
Lo menos agradable de este fragmento 02 es que ahora nuestro código ya no utilizará únicamente la sobrecarga de operadores. Sin embargo, podremos leer cualquier elemento cuando queramos. Para ello, necesitaremos utilizar lo que se muestra en el siguiente código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Include\C_Demo_02.mqh" 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. char info; 09. C_Demo <char> demo; 10. 11. demo << 10; 12. demo << 84; 13. demo << -6; 14. demo[2] <<= 35; 15. demo[0] <<= 47; 16. demo[4] <<= 110; 17. demo[3] = 18; 18. demo.Debug(__FUNCTION__, __LINE__); 19. for (uint pos = 1; pos < 3; pos++) 20. { 21. demo[pos].GetElement(info); 22. Print("The content of position ", pos, " is ", info); 23. } 24. }; 25. //+------------------------------------------------------------------+
Código 04
Observa lo que ocurre en el bucle de la línea 19 de este código 04. Aquí, concretamente en la línea 21, es donde obtenemos el valor de la posición solicitada. Al hacerlo de esta manera, NO ELIMINAMOS EL ELEMENTO DE LA LISTA. Simplemente lo leemos, y el elemento permanece allí, disponible para volver a utilizarse.
Bien, creo que nada de lo que hemos visto hasta aquí resulta especialmente complicado; de hecho, es bastante sencillo de entender. En cualquier caso, el resultado de ejecutar este código 04 se muestra a continuación.

Imagen 03
Ahora piensa un poco en lo que hemos visto hasta este momento, mi querido lector. Podemos modificar el código para que la línea 21 del código 04 también utilice la sobrecarga de operadores. Y, para hacerlo, solo necesitamos modificar el fragmento 02 y dejarlo como se muestra a continuación.
. . . 75. //+----------------+ 76. void operator>>=(T &arg) 77. { 78. arg = m_info; 79. } 80. //+----------------+ . . .
Fragmento 03
Con esto, ahora podemos modificar el bucle del código 04 para que quede como se muestra a continuación.
. . . 19. for (uint pos = 1; pos < 3; pos++) 20. { 21. demo[pos] >>= info; 22. Print("The content of position ", pos, " is ", info); 23. } . . .
Fragmento 04
Fíjate qué interesante. En el fondo, el código sigue funcionando exactamente de la misma manera. Sin embargo, al observar esta línea 21 del fragmento 04, muchos pueden pensar que se trata de un código completamente distinto.
¿Ves que realmente no es difícil adaptar el código para utilizar la sobrecarga de operadores? Solo necesitamos tomar ciertas precauciones para no estropear el código ni volverlo completamente ilegible.
Bien, esta línea 21 puede parecer bastante extraña. En realidad, no lo es tanto, mi querido lector. Si la observas con calma, notarás que equivale a lo que podemos ver entre las líneas 14 y 16. Incluso podemos implementar la sobrecarga para utilizar este código de formas mucho más exóticas, como en el ejemplo que se muestra a continuación.
. . . 75. //+----------------+ 76. T operator>>=(T &arg) 77. { 78. return (arg = m_info); 79. } 80. //+----------------+ . . .
Fragmento 05
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Include\C_Demo_02.mqh" 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. char info; 09. C_Demo <char> demo; 10. 11. demo << 10; 12. demo << 84; 13. demo << -6; 14. demo[2] <<= 35; 15. demo[0] <<= 47; 16. demo[4] <<= 110; 17. demo[3] = 18; 18. demo.Debug(__FUNCTION__, __LINE__); 19. for (uint pos = 1; pos < 3; pos++) 20. Print("The content of position ", pos, " is ", demo[pos] >>= info); 21. }; 22. //+------------------------------------------------------------------+
Código 05
Aunque estas modificaciones parecen generar un código nuevo, nos permiten realizar algunos cambios en la propia estructura del código principal, como puedes observar al analizar el bucle de la línea 19 de este código 05.
Bien, dejemos ya esta parte. Creo que a estas alturas todos habrán podido ver qué podemos hacer y qué no. Ahora las cosas tomarán otro rumbo, porque vamos a implementar la eliminación de elementos de la lista.
Como esta parte suele generar mucha más confusión que todo lo que hemos visto hasta ahora, simplificaremos aún más nuestra lista. De esta forma, la explicación será más clara y resultará más sencillo entender qué está ocurriendo.
El problema de implementar la eliminación con el código que has visto hasta este punto está precisamente en el destructor de la clase. Sin él, MetaTrader 5 disparará alertas por recursos no liberados. Con él, en cambio, el código fallará cada vez que intentemos eliminar un elemento que no se encuentre en la base de la lista. Es posible corregir esto manteniendo el código tal y como lo has visto hasta ahora. No obstante, resulta mucho más sencillo realizar algunos cambios en el código del archivo de cabecera que implementar la solución de otra manera.
Así que modificaremos el archivo de cabecera para dejarlo como se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class C_NODE 05. { 06. private : 07. //+----------------+ 08. T m_info; 09. C_NODE <T> *m_next, 10. *m_prev; 11. //+----------------+ 12. public : 13. //+----------------+ 14. C_NODE(const T arg1, C_NODE <T> *arg2, C_NODE <T> *arg3) : m_info(arg1), m_prev(arg2), m_next(arg3) {} 15. //+----------------+ 16. const T info(void) { return m_info; } 17. //+----------------+ 18. C_NODE <T> *prev(void) { return m_prev; } 19. //+----------------+ 20. C_NODE <T> *next(void) { return m_next; } 21. //+----------------+ 22. void setPrev(C_NODE <T> *arg) { m_prev = arg; } 23. //+----------------+ 24. void setNext(C_NODE <T> *arg) { m_next = arg; } 25. //+----------------+ 26. void operator=(const T arg) { m_info = arg; } 27. //+----------------+ 28. T operator>>=(T &arg) const { return (arg = m_info); } 29. //+----------------+ 30. void operator<<=(const T arg) 31. { 32. C_NODE <T> *loc = new C_NODE<T>(arg, m_prev, GetPointer(this)); 33. if (m_prev != NULL) (*m_prev).m_next = loc; 34. m_prev = loc; 35. } 36. //+----------------+ 37. }; 38. //+------------------------------------------------------------------+ 39. template <typename T> class C_Demo 40. { 41. private : 42. //+----------------+ 43. C_NODE <T> *m_Base, 44. *m_Top; 45. //+----------------+ 46. C_NODE<T> *AdjustBase(C_NODE <T> *arg) { return m_Base = (m_Base == NULL ? arg : ((*m_Base).prev() == NULL ? m_Base : (*m_Base).prev())); } 47. //+----------------+ 48. public : 49. //+----------------+ 50. C_Demo(): m_Base(NULL), m_Top(NULL) {} 51. //+----------------+ 52. ~C_Demo() 53. { 54. for(C_NODE <T> *ptr = (*m_Top).prev(); m_Top != NULL; m_Top = ptr, ptr = (ptr != NULL ? (*m_Top).prev() : NULL)) 55. delete m_Top; 56. } 57. //+----------------+ 58. void operator<<(const T arg) 59. { 60. C_NODE <T> *tmp = m_Top; 61. 62. m_Top = new C_NODE <T> (arg, m_Top, NULL); 63. if (tmp != NULL) tmp.setNext(m_Top); 64. AdjustBase(m_Top); 65. } 66. //+----------------+ 67. bool operator>>(T &arg) 68. { 69. C_NODE <T> *tmp; 70. 71. if ((tmp = AdjustBase(m_Base)) == NULL) return false; 72. arg = (*m_Base).info(); 73. if ((m_Base = (*m_Base).next()) != NULL) (*m_Base).setPrev(NULL); 74. delete tmp; 75. return true; 76. } 77. //+----------------+ 78. C_NODE <T> *operator[](const uint arg) 79. { 80. C_NODE <T> *loc = AdjustBase(m_Base); 81. for (uint c = 0; (loc != NULL) && (c < arg); loc = (*loc).next(), c++); 82. return loc; 83. } 84. //+----------------+ 85. void Debug(string fn, uint line) 86. { 87. Print("===== DEBUG [", fn, " :: ", line, "]====="); 88. for (C_NODE<T>*loc = AdjustBase(m_Base); (loc != NULL); loc = (*loc).next()) 89. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).prev(), loc, (*loc).next(), (*loc).info()); 90. Print("================="); 91. } 92. //+----------------+ 93. }; 94. //+------------------------------------------------------------------+
Código 06
Este código 06 es considerablemente más sencillo que todos los demás que hemos visto hasta aquí. Aun así, ofrece las mismas capacidades que los códigos mostrados anteriormente. Por lo tanto, considero completamente innecesario explicarlo, ya que todo lo que hace se ha visto hasta este momento. Y estoy completamente seguro de que cualquiera de ustedes que esté siguiendo esta secuencia podrá entender con facilidad cómo funciona este código.
Entonces, ¿por qué no nos lo mostraste antes? ¿Por qué presentas este código solo ahora? Pues bien, mi querido lector, hay una buena razón para ello. Si este código 06 se hubiera mostrado antes, difícilmente habrías conseguido entender cómo funciona. Aquí, la clase C_Demo mantiene la lista, mientras que la clase C_NODE crea los elementos.
Para verlo, basta con observar la línea 62 de este código 06. Esta línea crea un nuevo elemento de forma casi completamente automática. Una vez creado, solo queda ajustar las posiciones anterior y posterior, y esto se hace en las líneas 63 y 64. Fíjate en cuánto más sencillo resulta que todos aquellos ajustes que realizábamos anteriormente.
¿Por qué es importante haber llegado hasta este código 06? La razón es que, sin él, sería mucho más complicado implementar la eliminación de la forma en que quiero mostrarte que podemos hacerlo. Sería posible hacerlo, pero el nivel de complejidad acabaría perjudicando por completo la explicación y la comprensión del concepto que vamos a utilizar.
Una vez entendido esto, por fin podemos comenzar a implementar el código de eliminación. Lo haremos con calma, porque vamos a utilizar algo que, si solo conoces uno o dos lenguajes de programación, quizá no tenga ningún sentido para ti. El mecanismo que utilizaremos al final procede precisamente de C y C++. Hasta donde sé y por lo que he podido ver en código MQL5, este concepto nunca se ha utilizado. Al menos, no he encontrado ninguna referencia al respecto.
Bien, dejemos de perder tiempo y vayamos a lo que interesa. Para implementar la rutina de eliminación a partir de este código 06 que acabamos de ver, necesitamos hacer muy poco. Así de sencillo nos permite hacerlo este código. Básicamente, solo tenemos que añadir al código el fragmento que se muestra a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. template <typename T> class C_NODE 005. { . . . 015. //+----------------+ 016. ~C_NODE() 017. { 018. if (m_prev != NULL) (*m_prev).m_next = m_next; 019. if (m_next != NULL) (*m_next).m_prev = m_prev; 020. } 021. //+----------------+ . . . 043. }; 044. //+------------------------------------------------------------------+ 045. template <typename T> class C_Demo 046. { . . . 090. //+----------------+ 091. void Exclude(const uint arg) 092. { 093. C_NODE <T> *loc = AdjustBase(m_Base); 094. for (uint c = 0; (loc != NULL) && (c < arg); loc = (*loc).next(), c++); 095. delete loc; 096. } 097. //+----------------+ . . . 106. }; 107. //+------------------------------------------------------------------+
Fragmento 06
¿Qué? ¿Eso es todo lo que necesitamos añadir al código? No me lo creo. Tienes que estar bromeando. Pues sí, mi querido lector, es en serio: solo necesitamos añadir esto. Sin embargo, hay un pequeño problema, aunque lo veremos más adelante. Primero comprobemos si este código modificado funciona realmente. Para verificarlo, utilizaremos el código que se muestra 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[3] = 18; 17. demo.Debug(__FUNCTION__, __LINE__); 18. demo.Exclude(4); 19. demo.Debug(__FUNCTION__, __LINE__); 20. }; 21. //+------------------------------------------------------------------+
Código 07
Bien, ahora ejecutemos este código 07. Recuerda que, en la línea 18, estamos solicitando que se elimine de la lista el elemento cuyo índice es cuatro. Y no lo olvides: la numeración comienza en cero, precisamente debido a la línea 94 del fragmento 06. Así, cuando ejecutemos este código, obtendremos el resultado que se muestra en la siguiente imagen.

Imagen 04
Perfecto, ha funcionado de maravilla. Sin embargo, hay un pequeño problema: este fragmento 06 solo permite eliminar correctamente los elementos centrales. En realidad, también podemos eliminar los elementos situados en los extremos, pero se producirá un fallo debido a un desajuste de los punteros. Para verlo con mayor claridad, intentemos eliminar el elemento cuyo valor es 47, ya que se encuentra en el índice cero. Solo tenemos que cambiar el contenido de la línea 18 de este código 07 por lo que se muestra a continuación.
demo.Exclude(0);Ahora, cuando volvamos a ejecutar el código, obtendremos lo que se muestra en la siguiente imagen.

Imagen 05
Bien, esto ha sido un poco extraño. Probemos ahora a eliminar el elemento cuyo valor es -6. Su índice es cinco. Así que tenemos que sustituir esa misma línea mostrada anteriormente por la que se muestra a continuación.
demo.Exclude(5);El resultado de la ejecución es el que podemos observar en la siguiente imagen.

Imagen 06
Esto sí que resulta bastante extraño. En esta imagen 06 podemos observar claramente que el puntero se ajustó correctamente dentro del nodo, gracias al destructor que podemos ver en el fragmento 06. Sin embargo, como los punteros no se reajustaron correctamente, el código simplemente falla. Esta es una de esas situaciones en las que podemos adoptar diferentes enfoques para corregir el problema, cada uno con sus ventajas y desventajas. No obstante, en este artículo, precisamente por motivos didácticos, voy a proponerte, mi querido lector, la solución que se muestra a continuación.
. . . 090. //+----------------+ 091. void Exclude(const uint arg) 092. { 093. C_NODE <T> *loc = AdjustBase(m_Base), 094. *ptr = NULL, 095. *tmp; 096. for (uint c = 0; (loc != NULL) && (c < arg); ptr = loc, loc = (*loc).next(), c++); 097. tmp = (*loc).next(); 098. delete loc; 099. if (ptr != NULL) m_Top = ((*ptr).next() == NULL ? ptr : m_Top); 100. else m_Base = tmp; 101. } 102. //+----------------+ . . .
Fragmento 07
Ahora, utilizando lo que aparece en este fragmento 07, por fin podemos resolver el problema de eliminar los elementos situados en los extremos, como podrás comprobar al volver a ejecutar el código 07 con las modificaciones vistas anteriormente. Pero, por simple curiosidad, veamos cuál sería el resultado al eliminar el extremo superior. Este se muestra en la siguiente imagen.

Imagen 07
Simplemente perfecto. Fíjate en que resolver la eliminación de elementos ha sido mucho más sencillo de lo que podríamos haber imaginado al principio. Sin embargo, esto no es exactamente lo que quiero mostrar aquí. En este punto quiero enseñarte cómo podemos hacerlo mediante sobrecarga de operadores. Ahora te pido que prestes todavía más atención a lo que voy a explicar, porque al principio todo esto puede resultar bastante confuso. Para empezar a explicar lo que necesitamos hacer, vamos a aislar una parte del código. Esta se muestra a continuación.
. . . 083. //+----------------+ 084. C_NODE <T> *operator[](const uint arg) 085. { 086. C_NODE <T> *loc = AdjustBase(m_Base); 087. for (uint c = 0; (loc != NULL) && (c < arg); loc = (*loc).next(), c++); 088. return loc; 089. } 090. //+----------------+ 091. void Exclude(const uint arg) 092. { 093. C_NODE <T> *loc = AdjustBase(m_Base), 094. *ptr = NULL, 095. *tmp; 096. for (uint c = 0; (loc != NULL) && (c < arg); ptr = loc, loc = (*loc).next(), c++); 097. tmp = (*loc).next(); 098. delete loc; 099. if (ptr != NULL) m_Top = ((*ptr).next() == NULL ? ptr : m_Top); 100. else m_Base = tmp; 101. } 102. //+----------------+ . . .
Fragmento 08
Estas dos rutinas que aparecen en el fragmento 08 no están juntas por casualidad. Las coloqué así intencionadamente para que puedas entender lo que vamos a hacer. Observa que la línea 86 es igual a la línea 93, del mismo modo que la línea 87 es igual a la línea 96. La única diferencia entre ellas se encuentra precisamente en la línea 88, y la línea 98 realiza una operación equivalente, aunque con un objetivo distinto. ¿Y por qué quiero que prestes atención a estas líneas, mi querido lector? Porque vamos a utilizar precisamente la sobrecarga del operador de subíndice para eliminar un elemento de la lista.
Pero espera un momento. ¿Cómo pretendes hacer eso? Pues bien, esta es precisamente la parte confusa. Así que vayamos con calma, porque quiero que comprendas muy bien el concepto que vamos a utilizar aquí.
Si observas con atención, notarás que en distintos momentos utilizamos la constante NULL para comprobar si un valor es válido cuando trabajamos con punteros. Y cuando el operador delete destruye un elemento, como ocurre en la línea 98 del fragmento 08, además de destruirlo, asigna la constante NULL a esa posición para marcarla como no válida. De este modo, ya no puede volver a utilizarse dentro del código. Si intentas acceder a esa posición, verás que el código falla y muestra avisos de error como los que aparecen en las imágenes 05 y 06. Comprender esto es muy importante para entender lo que vamos a hacer a continuación.
Pues bien, si consultas la documentación de MQL5, encontrarás cuál es el valor de NULL. Esto puede verse en Otras constantes. Sin embargo, esto es solo una parte de lo que necesitamos hacer, y precisamente la más sencilla. Es decir, si asignas el valor NULL a un elemento determinado, el código deberá interpretar esa asignación como una solicitud para eliminar dicho elemento y liberar la memoria que ocupa, modificando así la lista enlazada. Esa es la idea básica. Sin embargo, para conseguirlo tendremos que hacer las cosas de una manera que, al principio, muchos no entenderán.
Para dejar claro lo que quiero mostrar, veamos cómo quedaría el código 07 aplicando este principio. Se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Include\C_Demo_05.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[3] = 18; 17. demo.Debug(__FUNCTION__, __LINE__); 18. demo[4] = NULL; 19. demo.Debug(__FUNCTION__, __LINE__); 20. }; 21. //+------------------------------------------------------------------+
Código 08
Ahora presta atención: cuando se ejecute la línea 18 de este código 08, estaremos solicitando que se elimine de la lista el elemento situado en el índice cuatro. Sin embargo, al ejecutar el código, obtenemos el resultado que se muestra a continuación.

Imagen 08
Observa que el elemento no se ha eliminado; únicamente se ha cambiado su valor a cero. Ahora viene la parte confusa. Existen dos formas básicas de eliminar el elemento en estos casos. La primera consiste en hacer que, cada vez que se intente asignar el valor cero a un elemento de la lista, el elemento correspondiente se elimine, siempre que exista. Si no existe, no se creará ni se añadirá a la lista. Esta es una forma de hacerlo. Sin embargo, no resulta adecuada en muchos casos, ya que, dependiendo del propósito de la lista, podemos tener elementos cuyo valor sea cero y que aparezcan varias veces en ella. Por lo tanto, necesitamos adoptar otro enfoque.
Y, para poder adoptar este enfoque, necesito que consultes en la documentación de MQL5 el siguiente tema: Tipo void y constante NULL. Y, por favor, no me juzgues por lo que voy a decir. Lo que aparece en la documentación de MQL5 es solo una parte de algo mucho más amplio. Creo que lo presentan de esta manera precisamente para no confundir a los programadores principiantes. Sin embargo, si algún día te interesa estudiar C y C++, te encontrarás con algo realmente inquietante: las variables de tipo void.
¿Pero qué puede tener eso de inquietante? No entiendo adónde quieres llegar. El problema, mi querido lector, es que un valor declarado como de tipo void puede tener cualquier tamaño de dato. Incluso puede cambiar completamente dependiendo de cómo se interprete. Es algo realmente muy confuso. En MQL5, los desarrolladores del lenguaje han limitado al máximo el tipo void, impidiendo que se utilice normalmente como un tipo de dato. Aun así, y es importante que entiendas muy bien esto, existen situaciones en las que realmente podemos declarar una variable de tipo void y pasarla a una función o procedimiento.
Al hacerlo, en muchas situaciones terminamos generando problemas extremadamente difíciles y complicados de corregir. Sin embargo, si se piensa muy bien y se hace correctamente, el simple hecho de utilizar una variable de tipo void en nuestro código puede abrirnos muchas posibilidades. Y para gran parte de ustedes, ya sea por curiosidad o porque quizá intenten convertirse algún día en profesionales, encontrarse con una variable de tipo void puede resultar tan confuso que llega a hacerse un nudo en la cabeza.
Bien, ahora quiero que entiendas que lo que veremos aquí no debe hacerse sin el debido cuidado. Si se hace de cualquier manera, puede causarte grandes problemas. Lo primero que necesitamos para que la línea 18 del código 08 indique al código que debe eliminar un determinado elemento de la lista es crear efectivamente una sobrecarga adicional del operador de asignación.
Esta cuestión de la sobrecarga se vio en el artículo Del básico al intermedio: Sobrecarga. Así que, al modificar el código del archivo de cabecera, tendremos lo que se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class C_NODE 05. { . . . 31. //+----------------+ 32. void operator=(const T arg) { m_info = arg; } 33. //+----------------+ 34. void operator=(void *arg) 35. { 36. Print(__FUNCTION__, " :: ", __LINE__, " Recognized void type variable..."); 37. } 38. //+----------------+ . . .
Fragmento 09
Ahora, para poder ver este fragmento 09 en funcionamiento, necesitamos modificar el código para que quede como se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Include\C_Demo_05.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[3] = 18; 17. demo.Debug(__FUNCTION__, __LINE__); 18. demo[4] = (void *)NULL; 19. demo.Debug(__FUNCTION__, __LINE__); 20. }; 21. //+------------------------------------------------------------------+
Código 09
Observa que la línea 18 del código 09 ha cambiado una vez más. Sin embargo, esta será la última vez que la modificaremos. Todo lo demás se implementará dentro del archivo de cabecera. Muy bien, al ejecutar este código 09 verás lo que se muestra en la siguiente imagen.

Imagen 09
Por el amor de DIOS. Madre mía, ¿qué locura sin sentido es esta que estoy viendo en la imagen 09? Esto no puede ser cosa de DIOS. Vamos, qué cosa más disparatada. Tranquilo, mi querido lector, mantengamos la calma. Ya te advertí que esto era bastante confuso. Y eso que todavía estoy siendo moderado. Ahora que has visto que funciona, podemos pasar al siguiente paso: tomar aquella rutina de eliminación y trasladarla a este punto del código.
Sin embargo, antes de hacer esto tenemos otro problema. No se trata simplemente de copiar y pegar, ya que el código de eliminación se implementó para utilizarse dentro de la clase C_Demo. No podemos utilizar aquí, en la clase C_NODE, ese mismo código, porque el compilador no podría interpretarlo. Por lo tanto, no obtendríamos el resultado que buscamos. Así que necesitamos adaptar el código Exclude de la clase C_Demo para poder utilizarlo aquí, en la clase C_NODE. ¿La programación es o no es extremadamente divertida? (RISAS).
Lo que vamos a hacer ahora es corregir un problema y generar otro. Después iremos corrigiendo los problemas que aparezcan hasta completar la implementación que queremos realizar. Para empezar, necesito que primero entiendas una cosa. Para ello, modificaremos el fragmento 09 y lo dejaremos como se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class C_NODE 05. { . . . 31. //+----------------+ 32. void operator=(const T arg) { m_info = arg; } 33. //+----------------+ 34. void operator=(void *arg) 35. { 36. PrintFormat("0x%08X ->> 0x%08X:[%d] <<- 0x%08X", m_prev, GetPointer(this), m_info, m_next); 37. } 38. //+----------------+ . . .
Fragmento 10
Ahora, al volver a ejecutar el código 09, veremos lo que aparece en la imagen 10 que se muestra a continuación.

Imagen 10
Observa que, en esta región marcada, estamos viendo exactamente el elemento que queremos eliminar de la lista. Así que primero resolveremos la eliminación de los elementos situados en medio de la lista y, después, nos ocuparemos de los elementos de los extremos. Una vez definido este objetivo inicial, modificaremos el fragmento 10 para dejarlo como se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class C_NODE 05. { . . . 31. //+----------------+ 32. void operator=(const T arg) { m_info = arg; } 33. //+----------------+ 34. void operator=(void *arg) 35. { 36. delete GetPointer(this); 37. } 38. //+----------------+ . . .
Fragmento 11
Volvemos a compilar y ejecutar el código 09. Ahora obtenemos el resultado que se muestra a continuación.

Imagen 11
¡Bien! Qué maravilla. Está funcionando. Es realmente increíble. Tranquilo, mi querido lector, mantengamos la atención en lo importante. Todavía tenemos que resolver el problema de los extremos, ¿recuerdas? Ahora el problema es un poco más complicado que antes. Esto se debe a que anteriormente contábamos con el propio procedimiento para ayudarnos a ajustar los extremos. Ahora ya no disponemos de ese apoyo. Necesitamos encontrar una forma de solucionarlo. Para ello, modificaremos el código 09 de manera que podamos centrar nuestra atención en eliminar el extremo superior. Tenemos que cambiar la línea 18 por lo que se muestra a continuación.
demo[5] = (void *)NULL;
Bien, al volver a ejecutar el código 09, obtenemos el resultado que aparece en la siguiente imagen.

Imagen 12
Fíjate en una cosa. En esta región marcada podemos ver que la estructura de la lista se ha reajustado correctamente al eliminar el extremo superior. Sin embargo, hemos vuelto a encontrarnos con el mismo problema que teníamos antes y que vimos en la imagen 06. Así que, si intentamos eliminar el extremo inferior de la lista, con toda seguridad obtendremos algo parecido a lo que se muestra en la imagen 05. Puedes probarlo después si quieres. Ahora veamos cómo vamos a corregirlo.
Para hacerlo, necesitamos entender la secuencia lógica de ejecución del código. Una vez que comprendas esta secuencia, terminarás viendo que el mejor lugar para corregir los extremos es la función AdjustBase. Pero ¿cómo debemos modificarla? Para entenderlo mejor, tenemos que volver al fragmento 08. Allí podemos observar claramente que, al eliminar un elemento, tenemos un código muy parecido al que utiliza el operador de subíndice. Tanto es así que ambos aparecen en el mismo fragmento. ¿Recuerdas que expliqué por qué los coloqué juntos?
Pues bien, resulta evidente que necesitamos dos punteros auxiliares para que todo funcione y podamos reajustarlos posteriormente. Así que, para corregir adecuadamente el código y poder trabajar tanto con los extremos como con los elementos intermedios de la lista, eliminando cualquiera de ellos sin problemas, tendremos que utilizar lo que se muestra en el siguiente código.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. template <typename T> class C_NODE 005. { 006. private : 007. //+----------------+ 008. T m_info; 009. C_NODE <T> *m_next, 010. *m_prev; 011. //+----------------+ 012. public : 013. //+----------------+ 014. C_NODE(const T arg1, C_NODE <T> *arg2, C_NODE <T> *arg3) : m_info(arg1), m_prev(arg2), m_next(arg3) {} 015. //+----------------+ 016. ~C_NODE() 017. { 018. if (m_prev != NULL) (*m_prev).m_next = m_next; 019. if (m_next != NULL) (*m_next).m_prev = m_prev; 020. } 021. //+----------------+ 022. const T info(void) { return m_info; } 023. //+----------------+ 024. C_NODE <T> *prev(void) { return m_prev; } 025. //+----------------+ 026. C_NODE <T> *next(void) { return m_next; } 027. //+----------------+ 028. void setPrev(C_NODE <T> *arg) { m_prev = arg; } 029. //+----------------+ 030. void setNext(C_NODE <T> *arg) { m_next = arg; } 031. //+----------------+ 032. void operator=(const T arg) { m_info = arg; } 033. //+----------------+ 034. void operator=(void *arg) 035. { 036. delete GetPointer(this); 037. } 038. //+----------------+ 039. T operator>>=(T &arg) const { return (arg = m_info); } 040. //+----------------+ 041. void operator<<=(const T arg) 042. { 043. C_NODE <T> *loc = new C_NODE<T>(arg, m_prev, GetPointer(this)); 044. if (m_prev != NULL) (*m_prev).m_next = loc; 045. m_prev = loc; 046. } 047. //+----------------+ 048. }; 049. //+------------------------------------------------------------------+ 050. template <typename T> class C_Demo 051. { 052. private : 053. //+----------------+ 054. C_NODE <T> *m_Base, 055. *m_Top; 056. //+----------------+ 057. struct st01 058. { 059. C_NODE <T> *prev, 060. *next; 061. }m_aux; 062. //+----------------+ 063. C_NODE<T> *AdjustBase(C_NODE <T> *arg) 064. { 065. if (m_aux.prev != NULL) m_Top = ((*m_aux.prev).next() == NULL ? m_aux.prev : m_Top); 066. else m_Base = (m_aux.next != NULL ? ((*m_aux.next).prev() == NULL ? m_aux.next : m_Base) : m_Base); 067. return m_Base = (m_Base == NULL ? arg : ((*m_Base).prev() == NULL ? m_Base : (*m_Base).prev())); 068. } 069. //+----------------+ 070. public : 071. //+----------------+ 072. C_Demo(): m_Base(NULL), m_Top(NULL) {} 073. //+----------------+ 074. ~C_Demo() 075. { 076. for(C_NODE <T> *ptr = (*m_Top).prev(); m_Top != NULL; m_Top = ptr, ptr = (ptr != NULL ? (*m_Top).prev() : NULL)) 077. delete m_Top; 078. } 079. //+----------------+ 080. void operator<<(const T arg) 081. { 082. C_NODE <T> *tmp = m_Top; 083. 084. m_Top = new C_NODE <T> (arg, m_Top, NULL); 085. if (tmp != NULL) tmp.setNext(m_Top); 086. AdjustBase(m_Top); 087. } 088. //+----------------+ 089. bool operator>>(T &arg) 090. { 091. C_NODE <T> *tmp; 092. 093. if ((tmp = AdjustBase(m_Base)) == NULL) return false; 094. arg = (*m_Base).info(); 095. if ((m_Base = (*m_Base).next()) != NULL) (*m_Base).setPrev(NULL); 096. delete tmp; 097. return true; 098. } 099. //+----------------+ 100. C_NODE <T> *operator[](const uint arg) 101. { 102. C_NODE <T> *loc = AdjustBase(m_Base); 103. m_aux.prev = NULL; 104. for (uint c = 0; (loc != NULL) && (c < arg); m_aux.prev = loc, loc = (*loc).next(), c++); 105. m_aux.next = (loc != NULL ? (*loc).next() : NULL); 106. return loc; 107. } 108. //+----------------+ 109. void Debug(string fn, uint line) 110. { 111. Print("===== DEBUG [", fn, " :: ", line, "]====="); 112. for (C_NODE<T>*loc = AdjustBase(m_Base); (loc != NULL); loc = (*loc).next()) 113. PrintFormat("0x%08X ->> 0x%08X <<- 0x%08X = [%d]", (*loc).prev(), loc, (*loc).next(), (*loc).info()); 114. Print("================="); 115. } 116. //+----------------+ 117. }; 118. //+------------------------------------------------------------------+
Código 10
Mira este código 10 y dime: ¿es o no es una maravilla de código? (RISAS). Ahora observa que, para conseguir el efecto necesario y corregir los fallos que vimos anteriormente, en la línea 57 creé una pequeña estructura destinada a almacenar los valores auxiliares que utilizaremos para reajustar los punteros. Estos valores se actualizan mediante el operador sobrecargado de la línea 100, aunque en realidad se utilizan en las líneas 65 y 66, realizando una tarea similar a la que vimos en el fragmento 08.
Ahora podrás eliminar cualquier elemento de la lista. Así, cuando ejecutes el código 09 para eliminar el primer elemento, obtendrás el resultado que se muestra en la siguiente imagen.

Imagen 13
Ahora sí podemos celebrarlo. Felicidades, mi querido lector: acabas de aprender un montón de cosas nuevas que antes quizá considerabas prácticamente imposibles de hacer.
Consideraciones finales
Este, sin duda, será un artículo que muchos de ustedes necesitarán estudiar durante bastante tiempo para poder asimilar todo lo que se ha mostrado aquí. Quiero recordarte que lo que hemos visto es solo una pequeña e insignificante parte de todo aquello que llamamos programación. Cuando digo que AMO PROGRAMAR, no me refiero a crear cosas banales y aburridas. La mayoría de las cosas pueden resolverse de una forma muy sencilla y sin demasiada dificultad.
Sin embargo, nada, absolutamente nada, nos quita el placer de crear algo que muchos consideran imposible o impracticable. Cuando algunos se quejaron del contenido de ciertos artículos que publiqué en el pasado, simplemente los ignoré. Prefiero mostrar mediante código aquello que ellos intentan expresar con palabras. Así que no te sientas menos cuando alguien te diga que eres incapaz. No necesitas rebajarte; simplemente demuestra lo que has aprendido a lo largo de los años y que sabes hacerlo, mientras ellos no. En el próximo artículo continuaremos esta aventura en busca de nuevos conocimientos con MQL5.
| 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 |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/17010
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.
Red neuronal en la práctica: El caso de la puerta XOR
Algoritmo de optimización del dingo — Dingo Optimization Algorithm (DOA)
Del básico al intermedio: Indicadores técnicos (I)
Redes neuronales en el trading: una visión unificada del espacio y el tiempo (Extralonger)
- 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