Порядок выполнения операций

Содержание:

Вступление

Наверняка, вам известно еще со школы, что от порядка выполнения арифметических операций будет зависеть результат вычислений. Например, чему равно выражение 2 + 2 * 2? Конечно же, вы ответите, что оно равно 6. Но если бы мы слепо выполнили операции слева направо, то у нас получилось бы 8. Так вот, помимо арифметических операций нам также доступны операции сравнения и логические операции, а у них тоже есть свой порядок выполнения. Если не учитывать этот порядок, то может получиться не тот результат вычислений, который вы ожидали. Помните, вычислять будете не вы, а компьютер и не по вашим представлениям, а по своим правилам. Данная тема очень простая, порядок большинства операций и так интуитивно понятен, но не будет лишним ознакомится с некоторыми нюансами.
Ни в коем случае не нужно запоминать каждое слово из этой статьи, лучше периодически возвращайтесь сюда, когда у вас возникнут проблемы. Воспринимайте эту статью как справочник.

Порядок выполнения

Давайте вспомним, что могут содержать в себе выражения:
  • литералы и переменные в основном примитивных типов. Напоминаю, что литералы - это числа (0, -2, 0.5), строки ("текст", "for the ...", "my age 24") и логические значения (true и false) посреди кода.
  • функции и массивы. Массивы мы еще не изучали, но не пугайтесь они отыгруют малозначительную роль в этом уроке.
  • арифметические операции (+, -, *, /), операция конкатенации строк (+), операции сравнения (==, !=, >, >=, <, <=) и логические операции (not, and, or).
  • другие выражения в скобках.
Вычисление выражений можно разделить на две большие части. В первой части, из переменных и массивов достаются значения, а также выполняются функции. Во второй части, выполняются все операции и выражения в скобках. Так как вторая часть более простая и при этом более важная, то начнем с неё.

Операции

Порядок выполнения операций:
1. Выражения в скобках. (рекурсивно)

2. Унарные операции:
2.1. унарный минус (-)
2.2. логическое НЕТ (not)

3. Арифметические операции и конкатенация строк:
3. 1. умножение (*) и деление (/)
3. 2. сложение (+) и вычитание (-)
3. 3. конкатенация строк (+)

4. Операции сравнения:
4.1. меньше (<), меньше-равно (<=), больше (>), больше-равно (>=)
4.2. равно (==), не равно (!=)

5. Логические операции:
5.1. логическое ИЛИ (or)
5.2. логическое И (and)

Операции с одинаковым приоритетом

Как видите некоторые операции обладают одинаковым приоритетом, например, сложение (+) и вычитание (-), или умножение (*) и деление (/)... Операции с одинаковым приоритетом выполняются по очереди слева направо. Например, в выражении 2 + 5 * 4 / 2 - 6 будет такой порядок выполнения операций:
  1. 5 * 4 = 20
  2. 20 / 2 = 10
  3. 2 + 10 = 12
  4. 12 - 6 = 6
Умножение и деление выполняется раньше сложения и вычитания, так как у них выше приоритет. Также умножение выполняется раньше деления, но только потому, что оно стоит левее. Можете переставить их местами и деление выполнится раньше умножения. То же самое касается и сложения с вычитанием, у них также одинаковый приоритет.
Когда несколько действий в математике обладают одинаковым приоритетом, это значит, что от их перестановки результат не меняется. В программировании так бывает не всегда. Например, перестановка умножения и деления может привести к нелогичным на первый взгляд последствиям. Например:
function Test takes nothing returns nothing
    local integer a = 2 * 2 / 3
    local integer b = 2 / 3 * 2

    call BJDebugMsg("a = " + I2S(a))
    call BJDebugMsg("b = " + I2S(b))
endfunction
Как думаете, чему будут равны локальные переменные "a" и "b"? Если вы не знаете, как работают целые числа, то ответ может вас шокировать:
a = 1
b = 0
А по правилам математики обе переменные должны быть равны 1.33333(3). Не волнуйтесь, такая ерунда случается только с типом данных integer. На самом деле, в этом примере всё логично и правильно, а в следующем уроке мы узнаем почему.
Также я встретился с непонятным мне багом. Выражение 5 > 4 == 5 < 7 корректно работало в обычном редакторе карт. Сначала выполнялись операции больше (>) и меньше (<), а затем выполнялась операция равно (==). Но в редакторе JNGP мне выдало ошибку компиляции, как будто там все операции сравнения имеют одинаковый приоритет. Вполне возможно, что это так и я просто еще не наткнулся на этот момент в документации.

Унарные и бинарные операции

Возможно, вас смутило название операции "унарный минус", сейчас я постараюсь всё объяснить. Все операции в джассе можно разделить на унарные и бинарные. Если взглянуть на этимологию этих слов, то они будут значить что-то типа "один" (унарный) и "два" (бинарный). Это очень неточная формулировка, но зато она полностью передает суть. Унарными называют те операции, которые выполняются над одним значением. Бинарными же называют те операции, которые выполняются над двумя значениями, а это по сути все операции кроме двух (not и "-"). При этом not бывает только унарным, а "-" может быть как унарным, так и бинарным. Унарные операции выполняются раньше бинарных операций. Поэтому not всегда будет выполняться раньше остальных операций, кроме унарного минуса, порядок выполнения которого будет зависеть от контекста. Пример:
function Test takes nothing returns nothing
    local integer a = 2
    local integer b = 4 - 2 * -a
    call BJDebugMsg(I2S(b))
endfunction
При вызове функции Test будет такой порядок вычисления:
  1. сначала сработает унарный минус, он перевернет значение переменной "а" с 2 на -2.
  2. затем умножение 2 * (-2) = -4
  3. и на конец вычитание 4 - (-4) = 8
Унарный минус сработал раньше всех арифметических операций, а бинарный сработал после умножения, как ему и положено.

Выражения в скобках

Ничего нового для вас я не открою, тут всё как в математике. Операции в скобках выполняются раньше остальных, а если и в скобках есть скобки, то операции во вложенных скобках выполняются еще раньше и т.д. Пример:
4 * (3 * (2 + 1))
Порядок выполнения:
  1. сначала выполняется операции во вложенных скобках 2 + 1 = 3.
  2. затем во внешних скобках 3 * 3 = 9
  3. и на конец в основном выражении 4 * 9 = 36
Скобки действуют на все операции. Иногда бывает полезно использовать скобки и для логических операций.

Переменные, функции, массивы

Теперь поговорим о первой части вычислений. Операциям для работы нужны значения, а их нужно достать из переменных, массивов и функций (если они есть). Поэтому всё о чем мы сейчас поговорим, будет выполняться раньше всех операций.
С переменными все очень просто, мы даже не будем разбираться. Давайте будем считать, что в самом начале вычислений из них достаются значения. Не вникая в то правда это или нет и в каком порядке это происходит. На результат это никак не повлияет.
С функциями и массивами всё намного сложнее. Если вкратце, то в начале вычислений, слева направо (игнорируя скобки) выполняются все функции и достаются значения с массивов. Но для того, чтобы достать значение с массива нужен индекс, также некоторые функции могут принимать несколько значений на вход. На месте аргументов и индексов могут быть выражения, результат которых вычисляется перед тем как выполнить функцию или достать значение с массива. Давайте разберемся со всем этим на примере функций.
В выражении можно использовать только функцию которая что-то возвращает. Давайте напишем две такие функции, которые ничего не принимают, а только выводят на экран и возвращают числа один и два:
function One takes nothing returns integer
    call BJDebugMsg("1")
    return 1
endfunction

function Two takes nothing returns integer
    call BJDebugMsg("2")
    return 2
endfunction
Функция One выводит на экран "1" и возвращает 1, а функция Two выводит на экран "2" и возвращает 2. Теперь напишем такую функцию:
function Test takes nothing returns nothing
    local integer a = One() + Two()
    call BJDebugMsg(I2S(a))
endfunction
После запуска функции Test на экран будут выведены три сообщения:
1
2
3
При запуске функции Test слева направо выполнились функции One и Two. В результате их выполнения получилось выражение 1 + 2, а на экран были выведены сообщения "1" и "2". Затем в выражении 1 + 2 выполнилась операция сложения (+), в результате чего получилось число 3, которое попало в переменную "а". На следующей строчке выполнился уже знакомый нам вывод на экран значения из переменной "a", то есть число 3.
Переходим к более сложным примерам, теперь функции будут принимать одно значение. Напишем такую функцию:
function I takes integer i returns integer
    call BJDebugMsg(I2S(i))
    return i
endfunction
Эта функция принимает целое число, выводит его на экран и возвращает его без изменений. Она нужна нам для демонстрации порядка выполнения функций. Теперь запустите такую функцию:
function Test takes nothing returns nothing
    local integer a = I(1) + I(1 + 2 + 3)
    call BJDebugMsg(I2S(a))
endfunction
На экран будут выведены три сообщения:
1
6
7
Как вы уже знаете, на место аргументов мы можем писать выражения и вызов функции I(1 + 2 + 3) это именно тот случай. Когда очередь доходит до функции, на месте аргумента которой стоит выражение, сначала вычисляется результат этого выражения, а уже потом он отправляется внутрь функции, где выполняется код до ближайшего return. То же касается и массива, на месте индекса которого стоит выражение. Сначала будет вычислен результат выражения, а потом по получившемуся индексу будет получено значение из массива. Все такие выражения вычисляются отдельно слева направо непосредственно перед вызовом функции. Напишем еще одну функцию Triplet и перепишем функцию Test:
function Triplet takes integer a, integer b, integer c returns integer
    local integer i = a + b + c
    call BJDebugMsg(I2S(i))
    return i
endfunction

function Test takes nothing returns nothing
    local integer a = I(1) + Triplet(I(2), I(3), I(4)) * I(5)
    call BJDebugMsg(I2S(a))
endfunction
После запуска функции Test на экран будут выведено семь сообщений:
1
2
3
4
9
5
46
Сперва выполнилась функция I(1) и в результате на экран вывелось "1". Затем очередь дошла до Triplet(I(2), I(3), I(4)), но так как внутри еще три функции, то сперва выполнились они (по порядку, слева направо) и на экран попало еще три сообщения "2", "3", "4". Потом их результат передался внутрь функции Triplet и в результате их сложения получилось число 9, которое попало на экран, а также было возращено. Затем выполнилась функция I(5) в результате выполнения которой на экран попало "5". А под конец на экран был выведен результат всего выражения 1 + 9 * 5 = 46, который перед этим попал в переменную “a”. Заметьте, что операция умножения никак не повлияла на порядок выполнения функций.
На этом, пожалуй, всё. Объяснить зачем вам это нужно, скорее всего, я не смогу. Порядок выполнения операций имеет больше практического смысла, чем порядок выполнения функций. Особенно для новичков, которые хотя бы раз в жизни совершат ошибку в логическом выражении, но вряд ли напишут такое выражение, в котором функции будут конфликтовать между собой.


Views: 52

There are no comments yet