Главная
К библиотеке

Military.com Free Screensavers!

Изучаем Perl. Глава 4
Андрей Сергиенко

e-mail - erazer@erazer.virtualave.net
site     - http://erazer.virtualave.net

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

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

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

$areaOfFirstCircle = areaOfCircle($firstRadius);

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

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

Давайте присмотримся к вызову функции - сначала мы видим скалярную переменную, затем - оператор присвоения. Вы уже знаете, что это означает - Perl присвоит переменной $areaOfFirstCircle значение, стоящее справа от знака присваивания. Но что в действительности стоит справа?

Первое, что вы видите - это имя функции areaOfCircle(). Круглые скобки справа и отсутствие перед именем символов $, @ и % говорит о том, что это - вызов функции. Внутри круглых скобок содержится список параметров или значений, передающихся функции.

Вычисление площади круга:

$areaOfFirstCircle = areaOfCircle(5);
print("$areaOfFirstCircle\n");
sub areaOfCircle {
$radius = $_[0];
return(3.1415 * ($radius ** 2));
}

Программа напечатает:

78.7375

Объявление функции:

sub имяФункции {
тело функции
}

И все. Ваша функция готова.

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

Внутри функции массив параметров имеет имя @_. Все параметры, передаваемые функции, содержатся в массиве @_, откуда их можно извлечь при необходимости.

Наша маленькая функция из примера выше могла бы сделать это следующей строкой:

$radius = $_[0];

Эта строка присваивает первый элемент массива @_ скалярной переменной $radius.

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

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

Использование массива параметров (@_)

Как уже говорилось ранее - все параметры функция может найти в массиве параметров @_. Это очень удобно - чтобы узнать, сколько параметров было передано функции, нужно всего лишь обратиться к массиву параметров @_ в скалярном контексте.

firstSub(1, 2, 3, 4, 5, 6);
firstSub(1..3);
firstSub("A".."Z");
sub firstSub {
$numParameters = @_ ;
print("The number of parameters is $numParameters\n");
}


Программа напечатает:

The number of parameters is 6

The number of parameters is 3

The number of parameters is 26

Perl позволяет вам передавать в функцию любое число параметров. Функция сама может определить, какие параметры ей использовать, и в какой последовательности. Массив параметров @_ может использоваться так же, как и любой другой массив.

Конечно, это не очень удобно - обращаться к переданным функции параметрам по их номерам - @_[0] или @_[1]. Вы можете воспользоваться более удобной технологией:

areaOfRectangle(2, 3);
areaOfRectangle(5, 6);
sub areaOfRectangle {
($height, $width) = @_ ;
$area = $height * $width;
print("The height is $height. The width is $width. The area is $area.\n\n");
}


Программа напечатает:

The height is 2. The width is 3. The area is 6.

The height is 5. The width is 6. The area is 30.

Передача параметров функции по ссылке.

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

@array = (0..5);
print("Before fuNCtion call, array = @array\n");
firstSub(@array);
print("After fuNCtion call, array = @array\n");
sub firstSub{
$_[0] = "A";
$_[1] = "B";
}


Программа напечатает:

Before fuNCtion call, array = 0 1 2 3 4 5

After fuNCtion call, array = A B 2 3 4 5

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

Вот пример той же программы, но написанной более правильно:

@array = (0..5);
print("Before fuNCtion call, array = @array\n");
firstSub(@array);
print("After fuNCtion call, array = @array\n");
sub firstSub{
($firstVar, $secondVar) = @_ ;
$firstVar = "A";
$secondVar = "B";
}


Программа напечатает:

Before fuNCtion call, array = 0 1 2 3 4 5

After fuNCtion call, array = 0 1 2 3 4 5

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

Но тогда вы можете столкнуться с другой проблемой:

$firstVar = 10;
@array = (0..5);
print("Before fuNCtion call\n");
print("\tfirstVar = $firstVar\n");
print("\tarray = @array\n");
firstSub(@array);
print("After fuNCtion call\n");
print("\tfirstVar = $firstVar\n");
print("\tarray = @array\n");
sub firstSub{
($firstVar, $secondVar) = @_ ;
$firstVar = "A";
$secondVar = "B";
}


Программа напечатает:

Before fuNCtion call
firstVar = 10
array = 0 1 2 3 4 5

After fuNCtion call
firstVar = A
array = 0 1 2 3 4 5

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

Области действия переменных.

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

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

firstSub("AAAAA", "BBBBB");
sub firstSub{
local ($firstVar) = $_[0];
my($secondVar) = $_[1];
print("firstSub: firstVar = $firstVar\n");
print("firstSub: secondVar = $secondVar\n\n");
secondSub();
print("firstSub: firstVar = $firstVar\n");
print("firstSub: secondVar = $secondVar\n\n");
}

sub secondSub{
print("secondSub: firstVar = $firstVar\n");
print("secondSub: secondVar = $secondVar\n\n");
$firstVar = "ccccC";
$secondVar = "DDDDD";
print("secondSub: firstVar = $firstVar\n");
print("secondSub: secondVar = $secondVar\n\n");
}


Программа напечатает:

firstSub: firstVar = AAAAA
firstSub: secondVar = BBBBB
secondSub: firstVar = AAAAA
Use of uninitialized value at test.pl line 19.
secondSub: secondVar =
secondSub: firstVar = ccccC
secondSub: secondVar = DDDDD
firstSub: firstVar = ccccC
firstSub: secondVar = BBBBB

Как вы видите, функция secondSub() не имеет доступа к переменной $secondVar, которая была создана функцией my() внутри функции firstSub(). Perl даже вывел сообщение, предупреждая вас об этом. В то же время, переменная $firstVar доступна и может быть изменена функцией secondSub().

По возможности старайтесь обходиться только функцией my() и не использовать функцию local() - так вы обеспечите себе более строгий контроль над областью действия переменных.

На самом деле, функция my() гораздо более сложна и функциональна. Но об этом пойдет речь в главе 15 - "Модули Perl".

Вы помните, в чем разница между передачей параметров функции по ссылке и по значению? Если вы передаете параметры по значению, то функция не может изменять значения переданных ей параметров (переменных), а значит это никак не отразится на всей программе. Если же вы передаете параметры по ссылке, то функция может изменять значения параметров (переменных), и это отразится на основной программе. Так вот, при использовании функции local() метод передачи параметров по ссылке работает немного по-другому - функции могут менять значения переданных им параметров (переменных), но это отразится только на самой "верхней" функции - той, где была использована функция local(), - на основную же программу это никак не повлияет.

Использование списка в качестве параметра функции.

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

firstSub((0..10), "AAAA");
sub firstSub{
local(@array, $firstVar) = @_ ;
print("firstSub: array = @array\n");
print("firstSub: firstVar = $firstVar\n");
}

Программа напечатает:

firstSub: array = 0 1 2 3 4 5 6 7 8 9 10 AAAA
Use of uninitialized value at test.pl line 8.
firstSub: firstVar =

Вы видите, что при инициализации переменных массив @array поглощает все элементы массива параметров @_, ничего не оставляя скаларной переменной $firstVar. Это подтверждает и предупреждающее сообщение интерпретатора Perl. Вы можете решить данную проблему путем перестановки местами параметров - если вы поставите сначала скалярную переменную, а потом массив, то все будет так как надо:

firstSub("AAAA", (0..10));
sub firstSub{
local($firstVar, @array) = @_ ;
print("firstSub: array = @array\n");
print("firstSub: firstVar = $firstVar\n");
}

Программа напечатает:

firstSub: array = 0 1 2 3 4 5 6 7 8 9 10
firstSub: firstVar = AAAA

 

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

 

Вложенные (рекурсивные) функции.

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

firstSub();
sub firstSub{
print("$count\n");
$count++;
firstSub();
}

Программа напечатает:

Error: Runtime exception

Перед тем, как напечатать это сообшение об ошибке, программа будет увеличивать значение переменной $count до тех пор, пока будет возможен рекурсивный вызов функции. Таким образом, по значению переменной $count вы можете узнать, сколько уровней рекурсивных вызовов допускает ваша система.

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

Частные функции.

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

$temp = performCalc(10, 10);
print("temp = $temp\n");
sub performCalc {
my ($firstVar, $secondVar) = @_;
my $square = sub {
return($_[0] ** 2);
};
return(&$square($firstVar) + &$square($secondVar));
};

Программа напечатает:

temp = 200

Как вы видите, у нас имеется функция $square. Точнее, $square - это обычная скалярная переменная, ссылающаяся на функцию. Для обращения к функции мы используем знак & перед именем переменной $square. Эту функцию мы можем использовать только внутри функции performCalc() - она недоступна для остальной программы.

Hosted by uCoz