
Del básico al intermedio: Definiciones (I)
Introducción
El contenido expuesto aquí tiene un propósito puramente didáctico. En ningún caso debe considerarse una aplicación final, cuyo objetivo no sea el estudio de los conceptos mostrados aquí.
En el artículo anterior, «Del básico al intermedio: recursividad», se explicó qué es y cómo podríamos utilizar una técnica de programación muy útil en diversos tipos de escenarios: la recursividad. Esta nos permite crear mecanismos e implementaciones de manera fácil y simple, aunque debemos tener en cuenta la posibilidad de que el código se ejecute más lentamente en algunos momentos, por lo que debemos armaros de paciencia en esos casos.
Pero, en general, la recursividad ayuda y facilita bastante nuestra vida, por lo que debería ser un objeto de estudio para quienes desean capacitarse o, al menos, tener un buen nivel de comprensión sobre cosas relacionadas con la programación en general.
Ahora bien, en este artículo vamos a tratar un tema que ya se ha tratado antes: el uso de definiciones, tanto para la construcción de macros como para declaraciones y el control más fino de ciertas partes que pueden ser implementadas por ti u otro programador, cuyo código te haya interesado y quieras modificarlo de manera controlada, para alcanzar y cubrir ciertos objetivos particulares.
¿Qué serían definiciones?
Las definiciones pueden adoptar diversas formas dentro de un mismo código, y experimentar ligeros cambios relacionados con los conceptos y los usos a los que estén destinadas, dependiendo del lenguaje y del objetivo que cada programador desee alcanzar. En general, una definición nos ayuda a controlar y modificar el código de manera simple, rápida y segura, ya sea mediante la creación de macros, directivas de compilación, o declarando constantes especiales. De cualquier forma, las definiciones son algo que te va a gustar y disfrutar bastante, siempre que procures entender cómo funcionan y practiques su utilización siempre que sea posible. Pero, sobre todo, vas a disfrutar mucho utilizando definiciones si te gusta crear pequeños cambios en los códigos para comprobar cuál es el resultado que proporcionan dichos cambios, esto es posible gracias a las definiciones, que permiten hacer esto de manera muy simple y práctica.
A continuación, veremos definiciones orientadas al uso de directivas de compilación. Existen otros tipos de definiciones que se realizan cuando utilizamos programación externa, e importamos códigos creados en otros entornos para usarlos junto con MQL5. Sin embargo, estas definiciones exigen que tengas experiencia programando en entornos integrados, aunque vayas a utilizar solamente MQL5 como lenguaje principal.
Por ejemplo, es posible importar códigos y funcionalidades creadas por otros programadores, normalmente en lenguaje C o C++, dentro de MQL5. Esto nos permite añadir funcionalidades o cosas de interés personal a MetaTrader 5, como, por ejemplo, un reproductor de vídeo, o incluso desarrollar un utilitario para crear gráficos científicos avanzados, con posibilidades de utilizar un lenguaje parecido a LaTeX, que, para quien no lo sepa, es un lenguaje que permite formatear expresiones matemáticas. Algo bastante interesante, dicho sea de paso.
Muy bien, como aquí nos centraremos en las directivas de compilación como forma de crear definiciones, el tema será mucho más sencillo y agradable, y estoy seguro de que lo entenderás muy rápidamente, ya que se ha presentado de alguna manera en otros artículos anteriores. Entonces, empezaremos con la propia directiva #define.
Esta directiva de compilación es muy específica. Esto es así porque, básicamente, al utilizarla, podrás crear: una constante o una macro. Eso es básicamente lo que podemos hacer con esta directiva. Al igual que la directiva #include, que ya se explicó en otro artículo, en este caso Del básico al intermedio: Directiva Include, esta directiva #define, junto con otras directivas de compilación, nos permite generar una serie de pequeños ajustes fáciles de realizar y modificar. Como, por ejemplo, en el artículo anterior, vimos un código que se muestra justo abajo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. const uchar who = 6; 07. 08. Print("Result: ", Fibonacci_Recursive(who)); 09. Print("Result: ", Fibonacci_Interactive(who)); 10. } 11. //+------------------------------------------------------------------+ 12. uint Fibonacci_Recursive(uint arg) 13. { 14. if (arg <= 1) 15. return arg; 16. 17. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 18. } 19. //+------------------------------------------------------------------+ 20. uint Fibonacci_Interactive(uint arg) 21. { 22. uint v, i, c; 23. 24. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 25. v += i; 26. 27. return (c == arg ? i : v); 28. } 29. //+------------------------------------------------------------------+
Código 01
Ahora, fíjate en que, en la línea seis, tenemos una constante del tipo uchar que se define. Sin embargo, observa que las funciones que van a recibir el valor esperan recibir un valor del tipo uint, como puedes ver en las líneas 12 y 20. Pero no quieres estar ajustando esto a cada momento. Entonces, podemos hacer uso de una directiva de compilación para que este tipo de cosas sean más agradables. Al mismo tiempo, en algunos casos, puede hacer que el código sea ligeramente más rápido. A continuación, verás por qué usar una directiva de compilación en lugar de una constante puede hacer que el código sea ligeramente más rápido, además de otras cuestiones que también ayudan a mejorar el rendimiento general.
Vale, entonces, suponiendo que quisiéramos usar una directiva de compilación aquí, este mismo código 01 podría reescribirse como se muestra justo abajo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element 6 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. Print("Result: ", Fibonacci_Recursive(def_Fibonacci_Element)); 09. Print("Result: ", Fibonacci_Interactive(def_Fibonacci_Element)); 10. } 11. //+------------------------------------------------------------------+ . . .
Código 02
Claro que no voy a repetir todo el código, ya que no hay necesidad de hacerlo. Sin embargo, nota cómo la cosa quedó implementada. Parece no hacer ninguna diferencia. Bien, pero de hecho sí existe una diferencia aquí, mi querido lector. Para ti, que estás leyendo el código, hasta puede parecer todo igual, pero, para el compilador, el código 01 es diferente del código 02, precisamente porque estamos utilizando una directiva #define en este código 02.
Entonces, para el compilador, el código que realmente será visto es el que se muestra justo abajo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print("Result: ", Fibonacci_Recursive(6)); 07. Print("Result: ", Fibonacci_Interactive(6)); 08. } 09. //+------------------------------------------------------------------+ . . .
Código 03
Pero espera un momento. Esto sería equivalente al código 01. Por tanto, no tiene mucho sentido utilizar esta directiva. En realidad, tiene mucho sentido, querido lector. Solo que necesitas recordar que aquí nos interesan las cosas de manera didáctica. Piensa que puedes tener un gran número de posiciones en tu código que deberán usar un determinado valor. Si utilizas una constante, aunque sea una constante global, en algún momento podrías tener problemas precisamente por el hecho de que esa constante exista.
Sin embargo, si utilizas una definición, como la que se ve en el código 02, podrás tener un control mucho mayor sobre el código. Además, una constante, a menos que sea una constante global, quedará retenida, o, mejor dicho, solo será visible en la rutina donde se declare. En cambio, una directiva sí, porque, desde el momento en que la declaras, puedes utilizarla en cualquier punto del código, sin ningún problema, siempre que aún exista. Digo esto porque, a diferencia de una constante global, una directiva puede ser destruida en cualquier momento, pudiendo tener su valor modificado, o incluso puede tener una función completamente diferente a la de otra directiva con el mismo nombre.
Entonces, vamos a entender esto por partes, pues cada punto mencionado aquí es importante, y podrías llegar a necesitarlo en algún momento. Entonces, veamos primero la cuestión de la visibilidad, esto en un caso bastante simple, como puedes observar en el código justo abajo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 6 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. Print("Result: ", Fibonacci_Recursive()); 09. Print("Result: ", Fibonacci_Interactive()); 10. } 11. //+------------------------------------------------------------------+ 12. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default) 13. { 14. if (arg <= 1) 15. return arg; 16. 17. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 18. } 19. //+------------------------------------------------------------------+ 20. uint Fibonacci_Interactive(uint arg = def_Fibonacci_Element_Default) 21. { 22. uint v, i, c; 23. 24. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 25. v += i; 26. 27. return (c == arg ? i : v); 28. } 29. //+------------------------------------------------------------------+
Código 04
Nota lo siguiente, mi querido lector: en la línea cuatro, estamos declarando una directiva de compilación del tipo constante. Es visible en todo el cuerpo del código y tiene el mismo tipo y resultado esperado, cuando definimos un valor predeterminado para un argumento en una función o procedimiento. Sin embargo, esta misma directiva de compilación podría ser sustituida por una constante global. Pero —y aquí es donde las cosas se ponen interesantes— no podrías hacer con una constante lo que se muestra en el código justo abajo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 6 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. Print("Result: ", Fibonacci_Recursive()); 09. Print("Result: ", Fibonacci_Interactive()); 10. } 11. //+------------------------------------------------------------------+ 12. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default) 13. { 14. if (arg <= 1) 15. return arg; 16. 17. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 18. } 19. //+------------------------------------------------------------------+ 20. #undef def_Fibonacci_Element_Default 21. //+------------------------------------------------------------------+ 22. uint Fibonacci_Interactive(uint arg = def_Fibonacci_Element_Default) 23. { 24. uint v, i, c; 25. 26. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 27. v += i; 28. 29. return (c == arg ? i : v); 30. } 31. //+------------------------------------------------------------------+
Código 05
Observa que, en la línea 20 de este código, estamos utilizando otra directiva de compilación. Esta es la #undef. Cuando esta directiva se utiliza, podemos eliminar o destruir una directiva que haya sido definida con el nombre que se encuentra en la directiva #undef. Este tipo de cosa es muy útil. Sin embargo, antes de hablar sobre las utilidades más inmediatas, necesitamos hablar de lo que sucede cuando intentamos compilar el código 05. Al intentar hacerlo, el compilador emitirá un error, que puede verse justo abajo.
Imagen 01
Señala que el error se ha generado en la línea 22. Sin embargo, la constante def_Fibonacci_Element_Default fue destruida en la línea 20. Así, cuando el compilador intente encontrar la constante nombrada en la base de datos para compilar el código, no la encontrará, lo que generará el error que puedes observar en la imagen 01. Por esta razón, muchos programadores tienen el hábito de colocar un prefijo para ayudar a identificar el tipo de error que se está generando. No se trata de una regla, sino de una buena práctica de programación. A mí, por ejemplo, me gusta poner el prefijo def_ antes del nombre de cualquier constante definida. Así, me ayuda a diferenciar entre una constante general y una directiva de compilación.
Vale, pero ¿y si deseamos declarar otro valor para la directiva justo después de haberla destruido, podemos hacerlo? Por supuesto que sí, mi querido lector. Solo que debemos tener cuidado al hacerlo. Pero después te mostraré cómo puedes evitarlo para evitar dolores de cabeza. No es algo 100 % seguro, pero al menos ayuda. Sin embargo, vamos a ver qué sucede si cambiamos la directiva, como propones.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 6 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. Print("Result: ", Fibonacci_Recursive()); 09. Print("Result: ", Fibonacci_Interactive()); 10. } 11. //+------------------------------------------------------------------+ 12. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default) 13. { 14. if (arg <= 1) 15. return arg; 16. 17. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 18. } 19. //+------------------------------------------------------------------+ 20. #undef def_Fibonacci_Element_Default 21. //+----------------+ 22. #define def_Fibonacci_Element_Default 7 23. //+------------------------------------------------------------------+ 24. uint Fibonacci_Interactive(uint arg = def_Fibonacci_Element_Default) 25. { 26. uint v, i, c; 27. 28. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 29. v += i; 30. 31. return (c == arg ? i : v); 32. } 33. //+------------------------------------------------------------------+
Código 06
Muy bien, en este código 06, podemos ver esto que se propuso siendo aplicado. Nota que, en la línea 22, estamos definiendo un nuevo valor para la directiva, y, al ejecutar este código, el resultado será el que se muestra justo abajo.
Imagen 02
Percibe que los resultados son diferentes, pero esto era de esperar precisamente por la razón del cambio en el valor. Si la directiva definida en la línea 4 fuera una constante de tipo global, no podríamos hacer el cambio que se ve en la línea 22, ni eliminar la constante global de otras partes del código. Esta es la base que debes procurar entender, mi querido lector. No se trata de algo que debas memorizar, sino de algo que debes comprender y asimilar.
Muy bien, aquí vimos que podemos utilizar las directivas de compilación #define y #undef en conjunto, esto de una forma simple. Porque, como mencioné hace poco, existen formas más elaboradas de usar estas dos directivas en conjunto. Sin embargo, para hacer esto, necesitamos utilizar otras directivas para hacer el trabajo más simple y fácil de controlar.
Controlar la versión de un código
Una de las formas más interesantes de utilizar estas directivas #define y #undef es en el control de versiones diferentes de un mismo código. Como este código del cálculo del elemento en la secuencia de Fibonacci es muy sencillo de entender, lo usaremos para explicar el control de versiones.
Supongamos que no sabes cómo crear una versión de cálculo iterativo para un elemento determinado de la secuencia de Fibonacci, o que quieras crear un cálculo diferente al que se ve en el código mostrado hasta ahora. Podrías pensar que esto sería fácil de hacer, pero terminarías cometiendo alguna tontería antes o después. Sin embargo, si usas las directivas de compilación, el riesgo de cometer algún error disminuye bastante. Por ejemplo, podemos aislar los cálculos para seleccionar si queremos un cálculo iterativo o recursivo. Para hacer esto, basta con crear algo parecido a lo que se muestra justo abajo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define DEF_INTERACTIVE 05. //+----------------+ 06. #define def_Fibonacci_Element_Default 6 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. Print("Result: ", Fibonacci()); 11. } 12. //+------------------------------------------------------------------+ 13. #ifndef DEF_INTERACTIVE 14. //+----------------+ 15. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 16. { 17. if (arg <= 1) 18. return arg; 19. 20. return Fibonacci(arg - 1) + Fibonacci(arg - 2); 21. } 22. //+----------------+ 23. #endif 24. //+------------------------------------------------------------------+ 25. #ifdef DEF_INTERACTIVE 26. //+----------------+ 27. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 28. { 29. uint v, i, c; 30. 31. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 32. v += i; 33. 34. return (c == arg ? i : v); 35. } 36. //+----------------+ 37. #endif 38. //+------------------------------------------------------------------+
Código 07
Aquí muestro una primera alternativa, solo para mostrar lo que podemos hacer. Observa que, en la línea 4 de este código 07, creamos una definición. Esta no necesita contener ningún valor, solo es necesario que exista, o, mejor dicho, que sea posible que lo vea el compilador. Ahora, observa que en la línea 10 solo tenemos una función Fibonacci. Pero te pregunto: ¿cuál se utilizará? ¿La de la línea 15 o la de la línea 27? Pero, en este punto, puedes decirme: que no tiene ningún sentido. ¿Podemos tener dos funciones con el mismo nombre? Sí, mi querido lector, podemos. Pero eso lo veremos en otro momento. Por ahora, centrémonos solo en lo que se muestra aquí, en el código 07.
Si nunca has visto este tipo de construcción, te volviste loco intentando entender lo que está pasando aquí. Esto es porque, aparentemente, no tiene el menor sentido. Pero observa atentamente el código. Fíjate en que, en la línea 13, estamos utilizando otra directiva de compilación. Esta comprueba si la directiva declarada existe o no, y el código entre esta directiva #ifndef y #endif solo se compilará si la directiva NO EXISTE. De lo contrario, el código no se compilará. Algo parecido ocurre en la línea 25, donde el código se compila si y solo si la directiva EXISTE. Es decir, como la línea cuatro define la directiva en la que queremos utilizar el código, solo se compilará el código entre las líneas 25 y 37. Mientras tanto, el código entre las líneas 13 y 23 será ignorado.
¿No lo crees? Bien, entonces vamos a probar esto en la práctica. Pero, para que esto sea realmente aceptable, vamos a añadir una pequeña línea en este código 07, de modo que quede como se muestra abajo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define DEF_INTERACTIVE 05. //+----------------+ 06. #define def_Fibonacci_Element_Default 6 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. Print("Result: ", Fibonacci()); 11. } 12. //+------------------------------------------------------------------+ 13. #ifndef DEF_INTERACTIVE 14. //+----------------+ 15. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 16. { 17. Print("Testing ", __LINE__); 18. if (arg <= 1) 19. return arg; 20. 21. return Fibonacci(arg - 1) + Fibonacci(arg - 2); 22. } 23. //+----------------+ 24. #endif 25. //+------------------------------------------------------------------+ 26. #ifdef DEF_INTERACTIVE 27. //+----------------+ 28. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 29. { 30. uint v, i, c; 31. 32. Print("Testing ", __LINE__); 33. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 34. v += i; 35. 36. return (c == arg ? i : v); 37. } 38. //+----------------+ 39. #endif 40. //+------------------------------------------------------------------+
Código 08
Al ejecutar este código 08, vas a ver la imagen justo abajo.
Imagen 03
Ahora, cambia la línea cuatro por lo que se muestra justo abajo.
// #define DEF_INTERACTIVE
Compila el código 08 nuevamente y el resultado será el que se muestra a continuación.
Imagen 04
Eso es todo. Está demostrado que funciona. En una versión, utilizamos el cálculo recursivo y, en otra, el cálculo iterativo. Y para elegir qué versión utilizar, bastó con cambiar una línea de código. ¿No es este tipo de implementación y uso de directivas en nuestro código algo interesante? Pero no solo esto. Podemos hacer algo aún más interesante. Fíjate en cómo este mismo código mostrado hasta ahora puede reconstruirse de una manera mucho más simple.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define DEF_INTERACTIVE "Interactive" 05. //+----------------+ 06. #define def_Fibonacci_Element_Default 6 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. Print("Result to ", 11. #ifdef DEF_INTERACTIVE 12. DEF_INTERACTIVE, " : " 13. #else 14. "Recursive : " 15. #endif 16. , Fibonacci()); 17. } 18. //+------------------------------------------------------------------+ 19. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 20. { 21. #ifdef DEF_INTERACTIVE 22. 23. uint v, i, c; 24. 25. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 26. v += i; 27. 28. return (c == arg ? i : v); 29. 30. #else 31. 32. if (arg <= 1) 33. return arg; 34. 35. return Fibonacci(arg - 1) + Fibonacci(arg - 2); 36. 37. #endif 38. } 39. //+------------------------------------------------------------------+
Código 09
En este código 09, en realidad, estamos haciendo uso de las directivas de una manera mucho más organizada, ya que podemos controlar diversas cosas cambiando solo una línea del código, al igual que se hizo con el código 08. Entonces, lo que haremos será lo siguiente: primero, ejecutaremos el código tal y como se ve justo arriba, es decir, con la línea 4 existente. El resultado de la ejecución puede verse justo debajo.
Imagen 05
Al colocar la línea cuatro de la misma forma que se hizo en el código 08, el resultado de la ejecución del código 09 se ve justo abajo.
Imagen 06
Pero ¿qué cosa más loca y extraña fue esta? Ahora sí que estoy bastante curioso e interesado en entender mejor esto, ya que no entendí lo que pasó en este código 09. Entonces, ¿podrías explicarme, por favor, qué ocurrió y por qué se obtuvieron estos resultados? Por supuesto, querido lector. Para eso estamos aquí, escribiendo estos artículos.
Lo que se hizo aquí, en este código 09, fue solo una pequeña broma para mostrar que podemos hacer mucho más con poco. Sé que muchos piensan que es difícil entender cómo piensan los programadores, pero en realidad no es tan complicado. Los buenos programadores siempre están buscando la forma de reducir su trabajo y aumentar su productividad. Y aquí, en este código 09, estamos haciendo precisamente eso, pero de una manera un poco más creativa.
Creo que ya has comprendido la cuestión de la directiva #ifdef. Pero vamos a hacer las cosas más interesantes. La instrucción IF, que se explicó en otro artículo, funciona de la misma manera en la directiva #ifdef y en la #ifndef. Es decir, todo lo que esté dentro del bloque se ejecutará o no. Sin embargo, la instrucción if tiene su bloque delimitado por una llave de apertura y una llave de cierre. Aquí, quien cierra la directiva #ifdef o #ifndef es la directiva #endif. SIEMPRE. Sin embargo, puede ocurrir que estemos probando algo y no queramos estar repitiendo comandos de directiva. En este caso, dentro del bloque construido de la directiva #ifdef o #ifndef, podemos colocar la directiva #else.
Ahora, presta atención: al igual que ocurre con la instrucción if, cuando la evaluación de la expresión es falsa y podemos ejecutar lo que esté en un else vinculado a esa instrucción if, podemos hacer lo mismo aquí, usando las directivas #ifdef y #ifndef. Es decir, entender cómo funciona la instrucción IF nos ayuda a implementar y usar de manera más adecuada las directivas similares a ella, ya que podemos colocar directivas #ifdef y #ifndef anidadas para probar partes específicas de un mismo código que queremos implementar.
Aunque no estamos haciendo esto aquí, necesitas entender que es posible. Bien, pero volvamos al código en sí. Observa que, en la línea 4, definimos algo. Al hacer esto, podemos utilizar las directivas #ifdef y #ifndef para modelar nuestro código. Pero tal vez tengas la duda de si, durante esta definición de la línea cuatro, estamos haciendo lo mismo que se hizo en la línea seis, y que esto podría llegar a interferir en las directivas de compilación #ifdef y #ifndef. Sin embargo, esto no es lo que ocurre. Esto se debe a que #ifdef y #ifndef comprueban si la definición existe o no. De hecho, existe la directiva #if, no aquí en MQL5, sino en el lenguaje C y C++, donde podemos comprobar el valor que se está definiendo en la directiva. Sin embargo, creo que, por motivos de simplificar el manejo del lenguaje, no se incluyó la directiva #if, solo las directivas #ifdef y #ifndef.
Así, podemos asignar un valor a la directiva y convertirla en una constante nombrada, como se ve en la línea 12, y utilizarla. A partir de este momento, podemos utilizar la directiva como lo haríamos con una constante convencional, por lo que resulta perfectamente fácil de entender para quien viene estudiando los artículos y comprender cómo funciona el código 09. Pero podemos hacer algo aún más interesante, que te servirá para entender cómo manipular datos usando las directivas de compilación, y que puede serte útil en tu día a día.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define DEF_INTERACTIVE 05. //+----------------+ 06. #ifdef DEF_INTERACTIVE 07. #define def_OPERATION "Interactive" 08. #else 09. #define def_OPERATION "Recursive" 10. #endif 11. //+----------------+ 12. #define def_Fibonacci_Element_Default 11 13. //+----------------+ 14. #define def_MSG_TERMINAL "Result of " + def_OPERATION + " operation of element" 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. Print(def_MSG_TERMINAL, " ", def_Fibonacci_Element_Default, " : ", Fibonacci()); 19. } 20. //+------------------------------------------------------------------+ 21. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 22. { 23. #ifdef DEF_INTERACTIVE 24. 25. uint v, i, c; 26. 27. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 28. v += i; 29. 30. return (c == arg ? i : v); 31. 32. #else 33. 34. if (arg <= 1) 35. return arg; 36. 37. return Fibonacci(arg - 1) + Fibonacci(arg - 2); 38. 39. #endif 40. } 41. //+------------------------------------------------------------------+
Código 10
Este código 10 parece muy complicado y difícil de entender para quien lo mira sin intentar0 intentar entender lo que está pasando. Parece muy complicado y difícil de entender. Sin embargo, absolutamente nada de lo que se está haciendo en este código 10 es una novedad, pues todo lo que se está haciendo ya se explicó en este artículo en algún momento. Sin embargo, puede resultar un poco confuso si no practicas lo que se mostró en este artículo. Como muchos de estos códigos estarán disponibles en el anexo, ya que están destinados únicamente a mostrar lo que necesita modificarse, podrás utilizarlos para practicar y estudiar cada detalle visto aquí.
De cualquier forma, vamos a ver qué ocurre cuando este código 10 se ejecuta. Primero, vamos a dejar que la directiva declarada en la línea cuatro, de hecho, haga su trabajo. Para esto, deberás compilar el código tal como se muestra arriba. Esto nos proporcionará la siguiente salida en el terminal, que se ve justo abajo.
Imagen 07
Muy bien, ahora procura entender lo que ocurrió antes de modificar el código como se muestra en el fragmento abajo. Esto hará mucho más sencillo cualquier cosa que necesitemos hacer en el futuro. Tan pronto como logres entender cómo fue creada la salida mostrada en la imagen 07, cambia el código como se muestra en el fragmento abajo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. // #define DEF_INTERACTIVE 05. //+----------------+ 06. #ifdef DEF_INTERACTIVE 07. #define def_OPERATION "Interactive" 08. #else 09. #define def_OPERATION "Recursive" 10. #endif 11. //+----------------+ 12. #define def_Fibonacci_Element_Default 11 13. //+----------------+ 14. #define def_MSG_TERMINAL "Result of " + def_OPERATION + " operation of element" 15. //+------------------------------------------------------------------+ . . .
Fragmento del código 10
Presta mucha atención para que este fragmento mostrado abajo no parezca igual que el código 10, pues, aunque muy sutil, de hecho hay un cambio. Este cambio se produce precisamente en la línea cuatro, donde convertimos la definición en un comentario. Al ser un comentario, el compilador ignorará esta línea. Por tanto, es como si la definición no se estuviera realizando.
Aunque esto parezca no significar absolutamente nada para el código, este simple hecho hace que el compilador genere un código diferente al que existía antes. Así, cuando este nuevo código sea ejecutado, vas a ver en el terminal lo que se muestra en la imagen justo abajo.
Imagen 08
Muy bien, creo que ha quedado muy claro cómo utilizar la definición y las directivas #ifdef, #ifndef, #else y #endif. Sin embargo, falta hablar de otro uso de la directiva #define. Recuerda que mencioné al principio de este artículo que esta directiva serviría para dos propósitos. El primero fue el que hemos explorado y que se ha demostrado en este artículo, y que podrás practicar y experimentar con los códigos que estarán en el anexo.
Esta forma tiene como objetivo permitirnos utilizar la directiva #define, para evitar la creación innecesaria de una variable global, y promover una manera simple, rápida y eficaz de analizar e implementar versiones diferentes de un mismo código. Esto es valioso para quienes son principiantes. Para los programadores más experimentados es algo trivial, ya que hacen uso de este tipo de cosas de manera casi automática, pues facilita mucho la vida. Lástima que no tengamos las directivas #if de C y C++. Pero está bien. Es parte de ello.
La segunda forma de utilizar la directiva #define es en la creación de macros. Sin embargo, como las macros necesitan tiempo para analizarlas sin prisa, he decidido no incluirlas por ahora, ya que, si se abordara ahora mismo, podría terminar complicándolo todo y haciéndolo excesivamente difícil de entender. Esto se debe a que las macros no son como simples fragmentos de código, como muchos pueden imaginar. Las macros son una herramienta que, si se emplea bien, ayuda bastante, pero, si se malinterpreta y se usa de manera indebida, hace que el código sea muy confuso y complejo.
Antes de terminar este artículo, quiero mostrar una última cosa. Sería más como un BONUS para ti, mi querido lector, que tuviste la paciencia de leer y, con toda certeza, ya debes estar ansioso por comenzar a experimentar el uso de la directiva #define.
Este bonus consiste en la posibilidad de crear, dentro de MQL5, y sin necesidad de cambiar nada, algunos comandos simples, de modo que tengan un poco más de sentido. Esto se explorará más a fondo cuando hablemos sobre macros, pero ya sirve como un adelanto de lo que será el tema futuro.
Tal vez no lo hayas notado o percibido, pero, cuando usamos la directiva #define, estamos diciendo al compilador que un determinado texto deberá ser reemplazado por otro. Debido a este concepto, podemos crear una sintaxis alternativa, esto sin tocar absolutamente nada en la forma de crear tus códigos.
Para demostrar esto, vamos a ver el código justo abajo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define BEGIN_PROC { 05. #define END_PROC } 06. #define RETURN return 07. #define ENTER_POINT void OnStart (void) 08. #define Z_SET_NUMBERS long 09. #define EQUAL == 10. #define IS = 11. #define MINOR < 12. #define OR || 13. #define MORE += 14. //+------------------------------------------------------------------+ 15. #define DEF_INTERACTIVE 16. //+----------------+ 17. #ifdef DEF_INTERACTIVE 18. #define def_OPERATION "Interactive" 19. #else 20. #define def_OPERATION "Recursive" 21. #endif 22. //+----------------+ 23. #define def_Fibonacci_Element_Default 11 24. //+----------------+ 25. #define def_MSG_TERMINAL "Result of " + def_OPERATION + " operation of element" 26. //+------------------------------------------------------------------+ 27. ENTER_POINT BEGIN_PROC 28. Print(def_MSG_TERMINAL, " ", def_Fibonacci_Element_Default, " : ", Fibonacci()); 29. END_PROC 30. //+------------------------------------------------------------------+ 31. Z_SET_NUMBERS Fibonacci(Z_SET_NUMBERS arg IS def_Fibonacci_Element_Default) 32. BEGIN_PROC 33. #ifdef DEF_INTERACTIVE 34. 35. Z_SET_NUMBERS v, i, c; 36. 37. for (c IS 0, i IS 0, v IS 1; c MINOR arg; i MORE v, c MORE 2) 38. v MORE i; 39. 40. RETURN (c EQUAL arg ? i : v); 41. 42. #else 43. 44. if ((arg EQUAL 1) OR (arg MINOR 1)) 45. RETURN arg; 46. 47. RETURN Fibonacci(arg - 1) + Fibonacci(arg - 2); 48. 49. #endif 50. END_PROC 51. //+------------------------------------------------------------------+
Código 11
Este código funciona de la misma manera que el código 10, solo que, aquí, estamos creando algo muy diferente a los estándares que muchos, de hecho, utilizan. Sin embargo, no obstante y, además, este código 11 será perfectamente comprendido por el compilador, tanto que, de hecho, va a generar los mismos resultados que obtendrías con el código 10.
Pero, al mirarlo, podrías estar pensando: amigo, pero ESTO NO es MQL5. Sin embargo, con lo que se vio aquí en este artículo, puedes mirarlo de una manera más adecuada y ver que, sí, aunque diferente, este código 11 es MQL5 puro y simple, solo que escrito de una manera distinta, orientada principalmente a parecerse a un lenguaje menos matemático y más natural.
Observa que, después de que las definiciones se realizaron —y esto entre las líneas cuatro y trece—, todo el resto del código, pero principalmente a partir de la línea 27, quedó muy diferente. Muchos podrían decir incluso que esto no generaría un ejecutable. Pero, para sorpresa de esas mismas personas, sí, este código funciona.
Consideraciones finales
En este artículo, hicimos diversas cosas que pueden parecer incluso extrañas y fuera de contexto para muchos de ustedes, pero que, si se aplican bien, harán que su fase de aprendizaje sea mucho más divertida y emocionante, ya que podemos construir cosas bastante interesantes y empezar a comprender mejor la sintaxis del propio lenguaje MQL5. Como el material aquí presentado necesita ser debidamente estudiado y tú necesitas practicar para comprender los conceptos adoptados, voy a cerrar el artículo en este punto. En el próximo artículo, hablaremos sobre la segunda forma de utilizar la directiva #define. Así que practica y estudia lo visto aquí para no crear confusión con el material del próximo artículo.
En el anexo, tendrás cinco de los códigos vistos aquí. Así que, que te diviertas mucho.
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/15573





- 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