Шаг 93.
Выражения. Приоритет операций

    На этом шаге мы рассмотрим приоритет операций.

    После выделения и вычисления термов выражение разбирается с целью выявления последовательности выполнения операций в выражении: какая из них должна быть выполнена раньше другой. Это достаточно ответственная процедура, так как порядок выполнения операций существенно влияет на результат вычисления всего выражения. Например, результатом вычисления выражения

    4 + 3 * 2
будет 14, если сначала выполнить сложение, а потом умножение, и 10, если сначала выполнить умножение, а потом сложение. Дабы избежать подобных двусмысленностей в языках программирования, вводится приоритет, или старшинство операций, который учитывается при вычислении выражения. Приоритет операции умножения выше приоритета сложения, а поэтому наше арифметическое выражение будет однозначно вычислено равным 10.

    В таблице 1 представлены все операции Perl в порядке убывания их приоритета, в ней также определен порядок выполнения операций с одинаковым приоритетом (столбец Сочетаемость).

Таблица 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 рассматривает его как терм, имеющий наивысший приоритет.

    На следующем шаге мы рассмотрим контекст.




Предыдущий шаг Содержание Следующий шаг