Perl операторы сравнения. WDH: PERL - Операторы

Для сравнения скалярных данных или значений скалярных переменных язык Perl предлагает набор бинарных операций, вычисляющих отношения равенства, больше, больше или равно и т. п. между своими операндами, поэтому эту группу операций еще называют операциями отношения. Для сравнения числовых данных и строковых данных Perl использует разные операции. Все они представлены в табл. 4.1.

Таблица 4.1. Операции отношения

Операция Числовая Строковая Значение
Равенство == eq Истина, если операнды равны, иначе ложь
Неравенство != ne Истина, если операнды не равны, иначе ложь
Меньше < lt Истина, если левый операнд меньше правого, иначе ложь
Больше > gt Истина, если левый операнд больше правого, иначе ложь
Меньше или равно <= le Истина, если левый операнд больше правого или равен ему, иначе ложь
Больше или равно >= ge Истина, если правый операнд больше левого или равен ему, иначе ложь
Сравнение <=> cmt 0, если операнды равны
1, если левый операнд больше правого
-1, если правый операнд больше левого

Результатом операций отношения (кроме последней сравнения) является Истина, значение 1, или Ложь, пустая строка "".

Замечание
Значение истина в арифметических операциях интерпретируется как число 1, а в строковых как строка "1". Значение ложь в арифметических операциях интерпретируется как число 0, а в строковых как пустая строка " ".

Числовые операции отношения

Числовые операции отношения применяются к числовым данным, причем один или оба операнда могут задаваться строкой, содержащей правильное десятичное число. Если в числовых операциях отношения какой-либо из операндов задан строкой, содержимое которой не представляет правильное десятичное число, то его значение принимается равным о и отображается предупреждение о некорректном использовании операнда в числовой операции отношения (если включен режим отображения предупреждений интерпретатора Perl). Смысл операций отношения для числовых данных соответствует обычным математическим операциям сравнения чисел (пример 4.7).

123 > 89; # Результат: 1 (истина)

123 < 89; # Результат: "" (ложь)

123 <= 89; # Результат: "" (ложь)

89 <= 89; # Результат: 1 (истина)

23 >= 89; # Результат: "" (ложь)

23 <=> 89; # Результат: -1 (правый операнд больше левого)

89 <=> 23; # Результат: 1 (правый операнд больше левого)

Применение числовых операций сравнения не представляет сложности, однако при сравнении на равенство десятичных чисел с плавающей точкой могут проявиться эффекты округления, связанные с ограниченным количеством значащих цифр в мантиссе представления действительных чисел в компьютере и приводящие к "неправильной", с точки зрения пользователя работе операций сравнения. Пример 4.8 иллюстрирует подобную ситуацию.

#! peri -w
$z = 0.7;

$zz = 10+0.7-10; # Переменная $zz содержит число 0.7

# Печать строки "z равно zz", если равны значения переменных $z и $zz print "z равно zz\n" if ($z == $zz);

При попытке выполнить пример 4.8 мы с удивлением обнаружим, что наша программа ничего не напечатает. В чем же дело? Разгадка лежит в операторе вычисления значения переменной $zz. При выполнении арифметических операций в результате ошибок округления получается значение 0.699999999999999 (можете вставить оператор печати переменной $zz и убедиться в этом), хотя и близкое к 0.7, но не равное ему в точности. Следовательно, операция сравнения отработала верно!

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

abs($a-$b) <= 0.00000001; # Проверка равенства

Строковые операции отношения

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

"A" It "a"; # Результат: истина (код "А" - \101, код "а" - \141)
"a" It "aa";
# с кодом \000, который меньше кода \141
# второго символа "а" строки правого операнда)
"a" It "a "; # Результат: истина (к строке "а" добавляется символ
# с кодом \000, который меньше кода \040
# замыкающего пробела строки правого операнда)
"12" It "9"; # Результат: истина (код "1" - \061, код "9" - \071)
"9" eq 09"; # Результат: ложь (код " " - \040, код "О" - \060)

Обратим внимание на две последние операции сравнения строковых литералов. Содержимое их операндов может быть преобразовано в правильные числа, и поэтому к ним применимы аналогичные числовые операции отношения. Однако их результат будет существенно отличаться от результата выполнения строковых операций отношения. При использовании операции < в предпоследнем выражении результат будет Ложь, а если в последнем выражении применить операцию ==, то результат будет Истина. Об этом всегда следует помнить, так как Perl автоматически преобразует символьные данные в числовые там, где это необходимо.

В этой части речь пойдет об условных операторах и циклах.

Условные операторы

Как всегда, начнем сразу с примеров.

$a = shift ;
if ($a > 10 ) {
print "a > 10\n " ;
}

Программистам на C-подобных языках эта конструкция должна быть до боли знакома, так что комментировать тут особо нечего. Скажу лишь, что в отличие от Си, опустить фигурные скобки тут нельзя. Точнее говоря, способ есть, но о нем чуть ниже. Конструкции if-else и if-else-if-… на языке Perl выглядят следующим образом:

$a = shift ;
if ($a > 10 ) {
print "a > 10\n " ;
} else {
print "a <= 10\n " ;
}

if ($a > 0 ) {
# do something
} elsif ($a == 0 ) {
# do something
} else {
# do something else
}

В общем, все все как мы и ожидаем за одним исключением. Никакого «else if» в Perl нет — вместо этого следует использовать elsif и только его. Elsif может повторяться несколько раз, else использовать не обязательно, фигурные скобки опустить нельзя.

В отличие от других языков программирования, в Perl также предусмотрен оператор unless. Следующие два куска кода делают одно и то же:

unless ($a == 0 ) {
# "... если только a не равно нулю"
...
}

if ($a != 0 ) {
# то же самое
# ...
}

Unless можно использовать в сочетании с elsif и else, однако никакого «elsunless» в Perl нет.

В Perl есть возможность уменьшить объем кода, если в случае выполнения условия должна быть выполнена только одна строка кода. Следующие примеры делают одно и то же:


if ($a > $b ) {
exit 1 ;
}


unless ($b == $c ) {
exit 2 ;
}

# если условие истинное - завершить скрипт с кодом 1
exit 1 if ($a > $b ) ;
# если b == c, продолжить выполнение скрипта
exit 2 unless ($b == $c ) ;

При этом скобки в последнем примере можно не использовать:

# если условие истинное - завершить скрипт с кодом 1
exit 1 if $a > $b ;

Если вы пишите на Java/PHP или другом Си-подобном языке, такая конструкция скорее всего будет для вас непривычной, но на практике она действительно удобна. В русском языке мы ведь тоже обычно говорим «завершить программу, если …», а не «если …, то …».

Также, как в C/C++ и PHP, в Perl имеется тернарный оператор?: . Работает он аналогично конструкции if-else, только внутри выражений:

if ($a > $b ) {
$a = $a / $b ;
} else {
$a = $b / $a ;
}

# аналогичный код, использующий оператор "знак вопроса"
# одна строчка кода вместо пяти
$a = $a > $b ? $a / $b: $b / $a ;

Также хотелось бы сказать пару слов об операторах сравнения. Операторы сравнения в языке Perl делятся на две группы — производящие сравнение чисел и сравнение строк.

Как вы помните, скаляры в Perl можно интерпретировать либо как строки, либо как числа. Например, число 123 больше числа 45, однако строка «123» меньше строки «45». Вот для чего потребовалось несколько групп операторов сравнения. Сравнение строк в Perl производится таким же образом, как и в других современных языках программирования, потому я надеюсь, что тут вопросов не возникает.

Циклы for, foreach, while/until, do..while/until

Цикл for прекрасно знаком всем программистам:

# вывести строку "0 1 2 3 4"
for ($i = 0 ; $i < 5 ; $i ++ ) {
print "$i " ;
}

В круглых скобках через точку с запятой записывается:

  1. Код, выполняемый перед началом цикла.
  2. Условие, проверяемое перед началом (а не в конце, как думают многие) каждой итерации. Если оно ложно, выполнение цикла завершается.
  3. Код, выполняемый после каждой итерации.

Как и в случае с условными операторами, в циклах опускать фигурные скобки нельзя (для этого также есть специальная форма записи — см ниже).

Цикл foreach должен быть хорошо знаком программистам на PHP:

@arr = (0 , 1 , 2 , 3 , 4 ) ;
# вывести строку "0 1 2 3 4"
foreach $i (@arr ) {
print "$i " ;
}

Тело цикла foreach выполняется для каждого элемента массива, указанного в скобках. Важная особенность foreach — в переменную $i не копируется элемент массива @arr, как думают многие . Переменная $i в теле цикла — это и есть сам элемент массива . Таким образом, следующий код увеличивает на единицу значение каждого элемента массива @arr:

$i = 19880508 ;
foreach $i (@arr ) {
$i ++;
}
# $i по прежнему равен 19880508

Также foreach можно использовать для работы с хэшами:

%hash = (
aaa => 1 ,
bbb => 2 ,
) ;
# функция keys возвращает массив, содержащий все ключи хэша
foreach $k (keys %hash ) {
print "$k => $hash{$k}\n " ;
}

В Perl цикл foreach имеет короткую форму:

# если Вы забыли, что делает оператор qw,
# вернитесь к первой части "основ программирования на Perl"
for $i (qw/1 2 3/ ) {
print "$i " ;
}

То есть фактически везде вместо foreach можно писать просто for. Perl не перепутает такую запись с настоящим циклом for, потому что в последнем нужно писать точки с запятой и так далее.

Циклы while, until и do работают точно так же, как и в C++ или Pascal/Delphi:

# выводим "1 2 3 4 5" четырьмя разными способами

$i = 0 ;
while ($i < 5 ) { # пока $i меньше пяти
print ++ $i . " " ;
}
print "\n " ; # новая строка

$i = 0 ;
until ($i == 5 ) { # пока $i не станет равно пяти
print ++ $i . " " ;
}
print "\n " ;

$i = 0 ;
do {
print ++ $i . " " ;
} while ($i < 5 ) ; # проверка в конце цикла
print "\n " ;

$i = 0 ;
do {
print ++ $i . " " ;
} until ($i == 5 ) ;
print "\n " ;

По аналогии с операторами if и unless, существует сокращенная форма записи циклов:

$i = 0 ;
print ++ $i . " " while ($i < 5 ) ;
print "\n " ;

$i = 0 ;
print ++ $i . " " until ($i == 5 ) ;
print "\n " ;

print "$_ " for (qw/1 2 3 4 5/ ) ; # можно и foreach(qw/1 2 3 4 5/);
print "\n " ;

Обратите внимание на последний цикл foreach. Мы ведь помним, что это сокращенная запись foreach, а не цикл for, верно? Последний кстати не имеет короткой записи. Так вот, здесь не было указано имя переменной, с помощью которой мы будем обращаться к элементам массива. В сокращенной записи foreach ее нельзя использовать. В этом случае используется специальная переменная — $_. Следующий пример также вполне рабочий:

for (qw/1 2 3 4/ ) {
print "$_" ;
}

Его, кстати, можно переписать следующим образом.

И узнали, как числа и строки конвертируются друг в друга на лету. Мы даже мельком взглянули на условное выражение if , но пока что не узнали, как сравнить два скаляра. Об этом пойдет речь в этой части.

Если у нас есть две переменные $x и $y, можно ли их сравнить? Равны ли 1, 1.0 и 1.00? А как насчет "1.00"? Что больше - "foo" или "bar"?

Два набора операторов сравнения

В Perl существует два набора операторов сравнения. Так же, как с уже изученными нами бинарными операторами сложения (+), конкатенации (.) и повторения (x), здесь тоже оператор определяет, как ведут себя операнды и как они сравниваются.

Вот эти два набора операторов:

Числовое Строковое Значение == eq равно!= ne не равно < lt меньше > gt больше <= le меньше или равно >= ge больше или равно

Операторы слева сравнивают числовые значения, а справа (в средней колонке) сравнивают значения, основываясь на ASCII таблице или на текущей локали.

Рассмотрим несколько примеров:

Use strict; use warnings; use 5.010; if (12.0 == 12) { say "TRUE"; } else { say "FALSE"; } В этом простейшем случае Perl выведет TRUE, так как оператор == сравнивает два числа, так что Perl"у не важно, записаны ли они как целые числа, или как числа с плавающей точкой.

В следующем сравнении ситуация немного интереснее

"12.0" == 12

это выражение также истинно, ведь оператор Perl"а == конвертирует строку в число.

2 < 3 истинно, так как < сравнивает два числа. 2 lt 3 также истинно, ведь 2 находистя перед 3 в таблице ASCII. 12 > 3 очевидно, истинно. 12 gt 3 вернет FALSE

Возможно, с первого взгляда кому-то это покажется неожиданным, но если подумать, Perl ведь сравнивает строки посимвольно. Так что он сравнивает "1" и "3", и раз они отличаются и "1" стоит перед "3" в таблице ASCII, на этом этапе Perl решает, строковое значение 12 меньше, чем строковое значение 3.

Всегда нужно быть уверенным, что сравниваешь значение именно так, как нужно!

"foo" == "bar" будет истинно

Также это выдаст предупреждение, если(!) предупреждения включены с помощью use warnings . Причина его в том, что мы используем две строки как числа в числовом сравнении ==. Как упоминалось в предыдущей части, Perl смотрит на строку, начиная с левого конца, и конвертирует ее в число, которое там находит. Поскольку обе строки начинаются с букв, они будут конвертированы в 0. 0 == 0, так что выражение истинно.

С другой стороны:

"foo" eq "bar" ложно

Так что всегда нужно быть уверенным, что сравниваешь значение именно так, как нужно!

То же будет при сравнении

"foo" == "" будет истинно

"foo" eq "" будет ложно

Результаты в этой таблице могут пригодиться:

12.0 == 12 ИСТИНА "12.0" == 12 ИСТИНА "12.0" eq 12 ЛОЖЬ 2 < 3 ИСТИНА 2 lt 3 ИСТИНА 12 > 3 ИСТИНА 12 gt 3 ЛОЖЬ! (внимание, может быть неочевидно с первого взгляда) "foo" == "" ИСТИНА! (Выдает предупреждение, если использована прагма "warnings") "foo" eq "" ЛОЖЬ "foo" == "bar" ИСТИНА! (Выдает предупреждение, если использована прагма "warnings") "foo" eq "bar" ЛОЖЬ

И наконец пример, когда можно попасть в ловушку, получив некоторые данные от пользователя, и, аккуратно отрезав перевод строки в конце, проверить, не является ли строка пустой.

Use strict; use warnings; use 5.010; print "input: "; my $name = ; chomp $name; if ($name == "") { # неверно! здесь нужно использовать eq вместо == ! say "TRUE"; } else { say "FALSE"; }

Если запустить этот скрипт и ввести "abc", мы получим ответ TRUE, так как perl решил, что "abc" это то же, что и пустая строка.

6.5.1. Простые операторы

Простой оператор в PERLе — это выражение, которое может иметь единственный модификатор. Каждый простой оператор должен закачиваться точкой с запятой, если только он не является последним оператором в блоке; в этом случае точка с запятой может быть опущена. Существует пять модификаторов простых операторов:

$count = 5; print "$count\n" if $count; print "$count\n" while $count--; @people = qw/Анна Борис Виктор/; print "$_\n" foreach @people;

Мы можем применять модификаторы не только к простым операторам, но и к блокам. Для этого перед блоком нужно поставить ключевое слово do :

Do { $line = ; ... } until $line eq ".\n";

Такой блок всегда исполняется хотя бы раз до проверки условия. Обратите внимание, что в таких конструкциях не работают операторы управления циклом, поскольку модификаторы не имеют меток.

Конструкция do БЛОК (без модификатора) также используется в PERLе: она позволяет превратить блок в выражение и возвращает значение последнего оператора в этом блоке.

6.5.2. Составные операторы

Составные операторы состоят из блоков , заключенных в фигурные скобки. Напомним, что, в отличие от языков C или Java, фигурные скобки в составных операторах обязательны, даже если в них заключен только один оператор. PERL содержит следующие составные операторы:

6.5.2.1. Условный оператор if

Условный оператор if позволяет проверить определенное условие и, в зависимости от его истинности, выполнить ту или иную последовательность операторов. Он имеет следующие формы:

if (выражение ) БЛОК1 if (выражение ) БЛОК1 else БЛОК2 if (выражение1 ) БЛОК1 elsif (выражение2 ) БЛОК2 ... else БЛОКn

выражение истинно, то выполняется БЛОК1 ; если оно ложно, то управление передается оператору, следующему за if .

выражение истинно, то выполняется БЛОК1 ; если оно ложно, то выполняется БЛОК2 .

Третья форма оператора означает, что если выражение истинно, то выполняется БЛОК1 ; если оно ложно и истинно выражение2 , то выполняется БЛОК2 и т. д. Если ложны выражения во всех ветках оператора, то выполняется БЛОКn .

Следующий пример присваивает переменной $m наибольшее из трех чисел $x , $y , $z:

If ($x >= $y) { $m = ($x >= $z) ? $x: $z; } else { $m = ($y >= $z) ? $y: $z; }

6.5.2.2. Условный оператор unless

Условный оператор unless if . Он имеет две формы:

unless (выражение ) БЛОК1 unless (выражение ) БЛОК1 else БЛОК2

Первая форма оператора означает, что если выражение ложно, то выполняется БЛОК1 ; если оно истинно, то управление передается оператору, следующему за unless .

Вторая форма оператора означает, что если выражение ложно, то выполняется БЛОК1 ; если оно истинно, то выполняется БЛОК2 .

Предыдущий пример можно переписать так:

Unless ($x < $y) { $m = ($x >= $z) ? $x: $z; } else { $m = ($y >= $z) ? $y: $z; }

6.5.2.3. Оператор цикла while

Оператор цикла while имеет две формы:

while (выражение ) БЛОК while (выражение ) БЛОК continue БЛОК1

Оператор while

  1. Вычисляется значение выражения
  2. Выполняется БЛОК .
  3. continue , то выполняется БЛОК1 .

$i = 0; while ($i++ < 10) { print "$i\n" }

Пользуясь второй формой оператора while , его можно записать так:

$i = 1; while ($i <= 10) { print "$i\n" } continue { $i++ }

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

While (1) { print "Привет всем!" }

6.5.2.4. Оператор цикла until

Оператор цикла until логически противоположен оператору while и также имеет две формы:

until (выражение ) БЛОК until (выражение ) БЛОК continue БЛОК1

Оператор until выполняется следующим образом:

  1. Вычисляется значение выражения . Если оно истинно, то управление передается оператору, следующему за данным оператором.
  2. Выполняется БЛОК .
  3. Если оператор содержит ключевое слово continue , то выполняется БЛОК1 .
  4. Управление передается этапу 1.

Следующий пример выводит на экран числа от одного до десяти:

$i = 1; until ($i > 10) { print "$i\n" } continue { $i++ }

6.5.2.5. Оператор цикла for

Оператор цикла for имеет вид:

for (инициализация ; условие ; изменение ) БЛОК

Оператор for выполняется следующим образом:

  1. Выполняется оператор инициализация (обычно он инициализирует счетчик или счетчики цикла).
  2. Вычисляется значение выражения условие . Если оно ложно, то управление передается оператору, следующему за данным оператором.
  3. Выполняется БЛОК .
  4. Выполняется оператор изменение (обычно он увеличивает или уменьшает счетчик или счетчики цикла) и управление передается этапу 2.

Данный оператор обычно используется в тех случаях, когда количество повторений цикла известно заранее. Так, мы могли бы записать предыдущий пример короче:

For ($i = 1; $i <= 10; $i++) { print "$i\n" }

6.5.2.6. Оператор итерации foreach

Оператор foreach выполняет заданные действия для каждого элемента списка или массива. Он имеет две формы:

foreach переменная (список ) БЛОК foreach переменная (список ) БЛОК continue БЛОК1

Оператор foreach выполняется следующим образом:

  1. Переменной присваивается очередной элемента списка . Если список исчерпан, то управление передается оператору, следующему за данным.
  2. Выполняется БЛОК .
  3. Если оператор содержит ключевое слово continue , то выполняется БЛОК1 .
  4. Управление передается этапу 1.

Приведем все тот же пример с печатью чисел от 1 до 10:

Foreach $i (1..10) { print "$i\n" }

Переменная является локальной для данного оператора, т. е. после завершения итерации ее предыдущее значение восстанавливается. Если перед переменной поставить ключевое слово my , то она будет лексически ограничена телом оператора. Если переменная опущена, то используется специальная переменная $_ :

Foreach (1..10) { print "$_\n" }

Если одним из элементов списка является массив, то тело цикла не должно его изменять, иначе результаты непредсказуемы.

В действительности, операторы for и foreach являются синонимами, и вы можете использовать любое из этих слов по своему усмотрению.

6.5.2.7. Метки операторов

Перед операторами while , until , for и foreach , а также перед блоками могут ставиться метки . Помеченный оператор имеет вид:

метка : оператор

В качестве метки может использоваться любой идентификатор, который не является зарезервированным словом, но на PERLе принято писать метки заглавными буквами.

Хотя в современном программировании использование меток считается дурным тоном, в некоторых случаях их применение существенно упрощает логику программы. Чаще всего это происходит, когда метки используются для передачи управления из глубоко вложенного цикла к внешнему. Рассмотрим следующий пример, написанный в лучший традициях C++:

For (my $i = 0; $i < @ary1; $i++) { for (my $j = 0; $j < @ary2; $j++) { if ($ary1[$i] > $ary2[$j]) { last; } $ary1[$i] += $ary2[$j]; } }

Метки и операторы управления циклом позволяют записать этот алгоритм намного короче и, главное, намного прозрачнее:

OUTER: for my $x (@ary1) { for my $y (@ary2) { next OUTER if $x > $y; $x += $y; } }

6.5.2.8. Оператор last

Оператор last немедленно завершает указанный цикл. Он имеет две формы:

last метка last

Первая форма оператора завершает выполнение цикла с заданной меткой . Вторая форма завершает выполнение самого внутреннего из выполняющихся в данный момент вложенных циклов. Если цикл имеет блок continue , то он не выполняется. Пример:

LINE: while () { last LINE if /^$/; # прервать цикл, если встретилась пустая строка... }

6.5.2.9. Оператор next

Оператор next начинает новую итерацию указанного цикла. Он имеет две формы:

next метка next

меткой continue , то он будет выполнен перед началом новой итерации. Пример:

LINE: while () { next LINE if /^#/; # пропускать комментарии... }

6.5.2.10. Оператор redo

Оператор redo начинает новую итерацию указанного цикла без проверки условия его выполнения. Он имеет две формы:

redo метка redo

Первая форма оператора начинает новую итерацию цикла с заданной меткой . Вторая форма начинает новую итерацию самого внутреннего из выполняющихся в данный момент вложенных циклов. Если цикл имеет блок continue , то он не выполняется. Следующий пример удаляет комментарии из программы на языке Pascal, где они имеют вид {…} (пример упрощен, т. к. не учитывает, что символы {} могут содержаться в строковых константах):

LINE: while () { while (s|({.*}.*){.*}|$1 |) {} s|{.*}| |; if (s|{.*| |) { $front = $_; while () { if (/}/) { s|^|$front\{|; redo LINE; } } } print; }

6.5.2.11. Блок как вырожденный цикл

Блок рассматривается в PERLе как цикл, безусловно выполняющийся один раз. Это означает, что мы можем использовать конструкцию

БЛОК1 continue БЛОК2

которая обычно означает, что выполняется БЛОК1 , а после него БЛОК2 . Однако, трактовка блоков как циклов означает и то, что мы можем пользоваться внутри блоков операторами управления циклом, и в этом случае указанная конструкция становится очень полезной.

Одна из причин, по которым в PERL не включен оператор выбора switch , состоит в том, что очень легко моделируется с помощью блоков и операторов управления циклом, например:

SWITCH: { $abc = 1, last SWITCH if /^abc/; $def = 1, last SWITCH if /^def/; $xyz = 1, last SWITCH if /^xyz/; $nothing = 1; }

6.5.2.12. Оператор перехода goto

PERL содержит оператор перехода goto трех видов:

goto метка goto выражение goto &имя

Первая форма оператора передает управление оператору с указанной меткой . Управление не может быть передано внутрь конструкции, требующей инициализации, например, внутрь подпрограммы или оператора foreach .

Вторая форма — это "вычисляемый goto ": он вычисляет значение выражения и передает управление на полученную в результате метку, например:

Goto ("LABEL1", "LABEL2", "LABEL3")[$i];

Третья форма — это вообще не goto в обычном смысле слова. Этот оператор подставляет вызов подпрограммы имя вместо выполняющейся сейчас подпрограммы. Она используется подпрограммами AUTOLOAD(), которые хотят незаметно подменить вызов одной подпрограммы другой. Имя не обязано быть именем подпрограммы; это может быть скалярная переменная или блок, значением которых является ссылка на подпрограмму.

Рассмотрены интересные неочевидные конструкции Perl.

Как известно, язык программирования Perl очень выразителен и имеет в своем арсенале множество средств, которые позволяют выразить намерения программиста в коде множеством совершенно разных образов. Также, в виду весьма хитрого синтаксиса, некоторые комбинации операторов могут приводить к интересным эффектам.

Также, некоторые вещи можно использовать не совсем стандартным образом для получения желаемого результата. Такие действия иногда называются «забивать гвозди микроскопом». Именно этим мы и будем заниматься.

  • Поведение операторов, приведенных в этой статье, может меняться от версии к версии Perl.
  • Данные операторы, скорее всего, не предназначены для использования их в production.
  • Большинство этих операторов были созданы людьми, которым приносит удовольствие исследовать любимый язык.
  • Все, что приведено ниже и названо операторами, на самом деле ими не является.

Оператор «Венера»

0+ +0

Название

Свое название оператор получил от внешней схожести с символом Венеры.

Что делает?

Приводит аргумент слева, или справа, в зависимости от версии, к числовому виду. Например:

Print 0+ "23a"; print 0+ "3.00"; print 0+ "1.2e3"; print 0+ "42 EUR"; print 0+ "two cents";

Результат:

23 3 1200 42 0

Этот оператор с натяжкой можно использовать у себя в проектах, но не следует забывать о том, что с точки зрения Perl числами, например, являются: 0 but true 0E0 и некоторые еще хитрые константы. Еще следует отметить, что 0+ это метод, используемый для числового преобразования по умолчанию при использовании overload .

«Черепашка», «Детская коляска» или «Тележка из супермаркета»

@{}

Название

Свое название оператор получил из-за внешней схожести с черепахой или коляской. Примечателен тем, что был открыт Ларри Уоллом в 1994 году.

Что делает?

Это так называемый контейнерный оператор, который позволяет производить интерполяцию массива внутри строки. Элементы массива в результате будут разделены содержимым переменной $" .

Как работает?

Сначала содержимое принудительно вычисляется в списковом контексте, затем, незамедлительно, проводится разыменование (@{ }).

Print "Test i am @{[ die()]}"; print "here";

Результат работы:

Died at demo.pl line 1.

Этот оператор можно использовать для выполнения произвольного кода в строках, когда происходит интерполяция. Например, с его помощью можно сделать некоторые вещи проще, например, построение SQL-запросов:

My $sql = "SELECT id, name, salary FROM employee WHERE id IN (@{[ keys %employee ]}) SQL ";

но выигрыш в пару строк кода не оправдывает потенциальные проблемы в безопасности (хорошая практика в SQL - использование т.н. bind variables, которые будут подставлены при prepare). В виду неочевидности, использовать оператор в production не стоит, потенциальная уязвимость и уменьшение читабельности.

Bang Bang

!!

Данный оператор использовался еще тогда, когда Perl не было, его часто использовали программисты на С.

Как работает?

Этот оператор делает следующую элементарную вещь - двойное отрицание, суть которого сводится к булевому преобразованию. Вспоминаем, что булевых типов в Perl нет.

My $true = !! "a string"; my $false = !! undef;

В результате $true содержит 1 , а $false пустую строку "" . Данный оператор можно использовать для проверки, есть ли значение, например, в ссылке на хеш, например

!! $hash->{value} or die("Missing value");

Червяк

~~

Название?

Просто и незатейливо похож на гусеницу-пяденицу, и на многих червей также.

Как работает?

Оператор ~~ внешне очень похож на smart matching, но им не является потому, что это унарный оператор.

Унарная операция - это операция над одним операндом

Тогда как smart matching - операция бинарная (операция над двумя операндами, сложение, например).

Что делает?

Всего-лишь сокращенное на четыре (!) символа ключевое слово scalar .

Perl -Esay~~localtime Tue Jul 30 17:43:16 2013

Принцип действия оператора схож с оператором Bang Bang (!!), но отличается тем, что в Perl оператор ~ операторозависимый. Более подробно можно узнать, посмотрев в документации про побитовые операции в Perl. Применять этот оператор можно, но следует быть осторожным, неизвестно, как он будет себя вести на всех версиях perl.

Червяк-на-палочке

-~ ~-

Это высокоприоритетный оператор инкремента/декремента. -~ инкрементирует только числа, которые меньше нуля, а ~- декрементирует числа больше нуля

Приоритет этого оператора выше арифметических операторов, кроме возведения в степень (**). Например:

$y = ~-$x * 4;

Будет исполняться идентично:

$y = ($x-1)*4;

Но не как:

$y = ($x * 4) -1

Для того, чтобы данные операторы работали с беззнаковыми типами данных, необходимо использовать прагму integer (use integer). Этот оператор работает весьма неплохо и его можно применять в production, но делать этого не стоит, т.к. на нестандартных архитектурах его поведение может отличаться от вышеуказанного. Хотелось бы попробовать его в действии на ARM-платформах, но автор статьи не располагает подобным устройством.

Космическая станция

-+-

Открыт Alistair McGlinchy в 2005 году.

Этот оператор производит высокоприоритетное приведение к числовому виду, по поведению он похож на оператор «символ Венеры», но он отличается следующими вещами. Оператор Венеры (0+ или +0) использует бинарный оператор + , тогда как «Космическая станция» использует сконкатенированные три унарных оператора, а потому имеет более высокий приоритет.

Также, стоит помнить, что этот оператор имеет меньший приоритет, чем сконкатенированные операторы * и x . Принципы его работы можно проиллюстрировать следующим примером, в котором мы попробуем распечатать приведенное к числовому виду "20GBP" три раза:

Неправильно, т.к. возвращает преобразованный вариант от строки "20GBP20GBP20GBP" :

Print 0+ "20GBP" x 3; # 20

Неправильно, т.к. эквивалентно (print "20") x 3:

Print(0+ "20GBP") x 3; # 20

Правильно, но сильно длинно, сильно «лиспово»:

Print((0 + "20GBP") x 3); # 202020

Правильно - используя оператор «Космическая станция»:

Print -+- "20GBP" x 3; # 202020

Однако, т.к. унарный минус - и унарный + просто заменяет результат строки на его значение, то данный оператор не будет работать со следующего вида строками:

  • С теми, что начинаются на - .
  • С теми, что начинаются на не числовой символ.

Тогда как оператор Венеры работает во всех этих случаях, но имеет меньший приоритет.

Goatse или Сатурн-оператор

=()=

Что делает?

Этот оператор вводит списковый контекст справа от него и возвращает количество элементов слева.

Как работает?

Список в скалярном контексте возвращает количество элементов. Не имеет значения, сколько элементов из них было присвоено переменным. В таком случае правая часть выражения будет приведена к пустому списку, а затем, следовательно, отброшена.

$n =()= /word1|word2|word3/g; $n =()= "abababab" =~ /a/; # $n = 1 $n =()= "abababab" =~ /a/g; # $n = 4

К тому же, данный оператор является контейнерным (!), что позволяет запросто «втянуть» в него результат правой части. Это значит, что мы можем поступать следующим образом:

$n =($b)= "abababab" =~ /a/g; # $n = 4; $b = "a" $n =(@c)= "abababab" =~ /a/g; # $n = 4; @c = qw(a a a a)

Следующий «хитрый» пример его использования, похоже, имеет право на жизнь, но существует другой секретный оператор, который может делать то же самое.

Допустим, мы хотим узнать, на сколько частей разобьет строку split, а сами элементы нас не интересуют, может быть, есть смысл попробовать нечто следующего вида:

My $count = split /:/, $string;

Этот пример вернет нам необходимое число, но при этом ругнется на:

Use of implicit split to @_ is deprecated

Для того, чтобы решить данную проблему мы можем воспользоваться данным оператором и написать нечто подобное:

My $count =()= split /:/, $string;

Что не вызовет warning, но при этом будет всегда возвращать 1 потому, что split никогда не разбивает строку на большее количество частей, чем необходимо. В свою очередь, компилятор расценивает попытку сохранить в () как утверждение в том, что данные элементы нам не надо и вернет неизмененную строку, что будет приведено к списку с одним элементом.

Есть два возможных решения.

Первое заключается в том, что мы можем запретить компилятору проводить оптимизацию split() , указав при помощи -1 желание получить бесконечное количество кусков строки:

My $count =()= split /:/, $string, -1;

Или использовать другой секретный оператор, «черепашку»:

My $count = @{[ split /:/, $string ]};

Воздушный змей

~~<>

На самом деле, данный оператор является всего-лишь комбинацией Червяка и <> . Он предоставляет скалярный контекст для операции readline() , но полезный он только в контексте списка.

Богато украшенный двусторонний меч

<> m ;

Этот секретный оператор предоставляет мультистрочные комментарии и не более того. Пример использования:

<> Use the secret operator on the previous line. Put your comments here. Lots and lots of comments. You can even use blank lines. Finish with a single m ;

Но следует учитывать тот факт, что данный комментарий - просто строка, заключенная в двойные кавычки, а потому может иметь некоторые побочные эффекты.

Отверточные операторы

Обнаруженный Дмитрием Карасиком в процессе поиска операторов, базирующихся на! . Как и отвертки, эти операторы бывают четырех основных типов, но с разной длиной рукоятки:

Прямая отвертка - обеспечивает декремент по условию:

$x -=!! $y # $x-- if $y; $x -=! $y # $x-- unless $y;

Крестовая отвертка - инкремент по условию:

$x +=!! $y; # $x++ if $y; $x +=! $y; # $x++ unless $y;

Отвертка-звездочка - сброс переменной в 0 по условию:

$x *=!! $y; # $x = 0 unless $y; $x *=! $y; # $x = 0 if $y;

Крестообразная отвертка-шлиц - сброс переменной в ’’ по условию:

$x x=!! $y; # $x = "" unless $y; $x x=! $y; # $x = "" if $y;

Enterprise-оператор

()x!!

Довольно часто возникает необходимость добавить элемент в список по условию. Это можно сделать следующим образом:

My @shopping_list = ("bread", "milk"); push @shopping_list, "apples" if $cupboard{apples} < 2; push @shopping_list, "bananas" if $cupboard{bananas} < 2; push @shopping_list, "cherries" if $cupboard{cherries} < 20; push @shopping_list, "tonic" if $cupboard{gin};

© 2024 mygj.ru
Компьютерные советы и хитрости