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

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

MetaTrader 5Ejemplos |
22 0
CODE X
CODE X

Introducción

En el artículo anterior, Del básico al intermedio: Colas, listas y árboles (VIII), presenté mi propuesta para uno de los muchos algoritmos de balanceo de árboles que pueden implementarse. Como se explicó allí, cada algoritmo de balanceo tiene como objetivo hacer que el árbol sea más eficiente en lo que respecta a las búsquedas. Algunos permiten adaptar mejor el árbol a determinados tipos de búsqueda, mientras que otros lo hacen más adecuado para búsquedas de otra naturaleza.

Por ahora, no entraremos en detalles sobre las búsquedas en árboles. Antes de mostrar ese tipo de implementación, quiero presentar y explicar otro tema. Es bastante interesante, aunque suele resultar muy confuso para muchos programadores principiantes, sobre todo cuando estudian otros lenguajes que no incluyen este recurso entre sus funcionalidades.

El tema que comenzaremos a tratar tiene como objetivo simplificar de varias formas cómo desarrollaremos el código más adelante. A medida que el código se vuelve más complejo, en muchos casos también pierde legibilidad, lo que dificulta comprender el objetivo que se pretende alcanzar.

Sin embargo, a diferencia de lo que muchos piensan, lo que veremos aquí hará que programar sea mucho más divertido y agradable. Aun así, debes tomar ciertas precauciones al implementarlo. El enfoque que seguiré pretende ser el más sencillo siempre que sea posible. Gran parte de lo que explicaré procede de mi experiencia con la programación en C/C++. No obstante, intentaré explicarlo de la forma más fácil posible.

Dicho esto, pasemos al tema principal del artículo.


Sobrecarga de operadores (I)

Muchas veces terminamos programando algo que, al principio, no parece tener demasiado sentido. Sin embargo, a medida que desarrollamos el código y creamos algunas funciones y procedimientos, podemos encontrarnos con situaciones que, a primera vista, resultan algo complicadas de resolver. El verdadero problema no es la dificultad de desarrollar el código en sí, sino lograr que sea lo más fácil posible de comprender, no solo para quien lo está desarrollando, sino también para quien pretenda estudiarlo con el fin de mejorarlo o, quizá, reutilizar parte del código desarrollado.

El principal problema de la mayoría de las implementaciones que debemos realizar radica en la gran cantidad de funciones y procedimientos implicados. Y el verdadero problema no es exactamente que utilicemos funciones o procedimientos, sino que, en muchos casos, pueden sustituirse por otro tipo de código. Sin embargo, sin los conocimientos adecuados, resulta muy difícil comprender cómo realizar correctamente esta sustitución. Esto acaba obligándonos a utilizar una implementación que, con el tiempo, transforma algo que al principio parecía sencillo en un conjunto extremadamente confuso y disperso, lo que dificulta enormemente comprender el código correctamente.

Si has seguido mis artículos, probablemente habrás notado que evito utilizar determinados tipos de recursos para no tener que explicar constantemente cómo funcionan. Sin embargo, como esta serie de artículos pretende transmitir otro tipo de conocimientos, no veo ningún motivo para limitarnos a utilizar recursos sencillos. Podemos profundizar un poco en algunas funcionalidades más avanzadas. De este modo, en el futuro podré desarrollar el código de una forma que, a mi parecer, permita analizar con mayor facilidad lo que se esté implementando, sin tener que explicar cada detalle, ya que los abordaremos precisamente en estos artículos.

Bien, aunque sé que muchos de ustedes quizá solo estén interesados en crear algún tipo de indicador o Asesor Experto y consideren que aprender lo que explicaré aquí no tiene demasiado sentido, también sé que otros podrán aprovechar muy bien lo que comenzaremos a explorar. En realidad, a mi parecer, un buen programador tendrá en su caja de herramientas recursos que casi nunca utilizará. Sin embargo, los conocimientos adquiridos para comprender por qué las cosas se hacen de una forma y no de otra pueden marcar una gran diferencia en situaciones más complejas. Por tanto, comprender correctamente cómo funciona la sobrecarga de operadores puede ayudarte a que el código que desarrolles sea mucho más fácil de entender. Y cuanto más fácil sea comprender el código, más rápido será corregirlo y más sencillo y eficiente resultará incorporar mejoras al sistema que estés diseñando.

Para comenzar correctamente, debemos comprender lo siguiente: muchas veces, la sobrecarga de operadores ya está incorporada en el propio lenguaje de programación. Y tú la utilizas con frecuencia sin comprender realmente que la estás utilizando. ¿No lo crees? Bien, veamos entonces un ejemplo muy sencillo en el que se utiliza la sobrecarga de operadores. El código siguiente implementa este ejemplo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     string sz1;
07. 
08.     sz1 = "Hello world.";
09.     sz1 = sz1 + "How has your day been ?";
10. 
11.     Print(sz1);
12. }
13. //+------------------------------------------------------------------+

Código 01

A estas alturas, creo que todos saben ya qué se imprimirá en el terminal de MetaTrader 5 al ejecutar este Code 01. Por tanto, no es necesario mostrar el resultado. Sin embargo, esto no cambia el hecho de que, en el código 01, aunque quizá no lo notes, estás utilizando la sobrecarga de operadores. Esto ocurre en la línea nueve, cuando aplicamos el operador de suma a dos operandos de tipo string. Pero espera un momento. Eso no es una sobrecarga de operador, porque le estoy indicando al compilador que concatene ambos valores string para formar una cadena más larga. Por tanto, no lo veo como una sobrecarga de operador. Aunque, en realidad, todavía no sé exactamente qué es. Solo me baso en lo explicado cuando estudié el artículo sobre la sobrecarga de funciones y procedimientos.

Pues bien, mi querido lector, por extraño que pueda parecer, en MQL5, al igual que en otros lenguajes, algunos operadores están sobrecargados por el propio lenguaje. Todo esto se implementa de forma que puedas realizar determinadas operaciones sin dificultad, haciendo que el código resulte más legible y fácil de comprender. Al estar integrado de forma nativa en el lenguaje, este mecanismo permite que la expresión de la línea nueve del código 01 reemplace una llamada explícita a una función o procedimiento de concatenación de valores string. Sin embargo, en principio, el operador de suma no está destinado a concatenar cadenas. Su objetivo es sumar dos valores y generar un tercero. No obstante, gracias a la sobrecarga de operadores implementada de forma nativa por el lenguaje, el compilador sabe cómo utilizar el operador de suma para concatenar los valores string.

El código siguiente implementa una versión equivalente del código 01 sin utilizar la sobrecarga de operadores, como se puede ver a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     string sz1;
07. 
08.     sz1 = "Hello world.";
09.     StringConcatenate(sz1, sz1, "How has your day been ?");
10.     Print(sz1);
11. }
12. //+------------------------------------------------------------------+

Código 02

Observa que el código 02 genera el mismo resultado que el código 01. La única diferencia entre ambos se encuentra precisamente en la línea nueve. Ahora observa los dos y responde con total sinceridad: ¿en cuál resulta más fácil comprender lo que se está haciendo? Creo que la mayoría responderá que en el código 01. Y eso es precisamente lo que nos permite hacer la sobrecarga de operadores: expresar mediante un operador una operación que, de otro modo, requeriría una llamada explícita a una función o procedimiento, lo que hace que el código sea más legible.

Ahora presta atención a lo siguiente. En principio, no existe una regla estricta para implementar la sobrecarga de operadores. Solo existe lo que podríamos llamar un pacto de caballeros para evitar situaciones absurdas. Por ejemplo, al ver el operador de suma, no esperas que se realice una división ni una resta de valores. Sin embargo, nada te impide implementar algo así. El problema es que el código se volvería extremadamente difícil de comprender, incluso para ti, mi querido lector, que lo estás desarrollando. Al final, algo que podría resultar interesante terminaría por quedar completamente olvidado. Por tanto, nunca está de más actuar con prudencia cuando se trata de sobrecarga de operadores.

Bien, cuando el lenguaje lo permite, podemos implementar la sobrecarga de operadores. Cada lenguaje admite una cantidad mayor o menor de operadores que pueden sobrecargarse. En el caso de MQL5, los operadores que pueden sobrecargarse se muestran en la imagen siguiente.

Imagen 01

Es muy importante saber qué operadores permite sobrecargar el lenguaje. El motivo es que, dependiendo de cómo esté implementado el código, quizá tengas que buscar en él qué operación realiza realmente un operador con los datos.

Muy bien, puedes observar que los operadores están divididos en pequeños grupos. Esto facilita mucho la implementación de la sobrecarga, ya que la estructura de la sobrecarga es muy similar dentro de cada grupo de operadores. Básicamente, la sobrecarga se declara mediante la sencilla sintaxis que se muestra a continuación.

tipo_de_retorno operator operador (lista de parámetros)

Presta atención a la información anterior, mi querido lector, porque esta es precisamente la sintaxis que deberemos utilizar SIEMPRE que necesitemos o deseemos sobrecargar un operador. Veamos ahora qué significa cada elemento. TIPO_DE_RETORNO especifica el tipo del valor que devolverá la función de operador. Si la función no devuelve ningún valor, utilizaremos el tipo void, aunque esto lo veremos más adelante en la práctica. OPERATOR es una palabra reservada del lenguaje que indica al compilador que se está declarando una función de operador. OPERADOR representa el operador que se sobrecargará.

Para saber qué operadores pueden sobrecargarse, consulta la imagen 01. La lista de parámetros funciona igual que en los casos tratados desde los primeros artículos. La única diferencia es que, según el operador, aquí pueden aparecer uno o más parámetros. En este punto, comprender la división por grupos nos ayuda a interpretar la propia lista de parámetros. Los operadores del grupo binario necesitan un único parámetro, mientras que los del grupo unario NO necesitan ninguno. Los demás operadores, sin embargo, pueden utilizar un número diferente de parámetros. Pero ya llegaremos a eso. Una cosa a la vez.

Perfecto, pero ¿cómo se aplica esto en la práctica? Bien, mi querido lector, esta es la parte divertida. En términos generales, las sobrecargas de operadores que podemos crear se utilizarán principalmente para trabajar con tipos definidos por el usuario. Por tanto, en este punto es necesario que conozcas y comprendas bien el concepto de estructuras.

Si acabas de llegar, ya hemos hablado sobre las estructuras. Puedes comenzar por el artículo Del básico al intermedio: Struct (I). Allí empezamos a explicar qué es una estructura y cómo podemos utilizarla. Sin embargo, daré por hecho que ya sabes qué es una estructura de datos. Por tanto, podemos centrarnos en la sobrecarga de operadores.

Una buena forma de empezar a comprender la sobrecarga de operadores consiste, en primer lugar, en entender el propio concepto de sobrecarga. Este concepto se presentó en el artículo Del básico al intermedio: Sobrecarga. Sin embargo, allí solo hablamos sobre la sobrecarga de funciones y procedimientos. Comprender aquello te ayudará a dar el siguiente paso. Comencemos, por tanto, con lo que podríamos considerar el modelo clásico para presentar la sobrecarga de operadores. El código siguiente implementa este modelo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06.     double m_r, m_i;
07. //+----------------+
08.     stComplex(): m_r(0), m_i(0) {}
09. //+----------------+
10.     stComplex(double r, double i): m_r(r), m_i(i) {}
11. //+----------------+
12.     stComplex add(const stComplex &arg1, const stComplex &arg2)
13.     {
14.         return stComplex(arg1.m_r + arg2.m_r, arg1.m_i + arg2.m_i);
15.     }
16. //+----------------+
17. };
18. //+------------------------------------------------------------------+
19. void OnStart(void)
20. {
21.     stComplex   a(2, 5),
22.                 b(8, -3),
23.                 c;
24. 
25.     c = c.add(a, b);
26. 
27.     PrintFormat("C = %.02f %c %.02fi", c.m_r, (c.m_i < 0 ? '-' : '+'), MathAbs(c.m_i));
28. }
29. //+------------------------------------------------------------------+

Código 03

Vaya, este código me parece muy confuso. ¿Estás seguro de que es tan sencillo como dijiste? Por supuesto que este código es sencillo, mi querido lector. Si te parece confuso, significa que no has estudiado ni leído los artículos anteriores. En ese caso, te recomiendo volver a ellos y estudiarlos antes de intentar comprender lo que haremos aquí. Sin entender los fundamentos, nada de esto tendrá el menor sentido.

Bien, al ejecutar este código 03, obtendremos el resultado que se muestra en la imagen siguiente.

Imagen 02

Como puedes ver, no resulta nada confuso comprender lo que ocurre aquí. Sin embargo, todavía no estamos utilizando la sobrecarga de operadores. Solo quiero mostrar cómo funciona todo paso a paso. Ahora presta atención a lo siguiente, mi querido lector. En la línea 25 indicamos que se sumen dos valores y que el resultado se almacene en otra variable. Tal como está implementado, puedes ver claramente que añadir un tercer valor al cálculo resultaría algo engorroso. Para hacerlo, tendríamos que añadir más llamadas a la función add, definida en la línea 12. Al final, el código se volvería bastante confuso debido al número creciente de llamadas. Sin embargo, si realizamos un pequeño cambio, la situación cambia por completo, ya que podríamos utilizar el operador de suma para sumar los dos valores complejos, tal como lo hace la función add definida en la línea 12.

Pero espera un momento. ¿Me estás diciendo que podemos utilizar el operador de suma en este código 03? Sí, mi querido lector, eso es exactamente lo que estoy diciendo. Sin embargo, quiero que comprendas muy bien lo que estoy diciendo. Por tanto, modificaremos el código para reemplazar la llamada a la función add de la línea 25 por el operador de suma. El código siguiente implementa este cambio.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06.     double m_r, m_i;
07. //+----------------+
08.     stComplex(): m_r(0), m_i(0) {}
09. //+----------------+
10.     stComplex(double r, double i): m_r(r), m_i(i) {}
11. //+----------------+
12.     stComplex add(const stComplex &arg1, const stComplex &arg2)
13.     {
14.         return stComplex(arg1.m_r + arg2.m_r, arg1.m_i + arg2.m_i);
15.     }
16. //+----------------+
17. };
18. //+------------------------------------------------------------------+
19. void OnStart(void)
20. {
21.     stComplex   a(2, 5),
22.                 b(8, -3),
23.                 c;
24. 
25.     c = a + b;
26. 
27.     PrintFormat("C = %.02f %c %.02fi", c.m_r, (c.m_i < 0 ? '-' : '+'), MathAbs(c.m_i));
28. }
29. //+------------------------------------------------------------------+

Código 04

Alto ahí. ¿Funciona este código 04? No, mi querido lector. Si intentas compilarlo, recibirás los siguientes errores del compilador.

Imagen 03

Esto se debe a que el compilador NO SABE cómo sumar dos números complejos. O, mejor dicho, no sabe cómo trabajar con un tipo de dato creado por un usuario o programador. Sin embargo, aun así podemos adoptar otro enfoque, recordando que cada caso es distinto. En este caso, podríamos utilizar el código siguiente.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06.     double m_r, m_i;
07. //+----------------+
08.     stComplex(): m_r(0), m_i(0) {}
09. //+----------------+
10.     stComplex(double r, double i): m_r(r), m_i(i) {}
11. //+----------------+
12.     stComplex add(const stComplex &arg1, const stComplex &arg2)
13.     {
14.         return stComplex(arg1.m_r + arg2.m_r, arg1.m_i + arg2.m_i);
15.     }
16. //+----------------+
17. };
18. //+------------------------------------------------------------------+
19. void OnStart(void)
20. {
21.     stComplex   a(2, 5),
22.                 b(8, -3),
23.                 c;
24. 
25.     c = stComplex(a.m_r + b.m_r, a.m_i + b.m_i);
26. 
27.     PrintFormat("C = %.02f %c %.02fi", c.m_r, (c.m_i < 0 ? '-' : '+'), MathAbs(c.m_i));
28. }
29. //+------------------------------------------------------------------+

Código 05

Sé que la línea 25 de este código 05 puede parecerte bastante extraña, pero realmente funciona. Esto se debe a que utilizamos el constructor de la línea 10 para crear un valor de tipo complejo. Al hacerlo, el compilador puede trabajar con un tipo de dato que, en principio, no sabía cómo manejar. Este ejemplo permite examinar otra operación que muchos consideran relativamente sencilla, pero que, según el caso, puede resultar bastante compleja: utilizar el operador de asignación para asignar un valor a una variable. Normalmente, solo podemos asignar valores de un tipo a una variable del mismo tipo. Sin embargo, hay situaciones en las que debemos asignar un valor de un tipo a una variable de otro completamente distinto. Cuando sea necesario, tendremos que sobrecargar el operador de asignación; de lo contrario, el código no funcionará.

Pero volvamos a nuestra cuestión inicial: implementar nuestra primera sobrecarga de operador. Como ya hemos visto, podemos realizar la suma de dos valores complejos de distintas maneras. Sin embargo, el código 04 todavía no permite realizarla. Ahora te pregunto: ¿cómo podríamos realizar esa suma? Bien, la respuesta está al comienzo del artículo. Pero ¿cómo es eso? No lo entiendo. Muy bien, ¿recuerdas que mencioné que existe una sintaxis específica para sobrecargar un operador?

Pues bien, si ya tenemos una función o procedimiento que realiza la operación que necesitamos, podemos reutilizar su implementación para sobrecargar un operador. No basta con cambiarle el nombre: debemos modificar su declaración para que siga la sintaxis tipo_de_retorno operator operador (lista de parámetros). Eso es precisamente lo que ocurre con la función definida en la línea 12 del código 03, que suma dos valores complejos y devuelve el resultado. Al sustituir su declaración por una declaración con operator +, el compilador la reconoce como una sobrecarga del operador de suma y el código 04 funciona. Quizá estés pensando: «Pero esto parece bastante complicado. ¿Cómo podría indicarle al compilador que lo haga?». Pues bien, esa es la parte sencilla. Compara la implementación del código 04 con la del código siguiente.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06.     double m_r, m_i;
07. //+----------------+
08.     stComplex(): m_r(0), m_i(0) {}
09. //+----------------+
10.     stComplex(double r, double i): m_r(r), m_i(i) {}
11. //+----------------+
12.     stComplex operator+(const stComplex &arg1)
13.     {
14.         return stComplex(m_r + arg1.m_r, m_i + arg1.m_i);
15.     }
16. //+----------------+
17. };
18. //+------------------------------------------------------------------+
19. void OnStart(void)
20. {
21.     stComplex   a(2, 5),
22.                 b(8, -3),
23.                 c;
24. 
25.     c = a + b;
26. 
27.     PrintFormat("C = %.02f %c %.02fi", c.m_r, (c.m_i < 0 ? '-' : '+'), MathAbs(c.m_i));
28. }
29. //+------------------------------------------------------------------+

Código 06

No lo entiendo. El código 06 es, en efecto, muy parecido al código 04. Sin embargo, en la línea 12 se utiliza un número distinto de argumentos. ¿Por qué? Bien, el problema es que la línea 25 contiene un operador de suma. Sin embargo, cuando utilizamos la sobrecarga de operadores, debes interpretar esta línea 25 de otra manera. Y es aquí donde muchos principiantes se sienten completamente perdidos al encontrarse con un código que utiliza la sobrecarga de operadores. Para un principiante, la expresión con el operador de suma no equivale a una llamada a una función sobrecargada, sino que parece una suma ordinaria. Sin embargo, y esto es lo que debes comprender, mi estimado lector, el compilador no interpretará la línea 25 tal como está escrita en el código 06. En realidad, la interpretará de forma equivalente al código siguiente.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06.     double m_r, m_i;
07. //+----------------+
08.     stComplex(): m_r(0), m_i(0) {}
09. //+----------------+
10.     stComplex(double r, double i): m_r(r), m_i(i) {}
11. //+----------------+
12.     stComplex operator+(const stComplex &arg1)
13.     {
14.         return stComplex(m_r + arg1.m_r, m_i + arg1.m_i);
15.     }
16. //+----------------+
17. };
18. //+------------------------------------------------------------------+
19. void OnStart(void)
20. {
21.     stComplex   a(2, 5),
22.                 b(8, -3),
23.                 c;
24. 
25.     c = a.operator+(b);
26. 
27.     PrintFormat("C = %.02f %c %.02fi", c.m_r, (c.m_i < 0 ? '-' : '+'), MathAbs(c.m_i));
28. }
29. //+------------------------------------------------------------------+

Código 07

El código 06 y el 07 representan lo mismo. Sin embargo, es mucho más habitual escribirlo como en el código 06. Aun así, como programador, debes interpretar y comprender el código del mismo modo que lo hará el compilador. Para ello, debes considerar la forma equivalente del código 07, donde la línea 25 representa una llamada a la función de la línea 12. Sé que, al principio, esto parece mucho más confuso de lo que realmente es. Sin embargo, esta equivalencia permite comprender por qué en la línea 12 solo necesitamos pasar un argumento a la función. De otro modo, sería muy difícil entenderlo mediante una explicación distinta.

Hum, perfecto. Ahora sí me ha gustado aprender algo que no conocía. Esto de la sobrecarga de operadores parece realmente interesante. Pero dime, ¿solo podemos sumar dos números complejos de esta forma? ¿No podríamos, por ejemplo, sumar un número complejo y un entero?

Bien, esto es algo un poco más complicado de comprender. Sin embargo, la respuesta es SÍ. Podemos sumar un valor de tipo stComplex y un entero. No obstante, debemos distinguir dos expresiones diferentes: stComplex + entero, donde el objeto stComplex es el operando izquierdo, y entero + stComplex, donde el entero ocupa esa posición. Pero, para simplificar la explicación, ya que este concepto puede resultar difícil de entender de inmediato, modificaremos un aspecto de esta estructura stComplex. Para empezar, puedes observar que estamos permitiendo acceder a las variables internas desde fuera de la estructura, lo que, en cierto modo, no es una buena idea. Por tanto, cambiaremos el código para ocultar esas variables dentro de la estructura. Una vez hecho esto, podremos implementar primero la operación stComplex + entero.

La primera parte de la modificación se muestra a continuación.

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 &arg1)
17.         {
18.             return stComplex(m_r + arg1.m_r, m_i + arg1.m_i);
19.         }
20. //+----------------+
21.         void Debug(void)
22.         {
23.             PrintFormat("Internal Value = %.02f %c %.02fi", m_r, (m_i < 0 ? '-' : '+'), MathAbs(c.m_i));
24.         }
25. //+----------------+
26. };
27. //+------------------------------------------------------------------+
28. void OnStart(void)
29. {
30.     stComplex   a(2, 5),
31.                 b(8, -3),
32.                 c;
33. 
34.     c = a + b;
35.     c.Debug();
36. }
37. //+------------------------------------------------------------------+

Código 08

Bien, comprobemos ahora si es posible evaluar la expresión stComplex + entero; es decir, una suma cuyo operando izquierdo es de tipo stComplex y cuyo operando derecho es un entero. Para ello, primero debes saber cómo implementar la sobrecarga de funciones y procedimientos. Al comienzo del artículo mencioné que este conocimiento sería necesario y señalé qué artículo contiene esa información. Una vez que sepas cómo realizar esa sobrecarga, podremos definir la operación para esos tipos y ese orden de operandos. Para ello, solo necesitamos definir el procedimiento siguiente.

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 &arg1)
17.         {
18.             return stComplex(m_r + arg1.m_r, m_i + arg1.m_i);
19.         }
20. //+----------------+
21.         stComplex add(const double arg2)
22.         {
23.             return stComplex(m_r + arg2, m_i);
24.         }
25. //+----------------+
26.         void Debug(void)
27.         {
28.             PrintFormat("Internal Value = %.02f %c %.02fi", m_r, (m_i < 0 ? '-' : '+'), MathAbs(c.m_i));
29.         }
30. //+----------------+
31. };
32. //+------------------------------------------------------------------+
33. void OnStart(void)
34. {
35.     stComplex   a(2, 5),
36.                 b(8, -3),
37.                 c;
38. 
39.     c = a + b;
40.     c.Debug();
41. 
42.     c = c.add(4);
43.     c.Debug();
44. }
45. //+------------------------------------------------------------------+

Código 09

Al ejecutar este código 09, obtendrás el resultado que se muestra en la imagen siguiente.

Imagen 04

Esto demuestra claramente que el código 09 admite la expresión stComplex + entero: el operando izquierdo es de tipo stComplex y el derecho es un valor entero. Sin embargo, aquí surge una cuestión, mi querido lector, y esta es precisamente la parte complicada. Por eso todavía no hemos implementado directamente la sobrecarga de operadores en este código 09. Primero necesito que comprendas algo: invertir los operandos produce una expresión distinta. Una implementación válida para stComplex + entero no admite necesariamente entero + stComplex, y esta diferencia determinará si el código puede compilarse.

Observa lo siguiente: cuando el compilador interprete el código para generar el ejecutable, tendrá que resolver la sobrecarga a partir del tipo y la posición de cada operando. Por tanto, stComplex + entero y entero + stComplex no son intercambiables: en la primera expresión, stComplex es el operando izquierdo; en la segunda, lo es el entero. Si no existe una sobrecarga compatible con el orden utilizado, el compilador NO GENERARÁ un ejecutable porque no podrá resolver la operación. Por eso, entender la sobrecarga de funciones y procedimientos es importante para poder utilizar la sobrecarga de operadores de una forma más amplia. Quizá pienses que una cosa no tiene nada que ver con la otra, pero todo está relacionado. Por tanto, aquellos conceptos que al principio parecían tener poco sentido ahora deben comprenderse correctamente, porque empiezan a encajar por completo.

Ahora presta atención: cuando pasamos del código 03 al código 06, eliminamos un argumento de la llamada. Para admitir también el orden inverso, debemos ir un poco más allá y pensar de otra manera. Existen dos casos distintos: el código 09 implementa stComplex + entero, con stComplex como operando izquierdo; el código siguiente implementa entero + stComplex, con el entero como operando izquierdo.

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 &arg1)
17.         {
18.             return stComplex(m_r + arg1.m_r, m_i + arg1.m_i);
19.         }
20. //+----------------+
21.         stComplex add(const double arg2)
22.         {
23.             return stComplex(m_r + arg2, m_i);
24.         }
25. //+----------------+
26.         void Debug(void)
27.         {
28.             PrintFormat("Internal Value = %.02f %c %.02fi", m_r, (m_i < 0 ? '-' : '+'), MathAbs(c.m_i));
29.         }
30. //+----------------+
31. };
32. //+------------------------------------------------------------------+
33. void OnStart(void)
34. {
35.     stComplex   a(2, 5),
36.                 b(8, -3),
37.                 c;
38. 
39.     c = a + b;
40.     c.Debug();
41. 
42.     c = b.add(4);
43.     c.Debug();
44. }
45. //+------------------------------------------------------------------+

Código 10

En efecto, al ejecutar este código 10, obtendremos el resultado que se muestra en la imagen siguiente.

Imagen 05

Observa que el código 09 evalúa stComplex + entero, mientras que el código 10 evalúa entero + stComplex. Aunque ambos casos combinan los mismos tipos, el orden de los operandos es diferente: en el primero, stComplex aparece a la izquierda del operador +; en el segundo, aparece a la derecha. Por este motivo, si utilizamos la sobrecarga de operadores, debemos contemplar ambas firmas por separado. Precisamente por eso comenté que este tema resulta algo más complicado y confuso, aunque nada que no pueda explicarse e implementarse aquí.

Bien, tenemos entonces dos órdenes de operandos que debemos admitir: stComplex + entero y entero + stComplex. ¿Cómo podemos admitir ambos de la forma más sencilla posible? La solución más simple se implementa en el código 11, incluido íntegramente a continuación.

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 &arg1)
17.         {
18.             return stComplex(m_r + arg1.m_r, m_i + arg1.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg2)
22.         {
23.             return stComplex(m_r + arg2, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg2)
27.         {
28.             return stComplex(m_r += arg2, m_i);
29.         }
30. //+----------------+
31.         void Debug(void)
32.         {
33.             PrintFormat("Internal Value = %.02f %c %.02fi", m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
34.         }
35. //+----------------+
36. };
37. //+------------------------------------------------------------------+
38. void OnStart(void)
39. {
40.     stComplex   a(2, 5),
41.                 b(8, -3),
42.                 c;
43. 
44.     c = a + b;
45.     c.Debug();
46. 
47.     (c += 4).Debug();
48.     c = b + 4;
49.     c.Debug();
50. }
51. //+------------------------------------------------------------------+

Código 11

En este caso, he modificado el código para introducir otro aspecto. Sin embargo, el tema principal sigue siendo la sobrecarga de operadores. Al ejecutar este código 11, obtendrás el resultado que se muestra en la imagen siguiente.

Imagen 06

Con esta implementación, hemos cubierto de una forma bastante sencilla y entretenida los dos órdenes de operandos: stComplex + entero, utilizado en el código 09, y entero + stComplex, utilizado en el código 10. Sin embargo, lo que hemos visto aquí es solo la base más elemental de lo que necesitamos comprender para abordar los próximos artículos.


Consideraciones finales

En este artículo vimos cómo podemos concebir y abordar la sobrecarga de operadores. Aunque aquí solo hemos abordado la parte más sencilla de todo un procedimiento que seguiremos explorando con mayor profundidad a medida que avancemos en los próximos artículos. Con el tiempo y la práctica, comprobarás que este recurso, muy poco utilizado en MQL5, puede abrirte muchas posibilidades, mi querido lector. Esto se debe a que, cuando se emplea correctamente, la sobrecarga de operadores permite crear un código mucho más legible y fácil de comprender. Precisamente por ello, el código se vuelve más seguro, estable y sencillo de mejorar. Además, por supuesto, nos permite controlar mejor lo que realmente estamos haciendo.

Sin embargo, quiero recordarte algo, mi querido lector. Si por casualidad has llegado directamente a este artículo y estás empezando a aprender programación en MQL5, te recomiendo leer y estudiar el contenido de los artículos anteriores. Cada uno contiene una pequeña parte del conjunto de conocimientos que necesitas para seguir lo que mostraremos en los próximos artículos. En cualquier caso, no olvides estudiar y practicar lo explicado aquí utilizando los archivos incluidos en el anexo. En el próximo artículo seguiremos profundizando en la sobrecarga de operadores, porque la diversión no ha hecho más que empezar.

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
Código 06  Demostración básica
Código 07  Demostración básica
Código 08  Demostración básica

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

Archivos adjuntos |
Anexo.zip (3.59 KB)
Simulación de mercado: Position View (XX) Simulación de mercado: Position View (XX)
En este artículo veremos cómo modificar el código del indicador de posición para crear una especie de sombra que nos permita visualizar dónde se encuentra actualmente el precio que continúa vigente en el servidor de trading. Este mecanismo pretende facilitar la planificación de operaciones en las que desplazamos las líneas de stop loss o take profit. Añadir esta función, es decir, las sombras de precio, puede parecer extremadamente complejo. En este artículo mostraré que puedes implementarla de una forma muy sencilla y práctica.
De novato a experto: Trayectorias predictivas del precio De novato a experto: Trayectorias predictivas del precio
Los niveles de Fibonacci proporcionan un marco práctico que los mercados suelen respetar, destacando las zonas de precios donde es más probable que se produzcan reacciones. En este artículo, desarrollamos un asesor experto que aplica la lógica de retroceso de Fibonacci para anticipar posibles movimientos futuros y operar con retrocesos mediante órdenes pendientes. En este artículo recorremos todo el flujo de trabajo, desde la detección de oscilaciones hasta la representación gráfica de niveles, los controles de riesgo y la ejecución.
Del básico al intermedio: Sobrecarga de operadores (II) Del básico al intermedio: Sobrecarga de operadores (II)
Este artículo puede parecer bastante confuso al principio por el contenido que presentaré. Sin embargo, he intentado presentar todo de la forma más sencilla y didáctica posible. Espero que comprendas lo que voy a mostrar aquí y que te resulte útil en algún momento.
Introducción a MQL5 (Parte 27): Cómo dominar las API y la función WebRequest() en MQL5 Introducción a MQL5 (Parte 27): Cómo dominar las API y la función WebRequest() en MQL5
Este artículo explica cómo utilizar la función WebRequest() y las API en MQL5 para comunicarse con plataformas externas. Aprenderá a crear un bot de Telegram, obtener identificadores de chat y de grupo, y enviar, editar y eliminar mensajes directamente desde MT5, sentando así una base sólida para dominar la integración de API en tus futuros proyectos MQL5.