Операторы цикла

Для реализации многократного вычисления группы операторов в Perl предусмотрены составные операторы цикла, которые, напомним еще раз, определяются в терминах блоков БЛОК. Их всего три: while, for и foreach, если не считать цикла until, который подобен циклу while, но выполняется, пока ложно его условие.

Циклы while и until

Цикл while предназначен для повторного вычисления группы операторов, определенных в его блоке БЛОК. Операторы блока будут циклически выполняться, пока остается истинным задаваемое в конструкции while выражение-условие. Синтаксис этого оператора цикла имеет две формы:
[МЕТКА] while (ВЫРАЖЕНИЕ) БЛОК
[МЕТКА] while (ВЫРАЖЕНИЕ) БЛОК continue БЛОК1
Метка, задаваемая как идентификатор с завершающим двоеточием, является не обязательной частью конструкции и используется для изменения естественного алгоритма выполнения оператора цикла командами управления циклом (next, redo, last).
Приведем схему выполнения оператора while.
  1. Вычисляется выражение-условие ВЫРАЖЕНИЕ.
  2. Если оно истинно, то выполняются операторы блока БЛОК и осуществляется возврат к пункту 1.
  3. Если выражение-условие ложно, то оператор цикла завершает свою работу и передает управление следующему после него оператору программы.
Таким образом, оператор цикла while является управляющей конструкцией цикла с предусловием: сначала проверяется условие завершения цикла, и потом только выполняется тело цикла, определяемое блоком БЛОК. Поэтому может оказаться, что тело цикла не будет выполнено ни одного раза, если при первом вхождении в цикл условие окажется ложным. Это обстоятельство следует учитывать при программировании с циклом while.
Для корректной работы цикла while необходимо, чтобы на каждом шаге цикла изменялось выражение-условие, иначе мы получим бесконечный цикл, если при вхождении в него это выражение истинно. Изменение выражения-условия на каждой итерации цикла можно реализовать либо непосредственно в самом выражении, например, использовать операцию чтения из стандартного входного потока <>, либо при выполнении операторов блока БЛОК обеспечить изменение значений переменных, входящих в выражение-условие.
Как и в случае с операторами ветвления, выражение-условие цикла while вычисляется в булевом контексте, но оно не обязательно должно быть представлено булевым выражением — его значение 0, "0" или "" трактуется как «ложь», все другие — как «истина».
Вместо ключевого слова while можно использовать ключевое слово until. В этом случае управляющая конструкция называется циклом until, который отличается от рассмотренного цикла whilе тем, что его тело выполняется только при условии ложности выражения-условия.
В листинге 5.6 приведены различные случаи использования операторов цикла while и until.
Листинг 5.6. Операторы цикла while и until
#!perl
use warnings;
# цикл 1 - изменение условия в теле цикла
$i = 1;
while ($i <= 3) {
  $а[$i] = sqrt $i:
  ++$i; # Изменение переменной из условия цикла
}
# цикл 2 - цикл until,  эквивалентный циклу 1
$i = 1;
until ($i > 3) {
  $a[$i] = sqrt $i;
  ++$i; # Изменение переменной из условия цикла
}
# цикл 3 - изменение условия в самой выражении -условии
# (завершается нажатием <Ctrl>+<C>).
while ($i = <>) {
  $a .= $i
}
# цикл 4 - тело цикла не выполняется ни одного раза 
$i = 5;
while ($i <= 3) {
  $a[$i] = 1/$i;
  ++$i;
}
#цикл 5 - бесконечный цикл (не изменяется условие)
$i=1;
while ($i <= 3) {
  $a[$i] = 1/$i;
}
ПРИМЕЧАНИЕ В языке нет специальной формы цикла с постусловием. Однако, как отмечалось ранее при рассмотрении модификаторов простых операторов, его легко реализовать с помощью модификатора while, примененного к конструкции do{}.
Вторая форма оператора цикла while (как и оператора цикла until) с блоком continue отличается от его первой формы только тем, что блок операторов БЛОК1, задаваемый после ключевого слова continue, выполняется всякий раз, когда осуществляется переход на выполнение новой итерации цикла. Это происходит после выполнения последнего оператора тела цикла или при явном переходе на следующую итерацию цикла командой next. Блок continue на практике используется редко, но с его помощью можно строго определить цикл for через оператор цикла while.
Листинг 5.7 демонстрирует использование цикла while для вычисления степеней двойки не выше шестнадцатой. В этом примере оператор цикла while функционально эквивалентен циклу for. Блок continue выполняется всякий раз после завершения очередной итерации цикла, увеличивая переменную $i на единицу. Он эквивалентен выражению увеличения/уменьшения оператора for (см. следующий раздел).
Листинг 5.7. Цикл while с блоком continue
#! perl -w
# Вычисление степеней числа 2 
$i = 1;
while ($i <= 16) {
  print "2 в степени $i: ", 2**$i, "\n"; 
} continue { 
    ++$i;  # Переменная $i всегда увеличивается
           # перед выполнением следующей итерации 
}
В циклах while и until допускается в выражении-условии объявлять локальную лексическую или динамическую переменную, область действия которой распространяется на все блоки цикла, включая блок continue. По завершении цикла будет восстановлено значение глобальной переменной с таким же именем, что и использованная локальная переменная цикла. Программа из листинга 5.8 демонстрирует использование локальной переменной $line в цикле while. В ней организован циклический ввод пользователем строк, пока он не введет строку "end" в любом регистре. По окончании цикла будет восстановлено значение глобальной переменной $line.
Листинг 5.8. Локальная переменная в цикле while с блоком continue
#!perl -w
$line = "end in main";
# Функцию my можно заменить на local 
while ((my $line = <>) !~ /^end$/i) {
  $line = lc $line;
} continue { 
    print $line;
}
# Напечатает "After while: end in main"
print "After while: $line\n";
ВНИМАНИЕ Объявление локальных переменных, доступных как в теле цикла, так и в блоке continue, возможно только и выражении-условии. Синтаксически допустимо объявлять локальные переменные в блоке самого цикла или блоке continue, но это приводит к тому, что такие переменные видны только в тех блоках, где они созданы. Объявление в выражении-условии или блоках нелокальных переменных приводит к их глобальному использованию.

Цикл for

При выполнении циклов while и until заранее не известно, сколько итераций необходимо выполнить, но иногда в программе необходимо выполнить заранее известное количество повторений определенной группы операторов. Например, нужно прочитать из файла 5 строк и видоизменить их по определенным правилам. Для решения подобных задач с заранее известным числом повторений язык Perl предлагает специальную конструкцию цикла — цикл for:
[МЕТКА] for (ВЫРАЖЕНИЕ1; ВЫРАЖЕНИЕ2; ВЫРАЖЕНИЕ3) БЛОК
ВЫРАЖЕНИЕ1, называемое инициализирующим выражением, используется для установки начальных значений переменных, управляющих циклом. Обычно это одна или несколько операций присваивания, разделенных запятыми.
ВЫРАЖЕНИЕ2 определяет условие, при выполнении которого будут повторяться итерации цикла. Оно, как и выражение-условие цикла while, вычисляется в булевом контексте и должно быть истинным, чтобы началась следующая итерация цикла. Как только это выражение становится ложным, цикл for прекращает свое выполнение и передает управление следующему за ним в программе оператору.
ВЫРАЖЕНИЕЗ вычисляется после завершения каждой итерации цикла и «отвечает» за увеличение/уменьшение значений переменных цикла на каждом его шаге. Его иногда называют изменяющим выражением.
Приведем схему выполнения цикла for.
  1. Вычисляется инициализирующее выражение ВЫРАЖЕНИЕ1.
  2. Вычисляется выражение-условие ВЫРАЖЕНИЕ 2. Если оно истинно, то выполняются операторы блока БЛОК, иначе цикл завершает свое выполнение.
  3. После выполнения очередной итерации вычисляется выражение увеличения/уменьшения (ВЫРАЖЕНИЕ З) и повторяется пункт 2.
Напомним, семантически цикл for эквивалентен циклу while с блоком continue. Например, следующий цикл:
for ($i = 1; $i <= 10; $i++) {
  . . .
}
эквивалентен циклу while
$i = 1;
while ($i <= 10) {
  . . .
} continue {
    $i++;
}
Цикл for, как и циклы while/until, определяет локальную лексическую, но не динамическую, область видимости для переменной цикла. Это позволяет использовать в качестве переменных цикла локальные переменные, объявленные с помощью функции my:
$line = "end in main";
for(my $line = 1; $line <=3; $line++) { 
  print "$line\n" ;
}
# Напечатает "After for: end in main"
print "After for: $line\n";
При выполнении этого фрагмента программы оператор печати будет последовательно отображать значения 1, 2 и 3 локальной переменной цикла $line. При выходе из цикла она будет уничтожена, и оператор печати вне цикла напечатает строку "After for: end in main", в которую будет подставлено значение глобальной переменной $line.
ВНИМАНИЕ. Цикл for не позволяет создавать локальные динамические переменные функцией local( ).
Все три выражения цикла for являются необязательными, и некоторые из них, или даже все, могут быть опущены, но соответствующие разделители ( ; ) должны быть оставлены. Если опущено выражение условия, то по умолчанию оно принимается равным «истина». Это позволяет организовать бесконечный цикл:
for (;;)  {
  # операторы этого блока будут выполняться бесконечно!
}
Выход из такого цикла осуществляется командами управления, о которых речь пойдет в следующем разделе этой главы.
Инициализировать переменную цикла можно и вне цикла, а изменять значение переменной цикла можно и внутри тела цикла. В этом случае инициализирующее и изменяющее выражения не обязательны:
$i=1;
for (; $i <= 3 ;) {
  . . .
  $i++;
}
СОВЕТ Хотя существует возможность изменения переменной цикла в теле цикла, не рекомендуется ею пользоваться. Цикл for был введен в язык именно для того, чтобы собрать в одном месте все операторы, управляющие работой цикла, что позволяет достаточно быстро изменить его поведение.
Цикл for позволяет использовать несколько переменных для управления своей работой. В этом случае в инициализирующем и изменяющем выражениях используется операция «запятая». Например, если мы хотим создать хэш-массив, в котором ключам, представляющим цифры от 1 до 9, соответствуют значения этих же цифр в обратном порядке от 9 до 1, то эту задачу можно решить с помощью цикла for с двумя переменными цикла:
for ($j = 1, $k = 9; $k>0; $j++, $k--) {
  $hash{$j} = $k;
}
Цикл for — достаточно гибкая конструкция, которую можно использовать не только для реализации цикла с заранее заданным числом итераций. Он позволяет в своих управляющих выражениях использовать вызовы встроенных и пользовательских функций, а не только определять и изменять переменные цикла. При таком подходе главное — помнить, что выражение-условие завершения цикла должно изменяться на каждой итерации. Листинг 5.9 демонстрирует именно такое использование цикла for.
Листинг 5.9. Ввод строк циклом for
#!реrl -w
for (print "Введите данные, завершение ввода <Enter>\n"; 
     mу $line = <>;
     print "Введите данные, завершение ввода <Enter>\n")
{
  last if $line eq "\n"; 
  print "Ввели строку: $line"; 
}
В этом примере пользователь вводит в цикле строки данных. Перед вводом новой строки отображается подсказка с помощью функции print ( ), которая определена в изменяющем выражении цикла. Выражение-условие завершения цикла представляет собой операцию ввода из стандартного потока <>. Так как это выражение вычисляется всякий раз, когда цикл переходит на очередную итерацию, то на каждом шаге цикла программа будет ожидать ввода с клавиатуры. Правда, для выхода из цикла мы прибегли к команде управления циклом last, вызываемой в случае ввода пользователем пустой строки, которая и завершает выполнение цикла.

Цикл foreach

В Perl списки являются столь полезными и часто используемыми конструкциями, что для организации цикла по их элементам в языке предусмотрен специальный оператор foreach, имеющий две формы синтаксиса:
[МЕТКА] foreach ПЕРЕМЕННАЯ (СПИСОК) БЛОК
[МЕТКА] foreach ПЕРЕМЕННАЯ (СПИСОК) БЛОК continue БЛОК
Он реализует циклическое вычисление операторов из блока БЛОК, последовательно присваивая переменной, определяемой параметром ПЕРЕМЕННАЯ, значения элементов списка СПИСОК. Группа операторов из блока continue выполняется всякий раз после завершения очередной итерации цикла и перед началом новой. Исключение представляет первая итерации, когда переменная цикла равна первому элементу списка, — в этом случае, естественно, перед началом итерации блок continue не вычисляется.
Список можно задавать или последовательностью значений, разделенных запятыми, или массивом скаляров, или функцией, возвращаемым значением которой является список. Главное — это то, что параметр СПИСОК вычисляется в списковом контексте.
ВНИМАНИЕ Задается лив цикле foreach параметр СПИСОК явно, как последовательность разделенных запятой значений, или неявно, как массив или обращение к функции, — в любом случае этот параметр цикла должен быть заключен в круглые скобки.
Этот цикл удобен для перебора элементов массива. Например, определить максимальный элемент массива можно с помощью программы из листинга 5.10, в которой цикл foreach используется для организации перебора элементов массива.
Листинг 5.10. Определение максимального элемента массива циклом foreach
#! perl -w
@array = (1,-6,9,18,0,-10);
@max = $array[0]; 
foreach $temp (@array) {
  $max = $temp if $temp > $max; 
} 
print "$max";
Отметим несколько особенностей цикла foreach. Прежде всего, следует сказать, что ключевое слово foreach является синонимом ключевого слова for. Цикл из листинга 5.10 можно было бы записать и так:
for $temp (@array) ( # Ключевое слово foreach синоним for. 
  $max = $temp if $temp > $max;
}
Однако, как нам кажется, использование foreach лучше отражает семантику этого оператора цикла, так как в самом ключевом слове уже отражена его сущность (foreach — для каждого).
Следующая особенность оператора foreach связана с переменной цикла. По умолчанию эта переменная является локальной, область видимости которой ограничена телом цикла, включая блок continue. Она создается только на время выполнения цикла foreach, доступна внутри всего цикла и уничтожается при выходе из него.
Обычно программисты Perl в циклах foreach вообще не применяют переменную цикла. Это связано с тем обстоятельством, что при отсутствии явно заданной переменной цикла Perl по умолчанию использует специальную переменную $_. С учетом этого факта цикл foreach листинга 5.10 можно переписать и так:
foreach (@аrrау) {  # В качестве переменной цикла
                    # используется $_.
  $max = $_ if  $_ > $max;
}
Последняя особенность оператора foreach, которая также связана с переменной цикла, заключается в том, что фактически на каждом шаге выполнения цикла эта переменная является синонимом того элемента списка, значение которого она содержит. Это позволяет в цикле изменять значения элементов списка, если они представляют правильные l-значения. Например, возвести в квадрат каждый элемент списка можно следующим оператором foreach:
foreach (@аrrау) {
  $_ **= 2; 
}
Как отмечалось, список в цикле foreach может быть задан и функцией, которая в списковом контексте возвращает целый список значений. Канонический способ печати хэш-массива в упорядоченном по ключам порядке представлен в программе из листинга 5.11.
Листинг 5.11. Упорядочивание и печать хэш-массива
#! perl -w 
%array = (
  blue   => 1,
  red    => 2,
  green  => 3,
  yellow => 3
); 
foreach (sort keys %array) {
  print "$_\t => $array{$_}\n"; 
}
Эта программа напечатает пары ключ/значение хэш-массива %array в соответствии с лексикографическим упорядочиванием его ключей:
blue   => 1
green  => 3
red    => 2
yellow => 3
ВНИМАНИЕ Цикл foreach, примененный к массиву, выполняется быстрее аналогичного цикла for, так как не требует дополнительных затрат на вычисление индекса элемента списка.
Как и в случае с оператором цикла while, если задан блок continue, то его операторы будут обязательно выполнены при переходе на новую итерацию цикла.
ВНИМАНИЕ Все рассмотренные нами операторы цикла могут быть вложенными друг в друга. Основное требование – вложенный оператор цикла должен завершаться в теле цикла, в котором он вложен.

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


Реклама