Variables externas
El material de esta sección es a la vez complejo y opcional; requiere el conocimiento de los conceptos que se basan en la analogía con C++ y los que se consideran a continuación. Al mismo tiempo, el efecto de la estructura lingüística descrita puede conseguirse de otra manera, mientras que su flexibilidad es una fuente potencial de errores.
MQL5 permite describir variables como externas. Esto se hace utilizando la palabra clave extern y sólo se permite en el contexto global.
Para una variable externa, la sintaxis básicamente repite una descripción normal pero adicionalmente tiene la palabra clave 'extern' mientras que la inicialización está prohibida:
extern type identifier; |
Describir una variable como externa significa que su descripción se retrasa y debe producirse más adelante en el código fuente, normalmente en otro archivo (la conexión de archivos mediante #includedirective se examinará en el capítulo dedicado al preprocesador). Varios archivos fuente diferentes pueden tener una descripción de la misma variable externa, es decir, que tengan tipos e identificadores idénticos. Todas estas descripciones se refieren a la misma variable.
Se supone que esta variable estará completamente descrita en uno de los archivos. Si la variable no está definida en ninguna parte del código sin la palabra clave extern, se devuelve el error de compilación «variable externa no resuelta» (similar a un error del enlazador en C++ en estos casos).
La descripción de una variable externa permite utilizarla eficazmente en el código fuente de un archivo concreto. En otras palabras, permite compilar un módulo determinado, aunque la variable no se cree en este módulo.
El uso de extern en MQL5 no es tan insistente como en C++ y en la mayoría de los casos, puede ser sustituido por la habilitación de un archivo de cabecera con descripciones generales de las variables que se van a declarar como extern. Basta con realizar estas definiciones de forma convencional. El compilador se asegura de añadir cada archivo adjunto al código fuente sólo una vez. Teniendo en cuenta que en MQL5 un programa siempre consta de una unidad compilable mq5, no hay aquí ningún problema de C ++, con el error potencial de las múltiples definiciones de la misma variable debido a la habilitación de la cabecera en diferentes unidades.
Aunque se adjunte un archivo mq5 adicional (no mqh) en la directiva #include, no compite en igualdad de condiciones con la unidad principal para la que se lanza la compilación, sino que se considera una de las cabeceras.
A diferencia de C++, MQL5 no permite especificar un valor inicial para una variable externa (la inicialización en C++ lleva a ignorar la palabra extern). Si intenta establecer un valor inicial obtendrá un error de compilación «no se permite la inicialización de variables externas».
En general, describir una variable como externa puede considerarse un tipo de descripción «imprecisa»: garantiza la aparición de la variable y excluye el error de anulación que se produciría si la variable se describiera en varios archivos sin el modificador extern.
Sin embargo, esto puede ser una fuente de errores. Si en diferentes archivos de cabecera, por coincidencia, se describen variables idénticas para diferentes propósitos, entonces ninguna palabra clave extern permite identificar un conflicto, mientras que con extern las variables se convertirán en una, y la lógica de funcionamiento del programa muy probablemente se romperá.
Como externos, tanto las variables como las funciones pueden describirse (se considerarán más abajo). Para las funciones, describirlas con el atributo como externas es un rudimento (es decir, se compila, pero no realiza ningún cambio). Las dos siguientes declaraciones de una función son equivalentes:
extern return_type name([parameters]);
|
En este sentido, la presencia o ausencia de extern sólo puede utilizarse para distinguir estilísticamente entre la descripción de una función desde la unidad actual (sin extern) o desde una externa (extern está presente).
Puede utilizar extern tanto en la unidad mq5 que se va a compilar como en los archivos de cabecera que se van a adjuntar.
Veamos algunas opciones para utilizar extern: se introducen en distintos archivos, a saber, el script principal ExternMain.mq5 y 3 archivos adjuntos: ExternHeader1.mqh, ExternHeader2.mqh y ExternCommon.mqh.
En el archivo principal sólo se adjuntan ExternHeader1.mqh y ExternHeader2.mqh, mientras que necesitaremos ExternCommon.mqh un poco más adelante.
// source code from mqh files will be substituted implicitly
|
En los archivos de cabecera se definen dos funciones de utilidad condicional: en el primero, la función inc para el incremento de la variable x , mientras que en el segundo, la función dec para el decremento de la variable x. Es la variable x la que se describe en ambos archivos como externa:
// ExternHeader1.mqh
|
Debido a esta descripción, cada uno de los archivos mqh se compila de forma regular. Cuando se incluyen juntos en un archivo mq5 se compila también todo el programa.
Si la variable se definiera en cada archivo sin la palabra extern, el error de redefinición se produciría al compilar el programa en su conjunto. Si hubiéramos transferido la definición de x de los archivos de cabecera a la unidad principal, los archivos de cabecera habrían dejado de compilarse (puede que alguien no lo considere un problema, pero en programas más grandes, a los desarrolladores les gusta comprobar la capacidad de compilación de las correcciones inmediatas sin compilar el proyecto entero).
En el script principal definimos una variable (en este caso, con un valor inicial de 2, mientras que si no especificamos el valor, se utilizará el 0 de forma predeterminada) e invocamos las funciones de utilidad condicional, además de imprimir el valor x.
int x = 2;
|
En el archivo ExternHeader1.mqh se encuentra la descripción de la variable short z (sin extern). Una descripción similar se comenta en el script principal. Si activamos esta cadena obtendremos el error antes mencionado («variable ya definida»). Esto se hace para ilustrar el problema potencial.
En ExternHeader1.mqh se describe también extern long y. Al mismo tiempo, en el archivo ExternHeader2.mqh, la variable externa homónima tiene otro tipo: extern short y. Si esta última descripción no se «moviera» a un comentario de forma preventiva, se produciría aquí el error de incompatibilidad de tipos («variable 'y' ya definida con un tipo diferente»). En resumen: o los tipos deben coincidir o las variables no deben ser externas. Si ambas opciones no son buenas, significa que hay un error tipográfico en el nombre de una de las variables.
Además, cabe señalar que la variable y no se inicializa explícitamente. Sin embargo, el script principal la invoca con éxito e imprime 0 en el registro:
long y;
|
Por último, existe la posibilidad prevista en el script de probar una alternativa de las variables gemelas externas, ejemplificada por la variable ya conocida x. En lugar de describir extern int x, cada uno de los archivos ExternHeader1.mqh y ExternHeader2.mqh puede incluir otra cabecera común, ExternCommon.mqh, en la que figura la descripción de int x (sin extern). Se convierte en la única descripción de x en el proyecto.
Este modo alternativo de ensamblar el programa se habilita al activar la macro USE_INCLUDE_WORKAROUND: esto se encuentra en el comentario al principio del script:
#define USE_INCLUDE_WORKAROUND // this string was in the comment
|
En esta configuración, los archivos de inclusión particulares seguirán siendo compilables, como también lo es el proyecto entero. En un proyecto real, sin utilizar este método, el archivo mqh común se incluiría en ExternHeader1.mqh y ExternHeader2.mqh de forma incondicional (sin condiciones USE_INCLUDE_WORKAROUND). En este ejemplo, la conmutación entre los dos hilos de instrucciones se basa en USE_INCLUDE_WORKAROUND y sólo es necesaria para demostrar ambos modos. Por ejemplo, la versión simplificada de ExternHeader2.mqh debería aparecer de la siguiente manera:
// ExternHeader2.mqh
|
Podemos comprobar en el registro de MetaEditor que el archivo ExternCommon.mqh sólo se ha cargado una vez, aunque se hace referencia a él tanto en ExternHeader1.mqh como en ExternHeader2.mqh.
'ExternMain.mq5'
|
Si la variable x está «registrada» en ExternCommon.mqh no la redefiniremos (sin extern) en la unidad principal, ya que esto provocaría un error de compilación, pero podemos simplemente asignarle el valor deseado al principio del algoritmo.