Книга: Язык программирования Perl

Лекция 4. Управляющие структуры

Лекция 4. Управляющие структуры

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

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

Минимальная синтаксическая единица языка программирования называется термом. Терм (term) - это все то, что может служить операндом в выражении, например, литерал или переменная. Выражение напоминает неоконченную фразу в естественном языке. Чтобы выражение стало законченным предложением (statement, называемым также утверждением), нужно после него поставить символ ";" (точка с запятой). Другими словами, простое предложение - это выражение, вычисляемое ради его побочного эффекта. Кроме предложений, в программе на Perl могут присутствовать объявления переменных и подпрограмм, которые будут рассмотрены позднее. Приведем примеры простых предложений:

$count = 0; # присваивание значения переменной
$count++; # изменение значения операнда
$result = 'Итого=' . $count . "n"; # подготовка к печати
1; # литерал - минимальное, но корректное предложение

Последнее предложение, кажущееся бессмысленным, применяется в последней строке модулей для возврата "истинного" значения при успешной загрузке модуля. О модулях будет подробно рассказано в лекции 13.

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

$count=0;$count++;$result='Итого='.$count."n";1;

Но это считается дурным тоном. Да и разобраться в подобной программе будет очень сложно даже ее автору, особенно если она большого размера и прошло некоторое время после ее написания!

Простые предложения, составленные из выражений, выполняются одно за другим, образуя простую последовательность действий. Последовательность может помещаться в блок - одно или несколько предложений, обрамленных фигурными скобками, которые рассматриваются как единое целое. Блоки применяются для группировки программных конструкций, а также для задания области видимости переменных. Точка с запятой может не ставиться в конце последнего предложения в блоке (как это делается в языке Pascal), но лучше ее ставить всегда, на случай, если позднее добавится еще одно предложение. Блок предложений может быть частью управляющей конструкции, такой как цикл или условный оператор. А блок, который не входит ни в одну управляющую конструкцию, называется голым блоком (bare block).

{ # начало блока
# последовательность предложений,
# образующих тело блока
} # конец блока

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

Как известно, условные предложения определяют выполнение тех или иных действий в программе в зависимости от проверки заданного условия. В Perl условная конструкция (или оператор if), проверяющая истинность одного условия, в самом простом виде записывается так:

if (условие) { # проверка истинности условия
# действия, выполняемые,
# если условие истинно
}
# продолжение программы

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

if ($count % 2 == 1) { # остаток от деления на 2 равен 1
print $count;
}

Другая общеизвестная форма условного предложения содержит блок, выполняемый при ложном условии, а именно:

if (условие) { # проверка истинности условия
# действия, выполняемые,
# если условие истинно
} else {
# действия, выполняемые в противном случае
# (если условие ложно)
}
# продолжение программы

В Perl имеется еще одна форма условного предложения, которая задает последовательную проверку нескольких условий, указанных в фразе if или elsif. Она выглядит следующим образом:

if (условие 1) { # проверка истинности 1-го условия
# действия, выполняемые,
# если условие 1 истинно}
elsif (условие 2) { # проверка истинности 2-го условия
# действия, выполняемые,
# если условие 2 истинно
# ... здесь могут быть еще фразы elsif ...
elsif (условие N) { # проверка истинности N-го условия
# действия, выполняемые,
# если условие N истинно
} else {
# действия, выполняемые,
# если все условия ложны
}
# продолжение программы

При этом выполнится один из блоков действий: соответствующий первому истинному условию или указанный за фразой else. Фраза else может отсутствовать, тогда при всех ложных условиях не делается ничего. Форма условного предложения с elsif заменяет отсутствующий в Perl оператор выбора (такой как do-case или switch).

Иногда требуется подчеркнуть, что, наоборот, ложность условия становится причиной выполнения каких-либо действий. Для этого в Perl есть еще одна разновидность условного предложения, которая записывается с помощью ключевого слова unless:

unless (условие) { # проверка ложности условия
# действия, выполняемые,
# если условие ложно
} else {
# действия, выполняемые
# в противном случае
}
# продолжение программы

Здесь фраза else также может отсутствовать, если при истинности условия не требуется выполнять никаких действий.

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

($count % 2 == 1) and print $count;

Условное выполнение действия можно также задавать с помощью модификаторов, о которых речь пойдет далее в этой лекции.

Как известно, циклом называется управляющая конструкция для повторения действий в программе, а однократное выполнение предложений в цикле называется итерацией. В Perl существует множество различных способов задать циклическую обработку данных. Один из них - это операторы управления циклом. Если требуется повторение действий в зависимости от истинности условия, можно воспользоваться циклом while (называемый также циклом с предусловием), в котором каждый раз перед выполнением цикла проверяется условие продолжения: если оно истинно, то блок тела цикла повторяется, иначе цикл заканчивается и выполняется следующее предложение программы.

while (условие) { # проверка истинности условия продолжения
# действия, выполняемые,
# пока условие истинно
}
# продолжение программы

Например, можно воспользоваться таким циклом while для вывода на системную консоль десяти случайных чисел от 0 до 1, сгенерированных встроенной функцией rand:

$i = 0;
while ($i < 10) { # повторять, пока счетчик не достиг 10
print "n", rand; # вывести с новой строки случайное число
$i++; # увеличить счетчик повторений
}

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

until (условие) { # проверка истинности условия окончания
# действия, выполняемые,
# пока условие ложно
}
# продолжение программы

Предыдущий пример вывода случайных чисел можно переписать с использованием цикла until:

$i = 0;
until ($i == 10) { # закончить, когда счетчик достигнет 10
print "n", rand; # вывести с новой строки случайное число
$i++; # увеличить счетчик повторений
}

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

for (первое выражение; условие; второе выражение) {
# действия, выполняемые,
# пока условие истинно
}
# продолжение программы

В заголовке цикла могут отсутствовать одно или оба выражения, а в случае отсутствия условия оно считается истинным. Однако при этом должны оставаться две точки с запятой, разделяющие выражения. Снова перепишем приведенный выше пример, на этот раз используя цикл for:

for ($i=0; $i<10; $i++) { # повторить 10 раз, увеличивая $i
print "n", rand; # вывести с новой строки случайное число
}

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

foreach переменная (список) { #
# работа с переменной, содержащей
# текущий элемент обрабатываемого списка
}

Примеры использования цикла foreach будут приведены в лекции 5, посвященной спискам. В ней будет рассмотрено еще несколько конструкций, также выполняющих повторение действий.

В Perl есть несколько предложений для управления выполнением программы с помощью перехода в указанную точку программы. Обычно это требуется при работе с циклами. Когда при работе цикла требуется досрочно закончить его выполнение при наступлении какого-то события, то для этого можно воспользоваться оператором last (аналог оператора break в языке C), который прерывает выполнение цикла и переходит к выполнению предложения, следующего за циклом. Например, напечатать 10 случайных чисел от 0 до 0,5 можно так:

$i = 0;
while (1) { # безусловно входим в цикл
$random = rand; # получаем случайное число
if ($i == 10) { # по достижении счетчика 10
last; # ПРЕРЫВАЕМ выполнение цикла
}
if ($random < 0.5) { # числа меньше 0.5
print "n", $random; # выводим на консоль
$i++; # и увеличиваем счетчик повторений
}
}
# сюда произойдет переход по last

Оператор next (аналог оператора continue в языке C) применяется, когда требуется пропустить выполнение оставшихся предложений в теле цикла и перейти к следующей итерации, начав с проверки условия в заголовке цикла. Вот так можно изменить последний пример, применив next:

$i = 0;
while ($i < 10) { # пока счетчик не достигнет 10
$random = rand; # получаем случайное число
if ($random > 0.5) { # числа больше 0.5
next; # ПРОПУСКАЕМ действия в теле цикла
}
print "n", $random; # выводим число на консоль
$i++; # и увеличиваем счетчик повторений
# сюда произойдет переход по next
}

Оператор redo используется, когда нужно повторить выполнение предложений в теле цикла без проверки условия в заголовке цикла. Вот таким станет последний пример, если использовать redo:

$i = 0;
while ($i < 10) { # пока счетчик не достигнет 10
# сюда произойдет переход по redo
$random = rand; # получаем случайное число
if ($random > 0.5) { # числа больше 0.5
redo; # СНОВА НАЧИНАЕМ действия в теле цикла
}
print "n", $random; # выводим число на консоль
$i++; # и увеличиваем счетчик повторений
}

Во всех рассмотренных циклах может присутствовать необязательный блок continue. В нем располагаются действия, которые нужно выполнить в конце цикла, в том числе при переходе к новой итерации по next. Действия в блоке continue не выполняются при переходах по last и redo. Это может показаться странным, но голый блок рассматривается в Perl как цикл, выполняющийся один раз. В таком блоке может присутствовать фраза continue и использоваться переходы last, next и redo. С учетом предложений управления циклом и блока continue циклическую структуру в общем виде можно изобразить так:

# может быть заголовок цикла: while, until или for
{
# сюда происходит переход по redo
# действие 1, выполняемое в цикле
if (условие выхода) {
last; # выход из цикла
}
# действие 2, выполняемое в цикле
if (условие продолжения) {
next; # выполнение следующей итерации цикла
}
# действие 3, выполняемое в цикле
if (условие возобновления) {
redo; # выполнение тела цикла сначала
}
# действие N, выполняемое в цикле
# сюда происходит переход по next
} continue {
# действие, выполняемое перед новой итерацией цикла
}
# сюда происходит переход по last

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

CYCLE_1:
while (условие продолжения цикла 1) {
CYCLE_2:
while (условие продолжения цикла 2) {
if (условие выхода из всех циклов) {
last CYCLE_1;
}
CYCLE3:
while (условие продолжения цикла 3) {
if (условия прерывания 2-го цикла) {
next CYCLE_2;
}
}
# сюда произойдет переход по next CYCLE_2
}
}
# сюда произойдет переход по last CYCLE_1

Метка может ставиться перед любым предложением. При помощи блока и операторов управления циклом с меткой можно имитировать управляющую структуру, подобную оператору switch в языке C. Например, так можно записать только одно присваивание переменной $say в зависимости от условия:

SWITCH: {
unless (defined $t) { # если $t не определено
$t = 25; redo SWITCH; # задать значение по умолчанию
}
if ($t < 10) { $say = 'холодно'; last SWITCH; }
if ($t < 18) { $say = 'прохладно'; last SWITCH; }
if ($t < 27) { $say = 'тепло'; last SWITCH; }
$say = 'жарко';
}
print "Сегодня $sayn";

В Perl имеется оператор перехода goto, в котором также используются метки. С его помощью можно перейти к выполнению помеченной конструкции в текущем или в вызывающем блоке. Но его нельзя применять для перехода в конструкцию, требующую инициализации: подпрограмму или цикл for. Этот оператор имеет три разновидности.

1 Переход к выполнению предложения, перед которым стоит метка:

goto МЕТКА;

2 Переход к метке, имя которой получено в результате вычисления выражения:

goto выражение;

3 Замена вызова указанной подпрограммы на подпрограмму, выполняющуюся в данный момент (применяется загрузчиками модулей Perl):

goto &подпрограмма;

Оператор goto заслуженно порицается теоретиками и практиками программирования, поскольку он сильно запутывает логику выполнения программы. Так что правилами хорошего стиля программирования рекомендуется использовать его только при крайней необходимости. Хотя goto и можно применить для выхода из цикла, но для этого лучше воспользоваться оператором last.

Порядок выполнения действий в простом предложении можно задавать с помощью модификаторов выражений. За любым выражением может стоять один из следующих модификаторов:

выражение if выражение
выражение unless выражение
выражение while выражение
выражение until выражение
выражение foreach список

Модификатор задает условие выполнения (в случае if или unless) или повторения (в случае while, until или foreach) выражения. Выражение модификатора вычисляется в первую очередь, хотя и стоит в конце конструкции. Хотя модификаторы похожи на условные и циклические управляющие конструкции, но они формируют простые предложения и поэтому не могут быть вложенными. Приведенную выше конструкцию выбора можно переписать с использованием условных модификаторов:

SWITCH: {
$t = -36, redo SWITCH unless defined $t;
$say = 'холодно', last SWITCH if $t < 10;
$say = 'прохладно', last SWITCH if $t < 18;
$say = 'тепло', last SWITCH if $t < 27;
$say = 'жарко';
}

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

++$count, --$sum while (rand < 0.1);
$random = rand until $random > 0.7;

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

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

$result = do { $x=rand; $a=0; } # в $result будет присвоен 0

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

SWITCH: {
(defined $t) || do { $t = 15; redo SWITCH; };
($t < 10) && do { $say = 'холодно'; last SWITCH; };
($t < 18) && do { $say = 'прохладно'; last SWITCH; };
($t < 27) && do { $say = 'тепло'; last SWITCH; };
$say = 'жарко';
}

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

do { $sum += rand; } until ($sum > 25);

Но поскольку эта конструкция - выражение, а не цикл, то операторы управления циклом в ней не работают.

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

eval выражение # вычислить выражение как код на Perl
eval блок # выполнить блок, перехватывая возможные ошибки

В любой форме eval возвращает значение последнего вычисленного выражения. В первой форме строковое выражение рассматривается eval как исходный код на Perl, который во время работы программы динамически компилируется и выполняется. Если при его компиляции или выполнении возникает ошибка, eval возвращает неопределенное значение, но программа не заканчивается аварийно, а сообщение об ошибке помещается в специальную переменную $@. Например:

$x = 0; $y = 5; # в выражении будет деление на 0
$expression = "$y/$x"; # строка, содержащая код для выполнения
$result = eval ($expression); # вычислить выражение
if ($@ eq '') { # проверить специальную переменную на ошибки
print "Выражение вычислено: $result";
} else {
print "Ошибка вычисления: $@";
}

Во второй форме блок предложений в конструкции eval, как и в конструкции do, становится выражением. Он компилируется обычным образом и выполняется во время работы программы, но возможные ошибки его выполнения также не приводят к аварийному завершению программы. Причину ошибки можно узнать из специальной переменной $@, а значением eval будет значение последнего предложения в блоке. Вот пример обработки ошибок в выражении eval:

$result = eval { # выполнить блок
$x = 0; $y = 5;
$z = $y / $x; # здесь будет деление на 0
}; # завершаем предложение точкой с запятой
unless ($@) { # проверить специальную переменную на ошибки
print "Выражение вычислено: $result";
} else {
print "Ошибка вычисления: $@";
}

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

use strict; # ограничить применение небезопасных конструкций
use warnings; # выводить подробные предупреждения компилятора
use diagnostics; # выводить подробную диагностику ошибок

Дополнительная диагностика компилятора поможет избежать многих ошибок при выполнении программы. Обычно прагмы могут включаться в любом месте программы с помощью ключевого слова use и выключаться при необходимости с помощью ключевого слова no, например:

use integer; # включить целочисленные вычисления
print 10 / 3; # результат: 3
no integer; # отключить целочисленные вычисления
print 10 / 3; # результат: 3.33333333333333

С помощью прагмы use constant можно определять в программе именованные константы, которые по традиции записываются заглавными буквами. Это делается таким образом:

use constant PI => 3.141592653; # число пи

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

use locale;

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

Материал этой лекции иллюстрирует упоминавшийся в лекции 1 принцип TMTOWTDI: в Perl часто существует несколько синонимичных конструкций, предоставляющих автору программы возможность наиболее точно выразить свой замысел в привычном для него стиле. Perl - демократичный язык, и каждый пишет на нем так, как ему удобнее и привычнее: начинающий программист использует простые средства, писавший ранее на другом языке найдет для себя знакомые конструкции, а опытный Perl-хакер может углубиться в синтаксические дебри. За многие годы использования Perl целой армией программистов в нем сложились устойчивые выражения (idioms, идиомы), подобные пословицам и поговоркам в естественных языках. Для примера можно привести некоторые из них:

# 1. Выполнять бесконечный цикл
for (;;) # читается "forever" - "всегда"
{ } # тело бесконечного цикла
# 2. Открыть файл или аварийно завершить программу
open FILE or die; # "открой файл или умри!"
# 3. Читать строки из входного потока и печатать их,
# используя буферную переменную по умолчанию
while (<>) { print; }
# 4. Присвоить переменной значение по умолчанию
# только, если ее значение не определено
$variable ||= $default_value;

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

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

perldoc perlstyle

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

# вводные комментарии к программе
use strict; # включение дополнительной...
use warnings; # ... диагностики
# use Env; # подключение нужных модулей (см. лекцию 13)
# package main; # объявление пакета (см. лекцию 13)
my $message = 'Привет!'; # объявление переменных и
print $message; # запись алгоритма программы
# описание форматов отчета (см. лекцию 10)
# описание подпрограмм (см. лекцию 12)
__END__ # необязательное обозначение конца программы

В этой лекции рассмотрены синтаксические правила составления предложений на языке Perl, изучив которые, можно начинать писать законченные программы. Многообразие синтаксических конструкций позволяет автору, исходя из своих предпочтений, применять любые из конструкций-синонимов для выражения особенностей алгоритма задачи. Дополнительные сведения о синтаксисе предложений, снабженные многочисленными примерами, можно узнать, выполнив Perl-утилиту вывода документации:

perldoc perlsyn

Оглавление книги


Генерация: 1.406. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз