- Inclusión de archivos fuente (#include)
- Visión general de las directivas de sustitución de macros
- Forma simple de #define
- Forma de #define como pseudofunción
- Operadores especiales '#' y '##' dentro de definiciones #define
- Anulación de la sustitución de macros (#undef)
- Constantes predefinidas del preprocesador
- Compilación condicional (#ifdef/#ifndef/#else/#endif)
- Propiedades generales del programa (#property)
Forma simple de #define
La forma simple de la directiva #define registra un identificador y la secuencia de caracteres por la que el identificador debe ser reemplazado en todas partes en los códigos fuente después de la directiva, hasta el final del programa, o antes de la directiva #undef con el mismo identificador.
Su sintaxis es:
#define macro_identifier [text] |
El texto comienza después del identificador y continúa hasta el final de la línea actual. El identificador y el texto deben ir separados por un número arbitrario de espacios o tabuladores. Si la secuencia de caracteres requerida es demasiado larga, para facilitar la lectura puede dividirla en varias líneas colocando un carácter de barra invertida '\' al final de la línea.
#define macro_identifier text_beginning \
|
El texto puede consistir en cualquier construcción de lenguaje: constantes, operadores, identificadores y signos de puntuación. Si sustituye macro_identifier en lugar de las construcciones encontradas en el código fuente, todas ellas se incluirán en la compilación.
La forma simple se utiliza tradicionalmente para varios fines:
- Declaraciones de banderas, que luego se utilizan para comprobaciones de compilación condicional ;
- Declaraciones de constantes con nombre;
- Notación abreviada de sentencias comunes.
El primer punto se caracteriza por el hecho de que no es necesario especificar nada después del identificador: la presencia de una directiva con un nombre ya es suficiente para que se registre el identificador correspondiente y pueda utilizarse en directivas condicionales #ifdef/ifndef. Para ellas, sólo es importante si el identificador existe o no, es decir, funciona en el modo bandera: declarado / no declarado. Por ejemplo, la siguiente directiva define la bandera DEMO:
#define DEMO |
Puede utilizarse, por ejemplo, para crear una versión de demostración del programa de la que se excluyan determinadas funciones (véase el ejemplo de la sección de compilación condicional).
La segunda forma de utilizar una directiva simple permite sustituir los «números mágicos» del código fuente por nombres fáciles de usar. Los «números mágicos» son constantes insertadas en el texto fuente cuyo significado no siempre está claro (porque un número es sólo un número: conviene al menos explicarlo en un comentario). Además, el mismo valor puede estar disperso por distintas partes del código, y si el programador decide cambiarlo por otro, tendrá que hacerlo en todos los sitios (y esperar que no se le haya escapado nada).
Con una macro con nombre, estos dos problemas se resuelven fácilmente. Por ejemplo, un script puede preparar un array con números de Fibonacci hasta una determinada profundidad máxima. Tiene sentido definir una macro con un tamaño de array predefinido y utilizarla en la descripción del propio array (Preprocessor.mq5).
#define MAX_FIBO 10
|
Si posteriormente el programador decide que es necesario aumentar el tamaño del array, basta con que lo haga en un lugar: en la directiva #define. Así, la directiva define de hecho un determinado parámetro del algoritmo que está «programado» en el código fuente y no está disponible para la configuración del usuario. Esta necesidad se plantea con bastante frecuencia.
Cabe preguntarse en qué se diferencia la definición a través de #define de una variable constante en el contexto global. De hecho, podríamos declarar una variable con el mismo nombre y finalidad, e incluso conservar las mayúsculas:
const int MAX_FIBO = 10; |
Sin embargo, en este caso, MQL5 no permitirá definir un array con el tamaño especificado, ya que sólo se permiten constantes entre corchetes, es decir, literales (y una variable constante, a pesar de su nombre similar, no es una constante). Para resolver este problema podríamos definir un array como dinámico (sin especificar antes un tamaño) y luego asignarle memoria mediante la función ArrayResize: pasar una variable como tamaño no es difícil en este caso.
Los enums ofrecen una forma alternativa de definir una constante con nombre, pero se limita únicamente a valores enteros. Por ejemplo:
enum
|
Pero la macro puede contener un valor de cualquier tipo.
#define TIME_LIMIT D'2023.01.01'
|
La búsqueda de nombres de macros en los textos fuente para su sustitución se realiza teniendo en cuenta la sintaxis del lenguaje, es decir, los elementos indivisibles, como identificadores de variables o literales de cadena, permanecerán inalterados, aunque incluyan una subcadena que coincida con una de las macros. Por ejemplo, dada la macro XYZ siguiente, la variable XYZAXES se mantendrá tal cual, y el nombre XYZ (dado que es exactamente el mismo que la macro) se cambiará a ABC.
#define XYZ ABC
|
Las sustituciones de macros le permiten incrustar su código en el código fuente de otros programas. Esta técnica la utilizan normalmente las bibliotecas que se distribuyen como archivos de encabezado mqh y se conectan a programas que utilizan las directivas #include .
En concreto, para los scripts podemos definir nuestra propia implementación de biblioteca de la función OnStart, que debe realizar algunas acciones adicionales sin afectar a la funcionalidad original del programa.
void OnStart()
|
Supongamos que esta parte se encuentra en el archivo de encabezado incluido (Preprocessor.mqh).
El preprocesador renombrará en el código fuente la función original OnStart (en Preprocessor.mq5) por _OnStart (se entiende que este identificador no se utiliza en ningún otro lugar para algún otro propósito). Y la nueva versión de OnStart del encabezado llama a _OnStart, «envolviéndola» en sentencias adicionales.
La tercera forma habitual de utilizar la sencilla #define es acortar la notación de las construcciones del lenguaje. Por ejemplo, el título de un bucle infinito puede denotarse con una palabra LOOP:
#define LOOP for( ; !IsStopped() ; ) |
Y aplicado en código:
LOOP
|
Este método es también la técnica principal para utilizar la directiva #define con parámetros (véase más adelante).