Глава 7. Подпрограммы
Коротко
Работа с подпрограммами
Идея подпрограммы по сути есть воплощение старого лозунга программистов: разделяй и властвуй. Подпрограммы позволяют разбить код на части обозримого размера, что несколько упрощает процесс программирования. Предположим, например, требуется напечатать два числа, выводя для чисел, которые меньше десяти, сообщение об ошибке:
$value = 10;
if ($value > 10) {
print "Value is $value.\n";
} else {
print "Value is too small.\n";
}
$value = 12;
if ($value > 10) {
print "Value is $value.\n";
} else {
print "Value is too small.\n";
}
Value is too small.
Value is 12.
Однако, можно сделать лучше — сэкономить место, упаковав повторяющиеся блоки условных операторов в виде подпрограммы. Назовем такую подпрограмму, например, printifOK:
sub printifOK
{
my $internalvalue = shift(@_);
if ($internalvalue > 10) {
print "Value is $internalvalue.\n";
} else {
print "Value is too small.\n";
}
}
Переданные подпрограмме параметры запоминаются, а потом по мере надобности извлекаются из специального массива @_. Весь оставшийся код — это уже знакомый нам условный оператор. Чтобы использовать подпрограмму, ей надо передать требуемые значения. Результат, конечно, получается ровно таким же, как и в предыдущем примере.
$value = 10;
printifOK($value);
$value = 12;
printifOK($value);
Value is too small.
Value is 12.
Подпрограммы могут также возвращать значения. Так в следующем случае, с помощью команды return, возвращается сумма двух переданных величин:
sub addem
{
($value1, $value2) = @_;
return $value1 + $value2;
}
print "2 + 2 = " . addem(2,2) . "\т"ж
2 + 2 = 4
Подсказка: В других языках программирования функции поддерживаются отдельно от подпрограмм. Но в Perl подпрограммы могут возвращать значения, и для роли функций не вводятся подпрограммы специального типа. Тем самым слова «функция» и «подпрограмма» в Perl являются синонимами.
Вот вкратце как работают подпрограммы — они позволяют разбивать код на полуавтономные куски, которым передаются одни данные, а обратно возвращаются другие. Такая разбивка кода делает программы более легкими для написания и сопровождения. Зачастую это еще и сильно сокращает размер программ. Теперь рассмотрим все более детально.
Непосредственные решения
Объявление подпрограмм
Объявление можно использовать, чтобы сообщить Perl о существовании подпрограммы, и уточнить при этом типы передаваемых аргументов и возвращаемого значения. Объявление (declaration) подпрограммы отличается от определения (definition) —при определении задается код, составляющий тело подпрограммы.
В отличие от других языков программирования, в Perl перед использованием объявлять подпрограммы не надо. Исключением является вызов подпрограмм (функций) без круглых скобок, охватывающих список параметров (например, списочных операторов). В этом случае перед тем, как использовать подпрограмму, необходимо ее объявить.
Подпрограмма может быть объявлена одним из следующих способов:
sub имя;
sub имя (прототип);
sub имя {блок};
sub имя (прототип) {блок};
(в последних двух случаях вместе с описанием подпрограммы производится ее определение, то есть задается код, составляющий тело подпрограммы).
При объявлении подпрограммы можно указать ее прототип — по нему Perl определяет параметры подпрограммы. Некоторые программисты предпочитают использовать прототипы, как способ проверки правильности кода, — более подробно об этом рассказывается в следующем разделе «Использование прототипов».
Разрешается импортировать подпрограммы из пакетов Perl:
use пакет qw(имя1 имя2 имя3);
(Использование псевдокавычек qw/.../ (см. таблицу 2.3 в главе 2) — самый простой способ создать список из строк-имен функций, заключенных в кавычки. Более подробно о команде use рассказывается в главе 13.)
Для подпрограмм можно использовать любые имена. Однако, следует помнить, что Perl резервирует имена, составленные целиком из заглавных букв, для неявно вызываемых Perl подпрограмм пакетов типа BEGIN или END.
Чтобы отличать имена подпрограмм от других типов данных перед каждым именем подразумевается символ &. В большинстве случаев его можно опустить — если Perl распознает из контекста, что имеет дело с подпрограммой, он самостоятельно подставляет в начале имени символ &. Например, если вы присвоили своей подпрограмме имя count, то можете вызывать ее как count(1,2) или &count(1,2) (см. раздел «Вызов подпрограмм» далее в этой главе).
Использование прототипов
Некоторые программисты любят использовать прототипы, как средство проверки правильности вызова подпрограмм — например, что в качестве параметра вместо массива не передается скалярная переменная, и т. д. Чтобы объявить прототип, надо перечислить в нужном порядке символы, соответствующие префиксам аргументов: $ для скаляров, @ для массивов, и т. д. (см. таблицу 7.1).
Если в прототипе в качестве формального параметра указаны символы @ или %, то этот параметр поглощает все последующие. То есть, массив или хеш могут быть только последним параметром подпрограммы:
sub NAME ($$@)
Это означает, что при вызове подпрограммы первыми двумя параметрами будут скаляры, за которыми следует список скалярных объектов, образуемый по известным правилам:
NAME $scalar1, $scalar2, $lstarg1, $lstarg2, $lstarg3;
Если вы хотите, чтобы при вызове подпрограммы аргумент и в самом деле начинался с префикса @ или %, а не представлял собой список скалярных аргументов, необходимо защитить эти символы с помощью обратной косой черты:
sub SUBNAME (\@)
Теперь можно вызывать подпрограмму, указывая ей переменную-массив в качестве аргумента:
SUBNAME @array;
При желании можно определить для подпрограммы необязательные параметры. Список необязательных параметров отделяется от обязательных точкой с запятой (см. таблицу 7.1).
Подсказка: Прототипы влияют на интерпретацию вызова подпрограммы, только когда при вызове для имени подпрограммы не указан префикс &.
Таблица 7.1. Как создать прототип подпрограммы
Описание |
Вызов подпрограммы |
||
sub имя($) |
имя $arg1; |
||
sub имя($$) |
имя $arg1, $arg2; |
||
sub имя($$;$) |
имя $arg1, $arg2; или имя $arg1, $arg2, $arg3; |
||
sub имя(@) |
имя $arg1, $arg2, $arg3, $arg4; |
||
sub имя(%) |
имя $key1 => $val1, $key2 => $val2; |
||
sub имя($@) |
имя $ARG, $arg1, $arg2, $arg3; |
||
sub имя($%) |
имя $ARG, $key1 => $val1, $key2 => $val2; |
||
sub имя(\@) |
имя @array; |
||
sub имя(\%) |
имя %{$ссылка-на-hash}; |
||
sub имя(&) |
имя непоименованная-подпрограмма; |
||
sub имя(*) |
имя *arg1; |
||
sub имя() |
имя; |
Определение прототипов
Определение отличается от описания тем, что в нем приводится код, составляющий тело подпрограммы. Чтобы определить подпрограмму, используется ключевое слово sub:
sub имя {блок};
sub имя (прототип) {блок};
Например, требуется задать подпрограмму printhello, просто выводящую сообщение «Hello!» (обратите внимание, что тело подпрограммы заключено в фигурные скобки { и }, хотя и состоит из одной команды):
sub printhello
{
print "Hello!\n";
}
Теперь можно вызвать эту подпрограмму:
printhello;
Hello!
Более подробно процедура вызова рассматривается в следующем разделе «Вызов подпрограмм».
Вызов подпрограмм
Если подпрограмма определена, ее можно вызвать с конкретными аргументами в качестве параметров:
&имя (список-аргументов);
Как всегда в Perl, выполнить вызов подпрограммы можно далеко не одним способом. Например, при использовании круглых скобок не обязательно ставить префикс &:
имя (список-аргументов);
Если подпрограмма была описана или экспортирована ее из пакета то заодно можно опустить и круглые скобки:
имя список-аргументов;
При вызове подпрограммы передаваемые ей аргументы помещаются в специальный массив @_. Если подпрограмма вызывается с префиксом &, но без списка параметров, то в качестве последнего ей передается текущее содержимое массива @_. Это полезно в том случае, когда одна подпрограмма вызывается из другой, причем ей требуется передать те же параметры, которые использовались при вызове первой подпрограммы. (Обратите внимание, что при использовании префикса & Perl не выполняет проверку соответствия прототипа подпрограммы списку фактически передаваемых величин.)
Аргументы, передаваемые подпрограмме, образуют единый унифицированный список. Если передается два массива, их содержимое будет объединено в единый массив и добавлено к списку значений, передаваемых подпрограмме. Если вы хотите передавать в качестве параметров массивы и сохранять их структуру, передавайте их по ссылке (см. раздел «Передача параметров по ссылке» далее в этой главе.)
Чтение аргументов, переданных подпрограмме
Доступ к аргументам, переданным подпрограмме, осуществляется через специальный массив @_, в который заносятся эти аргументы. Например, если переданы два параметра, подпрограмма может обратиться к ним как $_[0] и $_[1].
Предположим, что вы хотите сложить два числа и напечатать результат. Для этой цели создается процедура addem, которую можно вызвать, как addem(2,2). Посмотрим, как addem получает значения через массив @_:
sub addem
{
$value1 = $_[0];
$value2 = $_[1];
print "$value1 + $value2 = " . ($value1+$value2);
}
addem(2,2);
2 + 2 = 4
Чтобы извлечь параметры из массива @_, можно также использовать функцию shift:
sub addem
{
$value1 = shift @_;
$value2 = shift @_;
print "$value1 + $value2 = " . ($value1+$value2);
}
addem(2,2);
2 + 2 = 4
Наконец, в качестве третьего метода, чтобы получить значение параметров за один раз, можно использовать присвоение списком:
sub addem
{
($value1, $value2) = @_;
print "$value1 + $value2 = " . ($value1+$value2);
}
addem(2,2);
2 + 2 = 4
Использование переменного числа параметров
Perl упрощает передачу подпрограмме переменного числа параметров, поскольку они помещаются в массив @_. Чтобы определить, сколько параметров было передано подпрограмме, достаточно проверить длину этого массива (то есть переменную $#_).
Чтобы напрямую работать с аргументами, занесенными в массив @_, можно использовать цикл foreach. Он выполнит полный перебор переданных параметров независимо от того, сколько их было задано:
sub addem
{
$sum = 0;
foreach $element (@_) {;
$sum += $element;
}
print join(" + ", @_) . " = " . $sum . "\n";
}
addem(2, 2, 2);
2 + 2 + 2 = 6
Использование значений по умолчанию
Поскольку процедура может получать переменное число аргументов, иногда требуется обеспечить значения по умолчанию для опущенных параметров. Это можно сделать с помощью оператора ||=. Оператор || («логическое и») во-первых, предпочитает не загружать себя лишней работой и не вычисляет второй аргумент, если уже первый соответствует значению истино. Во-вторых, он возвращает не логические значения ложь и истина, а первый из двух аргументов, который не ложь:
sub addem
{
($value1, $value2) = @_;
$value2 ||= 1;
print "$value1 + $value2 = " . ($value1+$value2);
}
Так как после присвоения списком значение $value2 осталось неопределенным, а с точки зрения Perl неопределенное значение соответствует условию ложь, то скаляру $value2 будет присвоено используемое по умолчанию значение 1:
addem(2);
2 + 1 = 3
Однако, этот метод подразумевает, что в качестве второго параметра не будет задано число ноль или пустая строка. Правильнее осуществить проверку количества элементов массива @_ (это значение $#_) в явном виде. Это позволит определить, сколько же аргументов задано при вызове подпрограммы:
sub addem
{
$value1 = shift @_;
if ($#_ > 0) {
$value2 = shift @_;
} else {
$value2 = 1;
}
print "$value1 + $value2 = " . ($value1+$value2);
}
addem(2);
2 + 1 = 3
Значения, возвращаемые подпрограммами (функциями)
Задать возвращаемое значение и выйти из подпрограммы можно также с помощью команды return<$FЕсли в теле подпрограммы нет команды return, при выходе из подпрограммы возвращается последнее вычисленное значение. — Примеч. ред.>. (В некоторых языках программирования функции возвращают значение, а подпрограммы этого делать не могут. Однако в Perl функция и подпрограмма — это одно и то же.) Существенно: значение, возвращаемое оператором return, вычисляется в том контексте (скалярном или списковом), в котором вызывается подпрограмма.
Например, вот как передать подпрограмме два параметра и вернуть их сумму:
sub addem
{
($value1, $value2) = @_;
return $value1+$value2;
}
print "2 + 2 = " . addem(2, 2) . "\n";
2 + 2 = 4
Точно также вы можете вернуть не одно значение, а их список:
sub getval
{
return 1, 2, 3, 4, 5, 6, 7, 8;
}
Если подпрограмма возвращает список значений, то можно, например, использовать его при присвоении значения массиву:
@array = getvalues;
print join(", ", @array);
1, 2, 3, 4, 5, 6, 7, 8
Однако, если в качестве возвращаемого значения указано несколько списков, массивов или хешей, все они будут упакованы в один большой список. Поэтому значение, возвращаемое подпрограммой, можно присвоить только одному массиву. Например, следующая команда просто не сработает:
(@array1, @array2) = getvalues;
При ее выполнении все значения, возвращаемые подпрограммой getvalues, помещаются в массив @array1, а массив @array2 остается неопределенным. Чтобы справиться с этой проблемой, обратитесь к разделу «Передача параметров по ссылке» далее в этой главе.
Управление областью видимости (ключевые слова my и local)
По умолчанию переменные Perl являются глобальными. Это значит, что вы можете обращаться к ним из любого места программы. (На самом-то деле, они глобальны только в рамках текущего пакета — но о пакетах вы узнаете из главы 15.) Даже если переменная определена внутри тела подпрограммы, она становится глобальной и к ней можно обращаться после выхода из подпрограммы. Например, в следующем фрагменте кода мы легко получаем доступ к переменной $inner, находясь за пределами подпрограммы, в которой она определена и в которой ей присвоено значение:
sub printem
{
$inner = shift @_;
print $inner;
}
printem "Hello!\n";
print "/" . $inner;
Hello!
/Hello!
(как легко видеть, второе «Hello!» получено в результате прямого обращения к переменной, без вызова подпрограммы.)
Если бы на этом все и кончалось, Perl бы быстро стал неуправляемым из-за огромного числа глобальных переменных, мешающих друг другу в процессе работы программы. Однако, можно ограничить переменную пределами подпрограммы, если задать ее область видимости (scope).
Ключевое слово my ограничивает переменную текущим блоком — будь то одиночный блок, условный оператор, оператор цикла, подпрограмма, команда eval или файл, подключаемый командами do, require или use. Переменные, описанные с ключевым словом my, имеют ограниченную лексическую область видимости. В отличие от них переменные, описанные с ключевым словом local, имеют ограниченную динамическую область видимости. Различие между ними состоит в том, что переменные с динамической областью видимости доступны также подпрограммам, которые вызываются из текущего блока. Переменные с лексической областью видимости видны исключительно внутри того блока, в котором они описаны. (Более подробно о динамической области видимости можно узнать из раздела «Создание временных переменных (ключевое слово local)» далее в этой главе.)
Если после ключевого слова my перечислено более одной переменной, список должен быть заключен в круглые скобки. Все элементы списка должны быть допустимыми «левыми значениями» — то есть, их можно указывать в левой части оператора присваивания. Кроме того, лексическую область видимости можно объявлять только для переменных, имена которых составлены из букв, цифр и символа подчеркивания. Тем самым специальные (встроенные) переменные Perl типа $_ объявлять с использованием ключевого слова my нельзя — зато для таких переменных можно задавать ключевое слово local, что позволяет «защитить» значение переменной от изменений, производимых внутри подпрограммы (подробнее см. раздел «Создание временных переменных (ключевое слово local)» далее в этой главе). Наконец, объявление my действительно только для скалярных переменных, массивов и хешей — объявить таким образом, например, подпрограмму, нельзя (в частности потому, что подпрограмма не является «левым значением»).
В следующем примере область видимости переменной$inner ограничивается телом подпрограммы: она объявлена с ключевым словом my. Теперь доступ к ней за пределами подпрограммы запрещен (в частности, команда print выводит пустую строку):
sub printem
{
my $inner = shift @_;
print $inner;
}
printem "Hello!\n";
print "/" . $inner;
Hello!
/
Вот несколько примеров с использованием ключевого слова my:
my $variable1;
my ($variable1, $variable2);
my $variable1 = 5;
Предупреждение: Если вы объявляете с помощью ключевого слова my несколько переменных, то должны заключить их в круглые скобки. Иначе ключевое слово my будет относиться только к первой из перечисленных в списке переменной — как, например, в следующей команде:
my $variable1, $variable2 = 5;
Переменные, объявленные с ключевым словом my, не обязательно находятся внутри блока. Например, если объявление находится в заголовке цикла или условного оператора, то такие переменные приобретают лексическую область видимости в пределах этого оператора. В частности, переменная, объявленная как my в условии условного оператора, имеет лексическую область видимости по отношению к условиям и всем блокам, составляющим оператор:
$testvalue = 10;
if ((my $variable1 = 10) > $testvalue) {
print "Value, ", $variable1,
", is greater than the test value.\n";
} elsif ($variable1 < $testvalue) {
print "Value, ", $variable1,
", is less than the test value.\n";
} else {
print "Value, ", $variable1,
", is equal to the test value.\n";
}
Value, 10, is equal to the test value.
Требование обязательной лексической области видимости
Прагмаuse strict ’vars’ в программе предписывает программисту явно указывать для любой переменной, является ли она локальной или глобальной. То есть, любое упоминание любой переменной, начиная с прагмы и до конца блока или текущей области видимости, должно относиться либо к переменной, описанной с лексической областью видимости, либо к глобальной переменной с явным указанием имени пакета, к которому она относится.
Создание временных переменных (ключевое слово local)
Кроме переменных с лексической областью видимости, создающихся с помощью ключевого слова my, вы можете также образовывать переменные с динамической областью видимости — с помощью ключевого слова local<$FНастоятельно не рекомендуется пользоваться этой возможностью при создании программ на Perl. — Примеч. ред.>. Это ключевое слово создает временную копию глобальной переменной. Внутри динамической области видимости, работа идет с временной копией, и изменения, вносимые в переменную, не сказываются на основной версии переменной. При выходе из динамической области видимости, временная копия уничтожается и глобальная переменная восстанавливает прежнее значение.
Динамическая область видимости отличается от лексической тем, что относится не только к текущему блоку, но и ко всем вызываемым из этого блока подпрограммам. Например, если переменная $_ объявляется как локальная, затем ей присваивается некоторое значение и вызывается команду print без параметров, то print напечатает содержимое временной копии, а не глобальной переменной $_. При выходе из текущего блока временная копия будет уничтожена, и тогда вызовы команды print без параметров будут относиться к «истинной» специальной переменной $_.
Обычно вместо спецификатора local рекомендуется использовать my. Иногда, однако, local позволяет сделать вещи, недоступные для my. Например, может понадобиться сделать временную копию специальной переменной Perl, или изменить отдельный элемент массива или хеш-таблицы, или поработать локально с дескрипторами файлов или форматами Perl. Все эти функции доступны исключительно через спецификатор local.
Обратите внимание, что ключевое слово local создает не новую переменную, а всего лишь копию существующей (как правило) глобальной переменной, с которой вы будете работать в дальнейшем. Вот несколько примеров использования ключевого слова local:
local $variable1;
local ($variable1, $variable2);
local $variable1 = 5;
local *FILEHANDLE;
Итак, описатель local создает копии перечисленных в нем элементов, делает переменные локальными для текущего блока, команды eval, команды do, а также для любой подпрограммы (и всех ее вложенных подпрограмм), которая будет вызвана из этого блока. Как и в случае my, при перечислении более одной переменной, необходимо заключить их в круглые скобки. Все элементы, перечисляемые с local, должны быть правильными «левыми значениями» — то есть, их можно указывать слева от оператора присваивания.
Постоянные (статические) переменные
Иногда надо сделать так, чтобы переменная внутри подпрограммы сохраняла свое значение между вызовами. Однако, если определить переменные с ключевым словом my, то их значения переменных будут сбрасываться при каждом входе в подпрограмму. В следующем примере переменная $count сбрасывается в ноль<$FНа самом деле переменная $count, конечно же, не сбрасывается в ноль, а становится неопределенной. Просто с точки зрения оператора ++ неопределенное значение и ноль — равноправные величины. Вообще, операторы автоприращения и автоуменьшения ++ и -- умеют интерпретировать как арифметические многие входные данные, которые обычные арифметические операторы (+, -, *, /) сочтут за ошибку. (Чуть подробнее об этом свойстве операторов ++ и -- рассказано в главе 4, в разделе «Автоприращение и автоуменьшение».) — Прим. перев.> и следом увеличивается на единицу при каждом входе в подпрограмму, так что на выходе вместо желаемой последовательности натуральных чисел 1, 2, 3, 4 получаются четыре единицы:
sub incrementcount {
my $count;
return ++$count;
}
print incrementcount . "\n";
print incrementcount . "\n";
print incrementcount . "\n";
print incrementcount . "\n";
1
1
1
1
Если б можно было сделать переменную $count статической, как в языке С, это решило бы нашу проблему (статические переменные сохраняют свое значение между вызовами функций). К сожалению, Perl не поддерживает статические переменные напрямую: глобальные переменные являются статическими по умолчанию, а переменные, объявленные внутри подпрограмм, никогда таковыми не бывают.
Однако, существует трюк, с помощью которого эту проблему можно обойти. Переменные с лексической областью видимости не сбрасываются до тех пор, пока они находятся в пределах видимости (точнее, система автоматической сборки мусора не трогает их до тех пор, пока на переменную хоть кто-то ссылается — подпрограмма, ссылка, и т. д.). Поэтому даже если переменная не видна, это еще не значит, что она уничтожена. В следующем примере мы помещаем определение my вне тела подпрограммы, заключив и подпрограмму, и определение переменной в фигурные скобки. В результате $count начинает вести себя как статическая переменная:
{
my $count;
sub incrementcount {
return ++$count;
}
}
print incrementcount . "\n";
print incrementcount . "\n";
print incrementcount . "\n";
print incrementcount . "\n";
1
2
3
4
Здесь использован тот факт, что подпрограмма, даже объявленная внутри блока, является глобальной. Поэтому она доступна в том числе и вне охватывающего ее блока, тогда как локальная переменная $count — нет. При входе в блок в результате выполнения команды my для переменной $count выделяется память, но при выходе из блока память не освобождается — ведь на эту переменную все еще ссылается подпрограмма incrementcount. В результате подпрограмма все равно имеет дело с одной и той же областью памяти, не освобождая ее при выходе и не размещая в ней данных вновь при входе.
Весь этот фрагмент кода можно поместить в блок BEGIN, выполняемый при загрузке программы. Это гарантирует выделение памяти под «постоянные» переменные перед началом работы сценария, а не в процессе его выполнения:
sub BEGIN
{
my $count = 0;
sub incrementcount {
return ++$count;
}
}
print incrementcount . "\n";
print incrementcount . "\n";
print incrementcount . "\n";
print incrementcount . "\n";
1
2
3
4
Обратите внимание, что, когда при входе в блок выделяется память под переменную, но при выходе из блока эта память не освобождается (по причине занятости переменной), при новом входе в блок память не будет повторно выделяться. Как результат, прежнее значение «постоянной» переменной не будет потеряно из-за ее повторной инициализации, при выполнении программы типа:
$flag =1;
LABEL1:
{
my $count = 0;
sub incrementcount {
return ++$count;
}
}
for (1..4) {print incrementcount, "/";}
if ($flag) {
$flag = 0;
goto LABEL1;
}
1/2/3/4/5/6/7/8/
Рекурсивный вызов подпрограмм
В Perl вы можете вызывать подпрограммы рекурсивно — то есть подпрограмма может содержать (прямо или косвенно) вызовы самой себя. Привычный пример рекурсивных операций — это вычисление факториала (например, 5! = 5*4*3*2*1). Не будем отступать от этой традиции.
Мы разбиваем процедуру вычисления факториала на несколько рекурсивных шагов. На каждом шаге значение, переданное в качестве параметра, умножается на результат вычисления факториала с аргументом, уменьшенным на единицу. Только если подпрограмма вызывается с параметром, равным единице, будет возвращено значение единица, без углублений в дальнейшие вычисления:
sub factorial
{
my $value = shift @_;
if ($value == 1) {
return $value;
} else {
return $value * factorial($value-1);
}
}
$result = factorial(6);
print $result;
720
Как видите, эта подпрограмма может вызывать рекурсивно сама себя. (На самом деле, именно это она и делает, если только ее не попросили вычислить факториал единицы.)
Вложенные подпрограммы
В Perl разрешается использовать также и вложенные подпрограммы (то есть, определять подпрограммы внутри подпрограмм). В нашем примере мы определяем подпрограмму outer и подпрограмму inner внутри нее. Обратите внимание, что inner видна не только внутри outer, но и вне ее:
sub outer
{
sub inner
{
print "Inside the inner subroutine.\n";
}
inner;
}
outer;
inner;
Inside the inner subroutine.
Inside the inner subroutine.
Передача параметров по ссылке
Если подпрограмме передаются массивы и хеши, то их элементы объединяются в один список вместе с остальными параметрами. Если вы хотите передать два и более массивов или хешей, сохранив при этом их индивидуальность, то должны передавать ссылки на массивы или хеши (о ссылках подробнее рассказывается в главе 8).
В следующем примере создаются два массива:
@a = (1, 2, 3);
@b = (4, 5, 6);
Мы хотим создать подпрограмму addem, которая складывает попарно элементы двух массивов независимо от их длины. Чтобы добиться этого, мы вызываем подпрограмму addem, передавая ей ссылки на массивы:
@array = addem(\@a, \@b);
Теперь внутри подпрограммы addem надо обратиться к ссылкам на массивы, а затем организовать цикл по числу их элементов, возвращая в качестве результата массив, содержащий попарные суммы элементов входных массивов (этот код станет понятнее после того, как вы прочтете главу 8):
sub addem
{
my ($ref1, $ref2) = @_;
for ($loop_index = 0; $loop_index <= $#{$ref1};
$loop_index++) {
$result[$loop_index] = @{$ref1}[$loop_index] +
@{$ref2}[$loop_index];
}
return @result;
}
Вот как с помощью подпрограммы addem происходит сложение элементов двух массивов:
@array = addem (\@a, \@b);
print join(’, ’, @array);
5, 7, 9
Техника передачи ссылок позволяет непосредственно ссылаться на элементы данных, передаваемые подпрограмме. Это означает, что можно модифицировать параметры, передаваемые подпрограмме. В Perl скаляры и так передаются по ссылке, поэтому для модификации содержимого переменных внутри подпрограммы не требуется передавать на них ссылки в явном виде.
Передача записи таблицы символов (тип данных typeglob)
Передача типа данных typeglob до недавнего времени была единственным способом передать ссылку на переменную средствами Perl. До сих пор это лучший способ передавать в качестве параметров такие данные, как дескрипторы файлов. Поскольку typeglob — это полная запись таблицы символов, то при передаче данных этого типа, реально передаются ссылки на все типы данных, хранящиеся в программе под данным именем. Рассмотрим, как с помощью типа typeglob вместо ссылок реализовать подпрограмму попарного сложения элементов массива, рассмотренную в предыдущем разделе:
@a = (1, 2, 3);
@b = (4, 5, 6);
sub addem
{
local (*array1, *array2) = @_;
for ($loop_index = 0; $loop_index <= $#array1;
$loop_index++) {
$result[$loop_index] = $array1[$loop_index] +
$array2[$loop_index];
}
return @result;
}
@array = addem (\@a, \@b);
print join(’, ’, @array);
5, 7, 9
При передаче дескрипторов файлов в качестве параметра можно передать содержимое записи таблицы символов typeglob — например, в форме *STDOUT. Однако лучше передавать ссылки на записи таблицы символов, потому что такой код будет работать и в том случае, если будет активизирована прагма use strict ’refs’. (Прагмы — это директивы, передаваемые компилятору. Данная прагма проверяет символические ссылки, более подробная информация может быть найдена в главе 8.) В следующем примере мы передаем подпрограмме ссылку на запись *STDOUT таблицы символов:
sub printhello {
my $handle = shift;
print $handle "Hello!\n";
}
printhello (\*STDOUT);
Hello!
Проверка контекста вызова функции: функция wantarray
Подпрограммы могут возвращать как скалярные значения, так и списки. Это значит, что возвращаемое значение может зависеть от контекста, в котором вызвана подпрограмма. Если вы хотите самостоятельно обрабатывать списковый и скалярный контексты, вам нужен инструмент, который скажет, в каком контексте вызывается подпрограмма. Таким инструментом является функция wantarray.
Она возвращает значение истина, если результат работы подпрограммы будет интерпретироваться в списковом контексте, и значение ложь в противном случае. В примере ниже с помощью функции wantarray проверяется контекст вызова функцииswapxy, и в зависимости от контекста возвращается скаляр или список (функция swapxy заменяет в текстовых строках все вхождения латинской буквы «x» на латинскую «y»):
sub swapxy {
my @data = @_;
for (@data) {
s/x/y/g;
}
return wantarray ? @data : $data[0];
}
А вот как выглядит вызов функции swapxy в списковом контексте, когда мы передаем ей на вход список текстовых строк и ожидаем получить обработанный список:
$a = "xyz";
$b = "xxx";
($a, $b) = swapxy($a, $b);
print "$a\n";
print "$b\n";
yyz
yyy
Создание встраиваемых функций
Если для функции задан прототип (), она может встраиваться в код компилятором Perl. Встраиваемая (inline) функция оптимизируется для б’ольшей скорости вычислений, однако она должна подчиняться специальным ограничениям и состоять из константы или скаляра с лексической областью видимости. (В последнем случае на скаляр не должно быть никаких дополнительных ссылок.) Кроме того, на встраиваемую функцию нельзя ссылаться, указывая префикс & или ключевое слово do — такие вызовы функций никогда не преобразуются Perl во встраиваемые.
Например, вот такие функции будут встраиваться Perl:
sub e () {2.71828}
sub e () {exp 1.0}
Обе возвращают число e — основание натурального логарифма. Первая функция задает его в виде константы, а вторая использует встроенную функцию Perl exp. (Кстати, функция exp возвращает более точное значение для основания натурального логарифма, чем константа, указанная в первой функции.)
Замещение встроенных функций. Псевдопакет CORE
Под замещением подпрограммы понимают новое определение существующей подпрограммы. Разрешается замещать любые подпрограммы, в том числе и встроенные в Perl, но только в том случае, если подпрограмма уже импортирована из модуля. В противном случае объявления подпрограммы будет недостаточно.
Однако, для объявления подпрограммы, которая замещает впоследствии импортируемую подпрограмму, вы можете использовать прагму subs. Подпрограммы, чьи имена перечислены в subs, будут замещать встроенные функции Perl. Рассмотрим пример, в котором мы замещаем функцию Perl exit, заставляя ее спрашивать, действительно ли пользователь хочет выйти из программы:
use subs ’exit’;
sub exit
{
print "Do you really want to exit?";
$answer = <>;
if ($answer =~ /^y/i) (CORE::exit;}
}
while (1) {
exit;
}
Здесь для того, чтобы на самом деле выйти из программы, когда пользователь захочет это сделать, мы используем псевдопакет CORE, вызывая функцию CORE::exit. (В Perl можно указывать в явном виде, из какого пакета берется та или иная функция, используя символы :: в качестве разделителя.) Псевдопакет CORE содержит встроенные функции Perl в исходном виде, и даже замещая одну из них, все равно с помощью CORE можно вызвать исходную версию.
Непоименованные подпрограммы
Perl позволяет создавать безымянные (анонимные) подпрограммы. Например, в следующем случае мы создаем ссылку на подпрограмму (ссылки подробно разбираются в следующей главе), не присваивая подпрограмме имени:
$coderef = sub {print "Hello!\n";};
Обратите внимание, что эту команду требуется завершить точкой с запятой, хотя при обычном определении подпрограмм она не требуется. Хотя у подпрограммы и нет имени, ее можно вызвать с префиксом& (заключив ссылку в фигурные скобки):
&{$coderef};
Hello!
Создание таблиц диспетчеризации подпрограмм
Таблица диспетчеризации подпрограмм (subroutine dispatch table) — это создаваемая пользователем структура данных (массив или хеш), содержащая ссылки на подпрограммы. Можно указать, какую подпрограмму требуется вызвать, указав индекс (в случае массива) или ключ (в случае хеша). Если есть данные, которые нужно обрабатывать несколькими однотипно вызываемыми подпрограммами, то зачастую полезнее оказывается доступ, не по имени, а по индексу или ключу.
Рассмотрим следующий пример. У нас есть две подпрограммы: одна переводит градусы Цельсия в градусы Фаренгейта, другая совершает обратную процедуру:
sub ctof # centigrade to Fahrenheit
{
$value = shift(@_);
return 9 * $value / 5 + 32;
}
sub ftoc # Fahrenheit to centigrade
{
$value = shift(@_);
return 5 * ($value - 32) / 9;
}
Чтобы поместить функции в таблицу диспетчеризации, мы запоминаем ссылки на подпрограммы (ссылки рассматриваются в следующей главе):
$tempconvert[0] = \&ftoc;
$tempconvert[1] = \&ctof;
Теперь можно с помощью индекса выбирать, какую именно подпрограмму вызывать:
print "Zero centigrade is " . &{$tempconvert[1]}(0)
. " Fahrenheit.\n";
Zero centigrade is 32 Fahrenheit.
Подсказка: Чтобы передать параметры вызываемой таким образом подпрограмме, надо поместить список параметров в круглые скобки, следующие за ссылкой на подпрограмму.
[A1]Пример содержал ошибку + добавлено пояснение.