Управляющие конструкции

Perl относится к числу императивных алгоритмических языков. Это значит, что отдельные фразы языка Perl представляют собой предписания, говорящие «сделать то-то и то-то». Предписания исполняются в той последовательности, в которой они записаны в тексте программы. Изменить этот железный порядок можно лишь с помощью управляющих конструкций и операторов.

Для наглядного представления алгоритмов иногда используют блок-схемы.

Блок-схема представляет собой набор элементов нескольких видов, каждый из которых может иметь несколько входов и выходов (ноль или больше). Каждый выход элемента соединяется с входом некоторого другого элемента направленной стрелкой, и наоборот, каждый выход соединяется с некоторым входом.

Часто бывает нужно выполнить в программе некоторую последовательность команд при выполнении некоторого условия. Поскольку условие либо выполнено, либо не выполнено, решение о выполнении/невыполнении последовательности команд принимается на основании логического значения — да/нет.

Вот несколько примеров логических значений: «значение переменной $a не превосходит 2?» — $a<=2; «равна ли длина массива @ARGV четырём?» — @ARGV==4; «строка $x длиннее строки $y?» — length $x>length $y.

Рисунок 2.1. Блок-схема для конструкции if

Для того, чтобы выполнить последовательность команд при условии, используют условную конструкцию:

if(условие)
{
	действие
}

Рисунок 2.2. Блок-схема для конструкции if … else

Если нужно выполнить ту или иную последовательность команд в зависимости от выполнения/невыполнения условия, делают так:

Perl
if(условие) { действие₁ } else { действие₂ }

Простейший цикл — цикл с предусловием позволяет выполнять действия, пока выполнено некоторое условие.

Такой цикл записывается при помощи ключевого слова while, за которым следует логическое выражение в круглых скобках, а затем — блок команд (действия) в фигурных скобках. Блок называется ещё телом цикла:

while(условие)
{
	действие
}

Как видно рисунка 2.4. «Блок-схема для цикла с предусловием», выполнение цикла начинается с проверки условия. Если условие не выполнено (логическое выражение принимает ложное значение), выполнение программы продолжается, как если бы никакого цикла не было. В противном случае (логическое выражение истинно) выполняются команды из тела цикла, после чего снова проверяется условие, и, при необходимости, выполняются действия из тела цикла, и так далее.

Рисунок 2.4. Блок-схема для цикла с предусловием

Тело цикла может не выполниться ни разу, если при входе в цикл не выполняется условие.

Если желательно, чтобы выполнение цикла когда-нибудь завершилось (чаще всего это так), нужно позаботиться, чтобы действия, совершаемые в цикле, рано или поздно сделали бы условие цикла ложным. Но что же будет, если взять в качестве условия что-то, что заведомо истинно, например, выражение 2*2==4? Мы получим бесконечный цикл. Такое может случиться и при ошибках программирования. Если в теле цикла отсутствуют команды, выводящие текст на экран, работающая программа не будет подавать видимых признаков жизни, что неизменно вызывает панику у начинающих программистов. Они часто говорят в таких случаях: «компьютер повис». Вовсе нет! Вернуть контроль над ситуацией можно, прервав выполнение программы нажатием комбинации клавиш Ctrl+C. Появится командная строка, из которой можно запустить текстовый редактор и поправить текст программы.

[Примечание]Примечание

Можно написать такую программу, которая не будет прерываться при нажатии на Ctrl+C. Но, во-первых, мы ещё не скоро освоим технику такого программирования (об этом речь пойдёт в главе 37. ««Жизнь» Джона Конвея»), а во-вторых, найдутся и другие способы, чтобы прервать такую программу.

Иногда может возникнуть необходимость в бесконечных циклах. Нет нужды выдумывать всегда истинное условие; условие можно и пропустить:

Perl
while() { действие }

Отсутствие условия цикла или его постоянная истинность ещё не означает, что цикл бесконечный. Имеется команда last, позволяющие «выскочить» из цикла. Разговор об этой команде впереди.

Другая разновидность цикла, цикл с постусловием, отличается от цикла с предусловием тем, что условие проверяется не до, а после выполнения тела цикла (рисунок 2.5. «Блок-схема для цикла с постусловием»). Это, в частности, означает, что тело цикла будет выполнено как минимум один раз.

Рисунок 2.5. Блок-схема для цикла с постусловием

Для записи цикла с постусловием используется слово do, за которым следует тело цикла, а затем слово while и условие в круглых скобках:

Perl
do { действие } while(условие);

Обратите внимание на обязательную точку с запятой после условия.

Хотя, по нашим наблюдениям, цикл с постусловием применяется гораздо реже цикла с предусловием, он бывает в некоторых ситуациях удобен. Например, некоторые программы ведут диалог с пользователем, задавая вопросы и ожидая ответ определённого вида. Если ответ пользователя неприемлемый, вопрос повторяется. В таких случаях нужно прежде выполнить действия (задать вопрос и получить ответ), и лишь затем анализировать ответ, решая, повторять ли диалог ещё раз. В следующем примере пользователю будет задаваться вопрос, не прервать ли что-то, до тех пор, пока пользователь не введёт строку y (естественно, от слова yes):

Perl
do { print 'Прервать (y/n)? '; $_=<STDIN>; } while($_ ne "y\n");

Здесь строка, введённая пользователем, отправляется в переменную $_ по команде $_=<STDIN>. Условием цикла является несовпадение введённой строки и строки "y\n". Заметьте, что обозначением конца ввода служит нажатие клавиши Enter, при котором к считанной строке добавляется символ конца строки. Поэтому, вычисляя условие цикла, мы сравниваем $_ не со строкой "y", а с "y\n". Приведённый пример можно записать короче, не используя переменную:

Perl
do { print 'Прервать (y/n)? '; } while(<STDIN> ne "y\n");

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

$i=1;
while($i<=10)
{
	print "$i\n";
	$i++;
}

Здесь подготовительная работа заключается в присваивании начального значения 1 переменной $i, а изменения в конце тела цикла — увеличение на единицу этой переменной.

Рисунок 2.6. Блок-схема для цикла for

Как раз для таких ситуаций в Perl существует разновидность цикла с предусловием — цикл «для»:

Perl
for(инициализация; условие; модификация) { действие }

Такой цикл почти в точности равносилен циклу

Perl
инициализация while(условие) { действие модификация }

Спрашивается, а зачем нужна ещё одна конструкция, если она сводится к уже имеющейся? Цикл «для» записывается чуть короче, и, что важно, в нём заключено всё, что необходимо для цикла, включая подготовку. Использование цикла «для» во многих случаях делает программу более понятной.

Такой цикл идеален для очень часто встречающихся ситуаций, когда перебираются числа, причём следующее число вычисляется из предыдущего (в простейшем случае увеличением предыдущего на единицу).

Программу, выводящую натуральные числа, можно с учётом сказанного переписать так:

Perl
for($i=1; $i<=10; $i++) { print "$i\n"; }

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

Perl
for($s=0, $i=1; $i<=10; $i++) { $s+=$i; } print "$s\n";

Можно пойти ещё дальше, и перенести единственную команду из тела цикла в блок модификации:

Perl
for($s=0, $i=1; $i<=10; $s+=$i, $i++) {} print "$s\n";

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

Perl
for($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 предоставляет ещё одну циклическую конструкцию — цикл «для каждого». Она полезна в случаях, когда нужно выполнить какое-то действие поочерёдно для каждого элемента списка:

foreach переменная(список)
{
	действие
}

В таком цикле скалярная переменная принимает по очереди значения из списка; действие может как-то использовать эту переменную.

Например, так можно вывести названия планет на экран по одному в строке:

foreach $planet(qw/Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон/)
{
	print "$planet\n";
}

Можно не указывать переменную, в этом случае будет использоваться специальная переменная по умолчанию, $_:

foreach(список)
{
	действие
}

Например, для вывода квадратов первых десяти натуральных чисел напишем

foreach(1..10)
{
	print $_**2, "\n";
}

Это, между прочим, более лаконичная альтернатива циклу

for($i=1; $i<=10; $i++)
{
	print $i**2, "\n";
}

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

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

Цикл «для каждого» очень полезен для последовательной обработки элементов массива, когда обрабатываются сами элементы, а их индексы не используются. Вместо громоздких циклов

for($i=0; $i<@planets; $i++)
{
	print "$planets[$i]\n";
}

или

foreach(0..$#planets)
{
	print "$planets[$_]\n";
}

пишем просто

foreach(@planets)
{
	print "$_\n";
}

Кстати, вместо foreach можно всюду писать просто for. Мы так и будем поступать в дальнейшем в этой книге.

Иногда возникает необходимость завершить цикл досрочно. Например, проверяя данное число на простоту, мы делим это число с остатком на числа, меньшие данного. Появление первого же нулевого остатка свидетельствует о том, что число составное, и уже нет никакой нужды продолжать деление. Цикл следует прервать. Для этой цели служит команда last. Эта команда немедленно завершает цикл, и программа продолжит исполнение с команд, непосредственно следующих за циклом:

while()
{
	
	last; ─╮
	…      │
}          │
◀──────────╯
…

Конечно, применять команду last имеет смысл лишь при некотором условии, в противном случае команды тела цикла, следующие за last, никогда не выполнятся.

Команда last прерывает исполнение самого внутреннего цикла, охватывающего эту команду:

while()
{
	
	while()
	{
		
		last; ─╮
		…      │
	}          │
	◀──────────╯
	…
}

Если требуется завершить какой-то из внешних циклов, нужно

Вот как это делается:

outer: while()
{
	
	while()
	{
		
		last outer; ─╮
		…            │
	}                │
	…                │
}                    │
◀────────────────────╯
…

Информатика-54© А. Н. Швец