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

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

Блок-схемы

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

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

элементзначение

Инициатор — обозначает начало алгоритма. Имеет один выход.

Терминатор — обозначает конец алгоритма. Имеет один вход.

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

Коннектор. Имеет несколько входов и один выход.

Условный блок. Используется для направления алгоритма по одному их двух путей в зависимости от выполнения условия. Имеет один вход и два выхода.

Условная конструкция

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

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

Конструкция if

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

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

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

Конструкция if … else

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

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

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

Конструкция if … elsif … else

Вместо вложенных if … else

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

удобно применять конструкцию if … elsif … else:

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

Последний else не обязателен.

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

Рисунок 2.3. Блок-схемы для конструкций if … elsif и if … elsif … else

Пример условной конструкции

Perl
if($D>0) { print 'Решения: ', (-$b-sqrt $D)/2/$a, ', ', (-$b+sqrt $D)/2/$a, "\n"; } elsif($D==0) { print 'Решение: ', $x=-$b/2/$a, "\n"; } else { print "Решений нет!\n"; }

(Здесь команда die выводит сообщение об ошибке и немедленно завершает программу.)

Скалярные значения в качестве логических

Для хранения логических значений в Perl используются скалярные переменные (до сих пор мы знали, что в скалярных переменных можно хранить числа и строки). Более того, и числовые, и строковые значения могут интерпретироваться как логические согласно следующим правилам: ложными считаются неопределённые значения, пустая строка, строка "0" и число 0. Остальные скалярные значения считаются истинными. Например, логические выражения @a>0 и @a принимают одно и то же значение.

Конструкция unless

Имеется ещё одна условная конструкция, без которой можно было бы обойтись:

Perl
unless() { }

означает то же самое, что и

Perl
if(not()) { }

или

Perl
if(!()) { }

Unless по английски значит «если не».

Мы любим писать

Perl
unless()

вместо

Perl
if(==0)

Например,

Perl
unless($n % $k)

означает «если $n делится нацело на $k».

Циклы

Цикл с предусловием

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

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

Perl
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");

Цикл «для»

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

или

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

пишем просто

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

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

Оператор last

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

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

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

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

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

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

  • пометить нужный цикл, подлежащий досрочному прерыванию;
  • после команды last поместить метку.

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

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

Оператор next

Может потребоваться досрочно прервать выполнение тела цикла, не прерывая сам цикл. К примеру, в цикле по одному обрабатываются файлы из некоторого списка. Пусть обработка каждого файла заключается, скажем, в печати его содержимого на экран. Если какой-то из файлов отсутствует, совершенно незачем пытаться распечатывать его содержимое. Тогда обработку файла нужно прервать, и переходить к следующему:

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

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

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

Если требуется прервать исполнение тела одного из внешних циклов, нужно применить метки:

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

Оператор redo

Оператор redo перезапускает выполнение тела цикла:

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

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

Оператор goto

Пока нет текста

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