Процедуры

Важным свойством языка Perl является возможность создания процедур (функций, подпрограмм). Такая возможность роднит Perl с очень многими другими алгоритмическими языками.

Процедура — это часть кода программы, имеющая, как правило, имя. Эта часть кода называется телом процедуры. Использование процедуры называется вызовом. Процедура может быть вызвана любое число раз в тексте программы. Вызов процедуры эквивалентен (с некоторыми оговорками) вставке её тела в месте вызова.

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

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

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

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

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

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

sub helloWorld
{
	print "Hello, world!\n";
}

Определение процедуры должно предшествовать её вызовам. Поэтому удобно помещать определения процедур в начале программы. Однако если по какой-то причине нужно разместить определение процедуры после её вызова (иногда это бывает необходимо), следует поместить перед первым вызовом объявление. Объявление выглядит точно так же, как и определение, за исключением тела процедуры — оно в объявлении не указывается:

sub helloWorld;

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

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

Внутри тела процедуры переданные параметры видны как элементы массива @_. Например, определённая ниже процедура hello печатает Hello, world!, если вызвана без параметров, но, будучи вызвана с параметром — именем пользователя, приветствует этого пользователя:

sub hello
{
	unless(@_)
	{
		print "Hello, world!\n";
	}
	else
	{
		print "Hello, $_[0]!\n";
	}
}

То же самое, используя оператор ?:, можно запрограммировать короче:

sub hello
{
	print('Hello, '.(@_? $_[0]: 'world')."!\n");
}

Процедуры можно применять для вычислений таким образом, чтобы вычисленное значение могло быть использовано в программе. В таких случаях говорят, что значение возвращается из процедуры. Для примера запрограммируем процедуру hypot, которая по двум заданным числам — катетам прямоугольного треугольника, вычисляет гипотенузу по формуле Пифагора c = a 2 + b 2 :

sub hypot
{
	return sqrt($_[0]**2+$_[1]**2);
}

Оператор return прерывает выполнение тела процедуры, и делает вычисленное значение гипотенузы доступным для использования в программе в месте вызова процедуры hypot. Как воспользоваться возвращённым значением, мы обсудим в разделе «Вызов процедуры».

Заметим, что из процедуры могут возвращаться не только скалярные значения, но и значения-массивы (в том числе ассоциативные).

Оператор return может использоваться и без параметра — возвращаемого значения, если нужно досрочно прекратить выполнение тела процедуры.

Чтобы вызвать процедуру без параметров, нужно упомянуть её имя в программе. Так, вызов

hello;

приведёт к печати универсального приветствия:

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

hello('пользователь Мозжечок');

будет напечатано

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

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

Perl
print sqrt(2)+sqrt(3), "\n"; print sqrt 2+sqrt 3, "\n";

Будет напечатано

3.14626436994197 1.93185165257814

Первое из двух выведенных значений вычислено по формуле 2 + 3 , а второе — по формуле 2 + 3 .

Если имеются сомнения, лучше заключить параметры в скобки.

Текста пока нет, но он скоро будет.

Рекурсия — это ситуация, когда процедура вызывает сама себя. Если такой вызов происходит непосредственно в теле процедуры, говорят о прямой рекурсии.

Возможна также косвенная рекурсия, когда, к примеру, процедура a вызывает процедуру b, а та, в свою очередь, прямо или косвенно, вызывает процедуру a. И та и другая рекурсии допускаются в языке Perl.

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

Именно в случае косвенной рекурсии возникает необходимость в объявлениях процедур: как бы мы ни располагали определения процедур a и b, неизбежно вызов одной из них внутри тела другой встретится в тексте программы раньше, чем определение:

Perl
sub b; без этого объявления процедуры b… sub a { b; …компилятор пожалуется на неизвестное имя b } sub b { a; }

Рекурсия является источником очень интересных программистских решений (некоторые из них встретятся в настоящем руководстве). Однако рекурсивное программирование требует повышенного умственного напряжения. Причина этого проста: любая ошибка, допущенная при программировании тела рекурсивной процедуры, будет многократно «усилена» при многократных рекурсивных вызовах. И, глядя на неверно работающую программу, бывает очень трудно выявить ту самую, возможно, пустяковую ошибку. Кроме этого, рекурсия при программировании на Perl весьма ресурсоёмка. Когда процедура прерывает своё исполнение вызовом самой себя, требуется запомнить точку, в которой произошло прерывание (чтобы потом было возможно продолжить исполнение с этого места). К тому же следует запомнить состояние всех локальных переменных, определённых в теле процедуры. Всё это ведёт к существенному расходу памяти, поэтому слишком глубоко вложенная рекурсия может так загрузить компьютер, что будет сложно даже прервать выполнение программы (компьютер будет так занят изысканием памяти, что ему будет некогда откликаться на внешние раздражители). Эта проблема обсуждается в главе 12. «Вычисление НОД» в разделе «Алгоритм Евклида (итеративная версия)».

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

Под занавес приведём пример рекурсивной реализации процедуры hello:

Perl
sub hello { return hello('world') unless @_; print("Hello, $_[0]!\n"); }

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