Inicialización

Al describir variables, existe la posibilidad de establecer el valor inicial; este se especifica a continuación del nombre de la variable y del símbolo '=' y debe corresponderse con el tipo de variable o moldearse con arreglo al mismo (la conversión de tipos se puede encontrar en la correspondiente sección).

int i = 3jk = 10;

Aquí i y k se han inicializado explícitamente, mientras que j, no.

Como valor inicial se puede especificar tanto una constante (literal del tipo correspondiente) como una expresión (una especie de fórmula para realizar cálculos). Expondremos las expresiones por separado. Mientras tanto, veamos un ejemplo sencillo:

int i = 3j = ik = i + j;

Aquí, la variable j toma el mismo valor que la variable i, mientras que la variable k toma la suma de i y j. En sentido estricto, en los tres casos vemos expresiones. Sin embargo, la constante (3) es una opción de expresión degenerada especial. En el segundo caso, el único nombre de variable es una expresión, es decir, el resultado de la expresión será el valor de esta variable sin ninguna transformación. En el tercer caso, se accede en la expresión a dos variables, i y j, se ejecuta la operación de suma con sus valores y, después, el resultado pasa a la variable k.

Dado que la sentencia que contiene la descripción de varias variables se procesa de izquierda a derecha, el compilador ya conoce los nombres de las variables anteriores cuando analiza otra descripción.

Un programa suele contener muchas sentencias con descripciones de variables que son leídas por el compilador de forma natural de arriba a abajo. En inicializaciones posteriores se pueden utilizar nombres tomados de descripciones anteriores. Aquí están las mismas variables descritas por dos sentencias separadas.

int i = 3j = i;
int k = i + j;

Las variables sin inicialización explícita también reciben algunos valores iniciales, pero dependen del lugar en el que se describió la variable, es decir, de su contexto.

Cuando no hay inicialización, las variables locales toman valores aleatorios en el momento de ser generadas: el compilador se limita a asignarles memoria en función del tamaño del tipo, mientras que se desconoce qué habrá en una dirección concreta (a menudo se reasignan varias zonas de memoria del ordenador para utilizarlas en programas diferentes después de que hayan dejado de ser necesarias para aquellos que se ejecutaron anteriormente).

Normalmente se sugiere que los valores de trabajo se introduzcan en variables locales sin inicializar en algún punto posterior del código del algoritmo, como por ejemplo utilizando las operaciones de asignación de las que hablaremos más adelante. Sintácticamente, esto es similar a la inicialización, ya que también utiliza el signo igual '=' para transferir el valor de la «estructura» situada a su derecha (puede ser una constante, variable, expresión o llamada a función) en la variable de la izquierda. Sólo una variable puede estar a la izquierda de '='.

El programador debe asegurarse de que la lectura de la variable no inicializada sólo tiene lugar cuando se le asigna un valor significativo. Si no es así, el compilador emite un aviso («posible uso de una variable no inicializada»).

Todo es diferente con las variables globales.

Un ejemplo de variables globales es el parámetro de entrada GreetingHour del script GoodTime2 de la Parte 2. El hecho de que la variable haya sido descrita con la palabra clave input no afecta a sus otras propiedades como variable. Podríamos excluir su inicialización y describirla como sigue:

input uint GreetingHour;

Esto no cambiaría nada en el programa, ya que las variables globales son inicializadas implícitamente por el compilador usando cero si no hay inicialización explícita (mientras que antes también teníamos inicialización explícita con cero).

Cualquiera que sea el tipo de variable, la inicialización implícita es realizada siempre por un valor equivalente a cero. Por ejemplo, para una variable bool se establecerá false, mientras que para una variable datetime será D'1970,01.01 00:00:00'. Existe un valor especial, NULL, para las cadenas. Se trata, si se quiere, de una cadena aún más «vacía» que las comillas vacías «» porque aún queda memoria asignada a ellas, donde se coloca el único carácter nulo terminal.

Además de las variables locales y globales, existe otro tipo, esto es, las variables estáticas. El compilador las inicializa, también, implícitamente con cero, si el programador no ha escrito ningún valor explícitamente inicial. Se abordarán en la siguiente sección.

Vamos a crear un nuevo script, VariableScopes.mq5, con ejemplos de descripción de variables locales y globales (MQL5/Scripts/MQL5Book/VariableScopes.mq5).

// global variables
int ijk;    // all are 0s
int m = 1;      // m = 1                (place breakpoint on this line)
int n = i + m;  // n = 1
void OnStart()
{
  // local variables
  int xyz;
  int k = m// warning: declaration of 'k' hides global variable
  int j = j// warning: declaration of 'j' hides global variable
  // use variables in assignment statements  
  x = n;     // ok, 1
  z = y;     // warning: possible use of uninitialized variable 'y'
  j = 10;    // change local j, global j is still 0
}
// compilation error
// int bad = x; // 'x' - undeclared identifier

Hay que recordar que, al lanzar un programa de MQL, el terminal primero inicializa todas las variables globales y luego invoca una función que es el punto de partida para los programas de un tipo relevante. En este caso, se trata de OnStart para scripts.

Aquí, sólo las variables i, j, k, m, n son globales, ya que se describen fuera de la función (en nuestro caso, sólo tenemos una función, OnStart, que es necesaria para los scripts). i, j, k toman implícitamente el valor 0. m y n contienen 1.

Puede ejecutar el script en el modo de depuración paso a paso y asegurarse de que los valores de las variables cambian exactamente de esta manera. Para ello, debe establecer previamente un punto de interrupción en la cadena con la inicialización de una de las variables globales, como m. Coloque el cursor de texto sobre esta cadena y ejecute Depurar -> Cambiar punto de interrupción (F9); y la cadena se resaltará con un signo azul en el campo de la izquierda, lo que indica que la ejecución del programa se detendrá aquí si comienza a funcionar en el depurador.

A continuación, debe ejecutar realmente el programa para depurarlo, para lo cual ejecute el comando Depurar -> Empezar con datos reales (F5). En este momento se abrirá un nuevo gráfico en el terminal, en el que comienza a ejecutarse este script (leyenda «VariableScopes (Debugging)» en la esquina superior derecha), pero se suspende inmediatamente y volvemos a MetaEditor. Deberíamos ver en él una imagen como la siguiente.

Depuración paso a paso y visualización de variables en MetaEditor

Depuración paso a paso y visualización de variables en MetaEditor

Una cadena que contiene un punto de interrupción se marca ahora con un signo de flecha: es la sentencia actual que el programa se dispone a ejecutar pero que aún no se ha ejecutado. Abajo a la izquierda se muestra la pila actual del programa, que por el momento consta de una sola entrada: @global_initializations. Puede introducir expresiones en la parte inferior derecha para controlar sus valores en tiempo real. Nos interesan los valores de las variables; por lo tanto, introduzcamos i, j, k, m, n, x, y, z de forma consecutiva (cada uno en una cadena separada).

Verá además que MetaEditor añade automáticamente variables del contexto actual para su visualización (por ejemplo, variables locales y entradas de la función, donde las sentencias se ejecutan dentro de la función). Pero ahora vamos a añadir x, y y z manualmente y por adelantado, sólo para mostrar que no están definidos fuera de la función.

Tenga en cuenta que, para las variables locales, se escribe «Identificador desconocido» en lugar de un valor, ya que todavía no se ha creado el bloque de funciones OnStart, donde se encuentran. Las variables globales i y j tendrán primero valores cero. La variable global k no se utiliza en ninguna parte y, por lo tanto, el compilador la excluye.

Si ejecutamos un paso de la ejecución del programa (ejecutar la sentencia en la línea de código actual) utilizando los comandos Paso Into (F11) o Paso Over (F10), veremos cómo la variable m toma el valor 1. Otro paso continuará la inicialización para la variable n, y también se convertirá en 1.

Aquí terminan las descripciones de las variables globales y, como sabemos, el terminal invoca a la función OnStart una vez finalizada la inicialización de las variables globales. En este caso, para entrar en la función OnStart en el modo paso a paso, pulse F11 una vez más (o bien, puede establecer otro punto de interrupción al principio de la función OnStart).

Las variables locales se inicializan cuando la ejecución de las sentencias del programa llega al bloque de código donde se han definido. Por lo tanto, las variables x, y, z sólo se crean al entrar en la función OnStart.

Cuando el depurador entre en la función OnStart podrá ver, con un poco de suerte, que realmente hay valores inicialmente aleatorios en x, y y z. En este caso, la «suerte» consiste en que estos valores aleatorios pueden ser nulos. Entonces será imposible diferenciarlas de la inicialización implícita con cero que realiza el compilador para las variables globales. Si el script se lanza repetidamente, la «basura» de las variables locales será probablemente diferente y más ilustrativa. No se inicializan explícitamente y, por tanto, su contenido puede ser de cualquier tipo.

En la secuencia de imágenes siguiente puede ver la evolución de las variables utilizando el modo paso a paso del depurador. La cadena actual que va a ejecutarse (pero que aún no se ha ejecutado) se marca con una flecha verde en los campos con enumeración.

Depuración paso a paso y visualización de variables en MetaEditor (cadena 23)

Depuración paso a paso y visualización de variables en MetaEditor (cadena 23)

Depuración paso a paso y visualización de variables en el MetaEditor (cadena 24)

Depuración paso a paso y visualización de variables en MetaEditor (cadena 24)

Más adelante en el código se demuestra cómo se pueden utilizar estas variables de la forma más sencilla en operadores de asignación. El valor de la variable global n se copia en la local x sin problemas ya que se ha inicializado n. Sin embargo, en la cadena en la que el contenido de la variable y se copia en la variable z aparece un aviso del compilador, ya que y es local y, en este momento, no se ha escrito nada en ella; es decir, no hay ninguna inicialización explícita, así como tampoco otros operadores que puedan establecer su valor.

Dentro de una función se permite describir variables con los mismos nombres que ya se utilizan para las variables globales. Una situación similar puede producirse en bloques locales anidados si se crea una variable en un bloque interno con el nombre existente en un bloque externo. Sin embargo, esta práctica no es recomendable, ya que puede dar lugar a errores lógicos. En tales casos, el compilador emite un aviso («la declaración oculta una variable global/local»).

Debido a esta redefinición, una variable local, como k en el ejemplo anterior, se superpone a la homónima global dentro de la función. Aunque tienen el mismo nombre, se trata de dos variables diferentes. La variable k local se conoce dentro de OnStart, mientras que la variable k global se conoce en todas partes excepto en OnStart. En otras palabras: cualquier operación dentro del bloque con la variable k sólo afectará a la variable local. Por lo tanto, al salir de la función OnStart (como si no fuera la única y principal función del script), descubriríamos que la variable global k sigue siendo igual a cero.

La variable local j no sólo se solapa con la variable global j sino que también es inicializada por el valor de esta última. En la cadena que contiene la descripción de j dentro de OnStart, la versión local de j todavía se está creando cuando el valor inicial para ella se lee de la versión global de j. Una vez definida con éxito la variable j local, este nombre se superpone a la versión global, y es la versión local a la que pertenecen los cambios posteriores en j.

Al final del código fuente hemos comentado el intento de declarar una variable global más, bad, en cuya inicialización se invoca el valor de la variable x. Esta cadena provoca un error de compilación, ya que la variable x es desconocida más allá de la función OnStart en la que ha sido definida.