Perl относится к числу императивных алгоритмических языков. Это значит, что отдельные фразы языка Perl представляют собой предписания, говорящие «сделать то-то и то-то». Предписания исполняются в той последовательности, в которой они записаны в тексте программы. Изменить этот железный порядок можно лишь с помощью управляющих конструкций и операторов.
Для наглядного представления алгоритмов иногда используют блок-схемы.
Блок-схема представляет собой набор элементов нескольких видов, каждый из которых может иметь несколько входов и выходов (ноль или больше). Каждый выход элемента соединяется с входом некоторого другого элемента направленной стрелкой, и наоборот, каждый выход соединяется с некоторым входом.
Часто бывает нужно выполнить в программе некоторую последовательность команд при выполнении некоторого условия. Поскольку условие либо выполнено, либо не выполнено, решение о выполнении/невыполнении последовательности команд принимается на основании логического значения — да/нет.
Вот несколько примеров логических значений: «значение переменной
$a
не превосходит 2
?» — $a<=2
; «равна ли длина массива
@ARGV
четырём?» — @ARGV==4
;
«строка $x
длиннее строки $y
?» — length $x>length $y
.
Для того, чтобы выполнить последовательность команд при условии, используют условную конструкцию:
Perlif(условие
) {действие
}
Если нужно выполнить ту или иную последовательность команд в зависимости от выполнения/невыполнения условия, делают так:
Perlif(условие
) {действие₁
} else {действие₂
}
Вместо вложенных if … else
Perlif(условие₁
) {действие₁
} else { if(условие₂
) {действие₂
} else { if(условие₃
) {действие₃
} else {…
} } }
удобно применять конструкцию if … elsif … else:
Perlif(условие₁
) {действие₁
} elsif(условие₂
) {действие₂
} elsif(условие₃
) {действие₃
}…
else {действие
}
Последний else не обязателен.
Эта конструкция полезна, когда требуется выполнить те или иные действия в зависимости от выполнения одного из нескольких взаимоисключающих условий.
Perlif($D>0) { print 'Решения: ', (-$b-sqrt $D)/2/$a, ', ', (-$b+sqrt $D)/2/$a, "\n"; } elsif($D==0) { print 'Решение: ', $x=-$b/2/$a, "\n"; } else { print "Решений нет!\n"; }
(Здесь команда die
выводит сообщение об ошибке
и немедленно завершает программу.)
Для хранения логических значений в Perl используются скалярные переменные
(до сих пор мы знали, что в скалярных переменных можно хранить числа и строки).
Более того, и числовые, и строковые значения могут интерпретироваться как
логические согласно следующим правилам: ложными считаются неопределённые
значения, пустая строка, строка "0"
и число
0
. Остальные скалярные значения считаются истинными.
Например, логические выражения @a>0
и @a
принимают одно и то же значение.
Имеется ещё одна условная конструкция, без которой можно было бы обойтись:
Perlunless(…
) {…
}
означает то же самое, что и
Perlif(not(…
)) {…
}
или
Perlif(!(…
)) {…
}
Unless по английски значит «если не».
Мы любим писать
Perlunless(…
)
вместо
Perlif(…
==0)
Например,
Perlunless($n % $k)
означает «если $n
делится нацело на $k
».
Простейший цикл — цикл с предусловием позволяет выполнять действия, пока выполнено некоторое условие.
Такой цикл записывается при помощи ключевого слова while, за которым следует логическое выражение в круглых скобках, а затем — блок команд (действия) в фигурных скобках. Блок называется ещё телом цикла:
Perlwhile(условие
) {действие
}
Как видно рисунка 2.4. «Блок-схема для цикла с предусловием», выполнение цикла начинается с проверки условия. Если условие не выполнено (логическое выражение принимает ложное значение), выполнение программы продолжается, как если бы никакого цикла не было. В противном случае (логическое выражение истинно) выполняются команды из тела цикла, после чего снова проверяется условие, и, при необходимости, выполняются действия из тела цикла, и так далее.
Тело цикла может не выполниться ни разу, если при входе в цикл не выполняется условие.
Если желательно, чтобы выполнение цикла когда-нибудь завершилось (чаще всего
это так), нужно позаботиться, чтобы действия, совершаемые в цикле, рано или
поздно сделали бы условие цикла ложным. Но что же будет, если взять в качестве
условия что-то, что заведомо истинно, например, выражение 2*2==4
? Мы получим бесконечный цикл. Такое может
случиться и при ошибках программирования. Если в теле цикла отсутствуют
команды, выводящие текст на экран, работающая программа не будет подавать
видимых признаков жизни, что неизменно вызывает панику у начинающих
программистов. Они часто говорят в таких случаях: «компьютер повис». Вовсе нет!
Вернуть контроль над ситуацией можно, прервав выполнение программы нажатием
комбинации клавиш Ctrl+C. Появится
командная строка, из которой можно запустить текстовый редактор и поправить
текст программы.
Примечание | |
---|---|
Можно написать такую программу, которая не будет прерываться при нажатии на Ctrl+C. Но, во-первых, мы ещё не скоро освоим технику такого программирования (об этом речь пойдёт в главе 37. ««Жизнь» Джона Конвея»), а во-вторых, найдутся и другие способы, чтобы прервать такую программу. |
Иногда может возникнуть необходимость в бесконечных циклах. Нет нужды выдумывать всегда истинное условие; условие можно и пропустить:
Perlwhile() {действие
}
Отсутствие условия цикла или его постоянная истинность ещё не означает, что цикл бесконечный. Имеется команда last, позволяющие «выскочить» из цикла. Разговор об этой команде впереди.
Другая разновидность цикла, цикл с постусловием, отличается от цикла с предусловием тем, что условие проверяется не до, а после выполнения тела цикла (рисунок 2.5. «Блок-схема для цикла с постусловием»). Это, в частности, означает, что тело цикла будет выполнено как минимум один раз.
Для записи цикла с постусловием используется слово do, за которым следует тело цикла, а затем слово while и условие в круглых скобках:
Perldo {действие
} while(условие
);
Обратите внимание на обязательную точку с запятой после условия.
Хотя, по нашим наблюдениям, цикл с постусловием применяется гораздо реже цикла
с предусловием, он бывает в некоторых ситуациях удобен. Например, некоторые
программы ведут диалог с пользователем, задавая вопросы и ожидая ответ
определённого вида. Если ответ пользователя неприемлемый, вопрос повторяется.
В таких случаях нужно прежде выполнить действия (задать вопрос и получить
ответ), и лишь затем анализировать ответ, решая, повторять ли диалог ещё раз.
В следующем примере пользователю будет задаваться вопрос, не прервать ли
что-то, до тех пор, пока пользователь не введёт строку y
(естественно, от слова yes):
Perldo { print 'Прервать (y/n)? '; $_=<STDIN>; } while($_ ne "y\n");
Здесь строка, введённая пользователем, отправляется в переменную
$_
по команде $_=<STDIN>
.
Условием цикла является несовпадение введённой строки и строки
"y\n"
. Заметьте, что обозначением конца ввода служит
нажатие клавиши Enter, при котором к считанной строке
добавляется символ конца строки. Поэтому, вычисляя условие цикла, мы сравниваем
$_
не со строкой "y"
,
а с "y\n"
. Приведённый пример можно записать короче, не
используя переменную:
Perldo { print 'Прервать (y/n)? '; } while(<STDIN> ne "y\n");
Часто, когда применяется цикл, повторяется один и тот же сценарий: сначала выполняются некие подготовительные действия, затем запускается собственно цикл, в конце тела которого что-то меняется, что может повлиять на выполнение условия цикла. Наиболее простой и вместе с тем характерный пример такого сценария даёт программа, выводящая несколько первых натуральных чисел:
Perl$i=1; while($i<=10) { print "$i\n"; $i++; }
Здесь подготовительная работа заключается в присваивании начального значения
1
переменной $i
, а изменения в конце
тела цикла — увеличение на единицу этой переменной.
Как раз для таких ситуаций в Perl существует разновидность цикла с предусловием — цикл «для»:
Perlfor(инициализация
;условие
;модификация
) {действие
}
Такой цикл почти в точности равносилен циклу
Perlинициализация
while(условие
) {действие
модификация
}
Спрашивается, а зачем нужна ещё одна конструкция, если она сводится к уже имеющейся? Цикл «для» записывается чуть короче, и, что важно, в нём заключено всё, что необходимо для цикла, включая подготовку. Использование цикла «для» во многих случаях делает программу более понятной.
Такой цикл идеален для очень часто встречающихся ситуаций, когда перебираются числа, причём следующее число вычисляется из предыдущего (в простейшем случае увеличением предыдущего на единицу).
Программу, выводящую натуральные числа, можно с учётом сказанного переписать так:
Perlfor($i=1; $i<=10; $i++) { print "$i\n"; }
Можно в качестве инициализации записать несколько команд. Тогда их следует разделить запятыми (а не точками с запятой, как обычно), ведь точки с запятой в заголовке цикла отделяют друг от друга блок инициализации, условие и блок модификации. Следующий пример вычисляет сумму первых десяти натуральных чисел:
Perlfor($s=0, $i=1; $i<=10; $i++) { $s+=$i; } print "$s\n";
Можно пойти ещё дальше, и перенести единственную команду из тела цикла в блок модификации:
Perlfor($s=0, $i=1; $i<=10; $s+=$i, $i++) {} print "$s\n";
Здесь уже не совсем очевидно, что удобочитаемость программы повысилась. Любители запутанных программ могут написать и так:
Perlfor($s=0, $i=1; $i<=10; $s+=$i++) {} print "$s\n";
Если в предыдущих примерах можно было бы без всякого ущерба заменить $i++
на ++$i
, то
в последнем использование именно постфиксного оператора увеличения важно: иначе
к сумме $s
прибавлялось бы значение переменной
$i
уже после увеличения.
Иногда в каком-то из блоков — инициализации, проверки условия, модификации, — нет необходимости. Тогда его можно оставить пустым:
Perl$s=0; $i=1; for( ; $i<=10; $i++) { $s+=$i; }
Есть любители даже для бесконечных циклов использовать цикл for( ; ; )
, а не while()
,
и, хотя такое тоже возможно, мы не понимаем, какой глубокий смысл в этом
заключается.
Perl предоставляет ещё одну циклическую конструкцию — цикл «для каждого». Она полезна в случаях, когда нужно выполнить какое-то действие поочерёдно для каждого элемента списка:
Perlforeachпеременная
(список
) {действие
}
В таком цикле скалярная переменная
принимает
по очереди значения из списка
;
действие
может как-то использовать эту переменную.
Например, так можно вывести названия планет на экран по одному в строке:
Perlforeach $planet(qw/Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон/) { print "$planet\n"; }
Можно не указывать переменную
, в этом случае будет
использоваться специальная переменная
по умолчанию,
$_
:
Perlforeach(список
) {действие
}
Например, для вывода квадратов первых десяти натуральных чисел напишем
Perlforeach(1..10) { print $_**2, "\n"; }
Это, между прочим, более лаконичная альтернатива циклу
Perlfor($i=1; $i<=10; $i++) { print $i**2, "\n"; }
Мы рекомендуем лаконичную форму в тех случаях, когда требуется перебирать идущие подряд целые числа.
Разумеется, использование переменной по умолчанию в циклах, один из которых вложен в другой, приведёт к неминуемой путанице: циклы будут портить друг другу значение переменной.
Цикл «для каждого» очень полезен для последовательной обработки элементов массива, когда обрабатываются сами элементы, а их индексы не используются. Вместо громоздких циклов
Perlfor($i=0; $i<@planets; $i++) { print "$planets[$i]\n"; }
или
Perlforeach(0..$#planets) { print "$planets[$_]\n"; }
пишем просто
Perlforeach(@planets) { print "$_\n"; }
Кстати, вместо foreach можно всюду писать просто for. Мы так и будем поступать в дальнейшем в этой книге.
Иногда возникает необходимость завершить цикл досрочно. Например, проверяя данное число на простоту, мы делим это число с остатком на числа, меньшие данного. Появление первого же нулевого остатка свидетельствует о том, что число составное, и уже нет никакой нужды продолжать деление. Цикл следует прервать. Для этой цели служит команда last. Эта команда немедленно завершает цикл, и программа продолжит исполнение с команд, непосредственно следующих за циклом:
Perlwhile(…) { … last; ─╮ … │ } │ ◀──────────╯ …
Конечно, применять команду last имеет смысл лишь при некотором условии, в противном случае команды тела цикла, следующие за last, никогда не выполнятся.
Команда last прерывает исполнение самого внутреннего цикла, охватывающего эту команду:
Perlwhile(…) { … while(…) { … last; ─╮ … │ } │ ◀──────────╯ … }
Если требуется завершить какой-то из внешних циклов, нужно
Вот как это делается:
Perlouter: while(…) { … while(…) { … last outer; ─╮ … │ } │ … │ } │ ◀────────────────────╯ …
Может потребоваться досрочно прервать выполнение тела цикла, не прерывая сам цикл. К примеру, в цикле по одному обрабатываются файлы из некоторого списка. Пусть обработка каждого файла заключается, скажем, в печати его содержимого на экран. Если какой-то из файлов отсутствует, совершенно незачем пытаться распечатывать его содержимое. Тогда обработку файла нужно прервать, и переходить к следующему:
Perlwhile(…) { … next; ─╮ … │ ◀──────╯ }
Подобно команде last, next прерывает исполнение тела самого внутреннего охватывающего цикла:
Perlwhile(…) { … while(…) { … next; ─╮ … │ ◀──────╯ } … }
Если требуется прервать исполнение тела одного из внешних циклов, нужно применить метки:
Perlouter: while(…) { … while(…) { … next outer; ─╮ … │ } │ … │ ◀────────────────╯ }
Оператор redo перезапускает выполнение тела цикла:
Perlwhile(…) { ◀──────╮ … │ redo; ─╯ … }
Точно так же, как last и next, redo работает для самого внутреннего охватывающего его цикла. Для перезапуска тела внешнего цикла нужен redo с меткой. Не будем загромождать текст иллюстрациями использования меток, поскольку всё аналогично использованию меток с операторами last и next.