Подпрограммы и функции. Изучаем Perl

И снова говорю вам ЗДРАВСТВУЙТЕ! Если вы это читаете, значит продержались, к моей большой радости, до 9-го шага. Сегодня мы будем рассматривать достаточно трудные моменты языка. Если кому-то что-то будет непонятно (или было непонятно в предыдущих шагах), то пишите мне и я постраюсь объяснить. Если писем с "непонятками" на какой-то шаг наберётся определённое кол-во, то я буду переписывать этот шаг в более понятной форме. Поехали (запил кофеём)!

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

Для применения подпрограммы ее необходимо определить либо в текущем модуле (файле), либо во внешнем модуле (файле). Объявляются подпрограммы с помощью ключевого слова sub . Чтобы использовать подпрограмму объявленную во внешнем модуле, нужно перед её использованием указать интерпретатору где её искать следющим образом use имя_модуля qw(подрограмма) или require имя_файла . Как происходит вызов подпрограмм посмотрим на примере

#!/usr/bin/perl -w sub test { print "Привет из подпрограммы!\n"; } &test();

Cимвол & означает, что после него идёт вызов подпрограммы. Т.е. в данном случаем вызов подпрограммы test() . Но можно даже обойтись вообще без него. Теперь ещё одна тонкость. Обойтись можно и без скобок. Выглядеть это будет так:

#!/usr/bin/perl -w sub test { print "Привет из подпрограммы!\n"; } test;

Причем обойтись без скобок можно не только в тех подпрограммах, которые создаём мы, но и при вызове функций Perl . Скобки нужны только в том случае, если передаваемые функции параметры могут вызвать неоднозначность трактования. Мы уже не раз "своевольничали", не пользуясь скобками. Функция print правильно пишется так - print("Привет из подпрограммы!\n") . Теперь о том, как передаются подпрограммам параметры. Каждая подпрограмма при запуске получает массив @_ , в котором содержатся параметры. Ещё один пример:

#!/usr/bin/perl -w sub test { print $_; } &test("Привет из подпрограммы!\n");

Или так:

#!/usr/bin/perl -w sub test { print $_; } test "Привет из подпрограммы!\n";

Ощутили? Поехали дальше:) Подпрограммы могут быть объявленны как перед их вызовом, так и после. Подпрограммы могут возвращать какое-то значение. Для этого служит оператор return , он возвращает идущее за ним значение.

#!/usr/bin/perl -w sub test { return "Я вернулся:)"; } print &test();

Можно обходится и без него. Тогда Perl вернёт значение последнего оцененного выражения. Ещё о передаче подпрограмме параметров. В Perl все переменные передаются по ссылке. Поэтому нет смысла передавать подпрограмме указатель на переменную. При передаче параметром массива, мы фактически передаём ту же ссылку на первый элемент массива, поэтому можно так же, как с переменной, не указывать специально, что мы передаём указатель. Но я всё же покажу как это делается. Указатель - это переменная содержащая адрес другой переменной. Чтобы объявить указатель надо перед переменной поставить символ * . Примерно так:

*POINTER;

Чаще всего указатели служат для хранения хэндлера. Что такое хэндлер мы скоро рассмотрим. Операция взятия адреса в Perl обозначается сиволом "\ ". Небольшой пример:

#!/usr/bin/perl -w $x=0; print \$x;

Как видите мы получили не содержимое переменной, а её адрес - 0x18326bc (у вас может быть другой).

А сейчас рассмотрим как вынести свои подпрограммы в отдельный файл. Для этого достаточно сохранить свою подпрограмму в файл (предположим "test.pl"), а в том файле, в котором стоит вызов подпрограммы написать require "test.pl" . Как делать модули мы узнаем позже.

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

#!/usr/bin/perl -w $cnt=0; sub recurs { my $level = $_; $cnt++; print "$level\n"; if($cnt

  • Tutorial

В Perl заложено огромное количество возможностей, которые, на первый взгляд, выглядят лишними, а в неопытных руках могут вообще приводить к появлению багов. Доходит до того, что многие программисты, регулярно пишущие на Perl, даже не подозревают о полном функционале этого языка! Причина этого, как нам кажется, заключается в низком качестве и сомнительном содержании литературы для быстрого старта в области программирования на Perl. Это не касается только книг с Ламой, Альпакой и Верблюдом («Learning Perl », «Intermediate Perl » и «Programming Perl ») - мы настоятельно рекомендуем их прочитать.

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

Как работают функции Perl?

В большинстве языков программирования описание функции выглядит примерно так:
function myFunction (a, b) { return a + b; }
А вызывается функция так:
myFunction(1, 2);
На первый взгляд всё просто и понятно. Однако вызов этой функции в следующем виде:
myFunction(1, 2, 3); … в большинстве случаев приведёт к ошибкам, связанным с тем, что в функцию передано неверное количество аргументов.

Функция в Perl может быть записана так:
sub my_sub($$;$) : MyAttribute { my ($param) = @_; }
Где $$;$ - это прототип, а MyAttribute - это атрибут. Прототипы и атрибуты будут рассмотрены далее в статье. А мы пока рассмотрим более простой вариант записи функции:
sub my_sub { return 1; }
Здесь мы написали функцию, которая возвращает единицу.

Но в этой записи не указано, сколько аргументов принимает функция. Именно поэтому ничего не мешает вызвать её вот так:
my_sub("Туземец", "Бусы", "Колбаса", 42);
И всё прекрасно выполняется! Это происходит потому, что в Perl передача параметров в функцию сделана хитро. Perl славится тем, что у него много так называемых «специальных» переменных. В каждой функции доступна специальная переменная @_, которая является массивом входящих параметров.

Поэтому внутри функции мы можем поместить входные параметры в переменные так:
my ($param) = @_;
Это работает и в случае нескольких параметров:
my ($param1, $param2, $param3) = @_;

Очень часто в функциях пишут следующее:
sub my_sub { my $param = shift; ... }
Дело в том, что в Perl многие функции при вызове без аргументов используют переменные по умолчанию. shift же по умолчанию достаёт данные из массива @_. Поэтому записи:
my $param = shift; … и
my $param = shift @_; … совершенно эквивалентны, но первая запись короче и очевидна для Perl-программистов, поэтому используется именно она.

Shift можно использовать и для получения нескольких параметров, в том числе комбинируя в одно списочное присваивание:
my ($one, $two, $three) = (shift, shift, shift);
Другая запись:
my ($one, $two, $three) = @_; … работает точно так же.

А теперь внимание! Грабли, на которые рано или поздно наступает каждый Perl-программист:
sub my_sub { my $var = @_; print $var; }
Если вызвать данную функцию как my_sub(1, 2, 3) в $var мы внезапно получим не 1, а 3. Это происходит потому, что в данном случае контекст переменной определяется как скалярный, а в Perl массив в скалярном контексте возвращает свой размер, а не первый элемент. Чтобы исправить ошибку, достаточно взять $var в скобки, чтобы контекст стал списочным. Вот так:
sub my_sub { my ($var) = @_ }
И теперь, как и ожидалось, при вызове my_sub(1, 2, 3) в $var будет 1.

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

Например:
my $var = 5; my_sub($var); print $var; sub my_sub { # вспоминаем, что доступ к элементам массива выполняется в скалярном контексте # т. е. доступ к нулевому элементу массива @arr будет выглядеть как $arr, то же самое и с # @_. $_++; # $_ - первый элемент массива @_. }
Результат будет 6. Однако в Perl можно сделать в каком-то роде «передачу по значению» вот так:
my $var = 5; my_sub($var); print $var; sub my_sub { my ($param) = @_; $param++; }
А вот теперь результат будет 5.

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

Возьмём код из предыдущего примера и немного его модифицируем:
my $var = 5; my $result = my_sub($var); print $result; sub my_sub { my ($param) = @_; ++$param; }
Это будет работать точно так же, как если бы в последней строке функции был явный возврат значения:
return ++$param;
Функция вернёт 6.

И ещё одна особенность: если в теле функции вызывается другая функция с использованием амперсанда и без скобок, то внутренняя функция получает на вход параметры той функции, в теле которой она вызывается. Т. е. массив @_ будет автоматически передан из внешней функции во внутреннюю. Это может привести к неочевидным багам.
use strict; use Data::Dumper; my_sub(1, 2, 3); sub my_sub { &inner; } sub inner { print Dumper \@_; }
Результат:
$VAR1 = [
1,
2,
3
];

Однако, если явно указать (с помощью пустых скобок), что функция вызывается без параметров, то всё в порядке:
sub my_sub { &inner(); }
И вывод будет выглядеть вот так:
$VAR1 = ;

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

Анонимные функции

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

Элементарное объявление анонимной функции в Perl:
my $subroutine = sub { my $msg = shift; printf "I am called with message: %s\n", $msg; return 42; }; # $subroutine теперь ссылается на анонимную функцию $subroutine->("Oh, my message!");
Анонимные функции можно и нужно использовать как для создания блоков кода, так и для замыканий, о которых речь дальше.

Замыкания

Замыкание - это особый вид функции, в теле которой используются переменные, объявленные вне тела этой функции.

В записи это выглядит как, например, функция, находящаяся целиком в теле другой функции.
# возвращает ссылку на анонимную функцию sub adder($) { my $x = shift; # в котором x - свободная переменная, return sub ($) { my $y = shift; # а y - связанная переменная return $x + $y; }; } $add1 = adder(1); # делаем процедуру для прибавления 1 print $add1->(10); # печатает 11 $sub1 = adder(-1); # делаем процедуру для вычитания 1 print $sub1->(10); # печатает 9
Замыкания использовать полезно, например, когда необходимо получить функцию с уже готовыми параметрами, которые будут в ней сохранены. Или же для генерации функции-парсера, колбеков.

Бесскобочные функции

На наш взгляд, это самый подходящий перевод термина parenthesis-less.

Например, print часто пишется и вызывается без скобок. Возникает вопрос, а можем ли мы тоже создавать такие функции?

Безусловно. Для этого у Perl есть даже специальная прагма - subs. Предположим, нам нужна функция, проверяющая значение переменной на истинность.
use strict; use subs qw/checkflag/; my $flag = 1; print "OK" if checkflag; sub checkflag { return $flag; }
Эта программа напечатает OK.

Но это не единственный способ. Perl хорошо продуман, поэтому, если мы реструктуризируем нашу программу и приведём её к такому виду:
use strict; my $flag = 1; sub checkflag { return $flag; } print "OK" if checkflag; …то результат будет тот же.

Закономерность здесь следующая - мы можем вызывать функцию без скобок в нескольких случаях:

  • используя прагму subs;
  • написав функцию ПЕРЕД её вызовом;
  • используя прототипы функций.
Обратимся к последнему варианту.

Прототипы функций


Зачастую разное понимание цели этого механизма приводит к холиварам с адептами других языков, утверждающих, что «у перла плохие прототипы». Так вот, прототипы в Perl не для жёсткого ограничения типов параметров, передаваемых функциям. Это подсказка для языка: как разбирать то, что передаётся в функцию.

Есть, к примеру, абстрактная функция, которая называется my_sub:
sub my_sub { print join ", ", @_; }
Мы её вызываем следующим образом:
my_sub(1, 2, 3, 4, 5);
Функция напечатает следующее:
1, 2, 3, 4, 5,

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

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

Функция Perl с прототипами будет выглядеть так:
sub my_sub($$;$) { my ($v1, $v2, $v3) = @_; $v3 ||= "empty"; printf("v1: %s, v2: %s, v3: %s\n", $v1, $v2, $v3); }
Прототипы функций записываются после имени функции в круглых скобках. Прототип $$;$ означает, что в качестве параметров необходимо присутствие двух скаляров и третьего по желанию, «;» отделяет обязательные параметры от возможных.

Если же мы попробуем вызвать её вот так:
my_sub(); …то получим ошибку вида:
Not enough arguments for main::my_sub at pragmaticperl.pl line 7, near "()"
Execution of pragmaticperl.pl aborted due to compilation errors.

А если так:
&my_sub(); …то проверка прототипов не будет происходить.

Резюмируем. Прототипы будут работать в следующих случаях:

  • Если функция вызывается без знака амперсанда (&). Perlcritic (средство статического анализа Perl кода), кстати говоря, ругается на запись вызова функции через амперсанд, то есть такой вариант вызова не рекомендуется.
  • Если функция написана перед вызовом. Если мы сначала вызовем функцию, а потом её напишем, при включённых warnings получим следующее предупреждение:
    main::my_sub() called too early to check prototype at pragmaticperl.pl line 4
Ниже пример правильной программы с прототипами Perl:
use strict; use warnings; use subs qw/my_sub/; sub my_sub($$;$) { my ($v1, $v2, $v3) = @_; $v3 ||= "empty"; printf("v1: %s, v2: %s, v3: %s\n", $v1, $v2, $v3); } my_sub();
В Perl существует возможность узнать, какой у функции прототип. Например:
perl -e "print prototype("CORE::read")"
Выдаст:
*\$$;$

Оверрайд методов

Оверрайд - часто довольно полезная штука. Например, у нас есть модуль, который писал некий N. И всё в нём хорошо, а вот один метод, допустим, call_me, должен всегда возвращать 1, иначе беда, а метод из базовой поставки модуля возвращает всегда 0. Код модуля трогать нельзя.

Пусть программа выглядит следующим образом:
use strict; use Data::Dumper; my $obj = Top->new(); if ($obj->call_me()) { print "Purrrrfect\n"; } else { print "OKAY:(\n"; } package Top; use strict; sub new { my $class = shift; my $self = {}; bless $self, $class; return $self; } sub call_me { print "call_me from TOP called!\n"; return 0; } 1;
Она выведет:
call_me from TOP called!
OKAY:(

И снова у нас есть решение.

Допишем перед вызовом $obj->call_me() следующую вещь:
*Top::call_me = sub { print "Overrided subroutine called!\n"; return 1; };
А ещё лучше, для временного оверрайда используем ключевое слово local:
local *Top::call_me = sub { print "Overrided subroutine called!\n"; return 1; };
Это заменит функцию call_me пакета Top в лексической области видимости (в текущем блоке).
Теперь наш вывод будет выглядеть так:
Overrided subroutine called!
Purrrrfect

Код модуля не меняли, функция теперь делает то, что нам надо.

На заметку: если приходится часто использовать данный приём в работе - налицо архитектурный косяк. Хороший пример использования - добавление вывода отладочной информации в функции.

Wantarray

В Perl есть такая полезная штука, которая позволяет определить, в каком контексте
вызывается функция. Например, мы хотим, чтобы функция вела себя следующим образом:
когда надо возвращала массив, а иначе - ссылку на массив. Это можно реализовать, и
к тому же очень просто, с помощью wantarray. Напишем простую программу для демонстрации:
#!/usr/bin/env perl use strict; use Data::Dumper; my @result = my_cool_sub(); print Dumper @result; my $result = my_cool_sub(); print Dumper $result; sub my_cool_sub { my @array = (1, 2, 3); if (wantarray) { print "ARRAY!\n"; return @array; } else { print "REFERENCE!\n"; return \@array; } }
Что выведет:
ARRAY!
$VAR1 = 1;
$VAR2 = 2;
$VAR3 = 3;
REFERENCE!
$VAR1 = [
1,
2,
3
];

Также хотелось бы напомнить про интересную особенность Perl. %hash = @аrray; В этом случае Perl построит хэш вида ($array => $array, $array => $array);

Посему, если применять my %hash = my_cool_sub(), будет использована ветка логики wantarray. И именно по этой причине wanthash нет.

AUTOLOAD

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

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

Например:
#!/usr/bin/env perl use strict; Autoload::Demo::hello(); Autoload::Demo::asdfgh(1, 2, 3); Autoload::Demo::qwerty(); package Autoload::Demo; use strict; use warnings; our $AUTOLOAD; sub AUTOLOAD { print $AUTOLOAD, " called with params: ", join (", ", @_), "\n"; } sub hello { print "Hello!\n"; } 1;
Очевидно, что функций qwerty и asdfgh не существует в пакете Autoload::Demo. В функции AUTOLOAD специальная глобальная переменная $AUTOLOAD устанавливается равной функции, которая не была найдена.

Вывод этой программы:
Hello!
Autoload::Demo::asdfgh called with params: 1, 2, 3
Autoload::Demo::qwerty called with params:

Генерация функций на лету

Допустим, нам нужно написать множество функций, выполняющих примерно одинаковые действия. Например, набор аксессоров у объекта. Написание подобного кода вряд ли кому-то доставит удовольствие:
sub getName { my $self = shift; return $self->{name}; } sub getAge { my $self = shift; return $self->{age}; } sub getOther { my $self = shift; return $self->{other}; }
Это Perl. «Лень, нетерпение, надменность» (Л. Уолл).

Функции можно генерировать. В Perl есть такая штука как тип данных typeglob. Наиболее точный перевод названия - таблица имён. Typeglob имеет свой сигил - «*».

Для начала посмотрим код:
#!/usr/bin/env perl use strict; use warnings; package MyCoolPackage; sub getName { my $self = shift; return $self->{name}; } sub getAge { my $self = shift; return $self->{age}; } sub getOther { my $self = shift; return $self->{other}; } foreach (keys %{*MyCoolPackage::}) { print $_." => ".$MyCoolPackage::{$_}."\n"; }
Вывод:
getOther => *MyCoolPackage::getOther
getName => *MyCoolPackage::getName
getAge => *MyCoolPackage::getAge

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

И вот что у нас получилось:
#!/usr/bin/env perl use strict; use warnings; $\ = "\n"; my $person = Person->new(name => "justnoxx", age => "25", other => "perl programmer",); print "Name: ", $person->get_name(); print "Age: ", $person->get_age(); print "Other: ", $person->get_other(); package Person; use strict; use warnings; sub new { my ($class, %params) = @_; my $self = {}; no strict "refs"; for my $key (keys %params) { # __PACKAGE__ равен текущему модулю, это встроенная # волшебная строка # следующая строка превращается в, например: # Person::get_name = sub {...}; *{__PACKAGE__ . "::" . "get_$key"} = sub { my $self = shift; return $self->{$key}; }; $self->{$key} = $params{$key}; } bless $self, $class; return $self; } 1;
Эта программа напечатает:
Name: justnoxx
Age: 25
Other: perl programmer

Атрибуты функций

В Python есть такое понятие как декоратор. Это такая штуковина, которая позволяет «добавить объекту дополнительное поведение».

Да, в Perl декораторов нет, зато есть атрибуты функций. Если мы откроем perldoc perlsub и посмотрим на описание функции, то увидим любопытную запись:
sub NAME(PROTO) : ATTRS BLOCK
Таким образом, функция с атрибутами может выглядеть так:
sub my_sub($$;$) : MyAttr { print "Hello, I am sub with attributes and prototypes!"; }
Работа с атрибутами в Perl - дело нетривиальное, потому уже довольно давно в стандартную поставку Perl входит модуль Attribute::Handlers.

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

Допустим, у нас есть функция, которая может быть вызвана только в том случае, если пользователь авторизован. За то, что пользователь авторизован отвечает переменная $auth, которая равна 1, если пользователь авторизован, и 0, если нет. Мы можем сделать следующим образом:
my $auth = 1; sub my_sub { if ($auth) { print "Okay!\n"; return 1; } print "YOU SHALL NOT PASS!!!1111"; return 0; }
И это приемлемое решение.

Но может возникнуть такая ситуация, что функций будет становиться больше и больше. А в каждой делать проверку будет всё накладнее. Проблему можно решить с помощью атрибутов.
use strict; use warnings; use Attribute::Handlers; use Data::Dumper; my_sub(); sub new { return bless {}, shift; } sub isAuth: ATTR(CODE) { my ($package, $symbol, $referent, $attr, $data, $phase, $filename, $linenum) = @_; no warnings "redefine"; unless (is_auth()) { *{$symbol} = sub { require Carp; Carp::croak "YOU SHALL NOT PASS\n"; }; } } sub my_sub: isAuth { print "I am called only for auth users!\n"; } sub is_auth { return 0; }
В данном примере вывод программы будет выглядеть так:
YOU SHALL NOT PASS at myattr.pl line 18. main::__ANON__() called at myattr.pl line 6

А если мы заменим return 0 на return 1 в is_auth, то:
I am called only for auth users!

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

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

Статья написана в соавторстве и по техническому материалу от Дмитрия Шаматрина (@justnoxx) и при содействии программистов REG.RU: Тимура Нозадзе (@TimurN), Виктора Ефимова (@vsespb), Полины Шубиной (@imagostorm), Andrew Nugged (@nugged)

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

chomp (string), chomp (array) использует значение специальной переменной $/ для последнего символа строки string или каждого элемента массива array. Последний символ будет удален только в том случае, если он равен значению переменой $/.

chop (string), chop (array) делает то же самое, что и предыдущая функция, но в качестве результата эта функция возвращает сам удаленный символ.

chr (number) возвращает символ из ASCII-таблицы с кодом number. Например, chr(65) возвратит символ "A".

crypt (string1, string2) шифрует строку string1. Perl не предоставляет механизмов для дешифрования строки.

index (string, substring, position) возвращает позицию первого вхождения строки substring в строке string считая от позиции position. Если параметр position не задан, то сравнение строк ведется от начала строки string.

join (string, array) возвращает строку, в которой все элементы массива array соединены строкой string. Например, join (">>", ("a","b","c")) возвратит строку "a>>b>>c";

lc (string) возвратит строку, где все символы прописные. Например, lc ("ABCD") возвратит "abcd"

lcfirst (string) возвратит строку, в которой только первый символ прописной. Например, lcfirst ("ABCD") возвратит "aBCD"

length (string) возвращает длину строки.

rindex (string, substring, position) то же, что и index (смотрите выше), но возвращает позицию последнего вхождения.

substr (string, offset, length) возвращает часть строки string, как определено параметрами offset (смещение) и length (длина). Если length не указан, возвращается все, что находится на промежутке от offset до конца string. Отрицательные значения offset могут использоваться для сканирования с правого края строки string.

uc (string) то же самое, что и lc (смотрите выше), но все символы заглавные.

ucfirst (string) то же самое, что lcfirst (смотрите выше), но символ заглавный.

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

Некоторые функции из приведенных выше используют для своей работы специальную переменную $_, о которой вы узнаете в главе 9 "Файлы" и главе 12 "Использование специальных переменных".

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

Пример: изменение значения строки

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

$firstVar = substr("0123BBB789", 4, 3);


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

firstVar = BBB

Функция возвратит строку, начиная с 5-го символа, и длиной в 3 символа.

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

$firstVar = "0123BBB789";
substr($firstVar, 4, 3) = "AAA";
print("firstVar = $firstVar\n");

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

FirstVar = 0123AAA789

Пример: поиск строки

Другая интересная задача, которую вы можете решить с помощью строковх функций - это поиск заданной подстроки в строке. Например, у вас есть полное имя файла, включая путь: "C:\\WINDOWS \\TEMP\\WSREWE.DAT", а вам нужно получить из него только имя файла. Вы можете это сделать, найдя последний обратный слеш (символ "\"), а за тем применив функцию substr().

Помните, что в строке для указания символа "\" вы должны использовать двойной символ "\\". Если вы подзабыли материал, обратитесь к главе 2 "Числовые и стринговые литералы".

$pathName = "C:\\WINDOWS\\TEMP\\WSREWE.DAT";
$position = rindex($pathName, "\\") + 1;
$fileName = substr($pathName, $position);
print("$fileName\n");

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

WSREWE.DAT

Мы не указали здесь третий параметр функции substr() - length, - это потому что нам нужно взять подстроку до конца исходной строки.

Функции для работы с массивами.

Массивы - это большая часть Perl и Perl может вам предложить много функций для работы с ними.

Функции, которые вы можете использовать для работы с массивами:

defined (variable) возвращает true если variable имеет действительное значение, и false если variable пока не было присвоено значение. Это относится не только к массивам, данные любого типа могут быть проверены таким образом. В отношении подобных операций с ключами ассоциативных массивов смотрите функцию exists().

delete (key) удаляет пару "ключ-значение" из данного ассоциативного массива. Если вы удалите подобным образом значение из массива окружения %ENV, то изменится окружение только текущего процесса.

each (assoc_array) возвращает двухэлементый список, содержащий ключ и значение из заданного ассоциативного массива. Если уже был прочитан последний элемент массива, возвращается пустой список.

exists (key) возвращает true, если key является ключем заданного ассоциативного массива. Например, exists ($array("Orange")) возвратит true, если ассоциативный массив %array имеет ключ со значением "Orange".

join (string, array) возвращает строку, в которой все элементы массива array соединены строкой string. Например, join (">>", ("a","b","c")) возвратит строку "a>>b>>c".

keys (assoc_array) возвращает список всех ключей, имеющихся в заданном ассоциативном массиве. Список не составляется в каком-либо определенном порядке.

map (expression, array) вычисляет выражение expression для каждого элемента массива array. Специальная переменная $ присваивается каждому элементу массива array перед вычислением выражения expression.

pack (string, array) создает бинарную структуру, используя string как шаблон, массива array. Подробнее смотрите в главе 8 "Ссылки".

pop (array) возвращает последнее значение массива array, уменьшая его размер на единицу.

push (array1, array2) добавляет содержимое массива array1 к массиву array2, изменяя размер массива array1 должным образом.

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

scalar (array) рассматривает массив как скаляр и возвращает количество элементов в массиве.

shift (array) возвращает первое значение массива array, уменьшая его размер на единицу.

sort (array) возвращает список, содержащий элементы массива array, отсортированные в заданном порядке. Подробнее смотрите главу 8 "Ссылки".

splice (array1, offset, length, array2) заменяет элементы массива array1 элементами массива array2. Возвращает список, содержащий все элементы, которые были удалены.

split (pattern, string, limit) разбивает строку string на части, принимая за границу значение параметра pattern. Например, ($a, $b, $c) = split ("::", "1::2::3") присвоит переменным $a, $b, $c значения "1", "2", "3" соответственно. Если же результат используется в скалярном контексте, то функция возвращает количество найденных таким образом элементов.

undef (variable) всегда возвращает неопределенное значение. Кроме того, "разопределяет" переменную variable, которая должна быть скаляром, целым массивом или именем подпрограммы.

unpack (string, array) совершает действия, полностью противоположные деяниям функции pack() - смотрите выше.

unshift (array1, array2) добавляет элементы массива array1 к началу массива array2. Добавляемые элементы сохраняют оригинальный порядок. Возвращает новый размер массива array1.

values (assoc_array) возвращает все значения заданного ассоциативного массива. Возвращаемый список не формируется в каком-либо определенном порядке.

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

Пример: печать ассоциативного массива.

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

%array = ("100", "Green", "200", "Orange");
while (($key, $value) = each(%array)) {
print("$key = $value\n");
}

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

100 = Green
200 = Orange

По достижении конца массива, функция возвращает false.

Пример: проверка существования элемента массива

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

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





print("$key, $value\n");
};
sub createPair{
my($key, $value) = @_ ;
$array{$key} = $value;
};

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

100, George Orwell
200, Grace Kelly

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

createPair("100", "Kathy Jones");
createPair("200", "Grace Kelly");
createPair("100", "George Orwell");
while (($key, $value) = each %array) {
print("$key, $value\n");
};
sub createPair{
my($key, $value) = @_ ;
while (defined($array{$key})) {
$key++;
}
$array{$key} = $value;
};

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

100, George Orwell
101, Kathy Jones
200, Grace Kelly

Как вы видите, программа заметила, что ключ "100" уже существует, и новые данные добавила в массив с ключем "101". Если бы, например, ключ "101" уже существовал в массиве, то новые данные таким образом добавились бы с ключем "102".

Расмотрены наиболее интересные, по мнению автора, особенности языка Perl

Perl это вообще весьма занятный язык программирования общего назначения. Многие говорят что он очень древний, в нем что-то не поддерживается, что это вообще убогий язык и нужно бы его закопать.

Новые языки программирования появляются постоянно. Вместе с этим добавляются новые аспекты программирования, новые архитектуры, новые модные приемы. А Perl как будто старый и не способен соревноваться.

Где-то давно я находил фразу приблизительно следующего содержания:

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

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

Анонимные функции

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

Анонимная функция создается проще простого:

My $subroutine = sub { my $msg = shift; printf "I am called wit message: %s\n", $msg; return 42; }; # $subroutine теперь ссылается на анонимную функцию $subroutine->("Oh, my message!");

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

Замыкания

Как говорит нам википедия:

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

# возвращает ссылку на анонимную функцию sub adder($) { my $x = shift; # в котором x - свободная переменная, return sub ($) { my $y = shift; # а y - связанная переменная return $x + $y; }; } $add1 = adder(1); # делаем процедуру для прибавления 1 print $add1->(10); # печатает 11 $sub1 = adder(-1); # делаем процедуру для вычитания 1 print $sub1->(10); # печатает 9

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

Неизменяемый объект

Нет ничего проще. Очень часто приходится видеть простыни текстов на форумах, как это реализовать. Многие «современные», «модные» языки не могут похвастаться настолько простым и изящным решением, как то, которое имеет Perl на такой случай. Фактически, state работает как my , у него одинаковая область видимости, но при этом переменная state НИКОГДА не будет переинициализирована в рамках программы. Это иллюстрируют два примера.

Use feature "state"; gimme_another(); gimme_another(); sub gimme_another { state $x; print ++$x, "\n"; }

Эта программа напечатает 1, затем 2.

Use feature "state"; gimme_another(); gimme_another(); sub gimme_another { my $x; print ++$x, "\n"; }

А эта 1, затем 1.

Бесскобочные функции

Я думаю, что это правильный перевод parentheses less.

Очень часто print , например, пишется и вызывается без скобок. Возникает вопрос, а можем ли мы тоже создавать такие функции?

Можем. Для этого у Perl есть даже специальная прагма - subs . Синтетический пример, в котором мы хотим сделать красивую проверку. Допустим, мы хотим напечатать OK, если определенная переменная true . Я позволю себе напомнить, что в Perl нет такого типа данных, как Boolean, также, как и в C нет такого типа данных, тогда как в место него выступает не ложное значение - например, 1.

Use strict; use subs qw/checkflag/; my $flag = 1; print "OK" if checkflag; sub checkflag { return $flag; }

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

Use strict; sub checkflag { return $flag; } my $flag = 1; print "OK" if checkflag;

То результат будет тот же. Закономерность здесь слудеющая. Мы можем вызывать функцию без скобок в нескольких случаях:

  • Используя прагму subs.
  • Написав функцию ПЕРЕД ее вызовом.
  • Использовать прототипы функций.

Рассмотрим, что же такое прототипы.

Прототипы функций

Для начала стоит выяснить, как вообще функции работают в Perl.

Есть, например, какая-то абстрактная себе функция, которая называется, допустим my_sub:

Sub my_sub { print join ", ", @_; }

Например, мы ее вызываем следующим образом:

My_sub(1, 2, 3, 4, 5);

Функция напечатает следующее:

1, 2, 3, 4, 5,

Таким образом мы можем передавать в ЛЮБУЮ функцию Perl любое количество аргументов. И пусть сама функция разбирается, что мы от нее хотели.

В функцию передается «текущий массив», контекстная переменная. Поэтому, запись вида:

Sub ms { my $data = shift; print $data; }

Эквивалентна:

Sub ms { my $data = shift, @_; print $data; }

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

Функция Perl с прототипами будет выглядеть так:

Sub my_sub($$;$) { my ($v1, $v2, $v3) = @_; $v3 ||= "empty"; printf("v1: %s, v2: %s, v3: %s\n", $v1, $v2, $v3); }

Прототипы функций записываются после имени функции в круглых скобках. Прототип $$;$ означает, что в качестве параметров ОБЯЗАНЫ присутствовать два скаляра и третий по желанию, ; - отделяет обязательные параметры от возможных.

Теперь, если мы ее попробуем вызвать вот так:

My_sub();

То мы получим ошибку вида:

Not enough arguments for main::my_sub at pragmaticperl.pl line 7, near "()" Execution of pragmaticperl.pl aborted due to compilation errors.

Однако, если вызвать вот так:

&my_sub();

То проверка прототипов не будет происходить.

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

  • Если функция вызывается без знака амперсанда (&). Perlcritic, кстати, ругается на запись вызова функции через амперсанд.
  • Если функция написана ПЕРЕД вызовом. Если мы сначала вызовем функцию, а потом ее напишем, при включенных warnings получим следующее предупреждение:

    main::my_sub() called too early to check prototype at pragmaticperl.pl line 4.

Правильная программа с прототипами выглядит так:

Use strict; use warnings; use subs qw/my_sub/; sub my_sub($$;$) { my ($v1, $v2, $v3) = @_; $v3 ||= "empty"; printf("v1: %s, v2: %s, v3: %s\n", $v1, $v2, $v3); } my_sub();

ООП

OOП у Perl странное. Кто-то говорит, что его нет, а кто-то говорит, что это лучшая реализация, т.к. она не мешает жить программисту, а позволяет делать сложные вещи возможными. Я для себя, например, еще не определился.

Что такое ООП в случае Perl? Это, так называемый, blessed hashref. Инкапсуляции, как таковой, в Perl нет. Есть сторонние модули, например, Moose , Mouse , которые предоставляю все возможности ООП Perl программисту ценой потери скорости.

Итак, простейший пакет.

Use strict; use Data::Dumper; my $obj = Top->new(); print Dumper $obj; $obj->top_sub(); package Top; use strict; sub new { my $class = shift; my $self = {}; bless $self, $class; return $self; } sub top_sub { print "I am TOP sub\n"; } 1;

Данная программа выведет:

$VAR1 = bless({}, "Top"); I am TOP sub

Собственно, ООП сводится к тому, что есть объект и он может выступать хранилищем состояний и вызывать методы пакета. Через стрелочку ($obj->top_sub).

Множественное наследование

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

ваш код сегодняшний коллега напоминает даунхилл среди деревьев и говнища велосипед и костыли

Use strict; use Data::Dumper; my $obj = Bottom->new(); print Dumper $obj; # вызываем функции всех пакетов $obj->top_sub(); $obj->top2_sub(); $obj->bottom_sub(); package Top; use strict; sub new { my $class = shift; my $self = {}; bless $self, $class; return $self; } sub top_sub { print "I am TOP sub\n"; } package Top2 { sub new { my $class = shift; my $self = {}; bless $self, $class; return $self; } sub top2_sub { print "I am TOP2 sub\n"; } } package Bottom; use strict; # наследуем Top и Top2 use base qw/Top Top2/; sub new { my $class = shift; my $self = {}; bless $self, $class; return $self; } sub bottom_sub { print "I am BOTTOM sub\n"; } 1;

В данном случае программа выведет:

I am TOP sub I am TOP2 sub I am BOTTOM sub

Оверрайд методов

Оверрайд довольно часто хорошая штука. Например, у нас есть модуль, который писал некий N. И все в нем хорошо, а вот один метод, допустим, call_me, должен всегда возвращать 1, иначе беда, а метод из базовой поставки модуля возвращает всегда 0. Код модуля трогать нельзя.

Пусть программа выглядит следующим образом:

Use strict; use Data::Dumper; my $obj = Top->new(); if ($obj->call_me()) { print "Purrrrfect\n"; } else { print "OKAY:(\n"; } package Top; use strict; sub new { my $class = shift; my $self = {}; bless $self, $class; return $self; } sub call_me { print "call_me from TOP called!\n"; return 0; } 1;

Которая выведет:

Call_me from TOP called! OKAY:(

И снова у нас есть решение:

Допишем перед вызовом $obj->call_me() следующую вещь:

*Top::call_me = sub { print "Overrided subroutine called!\n"; return 1; };

И теперь наш вывод будет выглядеть примерно следующим образом:

Overrided subroutine called! Purrrrfect

Код модуля не меняли, функция теперь делает то, что нам надо.

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

Локальные переменные или динамическая область видимости:

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

Мы можем это сделать следующим образом:

Use strict; use warnings; my $x = 1; print "x: $x\n"; do_with_x(); print "x: $x\n"; sub do_with_x { my $y = $x; $y++; print "y: $y\n"; }

Вывод ожидаемый:

X: 1 y: 2 x: 1

Однако, в данном случае мы можем обойтись без y . Решение выглядит так:

Use strict; use warnings; use vars "$x"; $x = 1; print "x: $x\n"; do_with_x(); print "x: $x\n"; sub do_with_x { local $x = $x; $x++; print "x: $x\n"; }

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

Local $| = 1;

Или, если лениво писать в конце каждого print \n:

Local $\ = "\n";

Атрибуты функций

В Python есть такое понятие, как декоратор. Это такая штуковина, которая позволяет «добавить объекту дополнительное поведение».

Да, в Perl декораторов нет, зато есть атрибуты функций. Если мы откроем perldoc perlsub и посмотрим на описание функции, там есть такая любопытная запись:

Sub NAME(PROTO) : ATTRS BLOCK

Таким образом, функция с атрибутами может выглядеть так:

Sub mySub($$;$) : MyAttr { print "Hello, I am sub with attributes and prototypes!"; }

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

И скоро даже что-нибудь покажем. Все вышесказанное не касается книги с Верблюдом, Альпакой и Ламой, это прекрасная литература и тем кто ее не читал - я настоятельно рекомендую. Вернемся к нашим атрибутам.

Работа с атрибутами в Perl дело нетривиальное, потому уже довольно давно в стандартную поставку Perl входит модуль Attribute::Handlers .

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

Допустим, у нас есть функция, которая может быть вызвана только в том случае, если пользователь авторизован. За то, что пользователь авторизован отвечает переменная $auth , которая равна 1, если пользователь авторизован и 0, соответственно, нет. Мы можем сделать следующим образом:

My $auth = 1; sub my_sub { if ($auth) { print "Okay!\n"; return 1; } print "YOU SHALL NOT PASS!!!1111"; return 0; }

И это нормальное решение.

Но может возникнуть такая ситуация, что функций будет становиться больше и больше. А в каждой делать проверку будет становиться накладно. Проблему можно решить на атрибутах.

Use strict; use warnings; use Attribute::Handlers; use Data::Dumper; my_sub(); sub new { return bless {}, shift; } sub isAuth: ATTR(CODE) { my ($package, $symbol, $referent, $attr, $data, $phase, $filename, $linenum) = @_; no warnings "redefine"; unless (is_auth()) { *{$symbol} = sub { require Carp; Carp::croak "YOU SHALL NOT PASS\n"; goto &$referent; }; } } sub my_sub: isAuth { print "I am called only for auth users!\n"; } sub is_auth { return 0; }

В данном примере вызов программы будет выглядеть так:

YOU SHALL NOT PASS at myattr.pl line 18. main::__ANON__() called at myattr.pl line 6

А если мы заменим return 0 на return 1 в is_auth , то:

I am called only for auth users!

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

  • Анонимными функциями.
  • Оверрайдом функций.
  • Специальной формой оператора goto.

Не смотря на довольно громоздкий синтаксис атрибуты успешно применяются в Catalyst , например.

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

О чем хотелось бы еще написать и я напишу:

  • Фазы исполнения Perl программы.
  • Более пристальный взгляд на модули.
  • AUTOLOAD и DESTROY.
  • Особая форма оператора goto.
  • Прагмы Perl.
Директива use strict

Вспомните, как мы объявляли переменные в предыдущих статьях?

#/usr/bin/perl
$alpha = 1;
$beta = 2;
# ...
$alpha = $alpha + $beta;
# alpha равно 3


Однако что произойдет, если мы случайно сделаем опечатку в имени одной из переменных?

# опечатка в имени переменной - bAta вместо bEta
$alpha = $alpha + $bata;
# alpha равно 1


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

Для решения этой проблемы возьмите за правило в любых скриптах, которые сложнее "hello world", использовать директиву "use strict". Если в скрипте используется эта директива, объявление переменных следует начинать со слова "my":

#/usr/bin/perl
use strict;
my $alpha = 1;
my $beta = 2;
# ...
$alpha = $alpha + $beta;
# alpha равно 3


Совсем не сложно, правда? Что же произойдет, если мы сделаем опечатку в имени переменной? Поскольку переменная $bata ранее не была объявлено, интерпретатор будет ругаться на синтаксическую ошибку и скрипт просто не выполнится, пока мы не исправим опечатку:
Global symbol "$bata" requires explicit package name at ./test.pl line 7.
Execution of ./test.pl aborted due to compilation errors.

Еще раз повторяю - при написании скрипта, размером более 10-и строк кода, всегда используйте директиву use strict. В противном случае вы рискуете потратить много времени на исправление совершенно "мистических" ошибок.

Помните, в первой части "основ perl" мы использовали массивы и хэши. Любопытный читатель спросит - а как же быть с многомерными массивами, массивами хэшей и другими сложными структурами? Неужели Perl их не поддерживает? На самом деле это не так, и скоро Вы в этом убедитесь. Для начала рассмотрим простую задачу - объявить двумерный массив. Вот один из способов это сделать:

my @arr1 = (1, 2, 3);
my @arr2 = (4, 5, 6);
my @arr3 = (7, 8, 9);
my @matrix = (\@arr1, \@arr2, \@arr3);
for(my $i = 0; $i for(my $j = 0; $j print $matrix[$i][$j]." ";
}
print "\n";
}


Здесь был объявлен массив из трех элементов @matrix, элементами которого являются ссылки на массивы @arr1, @arr2 и @arr3. Смотрите, что происходит - в массиве по прежнему хранятся скаляры, но поскольку они, эти скаляры, представляют собой ссылки на массивы, мы фактически имеем дело с многомерным массивом!
Другой способ получить ссылку на массив - использовать квадратные скобки:

my @matrix = ([@arr1], [@arr2], [@arr3]);
my $aref = ;
my $aref2 = ;


Аналогичный способ получить ссылку на хэш - использовать фигурные скобки:

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


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

# используем оператор "стрелка"
print $aref2->."\n"; # выведет 4
print $href->{aaa}."\n"; # выведет 7
my @arr_copy = @{$aref2}; # копируем массив
my %hash_copy = %{$href}; # копируем хэш


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

my @matrix = (# объявляем массив
, # элементы которого - ссылки на массив
,
);
print $matrix->."\n"; # выведет 2
print $matrix."\n"; # выведет 9, оператор "стрелка" можно опустить
my @points = (# массив точек
{ x => 7, y => 13 },
{ x => -3, y => 2 },
{ x => 0, y => -9});
# координаты первой точки
print $points->{x}.";".$points->{y}."\n";
# координаты второй точки
# аналогично предыдущему примеру - оператор "стрелка" можно опустить
print $points{x}.";".$points{y}."\n";


Оператор "стрелка" всегда можно опускать между индексами. То есть следующие две строчки кода абсолютно аналогичны:

$ref->[$i]->[$j]->[$k]++;
$ref->[$i][$j][$k]++; # то же самое, только короче


Ссылки и утечка памяти

my @arr = (, );
my $arr_ref = $arr; # ссылка на массив (4,5,6)
@arr = undef; # аналогично вызову undef() в PHP
print $arr_ref->."\n"; # выведет 5!
# ^ тут, кстати, "стрелку" опустить нельзя
#иначе Perl решит, что мы работаем с
# не объявленной переменной @arr_ref
$arr_ref = undef;
# вот только теперь память, выделенная под
# безымянный массив (4, 5, 6) будет освобождена


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

Функции

Я считаю, что Вы уже знаете, что такое функции и процедуры, для чего они нужны. Функции в Perl похожи на функции в языке программирования C в том плане, что они могут принимать произвольное число аргументов. Аналогично языку C, нет разницы между функциями и процедурами, как в Pascal. Пример объявления и вызова функции:

#!/usr/bin/perl
use strict;
sub myfunc {
my($alpha, $beta) = @_;
$alpha + $beta;
}
print myfunc(2, 3)."\n";


Результат последней операции, выполненной внутри функции, будет возвращен в качестве результата. С помощью оператора return можно вернуть результат в произвольном месте функции, не только в конце:

#!/usr/bin/perl
use strict;
sub myfunc {
my($alpha, $beta) = @_;
if($alpha > $beta) {
# прекратить выполнение функции и вернуть результат
return $alpha - $beta;
}
$beta - $alpha;
}
print myfunc(2, 3)."\n";


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

#!/usr/bin/perl
use strict;
sub setval {
my($ref, $val) = @_;
$ref->{val} = $val;
}
my %hash;
setval(\%hash, 14);
print "$hash{val}\n"; # выведет 14


В следующих частях "Основ" я расскажу о работе с файлами, глобах, функциях eval и system, операторах sort и grep.

Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: