07 апреля 2020

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

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

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

Циклы while и until

      Цикл while предназначен для повторного вычисления блока операторов, пока остается истинным задаваемое в нем выражение-условие. Его общий синтаксис имеет две формы:
МЕТКА while (ВЫРАЖЕНИЕ) БЛОК
МЕТКА while (ВЫРАЖЕНИЕ) БЛОК continue БЛОК1
     Все операторы цикла могут быть снабжены не обязательными метками. В Perl метка представляет правильный идентификатор, завершающийся двоеточием ":". Она важна для команды перехода next, о которой мы поговорим в следующем разделе.
     Оператор while выполняется по следующей схеме. Вычисляется выражения-условия ВЫРАЖЕНИЕ. Если оно истинно, то выполняются операторы БЛОК. В противном случае оператор цикла завершает свою работу и передает управление следующему после него оператору программы (цикл 1 примера 5.5). Таким образом, оператор цикла while является управляющей конструкцией цикла с предусловием: сначала проверяется условие завершения цикла, а потом только тело цикла, определяемое операторами БЛОК. Поэтому может оказаться, что тело цикла не будет выполнено ни одного раза, если при первом вхождении в цикл условие окажется ложным (цикл 3 примера 5.5).
     Вместо ключевого слова while можно использовать ключевое слово until. В этом случае управляющая конструкция называется циклом until, который отличается от разобранного цикла while тем, что его тело выполняется, только если выражение условия ложно (цикл 2 примера 5.5).
# peri -w
# цикл 1
$i = 1;
while <$i•<= 3) {
$a[$i] = l/$i; # Присвоить значение элементу массива
++$i; >
print "Переменная цикла \$i = $i\n"; # $i = 4 print "Массив \@a: @a\n"; # @a = (I, 0.5. 0.333333333333333)
# цикл 2, эквивалентный предыдущему
$i = 1;
until ($i > 3) {
$a[$i] = l/$i; # Присвоить значение элементу массива
++$i; }
print "Переменная цикла \$i = $i\n"; # $i = 4 print "Массив \@a: @a\n"; # @a = (1, 0.5. 0.333333333333333)
# цикл З, тело цикла не выполняется ни одного раза
$i = 5;
while ($i-<= 3) {
$a[$i] = l/$i;
++$i; } print "Переменная цикла \$i = $i\n"; # $i = 5
# цикл 4, бесконечный цикл (не изменяется выражение условия)
$i = 1;
while ($i <= 3) {
$a[$i] = l/$i; } .
     Обратим внимание на то, что в теле цикла должны присутствовать операторы, вычисление которых приводит к изменению выражения условия. Обычно это операторы, изменяющие значения переменных, используемых в выражении условия. Если этого не происходит, то цикл while или until будет выполняться бесконечно (цикл 4 примера 5.5).
Замечание
Цикл с постусловием реализуется применением модификатора while к конструкции do{), и рассматривался нами в разделе 5.2.2 " Модификаторы while и until".

      Блок операторов БЛОК!, задаваемый после ключевого слова continue, выполняется всякий раз, когда осуществляется переход на выполнение новой итерации. Это происходит после выполнения последнего оператора тела цикла или при явном переходе на следующую итерацию цикла командой next. Блок continue на практике используется редко, но с его помощью можно строго определить цикл for через оператор цикла while.
     Пример 5.6 демонстрирует использование цикла while для вычисления степеней двойки не выше шестнадцатой. В этом примере оператор цикла while функционально эквивалентен циклу for. Блок continue выполняется всякий раз по завершении очередной итерации цикла, увеличивая переменную $i на единицу. Он эквивалентен выражению увеличения/уменьшения оператора for.
# peri -w
# Вычисление степеней числа 2 $1 = I;
while ($i <= 16) {
print "2 в степени $i: ", 2**$i, "\n"; } continue {
++$i; f Увеличение переменной $i перед выполнением следующей итерации }

Цикл for
     При выполнении циклов while и until заранее не известно, сколько итераций необходимо выполнить. Их количество зависит от многих факторов: значений переменных в выражении условия до начала выполнения цикла, их изменении в теле цикла, виде самого выражения условия и т. п. Но иногда в программе необходимо выполнить заранее известное количество повторений определенной группы операторов. Например, прочитать из файла 5 строк и видоизменить их по определенным правилам. Конечно, можно такую задачу запрограммировать операторами цикла while и until, но это может выглядеть не совсем выразительно. В том смысле, что при прочтении программы придется немного "пошевелить" мозгами, прежде чем понять смысл оператора цикла. Для решения подобных задач с заранее известным числом повторений язык Perl предлагает специальную конструкцию цикла — цикл for:
МЕТКА for (ВЫРАЖЕНИЕ1; ВЫРАЖЕНИЕ2; ВЫРАЖЕНИЕЗ) БЛОК
ВЫРАЖЕНИЕ1 используется для установки начальных значений переменных, управляющих циклом, поэтому его называют инициализирующим выражением. Обычно это одна или несколько операций присваивания, разделенных запятыми.
ВЫРАЖЕНИЕ2 определяет условие, при котором будут повторяться итерации цикла. Оно, как и выражение-условие цикла while, должно быть истинным, чтобы началась следующая итерация. Как только это выражение становится ложным, цикл for прекращает выполняться и передает управление следующему за ним в программе оператору.
ВЫРАЖЕНИЕЗ отвечает за увеличение/уменьшение значений переменных цикла после завершения очередной итерации. Обычно оно представляет собой список выражений с побочным эффектом или список операций присваивания переменным цикла новых значений. Его иногда называют изменяющим выражением.
Алгоритм выполнения цикла for следующий:
1. Вычисляется инициализирующее выражение (ВЫРАЖЕНИЕ!).
2. Вычисляется выражение условия (вырАЖЕШЕ2). Если оно истинно, то выполняются операторы блока БЛОК, иначе цикл завершает свое выполнение.
3. После выполнения очередной итерации вычисляется выражение увеличения/уменьшения (ВЫРАЖЕНИЕЗ) и повторяется пункт 2.
Как отмечалось в предыдущем разделе, цикл for эквивалентен циклу while с блоком continue. Например, следующий цикл
for ($i = 1; $i .<= 10; $i++) { }
эквивалентен циклу while
$i =1;
while ($i <= 10) {
} continue {
$i++; }
     Существует единственное отличие между этими двумя циклами. Цикл for определяет лексическую область видимости для переменной цикла. Это позволяет использовать в качестве переменных цикла локальные переменные, объявленные с помощью функции ту:
$i = "global";
for (my $i = 1; $i <= 3; $i++) {
print "Внутри цикла \$i: $i\n"; } print "Вне цикла \$i: $i\n ";

      При выполнении этого фрагмента программы оператор печати будет последовательно отображать значения 1, 2 и з локальной переменной цикла $1. При выходе из цикла локальная переменная $i будет уничтожена и оператор печати вне цикла напечатает строку global — значение глобальной переменной $1, определенной вне цикла for.
     Все три выражения цикла 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 переменная цикла может как увеличиваться, так и уменьшаться. Главное, чтобы выражение условия правильно отслеживало условия продолжения итераций цикла.
     Цикл for достаточно гибкая конструкция, которую можно использовать не только для реализации цикла с заранее заданным числом итераций. Он позволяет в инициализирующем и изменяющем выражениях использовать вызовы встроенных и пользовательских функций, а не только определять и изменять переменные цикла. Основное — чтобы изменялось выражение условия завершения цикла. Пример 5.7 демонстрирует именно такое использование цикла for.
# peri -w
for (print "Введите данные, для завершения ввода нажмите <Enter>\n"; <STDIN>;
print "Введите данные, для завершения ввода нажмите <Enter>\n") {
last if $_ eq "\n"; print "Ввели строку: $_"; }

      В этом примере пользователь вводит в цикле строки данных. Перед вводом новой строки отображается подсказка с помощью функции print (), которая определена в изменяющем выражении цикла. Выражение условия представляет операцию ввода из файла стандартного ввода <STDIN>. Так как это выражение вычисляется всякий раз, когда цикл переходит на очередную итерацию, то на каждом шаге цикла программа будет ожидать ввода с клавиатуры. Выход из цикла осуществляется командой last, вызываемой в случае ввода пользователем пустой строки. Введенные данные сохраняются во встроенной переменной $_, причем в ней сохраняется и символ перехода на новую строку, являющийся признаком завершения операции ввода данных. Поэтому при вводе пустой строки на самом деле в переменной $_ хранится управляющая последовательность "\n", с которой и осуществляется сравнение для реализации выхода из цикла.
     Пример 5.8 демонстрирует программу, читающую 3 строки файла егг.егг. Операция чтения из файла задается в инициализирующем и изменяющем выражении вместе с определением и изменением переменной цикла $1.
# peri -w
open (FF, "err.err") or die "Ошибка открытия файла";
for ($line=<FF>, $count = 1; $count <=3; $line=<FF>, $count++)
{
print "Строка $count:\n $line\n";
t ' • }
close(FILE);

Цикл foreach
     Одно из наиболее частых применений циклов в языках программирования — организация вычислений с элементами массивов: найти максимальный элемент, распечатать элементы массива, выяснить, существует ли элемент массива, равный заданному значению, и т. п. Подобные задачи легко решаются с помощью циклов while и for. В примере 5.9 определяется максимальный элемент массива (в предположении, что он содержит числовые данные).
#! peri -w
@array = (1,-6,9,18,0,-10);
$max = $array[0]; ,
for ($i = 1; $i <= $farray; $i++) {
$max = $array[$i] if $array[$i] > $max; }

      После выполнения программы примера 5.9 переменная $max будет иметь значение 18, равное максимальному элементу массива $аггау. Обратим внимание читателя на то, что в цикле for (как и в цикле while) доступ к элементам массива организуется с помощью индекса.
     В Perl списки и массивы, являющиеся, по существу, также списками, являются столь полезными и часто используемыми конструкциями, что для организации цикла по их элементам в языке предусмотрен специальный оператор foreach, имеющий следующий синтаксис:
МЕТКА foreach ПЕРЕМЕННАЯ (СПИСОК) БЛОК
МЕТКА foreach ПЕРЕМЕННАЯ (СПИСОК) БЛОК continue БЛОК

      Он реализует цикл по элементам списка список, присваивая на каждом шаге цикла переменной ПЕРЕМЕННАЯ значение выбранного элемента списка. Блок операторов continue выполняется всякий раз, как начинается очередная итерация, за исключением первой итерации, когда переменная $temp равна первому элементу списка. Список можно задавать или последовательностью значений, разделенных запятыми, или массивом скаляров, или функцией, возвращаемым значением которой является список. Определение максимального элемента массива можно переписать с циклом foreach (пример 5.10).
#! peri, -w
Sarray = (1,-6,9,18,0,-10) ; $max = $array[0]; foreach $temp (Sarray) (
$max = $temp if $temp > $max; } print "$max";

      На каждом шаге цикла переменная $temp последовательно принимает значения элементов массива $аггау. Обратите внимание на внешний вид программы — в отсутствии индексов массива она стала лучше читаемой.
      Отметим несколько особенностей цикла foreach. Прежде всего следует сказать, что ключевое слово foreach является синонимом ключевого слова for. Цикл из примера 5.10 можно было бы записать и так:
for $temp (@array) { # Ключевое слово foreach синоним for.
$max = $temp if $temp > $max; }
     Однако, как нам кажется, использование foreach лучше отражает семантику этого оператора цикла, так как в самом ключевом слове уже отражена его сущность (for each — для каждого).
     Следующая особенность оператора foreach связана с переменной цикла. По умолчанию эта переменная является локальной, область видимости которой ограничена телом цикла. Она создается только на время выполнения оператора foreach, доступна внутри тела цикла и уничтожается при выходе из цикла.
     Обычно программисты, работающие на языке Perl, вообще не применяют в циклах foreach переменную цикла. Это связано с тем обстоятельством, что в отсутствии явно заданной переменной цикла Perl по умолчанию использует специальную переменную $_. На каждом шаге цикла именно она будет содержать значение элемента списка или массива. С учетом этого факта цикл foreach примера 5.10 можно переписать так:
foreach (@array) { # В качестве переменной цикла используется $_.
$гоах = $_ if $_ > $max;
} . l

      Последняя особенность оператора foreach, которая также связана с переменной цикла, заключается в том, что фактически на каждом шаге выполнения цикла эта переменная является синонимом того элемента списка, значение которого она содержит. Это означает, что ее изменение в цикле приводит к изменению значения соответствующего элемента списка. Это свойство цикла foreach удобно для изменения значений элементов списка. Отметим, что его можно применять к спискам, хранящимся в массивах. Например, возвести в квадрат каждый элемент списка можно следующим оператором foreach:
foreach $temp (@array) {
$temp **= 2; }

       Список, по элементам которого организуется цикл, может быть задан не только явно конструктором или переменной массива, но и функцией, возвращаемым значением которой является список. Канонический способ печати хеш-массива в упорядоченном порядке представлен в примере 5.11.
# peri -w %array = {
blue => 1,
red => 2,
green => 3,
yellow => 3 ); foreach (sort keys %array) {
print "$_\t => $array{$_}\n"; } '

Эта программа напечатает пары ключ/значение хеш-массива %аггау в соответствии с алфавитным порядком его ключей:
blue => 1
green => 3
red => 2
yellow => 3
Замечание
Цикл foreach выполняется быстрее аналогичного цикла for, так как не требует дополнительных затрат на вычисление индекса элемента списка