Conceptos básicos

Antes de pasar a los grupos específicos de operadores, debemos introducir algunos conceptos básicos que son inherentes a todos los operadores y afectan a su aplicabilidad y comportamiento en un contexto determinado.

En primer lugar, por la cantidad de operandos requeridos, los operadores pueden ser unarios y binarios. Como se deduce de los nombres, los unarios procesan un operando, mientras que los binarios procesan dos. En el caso de los binarios, el operador se coloca siempre entre los operandos. Entre los unarios, hay operadores que deben colocarse delante del operando y otros que deben colocarse detrás. Por ejemplo, el operador unario menos ('-') permite invertir el signo del valor:

int x = 10;
int y = -x;  // -10

Al mismo tiempo, existe un operador binario para la resta que utiliza el mismo carácter, '-'.

int z = x - y// 10 - -10 -> 20

La elección de un operador (acción) correcto por parte del compilador en un contexto específico viene determinada por el contexto de uso en la expresión.

A cada operador se le asigna una prioridad. Esta determina el orden en que se calcularán los operadores en expresiones complejas en las que haya varios operadores. Los operadores de más alta prioridad se calculan primero, mientras que los de menor prioridad lo hacen en último lugar. Por ejemplo, en la expresión 1 + 2 * 3 hay dos operaciones (suma y multiplicación) y tres operandos. Dado que la multiplicación tiene una prioridad superior a la de la suma, primero se hallará el producto de 2 * 3 y luego se le sumará uno.

Más adelante facilitaremos la tabla completa de operaciones con prioridades.

Además, cada operador se caracteriza por la asociatividad, que puede ser izquierda o derecha y determina el orden en que se ejecutan los operadores sucesivos que tienen la misma prioridad. Por ejemplo, la expresión 10 - 7 - 1 puede calcularse en teoría de dos maneras:

  • Restar 7 a 10 y luego restar 1 al 3 resultante, lo que da 2; o bien,
  • Restar 1 a 7, lo que da 6, y luego restar 6 a 10, lo que da 4.

En el primer caso, los cálculos se realizaron de izquierda a derecha, lo que se corresponde con la asociatividad izquierda; dado que la operación de resta es, de hecho, asociativa izquierda, la primera respuesta es correcta.

La segunda opción de cómputo se corresponde con la asociatividad derecha y no se utilizará.

Veamos otro ejemplo en el que intervienen simultáneamente la prioridad y la asociatividad: 11 + 5 * 4 / 2 + 3. Ambos tipos de operaciones, es decir, la suma y la multiplicación, se ejecutan de izquierda a derecha. Si las prioridades no fueran diferentes, obtendríamos 35, aunque 24 es la respuesta correcta. Cambiando a la asociatividad derecha nos daría 14.

Para redefinir explícitamente las prioridades en las expresiones, se pueden utilizar, por ejemplo, paréntesis: (11 + 5) * 4 / (2 + 3). Lo que se encierra entre paréntesis se calcula antes, y el resultado intermedio se sustituye en la expresión que se utilizará en otras operaciones. Los grupos entre paréntesis pueden anidarse. Para obtener más detalles, consulte la sección Agrupación con paréntesis.

Un operador asociativo derecho puede ejemplificarse con el operador unario de negación lógica, '!'. En esencial, su tarea consiste en hacer true a partir de false, y viceversa. Al igual que con otros operadores unarios, la asociatividad significa en este contexto en qué lado del operador debe colocarse el operando. El símbolo '!' se coloca delante del operando, es decir, el operando está a la derecha.

int x = 10;
int on_off = !!x;  // 1

En este caso, la negación lógica se realiza dos veces: la primera vez con respecto a la variable x ('!' a la derecha) y la segunda vez con respecto al resultado de la negación anterior ('!' a la izquierda). Esta doble negación permite transformar cualquier valor distinto de cero en 1 al convertirlo en bool y viceversa.

La tabla final de operaciones también mostrará asociatividad.

Para terminar, el último punto, pero no por ello menos importante, en materia de procesamiento de expresiones es el orden de cálculo de los operandos. Debe distinguirse de la prioridad que pertenece a la operación, no a los operandos. El orden de cálculo de los operandos de las operaciones binarias no se define explícitamente, lo que da espacio al compilador para optimizar el código y mejorar su eficacia. El compilador sólo garantiza que los operandos se calcularán antes de ejecutar la operación.

Existe un conjunto limitado de operaciones para las que se define el orden de evaluación de los operandos. En particular, para la lógica AND ('&&') y OR ('||'), este es de izquierda a derecha, y la parte derecha puede omitirse si no afecta en nada debido al valor de la parte izquierda. Pero en que respecta al operador condicional ternario '?:', el orden es aún más intrincado, ya que se calculará una u otra rama al computar las primeras condiciones, dependiendo de su veracidad. Consulte las secciones siguientes para obtener más información.

El orden de evaluación de los operandos queda ilustrado por la situación en la que hay varias llamadas a una función en la expresión. Por ejemplo, utilicemos 4 funciones en la siguiente expresión:

a() + b() * c() - d()

Las reglas de prioridad y asociatividad sólo se utilizarán para los resultados intermedios de llamar a estas funciones, mientras que las llamadas en sí pueden ser generadas por el compilador en cualquier orden que «considere necesario» en función de las características del código fuente y la configuración del compilador. Por ejemplo, las funciones b y c que intervienen en la multiplicación pueden llamarse en el orden [b(), c()] o al revés, [c(), b()]. Si las funciones durante su ejecución pueden afectar a los mismos datos, su estado será ambiguo al calcular la expresión.

Un problema similar puede observarse al trabajar con arrays y operadores de incremento (véase Incremento y decremento).

int i = 0;
int a[5] = {01234};
int w = a[++i] - a[++i];

Dependiendo de si se calcula en primer lugar el operando de diferencia izquierdo o derecho, podemos obtener -1 (a[1] - a[2]) o +1 (a[2] - a[1]). Dado que el compilador MQL5 no deja de mejorar, nada garantiza que el resultado actual (-1) se mantenga en el futuro.

Para evitar posibles problemas se recomienda no utilizar un operando repetidamente si ya ha sido modificado en la misma expresión.

Normalmente, en todas las expresiones puede haber operandos de distintos tipos. Esto lleva a la necesidad de convertirlos en un determinado tipo común antes de realizar cualquier acción con ellos. Si no hay conversión explícita de tipos, MQL5 realiza la conversión implícita cuando es necesario. Además, las reglas de conversión son diferentes para las distintas combinaciones de tipos. La conversión de tipos explícita e implícita se examina en la sección correspondiente.