На этом шаге мы рассмотрим приоритет операций.
После выделения и вычисления термов выражение разбирается с целью выявления последовательности выполнения операций в выражении: какая из них должна быть выполнена раньше другой. Это достаточно ответственная процедура, так как порядок выполнения операций существенно влияет на результат вычисления всего выражения. Например, результатом вычисления выражения
4 + 3 * 2
В таблице 1 представлены все операции Perl в порядке убывания их приоритета, в ней также определен порядок выполнения операций с одинаковым приоритетом (столбец Сочетаемость).
Приоритет | Операция | Сочетаемость |
---|---|---|
1 | Вычисление термов и левосторонних списковых операций | Слева направо |
2 | -> | Слева направо |
3 | ++ -- | Не сочетаются |
4 | ** | Справа налево |
5 | ! ~ \ унарные + и - | Справа налево |
6 | =~ != = | Слева направо |
7 | * / % x | Слева направо |
8 | + - . | Слева направо |
9 | << >> | Слева направо |
10 | Именованные унарные операции | Не сочетаются |
11 | <> <= >= lt gt le ge | Не сочетаются |
12 | == != <=> eq ne cmp | Не сочетаются |
13 | & | Слева направо |
14 | | ^ | Слева направо |
15 | && | Слева направо |
16 | || | Слева направо |
17 | .. ... | Не сочетаются |
18 | ?: | Справа налево |
19 | = **= += -= .= *= /= %= х= &= |= ^= <<= >>= &&= ||= | Справа налево |
20 | , => | Слева направо |
21 | Правосторонние списковые операции | Не сочетаются |
22 | not | Справа налево |
23 | and | Слева направо |
24 | or xor | Слева направо |
Некоторые операции, приведенные в таблице 1, требуют пояснения. И первым в этом ряду стоят операции с наивысшим приоритетом: термы и левосторонние списковые операции. Термы мы определили в предыдущем шаге и там же разъяснили, что списковые операции и унарные именованные операции рассматриваются компилятором Perl как термы, если список их параметров заключен в круглые скобки. Так как умножение имеет больший приоритет, чем унарная именованная операция sin, то следующие операции вычисляются так, как указано в комментариях к ним:
use Math::Trig; # В пакете определена константа # pi = 3.14159265358979 sin 1 * pi; # sin( 1 * pi) = 1.22460635382238e-016 sin (1) * pi; # (sin 1) * pi = 2.64355906408146
В последнем выражении sin (1) рассматривается как терм, так как после имени операции первой распознаваемое лексемой стоит открывающая круглая скобка, а если это терм, - то и вычислять его надо в первую очередь, как операцию с наивысшим приоритетом.
Можно чисто визуально в тексте программы списковую или унарную именованную операцию с параметрами в круглых скобках сделать не похожей на вызов функции, поставив префикс + перед списком ее параметров:
sin +(1) * pi; # (Sin 1) * pi = 2.64355906408146
Этот префикс не выполняет никакой семантической роли в программе, даже не преобразует параметр в числовой тип данных. Он просто служит для акцентирования того факта, что sin не является функцией, а представляет собой унарную именованную операцию.
Если в списковой операции отсутствуют скобки вокруг параметров, то она может иметь либо наивысший, либо самый низкий (ниже только логические операции not, and, or и хог) приоритет. Это зависит от того, где расположена операция относительно других операций в выражении: слева или справа. Все операции в выражении, расположенные слева от списковой операции (сама операция расположена справа от них), имеют более высокий приоритет относительно такой списковой операции, и вычисляются, естественно, раньше нее. Именно это имелось в виду, когда в таблице 1 вносилась строка с правосторонними списковыми операциями. Следующий пример иллюстрирует правостороннее расположение списковой операции:
$m = $n || print "Нуль, пустая строка или не определена!";
По замыслу программиста это выражение должно напечатать сообщение, если только значение переменной $n равно нулю, пустой строке или не определено. На первый взгляд, кажется, так и должно быть: выполнится операция присваивания и возвратит присвоенное значение. Если оно не равняется нулю, пустой строке или значение переменной $n не определено, то в булевом контексте операции логического ИЛИ (||) оно трактуется как Истина, а поэтому второй операнд этой логической операции (операция печати) не вычисляется, так как мы помним, что логическое ИЛИ вычисляется по укороченной схеме. Однако реально переменной $m всегда будет присваиваться 1, правда сообщение будет печататься именно тогда, когда переменная $n равна нулю, пустой строке или не определена.
В чем дело? Программист забыл о приоритете правосторонних списковых операций! В этом выражении списковая операция print расположена справа от всех остальных операций, поэтому она имеет самый низкий приоритет. Выражение будет вычисляться по следующему алгоритму. Сначала будет вычислен левый операнд операции ||. Если он имеет значение Истина (переменная $n имеет значение, не равное нулю или пустой строке), то второй операнд этой операции (print) вычисляться не будет, а переменной $m будет присвоена Истина, т.е. 1. Если первый операнд вычисляется как Ложь (переменная $n равна нулю, пустой строке или не определена), то вычисляется второй операнд, выводящий сообщение на экран монитора. Но так как возвращаемым значением операции печати является Истина, то именно она и присваивается переменной $m.
Правильное решение - использовать низкоприоритетную операцию or логического ИЛИ:
$m = $n or print "Нуль, пустая строка или не определена!";
($m = $n) || print "Нуль, пустая строка или не определена!";
Теперь обратимся к случаю, когда списковая операция стоит слева от других операций в выражении (левосторонняя списковая операция). В этом случае, в соответствии с таблицей 1, она имеет наивысший приоритет и все, что стоит справа от нее, она рассматривает как список своих параметров. Рассмотрим небольшой пример. Предположим, что необходимо удалить из массива @а все элементы, начиная со второго, и вставить их в создаваемый массив @m после второго элемента. Списковая операция splice со списком параметров @а, 1 удаляет из массива @а все элементы, начиная с элемента с индексом 1, т. е. со второго элемента до конца массива, и возвращает список удаленных элементов. Ее можно использовать в конструкторе нового массива для решения поставленной задачи:
@а = ("a1", "a2", "аЗ", "а4"); @m = ("m0", "m1", splice @a, 1, "m2", "mЗ");
В конструкторе массива мы специально задали параметры операции splice без скобок. Если выполнить этот фрагмент и распечатать значения элементов массивов, то результат будет следующим:
@m: m0 m1 @а: a1 m3 а2 аЗ а4
Совершенно не то, что нам надо: в массив @m не вставлен фрагмент массива @а, да и из него самого не удалены элементы, начиная со второго. Все дело в том, что операция splice в этом выражении левосторонняя, и весь расположенный справа от нее список рассматривает как список своих параметров: @а, 1, "m2", "m3". Ее третьим параметром должно быть число, определяющее количество удаляемых из массива элементов, начиная с элемента, индекс которого определен вторым параметром. В нашем случае третий параметр не является числовым, и функция завершается с ошибкой, возвращая Ложь. Исправить положение помогут опять скобки:
@m = ("m0", "ml", (splice @a, 1), "m2", "m3");
@m = ("m0", "m1", splice (@a, 1), "m2", "m3");
Завершая разговор о приоритете выполнения операций, следует объяснить свойство сочетаемости операций и его практическое применение. Сочетаемость важна при вычислении выражений, содержащих операции с одинаковым приоритетом, и определяет порядок их вычисления. Рассмотрим выражение:
$m += $n += 1;
Как следует его понимать? Как ($m += $n) += 1 или как $m += ($n += 1)? Ответ дает правило сочетаемости. Смотрим в таблице 1 и видим, что все операции присваивания сочетаются справа налево. Это означает, что сначала должно выполниться присваивание $n += 1, а потом результат увеличенной на единицу переменной $n прибавляется к переменной $m. Следовательно, это выражение эквивалентно следующему: $m += ($n += 1);.
Аналогично применяется правило сочетаемости и к другим операциям языка Perl:
$а>$b<$с; # Эквивалентно: ($а>$b)<$с; Сочетаемость: слева направо. $а**$b**$с; # Эквивалентно: $а**($b**$с); Сочетаемость: справа налево.
Скобки изменяют порядок вычислений, определяемый по правилу приоритетов и сочетаемости. Любое, заключенное в скобки подвыражение, будет вычисляться с наивысшим приоритетом, так как Perl рассматривает его как терм, имеющий наивысший приоритет.
На следующем шаге мы рассмотрим контекст.