Команды управления циклом

Нормальное завершение цикла связано с удовлетворением в процессе его выполнения некоторого условия: в циклах while и for — с ложностью, а в цикле until, наоборот, с истинностью выражения-условия, тогда как в цикле foreach — с завершением перебора всех элементов списка. Иногда возникает необходимость либо завершить выполнение всего цикла, либо прервать выполнение операторов цикла на каком-то шаге и перейти на очередную итерацию, либо повторить выполнение операторов блока цикла, не переходя на выполнение очередной итерации. Для подобных целей в языке Perl предусмотрены три команды: last, next и redo, которые называются командами управления циклом.
Синтаксис этих команд прост — ключевое слово, за которым может следовать необязательный идентификатор метки:
Команда_управления_циклом ИДЕНТИФИКАТОР_МЕТКИ;
Они изменяют естественный порядок выполнения циклов, принятый по умолчанию в языке, и передают управление в определенное место программы, завершая выполнение цикла (last), переходя на следующую итерацию цикла (next) или повторяя выполнение операторов тела цикла при тех же значениях переменных цикла (redo). Место перехода задается меткой, которая определена в языке как идентификатор, завершающийся символом двоеточие (:). Если мы посмотрим на синтаксис каждого из операторов цикла, то обратим внимание, что все они могут быть помечены. В командах управления выполнением цикла задаются не сами метки, а их идентификаторы.
ВНИМАНИЕ Метка в Perl задается идентификатором, за которым следует двоеточие, и служит для указания места передачи управления командами управления циклом, в которых используется идентификатор метки, а не метка.
Несколько слов о терминологии. С точки зрения основных понятий языка Perl, команды управления циклом не являются ни операциями, ни операторами — они не выполняют никаких действий над данными программы и в то же время не определены как операторы. Но если каждую из них завершить точкой с запятой, то интерпретатор Perl такую конструкцию будет рассматривать как простой оператор. Поэтому их можно мыслить как некие «унарные операции», результатом вычисления которых является изменение последовательности выполнения операторов, (Мы специально написали «унарные операции» в кавычках, так как операция осуществляет некоторое действие над данными, но никак не над метками.) Более того, команды управления циклом можно использовать в любом выражении Perl. Заметим, однако, что использовать их следует в таких выражениях, где это имеет смысл, например в выражениях с операцией «запятая»:
open(INPUT FILE, $file)
  or warn ("Невозможно открыть $file: $!\n"), next FILE;
Приведенный оператор может являться частью программы, которая в цикле последовательно открывает и обрабатывает файлы. Команда next инициирует очередную итерацию цикла с меткой FILE, если не удалось открыть файл в текущей итерации. Обратите внимание, что она используется в качестве операнда операции «запятая». В таком контексте эта команда имеет смысл. Следующий оператор является синтаксически правильным, но использование в нем команды redo не имеет никакого смысла:
print "qu-qu", 5 * redo OUT, "hi-hi\n";
Результатом выполнения этого оператора будет повторение вычислений операторов цикла с меткой OUT, то есть простое выполнение команды redo OUT.
Относительно команд управления циклом следует сказать, что к ним можно применять модификаторы простых операторов, так как употребленные самостоятельно с завершающей точкой с запятой они рассматриваются как простые операторы
next if $a == 2;
Переход на следующую итерацию цикла осуществится при условии, что значение переменной $а равно 2.

Команда last

Команда last немедленно прекращает выполнение цикла, в котором она задана, и передает управление оператору, непосредственно следующему за оператором цикла. Ее целесообразно использовать, если, например, в массиве следует найти элемент с определенным значением или необходимо прервать выполнение цикла при возникновении условия, не определенного в выражении-условии цикла (листинг 5.12).
Листинг 5.12. Выход из цикла командой last при обнаружении первой пустой строки
#! perl -w 
LINE:
while (my $line = <STDIN>) { 
  last LINE if $line =~ /^$/; # выход при обнаружении первой
                              # пустой строки
                              # операторы обработки непустых строк 
}
Программа из листинга 5.12 будет читать и обрабатывать строки файла (предполагается, что файл открыт как стандартный поток ввода), пока не встретит первую непустую. Выход из цикла осуществляется командой last с модификатором if, в котором и осуществляется проверка на пустую строку.
Метка в команде last, как и в других командах управления выполнением цикла, используется для конкретизации передачи управления в случае вложенных циклов: управление передается непосредственно на оператор, следующий за оператором цикла с указанной меткой (листинг 5.13).
Листинг 5.13. Команда last с меткой
CYCLE_1: while (...) {
  . . .
  CYCLE_2: for(...) {
    . . .
    CYCLE_3: foreach (...) { 
      last CYCLE_2;
      }
    Операторы цикла CYCLE_2
    . . .
    }
  Операторы цикла CYCLE_1  # Сюда передает
                           # управление оператор
                           # last CYCLE_2;
  . . .
}
Если в листинге 5.13 в команде last указать метку CYCLE_1, то управление будет передано на первый после самого внешнего цикла оператор программы. Если в команде last задать метку CYCLE_3, то управление будет передано на первый оператор группы операторов, помеченной как Операторы цикла CYCLE_2.
ВНИМАНИЕ Передача управления командой last осуществляется не оператору цикла с соответствующей меткой, а оператору, непосредственно следующему за ним.
В случае вложенных циклов и команды last без метки управление передается на первый оператор после цикла, в котором эта команда встретилась. Если вернуться к листингу 5.13, то в нем управление передалось бы на оператор, непосредственно следующий за циклом CYCLE_3, если бы команда last была задана без метки.
Команда last осуществляет немедленный выход из цикла, не выполняя никаких операторов из блока continue, если он присутствует.
При изучении простых операторов мы говорили, что конструкция do {...} с модификаторами while/until позволяет реализовать цикл с постусловием. Однако так как сама конструкция do {...} не является блоком, то команды управления выполнением цикла в ней не работают. Но Perl позволяет их использовать в подобных ситуациях, если воспользоваться явным заданием блоков. Например, для команды last можно применить следующее решение:
LOOP: {
do {
      last if $x == $y**2; 
      # какие-то операторы обработки 
  } while $x++ <= $z; 
}
Как видим, вся конструкция do {...} заключена в блок, а так как семантически блок рассматривается как оператор цикла, выполняющийся только один раз, то в нем можно использовать команду last. Таким образом, при ее выполнении осуществится выход из блока LOOP, что приведет к завершению нашего цикла с постусловием.

Команда next

Команда next позволяет пропустить расположенные после нее в теле цикла операторы и немедленно перейти к выполнению следующей итерации цикла. Если в операторе цикла задан блок continue, то его операторы выполняются до начала инициализации следующей итерации. С помощью этой команды можно обработать определенные элементы массива, ничего не делая с другими. Например, программа из листинга 5.14 заменяет все элементы массива с отрицательными значениями на нулевые.
Листинг 5.14. Использование команды next для выборочной обработки элементов массива
#!perl  -w
@array = (22, -5, -78, 4, -97, 10);
print "До обработки: @array\n"; 
foreach (@аrrау) {
  next if $_ >= 0;
  $_ = 0;
}
print "После обработки; @аrray\n";
Результат выполнения программы из листинга 5.14 будет следующим:
До обработки: 22 -5 -78 4 -97 10
После обработки; 22 0 0 4 0 10
Команда next с меткой прерывает выполнение текущей итерации цикла, в теле которого она находится, и начинает новую итерацию цикла с указанной меткой, выполнив предварительно операторы блока continue этого цикла, если таковой имеется (листинг 5.15).
Листинг 5.15. Команда next с меткой
#!perl -w
$out = 0;
OUT:
while ($out < 2) {
  print "Начало внешнего цикла\n"; 
  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 внешнего цикла\n";
    $out++;
  }
Вывод программы из листинга 5.15 будет следующим:
Начало внешнего цикла
$out: 0 $in:  0
$out: 0 $in:  1
Блок continue внешнего цикла
Начало внешнего цикла
$out:  1 $in: 0
$out:  1 $in: 1
Блок continue внешнего цикла
Обратите внимание, что количество итераций внутреннего цикла for равно двум, так как на его второй итерации выполняется команда next OUT, прекращающая его выполнение и инициализирующая выполнение очередной итерации внешнего никла OUT. Оператор печати этого цикла пропускается, выполняются операторы его блока continue, проверяется условие, и, если оно истинно, тело цикла начинает снова выполняться. Таким образом, оператор печати внешнего цикла OUT не выполняется ни одного раза, что подтверждается приведенным выводом программы из листинга 5.15.
Подобно команде last, команда next не будет работать в цикле с постусловием, реализованным с помощью конструкции do {...}. Однако небольшая хитрость позволяет и в этом случае задействовать команду next. Для этого следует заключить все операторы в конструкции do {...} в фигурные скобки, создав тем самым блок, в котором команда next уже сможет отработать:
do {{
  next if $x == $y;
  # какие-то операторы обработки 
}} until $x++ > $z;
Для наглядности мы выделили фигурные скобки введенного блока полужирным шрифтом. При выполнении оператора next управление должно быть передано на оператор, непосредственно следующий за добавленным блоком. Такой оператор отсутствует, значит все операторы конструкции do {...} выполнены, и, следовательно, необходимо снова вычислять выражение модификатора until. А это и означает, что осуществится переход на следующую итерацию цикла.

Команда redo

Команда redo используется для повторного выполнения операторов тела цикла, не инициализируя следующую итерацию. Если она должна выполниться (обычно в силу выполнения некоторого условия) во время выполнения очередной итерации цикла, то расположенные за ней операторы тела цикла не выполняются, а управление передается на первый оператор тела цикла, при этом новая итерация не инициализируется. Это означает, что ни выражение изменения цикла for, ни операторы блока continue, если он задан, вместе с выражением-условием циклов while/until не вычисляются, а в случае цикла foreach переменной цикла не присваивается значение следующего элемента списка. Таким образом, при выполнении команды redo просто снова начинают выполняться операторы тела цикла. Программа из листинга 5.16 демонстрирует использование команды redo.
Листинг 5.16. Повторное вычисление тела цикла без инициализации новой итерации
#! perl  -w
$notempty = "";
$total = "";
for (my $i=1; $i<=5; $i++) {
  $line = <STDIN>; # Ввод строки
  $total .= $line;
  redo if $line =~ /^$/;  # Возврат на чтение строки,
                          # если введена пустая 
  $notempty .= $line;
}
print "Все строки:\n$totalНenycтые:\n$notempty";
Эта программа ожидает ввода пользователем пяти непустых строк данных, сохраняя в переменной $total все введенные строки (пустые и непустые) и накапливая в переменной $notempty введенные непустые строки. Ввод пустой строки (пользователь просто нажал клавишу Enter, не введя ни одного символа) инициирует команду redo. При этом последний оператор блока не выполняется (пустая строка не добавляется в переменную $notempty), переменная цикла $i не меняет своего значения, и программа ожидает ввода пользователем следующей строки. Таким образом, наша программа будет работать, пока пользователь не введет пять непустых строк.
Если команда redo используется с меткой, то ее действие аналогично действию команды next с той лишь разницей, что она просто передает управление на первый оператор тела цикла с указанной меткой, не инициируя следующей итерации и не вычисляя операторов блока continue. В качестве иллюстрации такого использования команды redo перепишем программу из листинга 5.16 таким образом, чтобы она завершала свою работу, только если пользователь ввел подряд пять непустых строк (листинг 5.17).
Листинг 5.17. Команда redo с идентификатором метки
#!perl -w
$in = 1;
OUT:
while ($in) { 
  $total = "";
  print "Введи подряд пять непустых строк:\n";
  for (mу $i=1; $i<=5; $i++) {
    $line = <STDIN>;  # Ввод строки
    $total .= $line; 
    redo OUT if $line =~ /^$/;  # Возврат на цикл while,
                                # если введена пустея
                                # строка 
    $in = 0;
  }                             # Конец цикла for
}                               # Конец цикла while
print "Все пять строк:\n$total":
В листинге 5.17 мы ввели внешний цикл OUT, в котором пользователя просят ввести подряд пять непустых строк. Если он все-таки ввел пустую строку, то команда redo инициирует очередное выполнение операторов цикла while, не вычисляя выражение-условие. Если введено подряд пять непустых строк, то осуществляется переход на следующую итерацию цикла while. При этом вычисляется его выражение-условие, которое будет ложно (переменная $in имеет значение 0), это означает, что цикл while завершен, а следовательно, завершена и вся программа.

Следующая страница Содержание главы


Реклама