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

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

MetaTrader 5Ejemplos |
53 0
CODE X
CODE X

Introducción

En el artículo anterior Del básico al intermedio: Sobrecarga de operadores (II), vimos cómo podíamos crear un mecanismo de depuración y los riesgos que implica depurar un código que utiliza sobrecarga de operadores. Aunque aquel artículo pueda parecer confuso y complicado al principio, debes comprender bien cómo funciona ese mecanismo, mi querido lector. Puede que en algún momento tengas dificultades para depurar un código que utilice sobrecarga de operadores. Y sin esos conocimientos, no podrás resolver adecuadamente algunos problemas bastante complicados.

Muy bien, aunque ya he presentado algunos de los operadores que pueden sobrecargarse en MQL5, al menos dos de ellos, creo que no te resultará difícil adaptar lo que ya hemos visto a buena parte de los demás. Al menos en lo que respecta a los operadores aritméticos. Sin embargo, existen otros operadores cuyo mecanismo de sobrecarga presenta diferencias importantes. En algunos casos presentan particularidades muy específicas.

Bien, todavía falta explicar cómo sobrecargar los operadores lógicos o, como muchos suelen llamarlos, operadores relacionales; ese será el tema de este artículo. Así que ha llegado el momento de que te apartes de todo lo que pueda distraerte y nos centremos en lo importante. Para ello, comenzaremos un nuevo tema.


Sobrecarga de operadores (III)

Genial, ya que hemos hablado un poco sobre cómo sobrecargar los operadores aritméticos, supongo que ahora quieres aprender a sobrecargar los operadores que controlan el flujo de ejecución del código en función de si una condición es verdadera o falsa. Normalmente, para determinar qué ruta de ejecución debe seguir el código, necesitamos comprobar varias variables que una estructura o clase puede contener. Sin embargo, existe una forma mucho más práctica y sencilla de evaluar esa condición, siempre que diseñes la clase o la estructura con mucho cuidado. De lo contrario, podrías terminar dándote de bruces contra un muro. A diferencia de los operadores aritméticos, los operadores lógicos y relacionales dejan poco margen para errores y exigen mucha más atención por parte del programador. Pero, si comprendiste las explicaciones de los dos artículos anteriores, este artículo te resultará mucho más fácil de entender.

Bien, empecemos. Para que todo resulte más agradable y fácil de asimilar, seguiremos el siguiente enfoque: no crearemos un código desde cero. Reutilizaremos los conceptos del artículo anterior. El siguiente bloque contiene todo el código inicial.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const stComplex &arg)
17.         {
18.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg)
22.         {
23.             return stComplex(m_r + arg, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg)
27.         {
28.             return stComplex(m_r += arg, m_i);
29.         }
30. //+----------------+
31.         stComplex Debug(uint arg)
32.         {
33.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
34.             return this;
35.         }
36. //+----------------+
37. };
38. //+------------------------------------------------------------------+
39. void OnStart(void)
40. {
41.     stComplex   a(2, 5),
42.                 b(8, -3),
43.                 c;
44. 
45.     c = (a + b).Debug(__LINE__);
46.     c.Debug(__LINE__);
47.     (c += 4).Debug(__LINE__);
48.     c = (b + 4).Debug(__LINE__);
49.     c.Debug(__LINE__);
50. }
51. //+------------------------------------------------------------------+

Código 01

En cierto modo, no necesitas reproducir este desarrollo paso a paso. En la práctica, puedes implementar directamente las sobrecargas necesarias. Sin embargo, como quiero que el artículo sea bastante didáctico, prefiero partir del código del artículo anterior, cuyo funcionamiento todos ya conocen. Ahora, la idea será implementar lo necesario para que funcione el fragmento propuesto.

                   .
                   .
                   .
38. //+------------------------------------------------------------------+
39. void OnStart(void)
40. {
41.     for (stComplex c(1, 0), step(1, 3), max(5, 6); c < max; c += step)
42.         c.Debug(__LINE__);
43. }
44. //+------------------------------------------------------------------+

Fragmento 01

Sí, lo sé, esta construcción parece una auténtica locura, porque nunca has visto públicamente un bucle de este tipo en MQL5. Sin embargo, eso no significa que no podamos crear un bucle for con la estructura que define este fragmento. Por extraño que parezca, en este momento muchos de ustedes deben estar dudando de mi cordura. Pero, a medida que implementemos cada parte, verás que en MQL5 sí podemos implementar comportamientos muy diversos. Solo necesitamos conocer los conceptos adecuados y aplicarlos correctamente. Muy bien, si modificamos el procedimiento OnStart según el fragmento 01, el compilador mostrará los siguientes mensajes.

Imagen 01

Bien, esto era de esperar. El motivo es sencillo: el compilador no sabe cómo utilizar el operador menor que cuando uno de los operandos es una variable stComplex, porque el código todavía no define la sobrecarga necesaria. Pero ¿qué ocurre con los otros dos errores? Bien, mi querido lector, esos otros dos errores son, en realidad, uno solo. Si observas el código 01, verás que stComplex define una sobrecarga del operador += cuyo operando derecho debe contener un valor double. Todavía no existe otra sobrecarga que acepte una variable stComplex como operando derecho. Puedes comprobarlo en la línea 26 del código 01.

Entonces, comenzaremos corrigiendo este primer error relacionado con el operador +=. Mantendremos la sobrecarga que suma un valor double al objeto stComplex situado a la izquierda del operador e implementaremos otra que sume el contenido de una segunda variable stComplex. Para ello, implementaremos en el código una segunda sobrecarga del operador +=.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const stComplex &arg)
17.         {
18.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg)
22.         {
23.             return stComplex(m_r + arg, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg)
27.         {
28.             return stComplex(m_r += arg, m_i);
29.         }
30. //+----------------+
31.         stComplex operator+=(const stComplex &arg)
32.         {
33.             return stComplex(m_r += arg.m_r, m_i += arg.m_i);
34.         }
35. //+----------------+
36.         stComplex Debug(uint arg)
37.         {
38.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
39.             return this;
40.         }
41. //+----------------+
42. };
43. //+------------------------------------------------------------------+
44. void OnStart(void)
45. {
46.     for (stComplex c(1, 0), step(1, 3), max(5, 6); c < max; c += step)
47.         c.Debug(__LINE__);
48. }
49. //+------------------------------------------------------------------+

Código 02

Ahora, si intentamos compilar nuevamente el código, el compilador mostrará el siguiente mensaje de error.

Imagen 02

Perfecto. Antes de comprobar el funcionamiento del bucle de la línea 46 del código 02, realizaremos una prueba previa. No es muy prudente probar directamente en un bucle la sobrecarga de un operador relacional o lógico. Conviene utilizar una instrucción if para realizar la prueba. Por tanto, modificaremos ligeramente el código 02 para llevar a cabo este análisis. El código 02 incorporará entonces una instrucción if de prueba.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const stComplex &arg)
17.         {
18.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg)
22.         {
23.             return stComplex(m_r + arg, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg)
27.         {
28.             return stComplex(m_r += arg, m_i);
29.         }
30. //+----------------+
31.         stComplex operator+=(const stComplex &arg)
32.         {
33.             return stComplex(m_r += arg.m_r, m_i += arg.m_i);
34.         }
35. //+----------------+
36.         bool operator<(const stComplex &arg)
37.         {
38.             return ((m_r <= arg.m_r) && (m_i < arg.m_i));
39.         }
40. //+----------------+
41.         stComplex Debug(uint arg)
42.         {
43.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
44.             return this;
45.         }
46. //+----------------+
47. };
48. //+------------------------------------------------------------------+
49. void OnStart(void)
50. {
51.     stComplex   a(1, 3),
52.                 b(1, 4);
53. 
54.     if (a < b) Print("Condition is: True"); else Print("Condition is: False.");
55.     // for (stComplex c(1, 0), step(1, 3), max(5, 6); c < max; c += step)
56.     //     c.Debug(__LINE__);
57. }
58. //+------------------------------------------------------------------+

Código 03

Observa que he comentado las líneas del bucle for y he insertado una instrucción if para comprobar si el código implementa correctamente el operador <. Al ejecutar este código 03, el terminal de MetaTrader 5 mostrará el siguiente resultado.

Imagen 03

Perfecto, funciona. Ahora ya podemos descomentar las líneas correspondientes al bucle for y comprobar si el bucle funciona correctamente. Sin embargo, como nunca está de más tomar precauciones, incorporaremos una comprobación de seguridad antes de probar este bucle for. El bucle podría entrar en un ciclo infinito, ya que todavía no conocemos con certeza el comportamiento del código con estos operadores sobrecargados. De este modo, obtenemos una versión del código con una salvaguarda adicional.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const stComplex &arg)
17.         {
18.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg)
22.         {
23.             return stComplex(m_r + arg, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg)
27.         {
28.             return stComplex(m_r += arg, m_i);
29.         }
30. //+----------------+
31.         stComplex operator+=(const stComplex &arg)
32.         {
33.             return stComplex(m_r += arg.m_r, m_i += arg.m_i);
34.         }
35. //+----------------+
36.         bool operator<(const stComplex &arg)
37.         {
38.             return ((m_r <= arg.m_r) && (m_i < arg.m_i));
39.         }
40. //+----------------+
41.         stComplex Debug(uint arg)
42.         {
43.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
44.             return this;
45.         }
46. //+----------------+
47. };
48. //+------------------------------------------------------------------+
49. void OnStart(void)
50. {
51.     stComplex   a(1, 3),
52.                 b(1, 3),
53.                 m(5, 6);
54.     int counter = 0;
55. 
56.     while (a < m)
57.     {
58.         a.Debug(__LINE__);
59.         a += b;
60.         counter++;
61.         if (counter > 50) break;
62.     }
63. 
64.     a.Debug(__LINE__);
65.     Print("The counter value is: ", counter);
66. 
67.     // for (stComplex c(1, 0), step(1, 3), max(5, 6); c < max; c += step)
68.     //     c.Debug(__LINE__);
69. }
70. //+------------------------------------------------------------------+

Código 04

Ahora analiza los cambios del código. Al ejecutarlo, obtendremos un recuento incorrecto.

Imagen 04

Hum, esto no parece correcto. ¿O sí? En realidad, no, mi querido lector. Por eso mencioné que siempre debemos probar las sobrecargas con cautela. Así como el recuento produjo un resultado incorrecto, también podríamos haber entrado en un bucle infinito. Por esta razón, los operadores lógicos y relacionales requieren mucho más cuidado que los operadores aritméticos al sobrecargarlos. Basta con cometer un pequeño descuido para perder horas intentando averiguar dónde está el error. Por eso repito en cada artículo que debes estudiar y practicar los procedimientos descritos, para saber cuándo conviene utilizar una sobrecarga recién implementada y cuándo no.

Bien, pero ¿dónde está el problema en el código? Esta es la parte sencilla, mi querido lector. El problema está en la línea 38. Observa lo siguiente: cuando realizamos la prueba mediante la instrucción if, esta línea 38 permitió ejecutar correctamente la prueba. Sin embargo, al utilizar una instrucción while, la prueba dejó de funcionar como debía. El valor de m_i provoca que la expresión devuelva false, incluso antes de que m_i alcance o supere el valor de m_r. ¿Ves lo sencillo que resulta identificar el error? Para corregirlo, debemos sustituir la línea 38 del código 04 por la expresión del fragmento 02.

                   .
                   .
                   .
38.            return ((m_r < arg.m_r) ? true : (m_r > arg.m_r ? false : (m_i < arg.m_i)));;
                   .
                   .
                   .

Fragmento 02

Al ejecutar nuevamente el código, este produce el siguiente resultado:

Imagen 05

Perfecto. Ha funcionado de maravilla. Ahora, por fin, podemos descomentar las líneas del bucle for y reutilizar la implementación del fragmento 01. Al ejecutar ahora el código con el bucle for, obtendremos el recuento correcto.

Imagen 06

Sencillo y muy divertido, ¿verdad, mi querido lector? Muy bien, esta ha sido la parte fácil. Ahora ampliaremos la implementación y, al mismo tiempo, la haremos mucho más interesante. Para ello, implementaremos el operador > mediante una nueva sobrecarga.

                   .
                   .
                   .
40. //+----------------+
41.         bool operator>(const stComplex &arg)
42.         {
43.             return ((m_r > arg.m_r) ? true : (m_r < arg.m_r ? false : (m_i > arg.m_i)));
44.         }
45. //+----------------+
                   .
                   .
                   .

Fragmento 03

Observa que también es bastante sencillo y casi el opuesto del operador que acabamos de implementar. Ahora podemos comprobar si un valor es mayor o menor que otro. Sin embargo, este tipo de comparación presenta una limitación y exige ciertas precauciones. Pero analizaremos todo esto paso a paso y veremos cómo corregir algunas de las limitaciones que, con toda seguridad, aparecerán.

El código 05 completo servirá como base para las modificaciones posteriores.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const stComplex &arg)
17.         {
18.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg)
22.         {
23.             return stComplex(m_r + arg, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg)
27.         {
28.             return stComplex(m_r += arg, m_i);
29.         }
30. //+----------------+
31.         stComplex operator+=(const stComplex &arg)
32.         {
33.             return stComplex(m_r += arg.m_r, m_i += arg.m_i);
34.         }
35. //+----------------+
36.         bool operator<(const stComplex &arg)
37.         {
38.             return ((m_r < arg.m_r) ? true : (m_r > arg.m_r ? false : (m_i < arg.m_i)));
39.         }
40. //+----------------+
41.         bool operator>(const stComplex &arg)
42.         {
43.             return ((m_r > arg.m_r) ? true : (m_r < arg.m_r ? false : (m_i > arg.m_i)));
44.         }
45. //+----------------+
46.         stComplex Debug(uint arg)
47.         {
48.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
49.             return this;
50.         }
51. //+----------------+
52. };
53. //+------------------------------------------------------------------+
54. void OnStart(void)
55. {
56.     stComplex   a(1, 3);
57.     int counter = 0;
58. 
59.     while (a < stComplex(5, 6))
60.     {
61.         a.Debug(__LINE__);
62.         a += stComplex(1, 3);
63.         counter++;
64.         if (counter > 50) break;
65.     }
66. 
67.     a.Debug(__LINE__);
68.     Print("The counter value is: ", counter);
69. }
70. //+------------------------------------------------------------------+

Código 05

Ahora quiero que prestes mucha atención, porque todas las modificaciones que introduciremos a partir de aquí estarán relacionadas con este código 05. Quizá estés pensando: «Vaya, estás completamente loco. No entiendo casi nada de este código 05». Sin embargo, la implementación del código 05 no es más que una reescritura equivalente del código 04. Pero quiero llamar tu atención sobre las líneas 59 y 62.

En este código 05 ya disponemos de los medios necesarios para modificar el funcionamiento de la línea 62. Esto ya lo explicamos y aplicamos en los artículos anteriores. Sin embargo, todavía no podemos modificar del mismo modo la condición de la línea 59. Todavía no. Aun así, quiero analizar algunas de las posibilidades que ofrece esta implementación. Primero ejecutaremos este código tal como está. La ejecución producirá el resultado de la imagen 07.

Imagen 07

Bien. Ahora reescribiremos la expresión de la línea 59.

while (stComplex(5, 6) > a)

El código seguirá produciendo el resultado de la imagen 07. Ahora reescribiremos la operación de la línea 62.

a += 1;

En este caso, la ejecución producirá un resultado diferente.

Imagen 08

Observa que podemos utilizar como operando derecho tanto un valor entero como una variable stComplex sin ningún problema. ¿Y qué ocurre con la condición que finaliza el bucle? ¿También podemos escribirla con cualquier orden de operandos? Bien, comprobaremos esta posibilidad directamente en el código. Por tanto, invertiremos ahora el orden de los operandos en la condición de la línea 59.

while (a < 5)

Al intentar compilar el código con este cambio, el compilador mostrará los siguientes mensajes de error.

Imagen 09

El problema es que la expresión coloca un valor de un tipo nativo de MQL5 como operando izquierdo y una variable stComplex como operando derecho, y el compilador no dispone de una sobrecarga para ese orden. Para admitir ambas combinaciones de operandos, implementaremos en el código 05 las sobrecargas correspondientes.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const double arg)
17.         {
18.             return stComplex(m_r + arg, m_i);
19.         }
20. //+----------------+
21.         stComplex operator+=(const double arg)
22.         {
23.             return stComplex(m_r += arg, m_i);
24.         }
25. //+----------------+
26.         bool operator<(const double arg)
27.         {
28.             return (m_r < arg);
29.         }
30. //+----------------+
31.         bool operator>(const double arg)
32.         {
33.             return (m_r > arg);
34.         }
35. //+----------------+
36.         stComplex operator+(const stComplex &arg)
37.         {
38.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
39.         }
40. //+----------------+
41.         stComplex operator+=(const stComplex &arg)
42.         {
43.             return stComplex(m_r += arg.m_r, m_i += arg.m_i);
44.         }
45. //+----------------+
46.         bool operator<(const stComplex &arg)
47.         {
48.             return ((m_r < arg.m_r) ? true : (m_r > arg.m_r ? false : (m_i < arg.m_i)));
49.         }
50. //+----------------+
51.         bool operator>(const stComplex &arg)
52.         {
53.             return ((m_r > arg.m_r) ? true : (m_r < arg.m_r ? false : (m_i > arg.m_i)));
54.         }
55. //+----------------+
56.         stComplex Debug(uint arg)
57.         {
58.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
59.             return this;
60.         }
61. //+----------------+
62. };
63. //+------------------------------------------------------------------+
64. void OnStart(void)
65. {
66.     stComplex   a(1, 3);
67.     int counter = 0;
68. 
69.     while (a < 5)
70.     {
71.         a.Debug(__LINE__);
72.         a += 1;
73.         counter++;
74.         if (counter > 50) break;
75.     }
76. 
77.     a.Debug(__LINE__);
78.     Print("The counter value is: ", counter);
79. }
80. //+------------------------------------------------------------------+

Código 06

Ahora sí, el compilador compila correctamente este nuevo código y genera el ejecutable.

Imagen 10

Ahora surge una limitación que MQL5 NO ADMITE. De hecho, hasta donde sé, solo el lenguaje SMALLTALK admite esta construcción, porque en él todo es un objeto. Literalmente, es un lenguaje que llevó la programación orientada a objetos hasta el extremo. Aparte de este, no conozco ningún otro lenguaje que admita esta construcción. Aunque existen tantos lenguajes de programación que desconozco que quizá algún otro también la admita. Y eso a pesar de haber estudiado diversos lenguajes a lo largo de los años. Sí, mi querido lector, puedo leer y comprender código escrito en varios lenguajes. Sin embargo, no domino la escritura en muchos de ellos, sino solo en unos pocos. Pero volvamos al orden de los operandos. La condición del fragmento 04 expone el problema.

                   .
                   .
                   .
63. //+------------------------------------------------------------------+
64. void OnStart(void)
65. {
66.     stComplex   a(1, 3);
67.     int counter = 0;
68. 
69.     while (5 > a)
70.     {
71.         a.Debug(__LINE__);
72.         a += 1;
73.         counter++;
74.         if (counter > 50) break;
75.     }
76. 
77.     a.Debug(__LINE__);
78.     Print("The counter value is: ", counter);
79. }
80. //+------------------------------------------------------------------+

Fragmento 04

Ahora, cuando intentes compilar el código 06 después de sustituir la condición por la del fragmento 04, el compilador mostrará el error correspondiente.

Imagen 11

¿Por qué muestra el compilador este error? En principio, stComplex define una sobrecarga del operador > que admite como operando derecho un valor de un tipo numérico nativo, ya sea entero o de punto flotante. La línea 31 del código 06 contiene esa definición.

La causa de este error es que quizá hayas olvidado cómo interpreta el compilador los operadores sobrecargados. El problema no consiste en si el código implementa el operador, sino en si el compilador admite ese orden de operandos. Muchos quizá estén pensando: «Claro que no funcionará; el literal 5 no es una variable. Por eso el compilador no sabrá cómo evaluar la comparación». Sin embargo, esa no es la causa, aunque el razonamiento se acerca bastante. Para demostrarlo, sustituiremos el literal por una variable.

                   .
                   .
                   .
63. //+------------------------------------------------------------------+
64. void OnStart(void)
65. {
66.     stComplex   a(1, 3);
67.     int counter = 0;
68. 
69.     while (counter > a)
70.     {
71.         a.Debug(__LINE__);
72.         a += 1;
73.         counter++;
74.         if (counter > 50) break;
75.     }
76. 
77.     a.Debug(__LINE__);
78.     Print("The counter value is: ", counter);
79. }
80. //+------------------------------------------------------------------+

Fragmento 05

Incluso así, el compilador seguirá mostrando el mismo tipo de error, lo que demuestra que el problema no está en utilizar un literal o una variable. El problema está en el tipo del operando izquierdo: la sobrecarga pertenece a stComplex y el compilador no puede invocarla cuando un valor nativo ocupa el operando izquierdo. Debemos construir un valor stComplex a partir de ese operando izquierdo. De este modo, el compilador interpretará correctamente el código y generará el ejecutable.

En cualquier caso, debemos aplicar la conversión mediante el mismo procedimiento tanto en el fragmento 04 como en el fragmento 05. Tomando como ejemplo el fragmento 04, debemos convertir explícitamente el operando izquierdo al tipo stComplex.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r): m_r(r), m_i(0) {}
15. //+----------------+
16.         stComplex(double r, double i): m_r(r), m_i(i) {}
17. //+----------------+
                   .
                   .
                   .
63. //+----------------+
64. };
65. //+------------------------------------------------------------------+
66. void OnStart(void)
67. {
68.     stComplex   a(1, 3);
69.     int counter = 0;
70. 
71.     while (stComplex(5) > a)
72.     {
73.         a.Debug(__LINE__);
74.         a += 1;
75.         counter++;
76.         if (counter > 50) break;
77.     }
78. 
79.     a.Debug(__LINE__);
80.     Print("The counter value is: ", counter);
81. }
82. //+------------------------------------------------------------------+

Fragmento 06

Ahora el compilador generará el ejecutable. Sin embargo, para ello tuvimos que declarar un nuevo constructor en la estructura. La línea 14 contiene esa declaración. Sin implementarlo, el compilador mostraría el error correspondiente.

Imagen 12

El mensaje de error también enumera con bastante precisión los constructores que define la estructura. Esta información permite corregir el código que intentamos compilar. Tras añadir el constructor necesario, el compilador genera el ejecutable correctamente.

Imagen 13

Bien, creo que ya entiendo cómo corregir este tipo de problema. Pero ¿qué ocurre con los demás operadores lógicos? ¿Cómo los sobrecargamos? ¿Podrías mostrarme algún ejemplo que sirva como punto de partida si algún día necesito sobrecargar uno de esos operadores? Claro que puedo mostrarte un ejemplo. De hecho, repasaremos casi todos los operadores. Aunque no los presentaré uno por uno, ya que, a mi entender, sería innecesario. Aun así, quiero mostrarte cómo podemos utilizar la sobrecarga que te sirva como punto de partida para seguir estudiando.

Así pues, analicemos de inmediato otros dos operadores. El código 07 implementa ambos.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. struct stComplex
005. {
006. //+----------------+
007.     private :
008.         double m_r, m_i;
009. //+----------------+
010.     public  :
011. //+----------------+
012.         stComplex(): m_r(0), m_i(0) {}
013. //+----------------+
014.         stComplex(double r): m_r(r), m_i(0) {}
015. //+----------------+
016.         stComplex(double r, double i): m_r(r), m_i(i) {}
017. //+----------------+
018.         stComplex operator+(const double arg)
019.         {
020.             return stComplex(m_r + arg, m_i);
021.         }
022. //+----------------+
023.         stComplex operator+=(const double arg)
024.         {
025.             return stComplex(m_r += arg, m_i);
026.         }
027. //+----------------+
028.         bool operator<(const double arg)
029.         {
030.             return (m_r < arg);
031.         }
032. //+----------------+
033.         bool operator>(const double arg)
034.         {
035.             return (m_r > arg);
036.         }
037. //+----------------+
038.         stComplex operator+(const stComplex &arg)
039.         {
040.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
041.         }
042. //+----------------+
043.         stComplex operator+=(const stComplex &arg)
044.         {
045.             return stComplex(m_r += arg.m_r, m_i += arg.m_i);
046.         }
047. //+----------------+
048.         bool operator<(const stComplex &arg)
049.         {
050.             return ((m_r < arg.m_r) ? true : (m_r > arg.m_r ? false : (m_i < arg.m_i)));
051.         }
052. //+----------------+
053.         bool operator>(const stComplex &arg)
054.         {
055.             return ((m_r > arg.m_r) ? true : (m_r < arg.m_r ? false : (m_i > arg.m_i)));
056.         }
057. //+----------------+
058.         bool operator&&(const stComplex &arg)
059.         {
060.             return m_r && arg.m_r && m_i && arg.m_i;
061.         }
062. //+----------------+
063.         stComplex operator&(const stComplex &arg)
064.         {
065.             union u1
066.             {
067.                 double p64;
068.                 ulong  u64;
069.             }v_r0, v_i0, v_r1, v_i1;
070. 
071.             v_r0.p64 = m_r;
072.             v_i0.p64 = m_i;
073.             v_r1.p64 = arg.m_r;
074.             v_i1.p64 = arg.m_i;
075. 
076.             v_r0.u64 &= v_r1.u64;
077.             v_i0.u64 &= v_i1.u64;
078. 
079.             return stComplex(v_r0.p64, v_i0.p64);
080.         }
081. //+----------------+
082.         stComplex Debug(uint arg)
083.         {
084.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
085.             return this;
086.         }
087. //+----------------+
088. };
089. //+------------------------------------------------------------------+
090. void OnStart(void)
091. {
092.     stComplex   a(15, 49),
093.                 b(7, 25);
094. 
095.     a.Debug(__LINE__);
096.     b.Debug(__LINE__);
097. 
098.     Print("The result of A & B is:");
099.     (a & b).Debug(__LINE__);
100. 
101.     Print("The result of A && B is: ", (a && b ? "TRUE" : "FALSE"));
102. }
103. //+------------------------------------------------------------------+

Código 07

Antes de explicar este código 07, ejecutémoslo. El programa produce el resultado de la imagen siguiente.

Imagen 14

Este resultado puede parecer poco intuitivo si no comprendes cómo interactúan la representación binaria y las operaciones bit a bit. Como ya expliqué y demostré en artículos anteriores de esta misma serie todo lo que utilizamos en el código 07, no repetiré esas explicaciones. En caso de duda, consulta los artículos anteriores para obtener más detalles. Sin embargo, ninguno de esos artículos explica cómo la representación binaria de los valores double determina el resultado registrado en la imagen 14.

Para comprender cómo la representación binaria determina el resultado, debes dominar lo que expliqué en otro artículo, al que quizá muchos no hayan dado la importancia que merece. Me refiero al artículo Del básico al intermedio: Punto flotante. Allí explico cómo interpretar un valor de punto flotante a partir de los bits que lo componen; ese conocimiento permite entender el resultado que imprime la línea 99 del código 07.

Como vengo destacando, mi querido lector, ningún artículo de esta serie es inútil ni tiene poca importancia. Planifiqué la publicación de cada artículo para un momento determinado, de modo que la complejidad aumente gradualmente. Sin embargo, quienes estudian la serie no percibirán ese aumento de forma abrupta, porque cada tema enlaza con los anteriores. Por tanto, estudia cada tema en el momento adecuado. No necesitas apresurarte. Comprende cada concepto; así entenderás las publicaciones futuras.

Dicho esto, podemos continuar. Podemos introducir un ajuste para mejorar un poco la presentación, ya que la imagen 14 dificulta la identificación de cada operación. Por tanto, modificaremos la instrucción de salida para etiquetar cada resultado con mayor claridad.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. struct stComplex
005. {
                   .
                   .
                   .
081. //+----------------+
082.         string ToString(void)
083.         {
084.             return StringFormat("%.02f %c %.02fi", m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
085.         }
086. //+----------------+
087.         stComplex Debug(uint arg)
088.         {
089.             Print("Debugging the line ", arg, " = ", this.ToString());
090.             return this;
091.         }
092. //+----------------+
093. };
094. //+------------------------------------------------------------------+
095. void OnStart(void)
096. {
097.     stComplex   a(15, 49),
098.                 b(7, 25);
099.     Print("Variable A: ", a.ToString());
100.     Print("Variable B: ", b.ToString());
101.     Print("The result of A & B is: ", (a & b).ToString());
102.     Print("The result of A && B is: ", (a && b ? "TRUE" : "FALSE"));
103. }
104. //+------------------------------------------------------------------+

Fragmento 07

Ahora, al ejecutar el código, obtendremos una salida más clara.

Imagen 15

Ahora sí, resulta mucho más sencillo comprenderlo. Pero volvamos a la operación bit a bit. ¿Por qué A & B produce este resultado? No tiene ningún sentido. Bien, quizá no lo tenga para ti, mi querido lector, si has llegado directamente a este artículo. Pero, para quienes han estado estudiando los artículos de esta serie, el resultado tiene pleno sentido. El compilador no aplica la operación bit a bit directamente a los valores de punto flotante, sino a sus representaciones enteras. ¿Qué? Espera un momento. Ahora soy yo, que he seguido los artículos, quien está confundido. ¿Cómo que los operandos de la operación son enteros? ¿La línea ocho del código 07 no declara que la estructura almacena valores double?

Sí, mi querido lector, la estructura almacena valores double, es decir, valores de punto flotante de doble precisión. Sin embargo, el compilador NO puede aplicar una operación bit a bit directamente sobre esos valores. Antes de ejecutar la operación, debemos suministrarle operandos enteros de 64 bits que representen el mismo contenido binario. Por eso obtienes el resultado registrado en las imágenes 14 y 15.

Todavía no lo entiendo. ¿Podrías explicarlo mejor? Sin problemas, mi querido lector. Para comprenderlo de manera definitiva, crearemos un pequeño código que permita reproducir la operación paso a paso.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     double  v1 = 15,
07.             v2 = 7,
08.             v3;
09. 
10.     v3 = v1 & v2;
11. 
12.     Print(v3);
13. }
14. //+------------------------------------------------------------------+

Código 08

Cuando intentes compilar este código, el compilador mostrará el siguiente error de compilación.

Imagen 16

El compilador muestra este error porque NO SABE cómo realizar una operación AND BIT A BIT cuando ambos operandos son de tipo double. Para resolverlo, debemos convertir cada operando double en un entero, ya que el compilador sí puede aplicar esa operación a operandos enteros. El código siguiente aplica esa conversión numérica explícita.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     double  v1 = 15,
07.             v2 = 7,
08.             v3;
09.     ulong   u1;
10. 
11.     u1 = ulong(v1) & ulong(v2);
12.     v3 = double(u1);
13. 
14.     Print(v3);
15. }
16. //+------------------------------------------------------------------+

Código 09

En efecto, en las líneas once y doce convertimos numéricamente los valores almacenados en v1 y v2 de double a entero, lo que permite al compilador generar el ejecutable. Sin embargo, quiero que observes una coincidencia concreta: los valores de v1 y v2 son exactamente los mismos que utilizamos como parte real en los códigos anteriores. Por tanto, deberíamos obtener un resultado igual al que genera la sobrecarga del operador AND bit a bit. Al menos, eso es lo que suponemos que debería ocurrir. Sin embargo, al ejecutar este código 09, el programa produce un resultado diferente.

Imagen 17

Pero ¿qué ha ocurrido aquí? ¿Por qué los resultados son diferentes? Expliqué el motivo en otros artículos, mi querido lector, en los que mostré cómo podemos manipular la memoria a nuestro favor. Uno de ellos es Del básico al intermedio: Unión (I). En esos artículos sobre uniones expliqué y demostré cómo organizar y manipular la memoria sin perder información. La conversión de un valor de punto flotante en un valor entero pierde información, ya que descarta la parte fraccionaria del valor de punto flotante. Precisamente, no expliqué este detalle cuando traté las uniones.

Bien, ahora empiezo a comprender el motivo de aquel resultado. Pero ¿existe algún método más sencillo para inspeccionar la representación binaria de los valores double? Repetir constantemente aquellas divisiones y multiplicaciones que mostraste al explicar cómo codificar un valor decimal en el formato de punto flotante resulta muy tedioso y agotador. Podemos inspeccionar directamente los bits que almacenan un valor double, sin convertirlo numéricamente en un entero. El único requisito es saber interpretar esa representación. Para ello, necesitas comprender el artículo en el que explico el modelo de punto flotante. Con ese conocimiento, podemos modificar el código 09 para leer los mismos bits como una representación entera y aplicarles la operación bit a bit.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     union _u
07.     {
08.         ulong   u64;
09.         double  pf64;
10.     }v1, v2, v3;
11. 
12.     v1.pf64 = 15;
13.     v2.pf64 = 7;
14.     v3.u64 = v1.u64 & v2.u64;
15. 
16.     Print(v3.pf64);
17. }
18. //+------------------------------------------------------------------+

Código 10

Bien, al ejecutar el código 09 modificado, el programa producirá el resultado de la nueva operación bit a bit.

Imagen 18

Perfecto, ahora sí el programa produce el mismo resultado que la sobrecarga anterior. Sin embargo, todavía quiero inspeccionar directamente el contenido almacenado en memoria. Había olvidado ese detalle. Discúlpame, mi querido lector. El código 11 imprime la representación interna de los valores utilizados en la operación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     union _u
07.     {
08.         ulong   u64;
09.         double  pf64;
10.     }v1, v2, v3;
11. 
12.     #define PrintContent(x) PrintFormat("Memory content in variable %s is: %I64X", #x, x.u64)
13. 
14.     v1.pf64 = 15;
15.     v2.pf64 = 7;
16.     v3.u64 = v1.u64 & v2.u64;
17. 
18.     PrintContent(v1);
19.     PrintContent(v2);
20.     PrintContent(v3);
21.     Print("The result of v1 & v2 is: ", v3.pf64);
22. }
23. //+------------------------------------------------------------------+

Código 11

Al ejecutar el código 11, el terminal mostrará la representación binaria almacenada en memoria para cada valor.

Imagen 19

Entonces, ¿ahora comprendes lo que está ocurriendo, mi querido lector? Una implementación incorrecta produciría un resultado completamente erróneo que, tarde o temprano, generaría una enorme cantidad de problemas y podría comprometer todo el código que desarrolláramos a partir de ese punto.

Sé que, para muchos, los procedimientos desarrollados en este artículo pueden parecer poco habituales. Sin embargo, como el objetivo es didáctico y no consiste en crear ningún tipo de aplicación, utiliza estos procedimientos únicamente con ese propósito. Por eso comienzo siempre los artículos con la misma frase, dejando claro que todo lo creado aquí tiene fines exclusivamente didácticos.

Según la tarea que necesites realizar, tendrás que adaptar el código y los conceptos a tus necesidades. Por tanto, no memorices el código. Comprende cómo funciona y qué conceptos utiliza. Ese aprendizaje sí te resultará útil en cualquier situación.


Consideraciones finales

En este artículo vimos cómo implementar la sobrecarga tanto de operadores lógicos como de operadores relacionales. Implementar estas sobrecargas exige cierto cuidado y una buena dosis de atención, ya que un simple descuido durante su implementación puede obligar a desechar todo el código. Si la sobrecarga presenta algún problema, toda una base de datos creada a partir de los resultados generados por el código deberá descartarse por completo o, como mínimo, revisarse íntegramente. Además, el problema también afectará a todas las relaciones que dependan de esos resultados.

Por eso, debes estudiar este artículo con mucha atención y comprender cómo conecta numerosos conceptos que muchos consideran innecesarios. Sin embargo, esos conceptos permiten confirmar que las pruebas produzcan resultados realmente correctos. Espero que te haya gustado este artículo. En el próximo artículo abordaremos la sobrecarga de otros operadores. Así que hasta pronto, nos vemos allí.

Archivo MQ5Descripción
Código 01 Demostración básica
Código 02 Demostración básica
Código 03 Demostración básica
Código 04 Demostración básica
Código 05  Demostración básica

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

Archivos adjuntos |
Anexo.zip (3.56 KB)
Simulación de mercado: La unión hace la fuerza (II) Simulación de mercado: La unión hace la fuerza (II)
Hasta ahora, la aplicación desarrollada a lo largo de esta serie de artículos se centraba exclusivamente en simular la parte gráfica. Sin embargo, para disponer de un sistema más completo, en el que podamos probar un Asesor Experto dentro del servicio de repetición/simulador, también debemos simular el servidor de trading. Observarás que esta simulación incluirá únicamente los elementos imprescindibles. No obstante, tú, estimado lector, podrás completar las partes que falten. Como esos componentes adicionales no influyen en lo que pretendo mostrar, ya contamos con más que suficiente para desarrollar lo que hemos planificado.
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 24): Herramienta de cuantificación de la acción del precio Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 24): Herramienta de cuantificación de la acción del precio
Los patrones de velas japonesas ofrecen información valiosa sobre los posibles movimientos del mercado. Algunas velas individuales señalan la continuación de la tendencia actual, mientras que otras presagian cambios de tendencia, dependiendo de su posición dentro de la acción del precio. Este artículo presenta un Asesor Experto (EA) que identifica automáticamente cuatro formaciones clave de velas japonesas. Explore las siguientes secciones para descubrir cómo esta herramienta puede mejorar su análisis de la evolución de los precios.
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 8): Análisis de múltiples estrategias (2) Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 8): Análisis de múltiples estrategias (2)
Únase a nosotros en nuestra próxima charla, donde combinaremos nuestras dos primeras estrategias de trading en una estrategia de trading conjunta. Demostraremos los diferentes esquemas posibles para combinar múltiples estrategias y también cómo ejercer control sobre el espacio de parámetros, para garantizar que la optimización efectiva siga siendo posible incluso a medida que aumenta el tamaño de nuestros parámetros.
Simulación de mercado: La unión hace la fuerza (I) Simulación de mercado: La unión hace la fuerza (I)
Estamos llegando a la recta final. El desarrollo del sistema de repetición/simulador está casi terminado. Es cierto que aún tendremos que completar algunas cosas, pero, en comparación con todo lo que ya hemos hecho, implementar lo que falta será sencillo. Sin embargo, todo lo que se mostrará en este artículo deberá asimilarse y comprenderse adecuadamente. Quiero que tú, estimado lector y entusiasta...