Каждый цикл в программе завершается при достижении некоторого
условия, определяемого самим оператором. В циклах while и
for это связано с ложностью выражения-условия, а в цикле foreach
с окончанием перебора всех элементов списка. Иногда возникает
необходимость при возникновении некоторых условий завершить
выполнение всего цикла, либо прервать выполнение операторов
цикла и перейти на очередную итерации. Для подобных целей
в языке Perl предусмотрены три команды last, next и redo,
которые и называют командами управления циклом.
Синтаксис этих команд
прост — ключевое слово, за которым может следовать необязательный
идентификатор метки:
last ИДЕНТИФИКАТОР_МЕТКИ; next ИДЕНТИФИКАТОР_МЕТКИ; redo ИДЕНТИФИКАТОР_МЕТКИ;
Семантика этих команд также проста. Они изменяют порядок выполнения циклов, принятый по умолчанию в языке, и передают управление в определенное место программы, завершая выполнение цикла (last), переходя на следующую итерацию цикла (next) или повторяя выполнение операторов тела цикла при тех же значениях переменных цикла (redo). Место перехода задается меткой. Помните синтаксис операторов цикла? Каждый из них может быть помечен. Именно идентификаторы меток операторов цикла и используются в командах управления для указания места передачи управления.
Метка в программе Perl задается
идентификатором, за которым следует двоеточие. В командах
управления циклом используется именно идентификатор метки,
а не метка.
Несколько слов о терминологии.
Читатель, наверное, обратил внимание, что мы не называем команды
управления циклом операторами. И это справедливо. Они не являются
операторами, хотя могут использоваться как операторы. Их следует
считать унарными операциями, результатом вычисления которых
является изменение последовательности выполнения операторов.
Поэтому команды управления циклом можно использовать в любом
выражении Perl. Заметим, что их следует использовать в таких
выражениях, где имеет смысл их использовать, например в выражениях
с операцией "запятая":
open (INPUT_FILE, $file) or warn ("Невозможно открыть $file: $!\n"), next FILE;
Приведенный оператор может
являться частью программы, которая в цикле последовательно
открывает и обрабатывает файлы. Команда next инициирует очередную
итерацию цикла с меткой FILE, если не удалось открыть файл
в текущей итерации. Обратите внимание, что она используется
в качестве операнда операции "запятая". В таком
контексте эта команда имеет смысл. Следующий оператор является
синтаксически правильным, но использование в нем команды redo
не имеет никакого смысла:
print "qu-qu", 5 * redo OUT, "hi-hi\n";
Результатом выполнения этого оператора будет повторение вычислений операторов цикла с меткой ODT, т. е. простое выполнение команды redo OUT.
Относительно команд управления циклом следует сказать, что к ним можно применять модификаторы, так как употребленные самостоятельно с завершающей точкой с запятой они рассматриваются как простые операторы: next if $a — 2;
Переход на следующую итерацию цикла осуществится только, если переменная $а равна 2.
Команда last
Команда last немедленно
прекращает выполнение цикла, в котором она задана, и передает
управление на оператор, непосредственно следующий за оператором
цикла. Ее целесообразно использовать для нахождения одного
определенного значения в массиве (пример 5.12).
#! peri -w @letters = ("A".."Z");
for ($index=0; $index<01etters; $index++) {
last if $letters[$index] eq "M"; } print $index;
Цикл в программе примера 5.12 будет выполняться, пока перебор элементов массива $ letters не достигнет элемента, содержащего символ "м". После чего будет выполнен первый после оператора for оператор программы. В результате будет напечатано число 12 — индекс элемента, содержащего символ "м".
Метка используется для конкретизации передачи управления в случае вложенных циклов: управление передается непосредственно на оператор, следующий за оператором цикла с указанной меткой (пример 5.13).
CYCLE_1: while (...){ , CYCLE_2: for (...) {
CYCLE_3: foreach (...) { last CYCLE_2; }
Операторы цикла CYCLE_2 } Операторы цикла CYCLE_1 # Сюда передает управление
t оператор last CYCLE_2; }
Если в команде last указать метку CYCLE_I, то управление будет передано на первый после самого внешнего цикла оператор программы. Если в команде last задать метку CYCLE_S (или задать ее вообще без метки), то управление будет передано на первый оператор группы операторы цикла CYCLE_2.
Передача управления командой last осуществляется не на оператор цикла с соответствующей меткой, а на оператор, непосредственно следующий за ним.
Команда last осуществляет выход из цикла, не выполняя никаких блоков операторов continue.
Команда next
Команда next позволяет
пропустить расположенные после нее в теле цикла операторы
и перейти на следующую итерацию цикла. Если оператор цикла
содержит блок continue, то его операторы выполняются до проверки
условия окончания цикла, с которой начинается следующая итерация.
Одно из применений этой команды — обработать определенные
элементы массива, ничего не делая с другими. Программа примера
5.14 присваивает всем элементам массива, содержащим четные
числа, символ звездочка "*".
#! peri -w
@array = (2, 5, 8, 4, 7,, 9); print "До: @array\n"; fоreach (Sarray) { next if $_ % 2;
$_ = »*»;
}
print "После: @array\n";
Результат выполнения программы примера 5.14 показан ниже:
До: 2 5 8 4 7 9 После: * 5 * * 7 9
Если элемент массива нечетное число, то результат операции $_ % 2 равен i (Истина) и команда next инициирует следующую итерацию цикла foreach, не изменяя значение текущего элемента массива. Если значением элемента массива является четное число, то команда next не выполняется и значение элемента меняется на символ "*".
Команда next, употребленная совместно с идентификатором метки прерывает выполнение цикла, в теле которого она находится, и начинает новую итерацию цикла с указанной меткой, выполнив предварительно его блок continue, если таковой имеется (пример 5.15).
#! peri -w
$out =- 0;
OUT: while ($out < 2) {
print "Начало внешнего цикла\п";
for($in=0; $in<=2; $in++) {
print "\$out: $out\t\$in: $in\n"; next OUT if $in =1; }
print "\$out: $out\n"; # Никогда не выполняется! } continue {
print "Блок continue внешнего цикла\п"; $out++; }
Вывод этой программы будет следующим:
Начало внешнего цикла
$out: 0 $in: 0
$out: 0 $in: 1
Блок continue внешнего цикла
Начало внешнего цикла
$out: I $in: 0
$out: I $in: 1
Блок continue внешнего цикла
Обратите внимание, что количество итераций внутреннего цикла for равно двум, так как на второй его итерации выполняется команда next OUT, прекращающая его выполнение и инициализирующая выполнение очередной итерации внешнего цикла OUT. Оператор печати этого цикла пропускается, выполняется блок операторов continue, проверяется условие и если оно истинно, то тело цикла выполняется. Таким образом, оператор печати внешнего цикла OUT не выполняется ни одного раза, что подтверждается приведенным выводом из программы примера 5.15.
Команда redo
Команда redo повторно
выполняет операторы тела цикла, не инициализируя следующую
итерацию. Это означает, что ни выражение изменения цикла for,
ни операторы блока continue, если он присутствует, ни выражение
условия не вычисляются. Операторы тела цикла, расположенные
за оператором redo, пропускаются и снова начинается выполнение
тела цикла со значениями переменных, которые они имели перед
выполнением этой передачи управления. Программа примера 5.16
демонстрирует использование команды redo.
I! peri -w $notempty•= 0; $total = 0;
for (;;) { tt Бесконечный цикл
$line=<STDIN>; # Ввод строки
chop($line);
last if $line eq "END"; # Выход из цикла
++$total;
redo if $line eq ""; # Возврат на чтение строки
++$notempty; } print "Всего прочитано строк: $total\nM3 них не пустых: $notempty\n";
Эта программа в бесконечном цикле ожидает ввода пользователем на клавиатуре строки данных и в переменной $ total подсчитывает количество введенных строк. В переменной $notempty вычисляется количество введенных не пустых строк. Если введена пустая строка, то команда redo начинает повторное выполнение операторов тела цикла, не увеличивая на единицу переменную $notempty. Для завершения бесконечного цикла следует ввести строку END. В этом случае выполняется команда last.
Функция chop используется для удаления из введенной пользователем строки символа перехода на новую строку "\п", поэтому в программе она сравнивается со строками без завершающего символа перехода на новую строку (сравни с примером 5.7).
Если команда redo используется с идентификатором метки, то ее действие аналогично действию команды next с той лишь разницей, что она просто передает управление на первый оператор тела цикла с указанной меткой, не инициируя следующей итерации и не вычисляя операторов блока continue. В качестве иллюстрации такого использования команды redo перепишем программу примера 5.16 следующим образом:
#! peri -w $notempty = 0; $total = 0; OUT: while (1) {
print "Введи строки\п"; # Сюда передает управление команда redo OUT; for {;;) {
$line=<STDIN>;
chop($line);
last OUT if-$line eq "END"; I Выход из всех циклов
++$total;
redo OUT if $line eq "";
++$notempty; } } print "Всего прочитано строк: $tptal\nH3 них не пустых: $notempty\n";
В примере 5.17 мы ввели внешний
бесконечный цикл OUT и изменили команды redo и last, добавив
к ним метку на внешний цикл. Теперь в случае, если пользователь
вводит пустую строку, команда redo OUT передает управление
на первый оператор внешнего цикла, и программа печатает приглашение
ввести строки.