4.14. Выражения
Глава 4 Операции и выражения
🕛 02.11.2006, 15:44
С помощью операций в программе можно выполнить определенные действия над данными. Если для реализации алгоритма решения поставленной задачи необходимо произвести несколько действий над определенными данными, то это можно осуществить последовательным выполнением операций, сохраняя при необходимости их результаты во временных переменных, а можно построить одно выражение, составленное из последовательности операций и их операндов, и получить необходимый результат. Итак, выражение можно представить как последовательность операндов, соединенных одной или более операциями, результатом выполнения которой является единственное скалярное значение, массив или хеш. Операнды, в свою очередь, сами могут быть выражениями, что приводит к заданию в программе достаточно сложных выражений, вычисление которых начинается с их синтаксического разбора.Когда компилятор начинает синтаксический разбор выражения Perl, он прежде всего выделяет в нем элементарные члены, называемые термами, а потом по определенным правилам соединяет их знаками операций, заданными в выражении, т. е. определяет последовательность выполнения операций над термами в соответствии с определенным в языке приоритетом операций. После такого разбора выражение вычисляется в соответствии с полученной последовательностью термов, соединенных знаками операций.
Таким образом, выражение можно мыслить как последовательность термов, соединенных знаками операций.
4.14.1. Термы
Чтобы понять, как вычисляется выражение, следует знать, что представляет собой терм - элементарный член арифметического или логического выражения. В Perl термом является любой литерал, любая переменная, выражение в круглых скобках, любая строка символов, к которой применена операция заключения в кавычки, а также любая функция с параметрами, заключенными в круглые скобки. В конечном итоге только терм может быть операндом вычисляемой операции в выражении.
Из всех перечисленных объектов языка, которые рассматриваются как термы, требует некоторого пояснения последний - функция с параметрами в круглых скобках.
В действительности в Perl отсутствуют истинные функции, понимаемые в смысле, например, языка С, в котором регламентировано обращение в программе к функции указанием ее имени с параметрами, заданными в круглых скобках. По существу, "функции" языка Perl являются списковыми и унарными именованными операциями, ведущими себя как функции, так как синтаксис языка позволяет заключать их параметры в круглые скобки. Вызывая в программе функцию (с заключенными или не заключенными в скобки параметрами), мы выполняем операцию (списковую или унарную именованную). Причем следует иметь в виду, что коль скоро выполняется операция, то она не только выполняет предписанные ей действия, но и возвращает определенный результат, который используется при вычислении выражения. Например, функция print возвращает Истину, если ее вывод завершен успешно, и Ложь в противном случае. Что напечатается при вычислении следующей операции?
print print "О";
Ответ - строка 01, так как первая операция print должна напечатать результат вычисления второй операции print, которая успешно выводит на экран монитора о. Следовательно, ее результат Истина, а она представляется числом 1.
При синтаксическом разборе выражений и операторов (о них в следующем разделе) как термы рассматриваются конструкции do{} и evaiu, вызовы подпрограмм и методов объектов, анонимные конструкторы массивов скаляров [ ] и хешей {}, а также все операции ввода/вывода. Все эти понятия будут рассмотрены в последующих главах книги, но мы сочли необходимым, в целях полноты изложения термов, просто привести их полный список.
4.14.2. Приоритет операций
После выделения и вычисления термов выражение разбирается с целью выявления последовательности выполнения операций в выражении: какая из них должна быть выполнена раньше другой. Это достаточно ответственная процедура, так как порядок выполнения операций существенно влияет на результат вычисления всего выражения. Например, результатом вычисления выражения
4+3*2
будет 14, если сначала выполнить сложение, а потом умножение, и ю, если сначала выполнить умножение, а потом сложение. Дабы избежать подобных двусмысленностей в языках программирования, вводится приоритет, или старшинство операций, который учитывается при вычислении выражения. Приоритет операции умножения выше приоритета сложения, а поэтому наше арифметическое выражение будет однозначно вычислено равным ю.
В табл. 4.4 представлены все операции Perl в порядке убывания их приоритета, в ней также рпределен порядок выполнения операций с одинаковым приоритетом (столбец Сочетаемость).
Таблица 4.4. Приоритет и сочетаемость операций Perl
Приоритет
Операция
Сочетаемость
1
Вычисление термов и левосторонних списковых операций
Слева направо
2
->
Слева направо
3
++ - Не сочетаются
4
* *
Справа налево
5
! ~ \ унарные + и Справа налево
6
=~ ! =
Слева направо
7
* / % х
Слева направо
8
+ - .
Слева направо
9
« »
Слева направо
10
Именованные унарные операции
Не сочетаются
11
<><=>= It gt le ge
Не сочетаются
12
== != <=> eq ne cmp
Не сочетаются
13
&
Слева направо
14
I л
Слева направо
15
&&
Слева направо
16
I I
Слева направо
17
. . ...
Не сочетаются
18 ?;
Справа налево
19 = **= += -= .= *= /= %= х= &= |= л=
Справа налево
«= »= &&=|| =
20 , =>
Слева направо
21 Правосторонние списковые операции
Не сочетаются
22 not
Справа налево
23 and
Слева направо
24 or xor
Слева направо
Некоторые операции, приведенные в табл. 4.4, требуют пояснения. И первым в этом ряду стоят операции с наивысшим приоритетом: термы и левосторонние списковые операции. Термы мы определили в предыдущем разделе и там же разъяснили, что списковые операции и унарные именованные операции рассматриваются компилятором Perl как термы, если список их параметров заключен в круглые скобки. Так как умножение имеет больший приоритет, чем унарная именованная операция sin, то следующие операции вычисляются так, как указано в комментариях к ним:
use Math::Trig; # В пакете определена константа
# pi = 3.14159265358979
sin I * pi; # sin( 1 * pi) = 1.22460635382238e-016 sin (1) * pi; f (sin 1) * pi = 2.64355906408146
В последнем выражении sin (i) рассматривается как терм, так как после имени операции первой распознаваемое лексемой стоит открывающая круглая скобка, а если это терм, - то и вычислять его надо в первую очередь, как операцию с наивысшим приоритетом.
Можно чисто визуально в тексте программы списковую или унарную именованную операцию с параметрами в круглых скобках сделать не похожей на вызов функции, поставив префикс + перед списком ее параметров:
sin +(1) * pi; # (sin 1} * pi = 2.64355906408146
Этот префикс не выполняет никакой семантической роли в программе, даже не преобразует параметр в числовой тип данных. Он просто служит для акцентирования того факта, что sin не является функцией, а представляет собой унарную именованную операцию.
Если в списковой операции отсутствуют скобки вокруг параметров, то она может иметь либо наивысший, либо самый низкий (ниже только логические операции not, and, or и хог) приоритет. Это зависит от того, где расположена операция относительно других операций в выражении: слева или справа. Все операции в выражении, расположенные слева от списковой операции (сама операция расположена справа от них), имеют более высокий приоритет относительно такой списковой операции, и вычисляются, естественно, раньше нее. Именно это имелось в виду, когда в табл. 4.4 вносилась строка с правосторонними списковыми операциями. Следующий пример иллюстрирует правостороннее расположение списковой операции:
$т = $п II print "Нуль, пустая строка или не определена!";
По замыслу программиста это выражение должно напечатать сообщение, если только значение переменной $п равно нулю, пустой строке или не определено. На первый взгляд, кажется, так и должно быть: выполнится операция присваивания и возвратит присвоенное значение. Если оно не равняется нулю, пустой строке или значение переменной $п не определено, то в булевом контексте операции логического ИЛИ (| |) оно трактуется как Истина, а поэтому второй операнд этой логической операции (операция печати) не вычисляется, так как мы помним, что логическое ИЛИ вычисляется по укороченной схеме. Однако реально переменной $т всегда будет присваиваться 1, правда сообщение будет печататься именно тогда, когда переменная $п равна нулю, пустой строке или не определена.
В чем дело? Программист забыл о приоритете правосторонних списковых операций! В этом выражении списковая операция print расположена справа от всех остальных операций, поэтому она имеет самый низкий приоритет. Выражение будет вычисляться по следующему алгоритму. Сначала будет вычислен левый операнд операции i i. Если он имеет значение Истина (переменная $п имеет значение, не равное нулю или пустой строке), то второй операнд этой операции (print) вычисляться не будет, а переменной $т будет присвоена Истина, т. е. 1. Если первый операнд вычисляется как Ложь (переменная $п равна нулю, пустой строке или не определена), то вычисляется второй операнд, выводящий сообщение на экран монитора. Но так как возвращаемым значением операции печати является Истина, то именно она и присваивается переменной $т.
Правильное решение - использовать низкоприоритетную операцию or логического ИЛИ:
$m = $n or print "Нуль, пустая строка или не определена!"; или скобками изменить порядок выполнения операций:
($m = $n) I I print "Нуль, пустая строка или не определена!";
Теперь обратимся к случаю, когда списковая операция стоит слева от других операций в выражении (левосторонняя списковая операция). В этом случае, в соответствии с табл. 4.4, она имеет наивысший приоритет и все, что стоит справа от нее, она рассматривает как список своих параметров. Рассмотрим небольшой пример. Предположим, что необходимо удалить из массива @а все элементы, начиная со второго, и вставить их в создаваемый массив @т после второго элемента. Списковая операция splice со списком параметров @а, 1 удаляет из массива @а все элементы, начиная с элемента с индексом 1, т. е. со второго элемента до конца массива, и возвращает список удаленных элементов. Ее можно использовать в конструкторе нового массива для решения поставленной задачи:
@а = ("al", "a2", "аЗ", "а4");
9m = ("mO", "ml", splice @a, 1, "т2", "тЗ"); ,
В конструкторе массива мы специально задали параметры операции splice без скобок. Если выполнить этот фрагмент и распечатать значения элементов массивов, то результат будет следующим:
@m: mO ml
@а: al тЗ а2 аЗ а4
Совершенно не то, что нам надо: в массив @т не вставлен фрагмент массива @а, да и из него самого не удалены элементы, начиная со второго. Все дело в том, что операция splice в этом выражении левосторонняя, и весь расположенный справа от нее список рассматривает как список своих параметров: @а, i, "m2", "тЗ". Ее третьим параметром должно быть число, определяющее количество удаляемых из массива элементов, начиная с элемента, индекс которого определен вторым параметром. В нашем случае третий параметр не является числовым, и функция завершается с ошибкой, возвращая Ложь. Исправить положение помогут опять скобки:
@m = ("mO", "ml", (splice @a, 1), "т2", "тЗ");
ИЛИ """' @т = ("mO", "ml", splice (@a, 1), "т2", "тЗ");
Завершая разговор о приоритете выполнения операций, следует объяснить свойство сочетаемости операций и его практическое применение. Сочетаемость важна при вычислении выражений, содержащих операции с одинаковым приоритетом, и определяет порядок их вычисления. Рассмотрим выражение:
$т += $п += 1;
Как следует его понимать? Как ($т += $п) +=1 или как $т += ($п += 1)? Ответ дает правило сочетаемости. Смотрим в табл. 4.4 и видим, что все операции присваивания сочетаются справа налево. Это означает, что сначала должно выполниться присваивание $п += 1, а потом результат увеличенной на единицу переменной $п прибавляется к переменной $т. Следовательно, это выражение эквивалентно следующему:
$т += ($п += 1) ;
Аналогично применяется правило сочетаемости и к другим операциям языка Perl:
$a>$b<$c; # Эквивалентно: ($а>$Ь)<$с; Сочетаемость: слева направо. $а**$Ь**$с; # Эквивалентно: $а**($Ь**$с); Сочетаемость: справа налево.
Скобки изменяют порядок вычислений, определяемый по правилу приоритетов и сочетаемости. Любое, заключенное в скобки подвыражение, будет вычисляться с наивысшим приоритетом, так как Perl рассматривает его как терм, имеющий наивысший приоритет.
4.14.3. Контекст
Наш разговор о выражениях Perl был бы не полным, если бы обошли стороной такое понятие, как контекст. Каждая операция и каждый терм вычисляются в определенном контексте, который определяет поведение операции и интерпретацию возвращаемого ею значения. Существует два основных контекста: скалярный и списковый. В главе 3 мы уже немного познакомились с ними, когда определяли поведение конструктора массива и переменной массива в правой части оператора присваивания. Их можно "определить" так: если для выполнения операции требуются скалярные данные, то действует скалярный контекст, если необходимы массивы скаляров, то программа находится в списковом контексте. Например, если левый операнд операции присваивания, скалярная переменная, то действует скалярный контекст, если же в левой части задан массив, хеш или фрагмент массива или хеша, то вычисления в правой части осуществляются в списковом контексте. Присваивание списку скалярных переменных также инициирует списковый контекст для вычислений, осуществляемых в правой части операции.
Некоторые операции Perl распознают контекст, в котором они вычисляются, и возвращают список в списковом контексте и скалярное значение в скалярном контексте. Обладает ли операция подобным поведением, можно всегда выяснить из ее описания в документации. Например, можно рассматривать префикс @ перед идентификатором как унарную операцию объявления массива скаляров, распознающую контекст, в котором она вычисляется. В списковом контексте она возвращает список элементов массива, а в скалярном - число элементов массива.
Некоторые операции поддерживают списковый контекст для своих операндов (в основном это списковые операции), и это также можно узнать из описания их синтаксиса, в котором присутствует СПИСОК (LIST). Например, известная уже нам операция создания фрагмента массива splice поддерживает списковый контекст для своих параметров, поэтому ее можно вызывать вот таким образом:
@s = (1, 2);
splice @m, @s; # Эквивалентно: splice @m, I, 2;
Скалярный контекст можно подразделить на числовой, строковый и безразличный. Если скалярный и списковый контекст некоторыми операциями распознается, то ни одна операция не может определить, вычисляется ли она в числовом или строковом скалярном контексте. Perl просто при необходимости преобразует возвращаемые операцией числа в строки и наоборот. В некоторых случаях вообще не важно, возвращается ли операцией число или строка. Подобное, например, происходит при присваивании переменной какого-либо значения. Переменная просто принимает подтип присваиваемого значения. Такой контекст называется безразличным.
В языке существует булевый контекст - специальный тип скалярного контекста, в котором вычисленное значение выражения трактуется только как Истина или Ложь. Как уже отмечалось ранее, в Perl нет специального булева типа данных. Здесь любая скалярная величина трактуется как Истина, если только она не равна пустой строке "" или числу о. (Строковый эквивалент ложности - строка из одного нуля "о"; любая другая строка, эквивалентная нулевому значению, считается в булевом контексте истинной, например, "оо" или "о.о".)
Другим специфическим типом скалярного контекста является void-контекст. Он не только не заботится о подтипе возвращаемого значения - скалярный или числовой, но ему и не надо никакого возвращаемого значения. Этот контекст возникает при вычислении выражения без побочного эффекта, т. е. когда не изменяется никакая переменная программы. Например, следующие выражения вычисляются в void-контексте:
$n; "текст";
Этот контекст можно "обнаружить", если установить ключ -w компилятора Perl. Тогда можно получить предупреждающее сообщение следующего типа:
Useless use of a variable in void context at D:\P\EX.PL line 3.
(Бесполезное использование переменной в, void-контексте в строке 3 программы D:\P\EX.PL) х
Завершит наш рассказ о контексте подстановочный контекст (interpolative context), в котором вычисляются операции заключения в кавычки (кроме заключения в одинарные кавычки). В этом контексте вместо любой переменной, заданной в строке, в нее подставляется значение этой переменной, а также интерпретируются управляющие последовательности.
* * *
В этой главе мы изучили практически все скалярные операции языка Perl, чуть-чуть коснулись операций сопоставления по образцу, создания ссылок и операций ввода\вывода, познакомились с основами работы со списковыми и унарными именованными операциями. Узнали, что такое выражение, а также в каком порядке вычисляются в нем операции на основе их приоритета и сочетаемости. Научились выделять термы в выражениях и выяснили, в каких контекстах могут вычисляться выражения.
Вопросы для самоконтроля
1. Какую роль выполняют операции в программе?
2. Какие основные группы операций существуют в Perl?
3. Объясните "укороченную схему" вычисления логических операций. Где она используется?
4. Что такое выражение?
5. Определите понятие терм. Что считается термом в языке Perl?
6. Что такое приоритет операций и как он применяется при вычислении выражений?
7. Когда необходимо применять свойство сочетаемости операции?
8. Объясните понятие "контекст". Какие два основных типа контекста используются в языке Perl?
Упражнения
1. Что будет отображено на экране монитора при вычислении выражения
print print 1;
2. Определите результат вычисления следующих выражений:
print "О" I I print "1"; print "О" or print "1";
3. Что будет отображено на экране монитора и каковы будут значения элементов массива @т в результате выполнения следующей операции присваивания:
@m = (print "p\n", 2, print 3, 4);
4. Определите результат выполнения следующих операторов:
$varO = 2;
$varl = 1;
$rezl = $varO ** 3 * 2 !I 4 + $varl, $varl++;
$rez2 = ($varl++, $varO ** 3 * 2 || 4 + $varl, "6");
@rez3 = ($varl++, $varO ** 3 * 2 || 4 + $varl, "6");
5. Что напечатает следующий фрагмент программы при вводе числа или строки и почему:
$input = <STDIN>; $hello = "Hello "; $hello += $input; print $hello;
6. Найдите ошибку в программе:
$first_number =34;
$second_number = 150;
if( $first_number It $second_number ) { print $first_number; }