Первый сайт на Perl

         

Блоки в операциях разыменования ссылок



Блоки в операциях разыменования ссылок

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

${$d[0]} = 7; ${$h{"one"}} = 1; ${&f()}[l] = 3;

Разберем первую строку. Начальный символ $ является признаком скалярной переменной, за которым должно следовать ее имя. Вместо имени используется блок, следовательно, выражение внутри блока интерретируется как ссылка. В данном случае осуществляется разыменование ссылки $d[0], являющейся элементом массива @а. Аналогично, во второй строке осуществляется обращение к скалярной переменной, на которую указывает ссылка $h{"one"}, являющаяся элементом ассоциативного массива %ь. В третьей строке блок, возвращающий ссылку, состоит из одного обращения к функции f (). Ее значение интерпретируется как ссылка на массив, и второму элементу этого массива присваивается значение 3.



Другие способы



Другие способы

В предыдущих разделах рассмотрены основные способы создания ссылок: . О применение операции " \ " к объекту ссылки;

специальные конструкции [ ] и { }, создающие в определенном контексте ссылку соответственно на анонимный массив и анонимный ассоциативный массив.

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

Конструктор анонимной подпрограммы

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

Ссылка на анонимную подпрограмму может быть создана при помощи ключевого слова sub, за которым следует блок — последовательность операторов, заключенная в фигурные скобки:

$sub_ref = sub { print "Привет!\n");

В результате операции присваивания в переменную $sub_ref заносится адрес, по которому размещается код анонимной подпрограммы. В данном примере подпрограмма состоит из единственного обращения к функции print, выводящей строку "Привет!".

Пример, иллюстрирующий данный вид ссылки, будет рассмотрен далее в этой главе.

Ссылка, создаваемая конструктором объекта

В версию 5.0 языка Perl была добавлена поддержка объектно-ориентированного программирования. Основой объектно-ориентированного подхода являются понятия класс и объект.

(Классы и объекты рассматриваются .)



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

Ссылки на данные типа typeglob


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

(О пакетах и таблицах символов рассказывается в .)

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

$а=5;

@а=(1,2,3,4,5);

%а=("опе"=>1,"two"=>2,"three"=>3);

sub a {return "Hello, Mike!";};

то таблица символов содержит гнездо для идентификатора "а", состоящее из четырех элементов, хранящих адреса: скалярной переменной $а, массива @а, ассоциативного массива %а и кода подпрограммы &а.

В языке Perl существует внутренний тип данных typeglob. Признаком этого типа является наличие префикса "*" в имени переменной. Тип typeglob служит для ссылки на все переменные разных типов с одинаковыми именами. Например, переменная *а обозначает ссылку на гнездо "а" в таблице символов. Используя специальную запись, можно при помощи переменной typeglob получить ссылки на отдельные элементы гнезда: $scalarref = *a{SCALAR}; # эквивалентно $scalarref = \$a; $arrayref = *a{ARRAY}; # эквивалентно $arrayref = \@а; $hashref = *a{HASH}; # эквивалентно $hashref = \%a; $coderef = *a{CODE}; # эквивалентно $coderef = \&a; $globref = *a{GLOB}; # эквивалентно $globref = \*a;

Неявное создание ссылок

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




Другие структуры данных



Другие структуры данных

На основе массива Ocaiendar, содержащего календарь на 2000 год, покажем, как можно строить более сложные структуры данных. Структура двумерного массива не очень удобна для представления содержащихся в ней данных в привычном виде настенного календаря. Перегруппируем данные, объединяя их в группы по дням недели. Для этого построим новую структуру, которую для краткости назовем "массив хешей массивов", отдавая себе отчет в том, что такое словосочетание не только далеко не изящно, но и по существу неточно.

Новая структура представляет собой массив @months, состоящий из 12 элементов по числу месяцев в году. Каждый элемент содержит ссылку на анонимный хеш-массив. Каждый вложенный хеш-массив содержит набор ключей, имеющих имена, совпадающие с английскими названиями дней недели: "Monday", "Tuesday" и т. д. Каждому ключу соответствует значение, являющееся, в свою очередь, ссылкой на анонимный массив, содержащий все числа данного месяца, приходящиеся на день недели, соответствующий ключу: все понедельники, все вторники и т. д. Структура массива @months представлена на Рисунок 9.2.

Рис 9.2. Структура массива @months

for $i (0..11) { . for $j (0..$#{$calendar[$i.]}> {

push @{$months[$i]{$calendar[$i][$j]}}, $j+l; } };

Замечание
Функция push @array, list помещает список list в конец массива garray.

Первым аргументом встроенной функции push является массив, в который попадают все дни (i+l)-ro месяца, приходящиеся на один и тот же день недели: все понедельники, все вторники и т. д. На этот массив указывает ссылка $months[$i] {"key"}, где ключ "key" принимает значения "Monday", "Tuesday" и т. д. Для обращения к самому массиву ссылку следует разымено-вать, заключив в фигурные скобки: @{$months[$i] ("key"}}. Если вместо ключа "key" подставить нужное значение из $caiendar[$i] [$j], то получим аргумент функции push.

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


for $i (0..11) {

print "month # ", $i+l, "\n";

for $DayName (keys %{$months[$i]}) {

print " ${DayName}: @{$months[$i]{$DayName}}\n";

} };

распечатает календарь в виде

month #1

Monday 3 10 17 24 31

Thursday 6 13 20 27

Wednesday 5 12 19 26

Sunday 2 9 16 23 30

Saturday 1 8 15 22 29

Friday 7 14 21 28

Tuesday 4 11 18 25

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

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

sort SUBNAME LIST



Замечание
Функция sort () сортирует список LIST и возвращает отсортированный список значений. По умолчанию используется обычный лексикографический (словарный) порядок сортировки. Его можно изменить при помощи аргумента SUBNAME, представляющего собой имя подпрограммы. Подпрограмма SUBNAME возвращает целое число, определяющее порядок следования элементов списка. Любая процедура сортировки состоит из последовательности сравнений двух величин. Для того чтобы правильно задать порядок сортировки, надо представить себе SUBNAME как функцию двух аргументов. В данном случае аргументы в подпрограмму SUBNAME передаются не общим для Perl способом - через массив @_, а через переменные $а и $ь, обозначающие внутри подпрограммы соответственно первый и второй аргумент. Подпрограмму SUBNAME надо составить таким образом, чтобы она возвращала положительное целое, нуль, отрицательное целое, когда при сравнении аргумент $а назначается меньшим аргумента $ь, равным аргументу $b, большим аргумента $ь соответственно. Для этого внутри подпрограммы удобно использовать операции числового (<=>) и строкового (стр) сравнения, возвращающие значения -1,0, 1, если первый аргумент соответственно меньше второго, равен второму, больше второго.
Вместо имени подпрограммы в качестве аргумента SUBNAME может использоваться блок, определяющий пррядок сортировки.



Зададим функцию weekOrder, определяющую порядок сортировки

sub WeekOrder {

my %week=("Monday"=>0,

"Tuesday"=>1,

"Wednesday"=>2,

"Thursday"=>3,

"Friday"=>4,

"Saturday"=>5,

"Sunday"=>6) ; $week{$a}<=>$week{$b} };

Используя функцию sort () с заданным порядком сортировки

for $i (0..11) {

print "month # ", $1+1, "\n";

for $DayName (sort WeekOrder keys %{$months[$i]}) { print " $DayName @{$months[$i]{$DayName}}\n";

} • ' ' };

получим структурированный вывод календаря в виде, упорядоченном по месяцам и дням недели:

month f 1

Monday 3 10 17 24 31

Tuesday 4 11 18 25

Wednesday 5 12 19 26

Thursday 6 13 20 27

Friday 7 14 21 28

Saturday 1 8 15 22 29

Sunday 2 9 16 23 30

В качестве следующего примера построим на основе массива gmonths новую структуру, которую можно было бы назвать "хеш-массив хеш-массивов массивов", если бы такое название имело право на существование. В действительности, все просто. Речь идет о том, чтобы заменить в массиве @months числовые индексы ключами, совпадающими с названиями месяцев, и таким образом получить ассоциативный массив %months со сложной внутренней структурой (см. Рисунок 9.3).

Рис 9.3. Ассоциативный массив %months со сложной внутренней структурой

При построении хеш-массива %months воспользуемся вспомогательным хеш-массивом %OrderedMonths, который будем использовать для задания порядка сортировки:

# вспомогательный массив %OrderedMonths %OrderedMonths =( "January"=>0,

"February"=>l,

"March"=>2,

"April"=>3,

"Мау"=>4, "June"=>5, "July"=>6, "August"=>7, "September"=>8, "October"=>9, "November"=>10, "December"=>ll ); # формирование структуры for $month (sort {$OrderedMonths{$a}<=>$OrderedMonths{$b}}

keys %OrderedMonths) { $i = $OrderedMonths{$month}; $months{$month}=$months[$ i];' };



# Вывод элементов хеш-массива %months for $month (sort {$OrderedMonths{$a}<=>$OrderedMonths{$b}}

keys %OrderedMonths) { print "$month\n"; $i = $OrderedMonths{$month); for $DayName (sort WeekOrder keys %{$months{$month}}) {

print " $DayName @{$months[$i]{$DayName}}\n"; } };

В результате выполнения примера 9. 3 будет распечатан календарь на 2000 год в виде:

January

Monday 3 10 17 24 31

Tuesday 4 11 18 25

Wednesday 5 12 19.26

Thursday 6 13 20 27

Friday 7 14 21 28

Saturday 1 8 15 22 29

Sunday 2 9 16 23 30

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

@аггау = (1, 2 ,3, ("опе"=>1, "two"=>2}, \sfunc, 4, 5};

Читатель может поупражняться в построении таких структур и открыть для себя новые нюансы применения этого гибкого и мощного подхода.

В заключение несколько слов о фрагментах массивов. Для доступа к элементам массива мы имеем специальную нотацию, состоящую из префикса $, имени массива и индекса элемента в квадратных скобках, например, $аггау[7]. Если здесь вместо индекса поместить список индексов, а префикс $ заменить префиксом @, то такая запись будет обозначать фрагмент массива, состоящий из элементов с индексами из заданного списка. Подобную нотацию можно использовать в выражениях, например,



Ssubarrayl = @array[7..12]; @subarray2 = @array[3,5,7];

Массив @ subarrayi является фрагментом массива garray, состоящим из элементов со значениями индекса от 7 до 12. Массив @subarray2 является фрагментом массива @аггау, состоящим из элементов со значениями индекса 3, 5 и 7. В первом случае список индексов задан при помощи операции "диапазон", во втором случае - перечислением.

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

@april_first_week = @{'$calendar [3] } [0. . 6];

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

for $i (0..2) { .

for $j (0..$#{$calendar[$i]}) {

$quarter.l[$i] [$j] = $ calendar [$i] [$j ] ; } ' };


Использование ссылок



Использование ссылок

В данном разделе мы рассмотрим некоторые примеры, связанные с основным применением ссылок — конструированием структур данных. В качестве первой структуры построим массив массивов или двумерный массив. Для примера рассмотрим массив @calendar, содержащий календарь, например, на 2000 год. Значением элемента $caiendar[$i] [$j] является название дня недели, приходящегося на (j+l)-ft день (i+l)-ro месяца, i=(0..11),j=(0..30)(p H c. 9.1).

Рис 9.1. Структура массива @calendar



Конструктор анонимного ассоциативного массива



Конструктор анонимного ассоциативного массива

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

%hash_ref = {

'Опе'=>1,

'Two'=>2,

"Three'=>3 };

создаст анонимный хеш-массив cone'=>i, 'Two'=>2, "rhree'=>3) и присвоит переменной %hash_ref значение ссылки на этот хеш.

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

$а = $myhash{"first"}

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

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

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

sub get_hash_ref { {@_} }

В данном примере внутренняя конструкция в фигурных скобках интерпретируется как блок. Для того чтобы она интерпретировалась как ссылка на анонимный хеш, необходимо использовать функцию return или поставить перед внутренней конструкцией знак "+":

sub get_hash_ref { return {(?_} } sub get_hash_ref { +{@_} }



Конструктор анонимного массива



Конструктор анонимного массива

В рассмотренном выше примере операция "\" применялась к переменным, обладающим именами. Perl позволяет создавать ссылки на анонимные массивы при помощи специальной конструкции, использующей квадратные скобки:

$arr_ref = [1,2,3];

В результате данной операции присваивания будет создан анонимный массив с элементами (1,2,3), а переменной $arr_ref будет присвоено значение ссылки на этот массив.

Компилятор различает случаи использования квадратных скобок для создания ссылки на анонимный массив и для обращения к отдельным элементам массива, как, например, в операции присваивания $а = $myarray[2].

Замечание
Свободный синтаксис языка Perl допускает существование конструкций, смысл которых не очевиден. К рассматриваемой теме имеет отношение следующий пример. Формально выражение \($а, $Ь, $с) представляет собой анонимный массив из трех элементов ($а, $ь, $с), к которому применяется операция ссылки "\". Означает ли это, что значением выражения является ссылка на анонимный массив? Нет, это просто сокращенная запись массива, состоящего из трех элементов-ссылок (\$а, \$Ь, \$с), а для создания ссылки на анонимный массив существует единственный способ, рассмотренный выше.



Массив массивов



Массив массивов

Сформируем массив @caiendar, используя результаты предыдущего раздела.

for $i (1,3..12) { . '

for $j (1..30) {

$calendar[$i-l][$j-l] =&$f($i, $j);

}

}; • . for $i (1,3,5,7,8,10,12) {

$calendar[$i-l][30] = &$f($i, 31); }; for $j (1..28) {

$calendar[l][$j-l] = &$f(2, $j); };

# Если год високосный, то добавляется еще один элемент массива $calendar[l][28] = &$f(2,29);

Массив @caiendar состоит из 12 элементов по числу месяцев в году. Каждый элемент массива является ссылкой на другой массив, имеющий столько элементов, сколько дней в соответствующем месяце. Значениями элементов вложенных массивов являются английские названия соответствующих дней недели: "Monday", "Tuesday" и т. д.

Обращаем внимание на то, что при формировании массива ^calendar осуществляется неявное создание ссылок $caiendar [$i] и применяется компактная запись $calendar[$i] [$j] для обозначения индивидуального элемента двумерного массива, обсуждавшаяся ранее.

Содержимое массива @calendar можно вывести для просмотра при помощи следующих операторов:

for $i (0..11) {

for $j (0..$#{$calendar[$i]}) {

print $j+l,".",$i+l," is $calendar[$i][$j]\n";

} };

Напомним, что запись $#array обозначает верхнее значение индекса массива @аггау. В результате выполнения данного цикла будет выведена длинная последовательность строк вида

1.1 is Saturday 2.1 is Sunday



Операция разыменования "->"



Операция разыменования "->"

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

$<$а[0]'}[1] = 17; , ${$Ь[0]}{"опе"} = 1;

В первой строке осуществляется обращение к отдельному элементу массива массивов, во второй — к отдельному элементу массива хеш-массивов.

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

Несколько упростить запись и улучшить наглядность можно, используя операцию "->" ("стрелка").

Аргумент слева от стрелки может быть любым выражением, возвращающим ссылку на массив или хеш-массив.

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

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

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

$а[0]->[1] = 17; $Ь[0]->{"опе"} = 1;

Конструкция $а[0]->[1] обозначает второй элемент массива, определяемого ссылкой $а[0]. Конструкция $ь[0]->{"опе"} обозначает элемент, соответствующий ключу "one" хеш-массива, задаваемого ссылкой $Ь[0].


Вообще, если $arr_ref — ссылка на массив, то $arr_ref->[$i] обозначает i- й элемент этого массива. Если $hash_ref — ссылка на хеш-массив, то $hash_ref->{"key"} обозначает элемент этого хеш-массива, соответствующий ключу "key".

Если бы в последнем примере вместо именованных массивов @а и @ь использовались ссылки на массив, например, $ref_a и $ref_b, то соответствующие операции присваивания имели вид

$ref_a->[0]->[l] = 17; $ref_b->[0]->{"one"} = 1;

Здесь мы снова сталкиваемся с неявным созданием ссылок. По контексту элемент массива $ref_a->[0] должен быть ссылкой на массив, а $ref_b->[0] — ссылкой на хеш-массив. Обе ссылки ранее не были определены, но их существование предполагается в контексте выражения. Данные ссылки будут созданы автоматически.

Операция "->" позволяет для обращения к отдельному элементу составного массива или хеш-массива использовать более простые выражения, например,

$a[$i]->[$j]->[$k] вместо ${${$a[$i]}[$j]}[$k], $b[$i]->{"key"}->[$j] вместо ${${$b[0]}{"key"}}[$j]

и т. д.

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

и хеш-массивов, можно опустить символы "->" между квадратными и/или фигурными скобками, содержащими индексы или ключи элементов. Предыдущие выражения примут еще более простой вид: $a[$i][$j][.$k] и $b[$i] {"key"} [$j]соответственно.


Операция ссылки "\"



Операция ссылки "\"

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

$а=\5;

$scal_ref=$a; $arr_ref=\@myarray; $hash_ref=\%myhash; $funC_ref=\ anyfunc ;

В данном примере скалярной переменной $а присваивается значение ссылки на константу 5, т. е. адрес ячейки памяти, в которой хранится число 5. Адрес самой переменной $а хранится в переменной $scai_ref. Переменные $arr_ref, $hash_ref, $func_ref хранят адреса ячеек памяти, являющихся начальными точками размещения соответственно массива gmyarray, хеш-массива %myhash и кода функции myfunc. К переменным, содержащим ссылки, можно применять все операции допустимые для скалярных величин. Их можно присваивать другим переменным, складывать, умножать, делить, выводить на экран и т. д. За исключением присваивания применение подобных операций к ссылкам, как правило, смысла не имеет. Например, вывод рассмотренных выше переменных

print $scal_ref, "\n", $arr_ref,"\n", $hash_ref, "\n", $func_ref, "\n"; будет состоять иа строк, подобных следующим:

SCALAR(Ox9b8994) ARRAY(Ох9Ь8а18) HASH(Ox9b8a60) CODE(Ox9b3dl4)

Здесь каждая строка содержит слово, обозначающее тип ссылки и ее значение — адрес в виде шестнадцатеричного числа.

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



Разыменование ссылок



Разыменование ссылок

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

Разыменование простой скалярной переменной

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

1 $а = $$scal_ref;

2 @b = @$arr_ref;

3 %с = %$hash_ref;

4 &f - &$code_ref;

5 $$d[0] = 7;

6 $$h{"one"} = 1;

Здесь предполагается, что переменная $scal_ref содержит ссылку на скалярную величину, $arr_ref - ссылку на массив, $hash_ref - ссылку на ассоциативный массив, $code_ref — ссылку на подпрограмму.

Рассмотрим подробно пятую строку.

Во-первых, следует определить, что является ссылкой: скалярная переменная $d, указывающая на анонимный массив, или элемент $d[0] массива @d. Ответ содержится в сформулированном выше правиле разыменования. Поскольку в строке 5 применяется именно оно, то индексированная переменная $d[0] ссылкой быть не может. Ссылкой является простая скалярная переменная $d, которая используется в качестве имени. Из контекста видно, что на ее месте должно стоять имя массива, следовательно, $d является ссылкой на анонимный массив

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

Все сказанное можно отнести к шестой строке с единственным отличием: вместо ссылки на анонимный массив здесь фигурирует ссылка $ь на анонимный хеш-массив.



Символические ссылки



Символические ссылки

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

1 $name_a = "a";

2 $$name_a = 17;

3 @$name_a = (1,2,3);

4 $name_a->[3] = 4;

5 %$name_a = ("one"=>l, "two"=>2, "three"=>3);

6 &$name_a () ;

В строке 2 переменной $а присваивается значение 17. В строке 3 определяется и инициализируется массив @а с элементами (1,2,3). В строке 4 к массиву @а добавляется четвертый элемент со значением 4. В строке 5 инициализируется хеш-массив %а. В строке 6 осуществляется вызов функции а о (предположим, что такая функция существует).

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

(О пакетах и таблицах символов описано в )

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

(О лексических переменных и применении функции ту () рассказывается в .) Для иллюстрации рассмотрим следующий пример:

$name_a="a"; { my $a="Hello!";

print $$name_a; };

Здесь переменная $name_a используется в качестве символической ссылки на переменную $а, и можно предположить, что результатом выполнения этой последовательности будет вывод строки "Hello!". В действительности переменная $а является невидимой для символической ссылки, поскольку она определена как лексическая переменная внутри блока {...}. Поэтому в результате выполнения данного фрагмента будет напечатана пустая строка.


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

1 $а[0]="b";

2 #..............

3 $b[0]=2;

4 $b[1]=2;

5 #..............

6 $а[0] [0]=0;

7 #..............

8 $prod = $Ь[0]*Ь[1];

переменная $prod получит значение 4. Но это не так. В строке 6 мы осуществляем присваивание, рассчитывая на то, что будет применен известный механизм неявного создания жесткой ссылки $а [0]. Мы "забыли" о том, что значение $а[0] уже использовалось в строке 1 и, следовательно, в строке 6 элемент массива $а[0] является символической ссылкой, указывающей на переменную с именем "Ь". Это имя будет подставлено вместо символической ссылки, в результате чего элемент массива ь[0] получит новое значение 0. В итоге значение переменной $prod будет равно 0.

Во избежание подобных ошибок можно запретить использование символических ссылок в пределах текущего блока при помощи директивы

use strict 'refs';

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

no strict 'refs 1 ;

(Директивы use, no рассматриваются .)

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

1 use strict 'refs ';

2 ${name};

3 ${"name"};

вторая строка представляет собой просто значение переменной $name, а третья строка интерпретируется как символическая ссылка, указывающая на переменную $name и вследствие применения директивы use strict 'refs 1 вызывает сообщение об ошибке вида

Can't use string ("name") as a SCALAR ref while "strict refs" in use


Создание ссылок



Создание ссылок

Существует несколько способов порождения ссылок. Рассмотрим их в порядке следования от чаще употребляемых синтаксических конструкций к более редким.



Добавьте текст, содержащий последовательность операций,



Упражнения

1. Добавьте текст, содержащий последовательность операций, которые надо применить к переменной $b, чтобы получить значение переменной $а
$а = 7;
$b = \\\\$а;
В упражнениях 2-4 используйте результаты, полученные в примерах 9.1-9.3.
2. Вывести на экран все дни 2000 года, приходящиеся на воскресенья. Вывод должен содержать строку-заголовок, например, "All 2000 1 Sundays
are: ", И ПО ОДНОЙ строке на каждый месяц года В виде: <название месяца^ <дни месяцах
3. Вывести на экран календарь на второй квартал года в виде
<название месяца> <Monday> <дни месяца>
<Sunday> <дни месяца>
4. Вывести на экран календарь на первую неделю любого месяца. Вывод должен содержать строку-заголовок и по одной строке на каждый день недели в виде
<название месяца> <день месяца> <название дня недели>
5. Треугольником Паскаля называется следующая бесконечная таблица чисел:
Рис 9.4. Треугольник Паскаля

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

Виды ссылок



Виды ссылок

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

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

Ссылка в языке Perl — это обычная скалярная величина, в которой хранится некоторый адрес в оперативной памяти.

Ссылка в языке Perl может указывать на любой фрагмент данных. Фрагментом данных здесь мы называем любую переменную, константу или часть кода программы. Тип ссылки определяется типом данных, на которые она указывает. Таким образом, существуют следующие типы ссылок: ссылка на скалярную величину, ссылка на массив, ссылка на хеш, ссылка на функцию. Нельзя использовать ссылку одного типа там, где контекст выражения требует присутствия ссылки другого типа, например, использовать ссылку на массив вместо ссылки на хеш-массив. Поскольку сама ссылка является скалярной величиной, то, естественно, существует ссылка на ссылку. Имеется еще один вид ссылок, который мы в свое время рассмотрим подробнее. Это ссылки на данные типа typegiob. Тип typeglob является внутренним типом языка Perl, который служит для обозначения переменных разных типов, имеющих общее имя. Принадлежность к типу typeglob, обозначается префиксом "*". Например, запись *abc обозначает всю совокупность, а также любую из следующих переменных: скаляр $abc, массив @аЬс, хеш %abc. В данной главе мы не будем рассматривать этот вид ссылок. Отметим только, что он лежит в основе механизма экспорта/импорта модулей.


(Работа с модулями обсуждается в .)

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

REF ссылка на ссылку;

SCALAR ссылка на скаляр;

ARRAY ссылка на массив;

HASH ссылка на ассоциативный массив;

CODE ссылка на подпрограмму;

GLOB ссылка на переменную типа typegiob.

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

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



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

Жесткие и символические ссылки в языке Perl напоминают одноименные понятия в файловой системе UNIX. Жесткая ссылка в Perl — это скалярная величина, которая содержит адрес некоторой области памяти, являющейся носителем данных. Сами данные будем называть субъектом ссылки. Символическая ссылка — это скалярная переменная, которая содержит имя другой скалярной переменной. "Истинной" ссылкой является жесткая ссылка. Она создается одним из способов, перечисленных в разделе 9.1. Внутренняя организация жестких ссылок такова, что для каждого субъекта ссылки поддерживается счетчик ссылок. Область памяти, занимаемая субъектом ссылки, освобождается, когда значение счетчика ссылок становится равным нулю. В большинстве случаев мы имеем дело с жесткими ссылками, а использование символических ссылок будем специально оговаривать.

Главным применением ссылок в языке Perl является создание сложных структур данных. Мы знаем, что основными типами данных в Perl являются скаляры, массивы и хеш-массивы. Многомерные массивы или более сложные структуры данных, аналогичные записям языка Pascal или структурам языка С, в языке Perl отсутствуют. В более ранних версиях языка отсутствовала и возможность создания сложных структур данных на основе имеющихся типов. Такая возможность появилась в версии Perl 5.0 вместе с появлением ссылок. В практике программирования часто встречаются данные, которые удобно представлять, например, в виде двумерных массивов, реже трехмерных массивов или других подобных структур. Двумерный массив можно рассматривать как одномерный массив, элементами которого являются также одномерные массивы. Возможность такого представления есть во многих языках программирования. В языке Perl невозможно создать массив с массивами в качестве элементов. То же самое относится и к хешам. Элементом массива или хеша может быть только скалярная величина. Поскольку ссылка является скалярной величиной, можно создать массив или хеш, элементами которого являются ссылки на массивы или хеши, и таким образом получить структуру, которую можно использовать как массив маесивов (соответственно массив хешей, хеш массивов, хеш хешей). Благодаря ссылкам можно на основе массивов и хешей конструировать структуры данных произвольной сложности.

Помимо создания сложных структур данных, ссылки активно применяются для работы с объектами. Слово "объект" здесь обозначает основное понятие объектно-ориентированного подхода к программированию. (Объекты рассматриваются .)

В этой главе мы рассмотрим основное применение ссылок как средства для конструирования структур данных. Другие применения будут рассмотрены в соответствующих главах.


b после выполнения следующих операторов:



Вопросы для самоконтроля

1. Что такое ссылка?
2. Объясните разницу между жесткой и символической ссылкой.
3. Все ли корректно в следующем фрагменте
$href = \%hash; $$href[0] = 17;
4. Каким будет значение переменной $ b после выполнения следующих операторов:
$а = 1;
$b = ref $a;
5. Что обозначает каждое из выражений:

$$а[0]; ${$а[0]}; $а->[0]; '$а[0];
6. Приведите пример неявного создания ссылки.
7. $arr_ref — ссылка на анонимный массив. Как с ее помощью обратиться к третьему элементу этого массива? Напишите выражение.
8. Что такое "замыкание"?


Замыкания



Замыкания

Для заполнения массива ecaiendar нам потребуется функция, которая по заданному году, месяцу и дню месяца вычисляет соответствующий день недели. Читатель может сам написать свой вариант функции, может быть, более изящный. Мы предлагаем наш вариант (пример 9.1) с основной целью: рассказать об одном интересном свойстве анонимных подпрограмм. Кроме того, он правильно работает для любого года нашей эры.

Вычисление дня недели основано на том, что:

1 января 1 года нашей эры было понедельником;

каждый год, номер которого делится на 4, является високосным, за исключением тех номеров, которые делятся на 100 и не делятся на 4.

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

sub GetDay (

my $year = shift;

my @days = (Q,31,59,90,120,151,181,212,243,273,304,334);

my @week = ("Monday","Tuesday","Wednesday","Thursday",

"Friday","Saturday","Sunday"); my $previous_years_days = ($year -1 ).*365 + int (($year-l) /4)

- int(($year-l)/100) + int(($year-l)/400); return sub { my ($month, $day)=@_;

my $n « $previous_years_days + $days[$month-l] + $day -1; $n++ if ($year%4 == 0 and $year%100 != 0 or

$year%400 == 0 and $month > 2) ; return $week[$n%7]; } };.

Аргументами функции GetDay о являются номер года, номер месяца и номер дня месяца. Внутри тела функции им соответствуют переменные $уеаг, $month и $day. Функция подсчитывает число дней $п, прошедших с 1 января 1 года. Остаток от деления этого числа на 7 — $п%7 — определяет день недели как элемент массива $week[$n%7].

Необходимые пояснения к тексту

Для передачи параметров в подпрограмму используется предопределенный массив @_. Встроенная функция shift•() без параметров, вызванная внутри подпрограммы, возвращает первый элемент массива @_ и осуществляет сдвиг всего массива влево, так, что первый элемент пропадает, второй становится первым и т.д. Элемент массива $days[$l] равен суммарному числу дней в первых i месяцах не високосного года, i = (0..11). В переменной $previous_years_days запоминается вычисленное значение общего количества дней, прошедших с 1 января 1 года до начала заданного года.


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

Если бы функция GetDay (} возвращала день недели, то для заполнения календаря на 2000 год, к ней необходимо было бы сделать 366 обращений, вычисляя каждый раз значение переменной $previous_years_days. Для каждого года это значение постоянно, поэтому его достаточно вычислить всего один, а не 366 раз.

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

Анонимные процедуры в Perl обладают тем свойством, что по отношению к лексическим переменным, объявленным при помощи функции ту (), выступают в роли замыканий. Иными словами, если определить анонимную функцию в некоторый момент времени при некоторых значениях лексических переменных, то в дальнейшем при вызове этой функции ей будут доступны значения этих лексических переменных, существовавшие на момент ее определения.

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

$f = GetDay(2000,1,1);

Во время обращения к GetDay о было сформировано вычислительное окружение анонимной функции, на которую сейчас указывает переменная $f. Вычислительное окружение включает, в том числе, и переменную $previous_years_days с ее значением. Обратите внимание, что внутри анонимной функции значение этой переменной не вычисляется. В дальнейшем для заполнения календаря мы будем вызывать анонимную функцию через ссылку $f.




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



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

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

ФуНКЦИЯ chop () chop [list]

удаляет последний символ из всех элементов списка list, возвращает последний удаленный символ. Список может состоять из одной строки. Если аргумент отсутствует, операция удаления последнего символа применяется к встроенной переменной $_. Обычно применяется для удаления завершающего символа перевода строки, остающегося при считывании строки из входного файла.

ФуНКЦИЯ length() length EXPR

возвращает длину скалярной величины EXPR в байтах.

#!/usr/bin/perl \^_____,.

$input = <STDIN>;

$Len = length($input);

print "Строка до удаления последнего символа: $input\n";

print "Длина строки до удаления последнего символа: $Len\n";

$Chopped = chop($input};

$Len = length($input);

print "Строка после удаления последнего символа: $input\n";

print "Длина строки после удаления последнего символа: $Len\n";

print "Удаленный символ: <$Chopped>\n";

Если после запуска данного скрипта ввести строку "qwerty", то вывод будет иметь вид:

qwerty

Строка до удаления последнего символа: qwerty

Длина строки до удаления последнего символа: 7 Строка после удаления последнего символа: qwerty Длина строки после удаления последнего символа: 6 Удаленный символ: < >

Последним символом, удаленным функцией chop (), является символ новой строки, сохраненный в переменной $chopped. При выводе он вызывает переход на следующую строку, поэтому в данном выводе третья строка — пустая. В последней операции print вывод осуществляется в две строки, так как переменная $chopped содержит символ новой строки.

Функции lc(), uc(), Icfirst(), ucfirstO

предназначены для преобразования строчных букв в прописные и наоборот.

ФуНКЦИЯ 1с EXPR

возвращает выражение, полученное из выражения EXPR преобразованием всех символов в строчные.


ФуНКЦИЯ UC EXPR

возвращает выражение, полученное из выражения EXPR преобразованием всех символов в прописные.

ФуНКЦИЯ Icfirst EXPR

возвращает выражение, полученное из выражения EXPR преобразованием первого символа в строчный.

ФуНКЦИИЯ ucfirst EXPR

возвращает выражение, полученное из выражения EXPR преобразованием первого символа в прописной.

#!/usr/bin/perl

print "\n Ф-ция uc() преобразует ",$s="upper case"," в ",uc $s;

print "\n Ф-ция ucfirst() преобразует ",$s="uPPER CASE"," в ",ucfirst $s;

print "\n Ф-ция 1с() преобразует ", $s="LOWER CASE"," в ",1с $s;

print "\n ф-ция IcfirstO преобразует ",$s="Lower case"," в ",lcfirst $s;

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

Ф-ция uc() преобразует upper case в UPPER CASE Ф-ция ucfirst() преобразует UPPER CASE в UPPER CASE Ф-ция 1с() преобразует LOWER CASE в lower case Ф-ция IcfirstO преобразует Lower case в lower case

ФуНКЦИЯ join() join EXPR, LIST

объединяет отдельные строки списка LIST в одну, используя в качестве разделителя строк значение выражения EXPR, и возвращает эту строку.

Функция split ()

split [/PATTERN/[, EXPR[, LIMIT]]]

разбивает строку EXPR на отдельные строки, используя в качестве разделителя образец, задаваемый регулярным выражением PATTERN. В списковом контексте возвращает массив полученных строк, в скалярном контексте — их число. Если функция split о вызывается в скалярном контексте, выделяемые строки помещаются в предопределенный массив @_. Об этом не следует забывать, так как массив §_ обычно используется для передачи параметров в подпрограмму, и обращение к функции split о неявно в скалярном контексте эти параметры уничтожит.

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



Если параметр EXPR опущен, разбивается строка $_. Если отсутствует также и параметр PATTERN, то в качестве разделителей полей используются пробельные символы после пропуска всех начальных пробельных символов (что соответствует заданию образца в виде /\s+/). К пробельным символам относятся пробел (space), символ табуляции ЛаЬ), возврат каретки (carriage feturn), символ перевода строки (line feed) и^симврл перевода страницы (form feed). .



Замечание
Предопределенная глобальная переменная $_ служит для обозначения используемой по умолчанию области ввода и поиска по образцу. Обычно мы осуществляем ввод при помощи операции "0" ("ромб"). Внутри угловых скобок о может стоять дескриптор файла ввода, например, <STDIN>. Если дескриптор файла отсутствует, то в качестве файлов ввода используются файлы, переданные программе Perl в качестве аргументов командной строки. Пусть, например, программа содержится в файле script. pi.
#!/usr/bin/perl while (<>) {

print; };

Программа вызвана следующим образом script.pl filel file2 file3

Тогда операция о будет считывать строки сначала из файла filel, затем из файла file2 и, наконец, из файла file3. Если в командной строке файлы не указаны, то в качестве файла ввода будет использован стандартный ввод.

Только в случае, когда условное выражение оператора while состоит из единственной операции "ромб", вводимое значение автоматически присваивается предопределенной переменной $_. Вот что означают слова о том, что переменная $_ применяется для обозначения используемой по умолчанию области ввода. Аналогично обстоит дело с поиском по образцу.

I!/usr/bin/perl while (<>} {

chop;

print "Число полей во входной строке '$_' равно ", $n=split;

print "ХпВходная строка разбита на строки:\n";

foreach $i (@_) {

print $i . "\n"; }

print "Объединение списка строк в одну строку через ' +':\n";

$joined = join "+", @_;

print "$joined\n"; }



В результате применения операции ввода О внутри условного выражения оператора while вводимая строка будет присвоена переменной $_. Функция chop о без параметров применяется к переменной $_. В операции print вторым операндом является выражение $n=spiit, в котором функция split вызывается в скалярном контексте и без параметров. Поэтому она применяется по умолчанию к переменной $_. В качестве разделителей полей по умолчанию используется множество пробельных символов, а результат помещается в масссив @_. Затем к массиву @_ применяется функция joint), объединяющая строки-элементы массива в одну строку.

Если ввести строку "one two three", то вывод будет иметь вид:

one two three

Число полей во входной строке 'one two three' равно 3

Входная строка разбита на строки:

one

two

three

Объединение списка строк в одну строку через '+':

one+two+three

ФУНКЦИЯ index()

index STR, SUBSTR[, POSITION]

находит первое, начиная с позиции POSITION, вхождение подстроки SUBSTR в строку STR, и возвращает найденную позицию. Если параметр POSITION не задан, по умолчанию принимается значение POSITION = $[. Если подстрока SUBSTR не найдена, возвращается значение $ [ - 1.



Замечание
Предопределенная переменная $ [ содержит индекс первого элемента в массиве и первого элемента в строке. По умолчанию ее значение равно 0. В принципе его можно изменить, но делать это не рекомендуется. Таким образом, по умолчанию значение параметра POSITION полагается равным 0, а функция index возвращает-1, если не найдена подстрока SUBSTR.
ФУНКЦИЯ rindex{}

rindex STR, SUBSTR, POSITION

находит последнее, ограниченное справа позицией POSITION, вхождение подстроки SUBSTR в строку STR, и возвращает найденную позицию. Если подстрока SUBSTR не найдена, возвращается значение $ [ - i.

f!/bin/peri • ,

$STR = "Этот безумный, безумный, безумный, безумный мир!";

$ SUBSTR = "безумный"; ^~— --''

$POS = 7;

print "Индекс первого символа строки по умолчанию равен $[\n";



print "Позиция первого вхождения подстроки '$SUBSTR'

в строку '$STR' = ",index($STR, $SUBSTR), "\n"; print "Позиция первого после позиции $POS вхождения подстроки '$SUBSTR'

в строку '$STR' = ",index($STR, $SUBSTR, $POS), "\n"; print "Позиция последнего вхождения подстроки '$SUBSTR'

в строку '$STR 1 = ",rindex($STR, $SUBSTR), "\n"; print "Позиция последнего перед позицией $POS вхождения подстроки '$SUBSTR'

в строку '$STR' = ",rindex($STR, $SUBSTR, $POS), "\n";

$[=2;

print " ХпИндекс первого символа строки по умолчанию изменен на $[\n"; print "Позиция первого вхождения подстроки '$SUBSTR'

в строку '$STR' = ",index($STR, $SUBSTR), "\n"; print "Позиция первого после позиции $POS вхождения подстроки '$SUBSTR'

в строку '$STR' = ",index($STR, $SUBSTR, $POS), "\n";

print "Позиция последнего вхождения подстроки '$SUBSTR' в строку '$STR' = ",rindex($STR, $SUBSTR), "\n";

print "Позиция последнего перед позицией $POS вхождения подстроки '$SUBSTR' в строку '$STR' = ",rindex($STR, $SUBSTR, $POS), "\n";

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

Индекс первого символа строки по умолчанию равен О Позиция первого вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 5 Позиция первого после позиции 7 вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 15 Позиция последнего вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, .безумный мир!' = 35 Позиция последнего перед позицией 7 вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 5

Индекс первого символа строки по умолчанию изменен на 2 Позиция первого вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 7 Позиция первого после позиции 7 вхождения подстроки 'безумный'



в строку ' Этот безумный, безумный, безумный, безумный мир!' = 7 Позиция последнего вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 37 Позиция последнего перед позицией 7 вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 7

Функция substr ()

substr EXPR, OFFSET [,LENGTH [,REPLACEMENT ]]

извлекает из выражения EXPR подстроку и возвращает ее. Возвращаемая подстрока состоит из LENGTH символов, расположенных справа от позиции OFFSET. Если параметр LENGTH опущен, возвращается вся оставшаяся часть выражения EXPR. Если параметр LENGTH отрицательный, его абсолютное значение задает количество символов от конца строки, не включаемых в результирующую подстроку. Если параметр OFFSET имеет отрицательное значение, смещение отсчитывается с конца строки. Функция substr о может стоять в левой части операции присваивания. Например, в результате выполнения операторов

$Str = "Язык Pascal"; substr($Str, 5,6) = "Perl";

переменная $str получит значение "язык Peri". Тот же результат будет достигнут, если указать параметр REPLACEMENT, значение которого будет подставлено в EXPR вместо выделенной подстроки. Сама подстрока в этом случае возвращается в качестве значения функции substr ().

#!/bin/peri

# Исходная строка

$Str = "Карл у Клары украл кораллы";

$0ffset = 13;

print "Исходная строка:'$Str'\n";

# Смещение 13, длина подстроки не задана

$Substr = substr $Str, $Offset;

print "Смещение $0ffset, длина подстроки не задана, результат:\n";

print "$Substr\n";

# Смещение 13, длина подстроки +5

$Substr = substr $Str, $0ffset, 5;

print "Смещение $0ffset, длина подстроки +5, результат:\n";

print "$Substr\n"; /

# Смещение 13, длина подстроки -1 \

$Substr = substr $Str, $0ffset, -1;

print "Смещение $0ffset, длина подстроки -1, результат:\n";

print "$Substr\n";

# Отрицательное смещение -7, длина подстроки +7



$0ffset = -7;

$Substr = substr $Str, $Offset, 7;

print "Отрицательное смещение $Offset, длина подстроки +7, результат:\n";

print "$Substr\n";

f Отрицательное смещение -7, длина подстроки -1

$Substr = substr $Str, $0ffset, -1;

print "Отрицательное смещение $Offset, длина подстроки -1, результат:\n";

print "$Substr\n";

t Замена подстроки

$Repl = "бокалы";

$Substr = substr $Str,$Offset,7,$Repl;

print "В строке '$Str' слово '$Repl' заменяет слово '$Substr'\n";

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

Исходная строка:'Карл у Клары украл кораллы'

Смещение 13, длина подстроки не задана, результат:

украл кораллы

Смещение 13, длина подстроки +5, результат:

украл

Смещение 13, длина подстроки -1, результат:

украл коралл

Отрицательное смещение -7, длина подстроки +7, результат:

кораллы

Отрицательное смещение -7, длина подстроки -1, результат:

коралл

В строке 'Карл у Клары украл бокалы' слово 'бокалы' заменяет слово 'кораллы'

Функция eval () eval EXPR

рассматривает параметр EXPR как текст Peri-программы, компилирует его и, если не обнаруживает ошибок, выполняет в текущем вычислительном окружении. Если параметр EXPR отсутствует, вместо него по умолчанию используется глобальная переменная $_. Компиляция программного кода EXPR осуществляется при каждом вызове функции eval () во время выполнения основной программы. Если выполнение мини-программы EXPR завершается успешно, функция eval о возвращает значение последнего выражения, вычисленного внутри EXPR. Если код EXPR содержит синтаксические ошибки, или обращение к функции die (}, или возникла ошибка во время выполнения EXPR, то в специальную переменную $@ помещается сообщение об ошибке, а функция eval () возвращает неопределенное значение.

Замечание

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



В следующем примере функция eval о применяется для перехватывания ошибки, связанной с делением на 0 при вычислении функции ctg(x). Используются встроенные функции sin, cos и warn. Последняя функция осуществляет вывод сообщения, задаваемого ее аргументом, на стандартное устройство вывода ошибок STDERR.

#!/bin/peri

$fi = 0.314159265358979;

$f = '$ctg = cos($x)/sin($x)

for $i (0..10) {

$x = $i*$fi;

eval $f;

print "x= $x ctg(x) = $ctg\n" if defined $ctg;

warn "x= $x ", $@ if not defined $ctg; };

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

х= 0 Illegal division by zero at (eval 1) line 1. x= 0.314159265358979 ctg(x) =3.07768353717526 x= 0.628318530717958 ctg(x) = 1.37638192047118



Замечание
Иногда бывает полезно искусственно вызвать исключительную ситуацию. Для этого можно воспользоваться функцией die () LIST. Назначение функции die () — генерировать исключения. Если функция die () вызывается в основной программе вне функции eval (), то она осуществляет аварийное завершение основной программы и выводит сообщение об ошибке LIST на стандарт* ное устройство вывода ошибок STDERR. Если она вызывается внутри функции eval (), то осуществляет аварийное завершение eval () и помещает сообщение об ошибке в специальную переменную $@.
Функция pos()

pos [$SCALAR]

возвращает позицию, в которой завершился последний глобальный поиск $scALAR=~m/.../g, осуществленный в строке, задаваемой переменной $SCALAR. Возвращаемое значение равно числу length($') + length($&). Следующий глобальный поиск m/.../g в данной строке начнется именно с этой позиции.

Если аргумент $ SCALAR отсутствует, возвращается позиция завершения последнего глобального поиска, осуществленного в строке $_.

$words = "one two three four"; while ($words =~ m/\w+/g) {

print "pos=",pos($words)," length(\$~)=",length($'),

" length(\$s)=",length($s),"\n"; }

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



pos=3 length($~}=0 length($&)=3

pos=7 length{$')=4 length($&)=3

pos=13 length($~)=8 length($&)=5

pos=18 length($')=14 length($&)=4

Функцию pos () можно использовать в левой части операции присваивания для изменения начальной позиции следующего поиска:

I изменение начальной позиции для последующего поиска

$words = "one two three four";

pos $words = 4;

while ($words =~ m/\w+/g) {

print pos $words, "\n"; }

В последнем случае поиск слов начнется со второго слова, и будут выведены номера позиций 7, 13 и 18.

ФУНКЦИЯ quotemeta () quotemeta [EXPR]

возвращает строку EXPR, в которой все символы, кроме алфавитно-цифровых символов и символа подчеркивания "_", экранированы символом "\". Например, в результате выполнения

print quotemeta "*****", "\n";

будет выведена строка

\*\*\*\*\*

Если аргумент EXPR отсутствует, вместо него используется переменная $_.


Операции с регулярными выражениями



Операции с регулярными выражениями

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



Операция поиска



Операция поиска

m/PATTERN/cgimosx

Операция поиска HI/PATTERN/ осуществляет поиск образца PATTERN. Результатом операции является значение 1 (ИСТИНА) или пустая строка"

(ЛОЖЬ). По умолчанию поиск осуществляется в строке, содержащейся в специальной переменной $_. Можно назначить другую строку для поиска в ней заданного образца при помощи операций связывания =~ или ! ~:

$var =~ m/PATTERN/cgimosx

В результате последней операции поиск образца PATTERN будет осуществляться в строке, задаваемой переменной $var. Если в правой части операции связывания стоит операция поиска т//, то в левой части может находиться не обязательно переменная, а любое строковое выражение.

Операция ! ~ отличается от операции =~ тем, что возвращает противоположное логическое значение. Например, в результате поиска в строке "aaabbbccc" образца /b+/Г

$s="aaabbbccc" =~ m/b+/; $s="aaabbbccc" !~ m/b+/;,

в обоих случаях будет найден фрагмент ььь. Но в первом случае возвращаемое значение, сохраненное в переменной $s, будет равно 1 (ИСТИНА), а во втором случае — пустой строке " (ЛОЖЬ).

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

Флаги cgimosx модифицируют выполнение операции поиска. Флаги imsx имеют тот же смысл, что и в рассмотренном выше случае конструкции расширенного регулярного выражения (?imsx-imsx) .

i — поиск без учета регистра;

m — трактуется как мульти-строка, состоящая из нескольких строк, разделенных символом новой;

s — строка трактуется как одна строка, в этом случае метасимволу "." со-, ответствует любой одиночный символ, включая символ новой строки;

х — разрешается использовать в образцах пробелы и комментарии.

стальные флаги выполняют следующие функции.

g — Задает глобальный поиск образца в заданной строке. Это означает, что будут найдены все фрагменты текста, удовлетворяющие образцу, а не только первый из них, как имеет место по умолчанию. Возвращаемое значение зависит от контекста. Если в составе образца имеются подоб-разцы, заключенные в скобки (), то в контексте массива для каждого заключенного в скобки подобразца возвращается список всех найденных фрагментов. Если в составе образца нет подобразцов, заключенных в скобки, то в контексте массива возвращается список всех найденных фрагментов, удовлетворяющих образцу. В скалярном контексте каждая операция m//g осуществляет поиск следующего фрагмента, удовлетворяющего образцу, возвращая значение 1 (ИСТИНА), если он найден, и пустую строку ", если не найден. Позиция строки, в которой завершился последний поиск образца при установленном флаге g, может быть получена при помощи встроенной функции роз о (см. ниже). Обычно при неудачном поиске начальная позиция поиска восстанавливается в начало строки. Флаг с отменяет восстановление начальной позиции поиска при неудачном поиске образца.


Рассмотрим следующий скрипт.

$str="abaabbaaabbbaaaabbbb";

tt контекст массива, нет подобразцов в скобках

@result=$str =~m/a+b+/g;

print "контекст массива, нет конструкций в скобках:\n";

print "\@result=@result\n";

# контекст массива, есть конструкции в скобках, задающие обратные ссылки

@result=$Str =~m/(a+)(b+)/g;

print "контекст массива, есть конструкции в скобках:\n";

print "\@result=@result\n";

# скалярный контекст

print "скалярный контекст:\n";

while ($result=$str =~m/(a+)(b+)/g) {

print "result=$result, current match is $&, position=",pos($str),"\n"; }

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

контекст массива, нет конструкций в скобках:

@result=ab aabb aaabbb aaaabbbb

контекст массива, есть конструкции в скобках:

@result=a b aa bb ааа bbb aaaa bbbb

скалярный контекст:

result=l, current match is ab, position=2

result=l, current match is aabb, position=6

result=l, current match is 'aaabbb, position=12

result=l, current match is aaaabbbb, position=20

HI с — Используется совместно с флагом g. Отменяет восстановление начальной позиции поиска при неудачном поиске образца.

Рассмотрим скрипт

$str="abaabbaaabbbaaaabbbb";

while ($result=$str =~m/(a+)(b+)/g) {

print "result=$result, current match is $&, position=",pos($str),"\n";

} • print "last position=", pos($str), "\n";

Здесь поиск образца /а+ь+/ в строке $str осуществляется в цикле до первой неудачи. При последнем (неудачном) поиске начальная позиция поиска по умолчанию устанавливается в начало строки, в этом случае вывод имеет вид:

result=l, current match is ab, position=2 result=l, current match is aabb, position=6 result=l, current match is aaabbb, position=12 result=l, current match is aaaabbbb, position=20 last position=

Если глобальный поиск осуществлять при установленном флаге с:

while ($result=$str =~m/ (a+) (b+)/gc) {

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



result=l, current match is ab r position=2 result=l, current match is aabb, position=6 result=l, current match is aaabbb, position=12 result=l, current match is aaaabbbb, position=20 last position=20

При задании образца для глобального поиска m//g можно использовать ме-тапоследовательность \с, представляющую точку, в которой закончился последний поиск m//g. Например, в результате выполнения скрипта .

^^х

$str="l) abc 2) aabbcc 3) aaabbbccc 4) aaaabbbbcccc";

$str=~m/3\)\s+/g; \

! $str=~m/\Ga+/; ,'

сначала по образцу будет найден фрагмент "3)", а затем фрагмент, удовлетворяющий образцу /а+/ и расположенный сразу за точкой, в которой завершился последний поиск. Этим фрагментом является "ааа".

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

@pattnlist=("a+", "Ы-", "с+", "d+") ; foreach $pattn (@pattnlist) (

$line = <STDIN>; $line =~ m/$pattn/o;

print "pattn=$pattn \$&= $&\n"; }

Массив gpattniist содержит список образцов "a+", "b+", "с+" и "d+". В цикле по элементам этого списка в переменную $iine считывается очередная строка из стандартного ввода. В ней осуществляется поиск по образцу, совпадающему с текущим элементом списка. Поскольку использован флаг о, подстановка значений в образце /$pattn/ будет осуществлена один раз за время жизни данной Peri-программы, т. е. в качестве образца на каждом шаге цикла будет использовано выражение "а+". Если операцию поиска осуществлять без флага о:

$line =~ m/$pattn/,

то в качестве образца будут последовательно использованы все элементы списка "а+", н b+", "с+" и "d+".

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

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

Если в качестве ограничителя используется символ "?": ?PATTERN?, то при применении операции поиска находится только одно соответствие. Например, в результате выполнения скрипта

$str="abaabbaaabbbaaaabbbb";

while ($result = $str =~ m?a+b+?g) (

print "result=$result, current match is $&, position=", pos($str),"\n";

}

будет найдено только первое соответствие образцу:

result=l, current match is ab, position=2




Операция транслитерации



Операция транслитерации

tr/SEARCHLIST/REPLACEMENTLIST/cds

Преобразует каждый символ из списка поиска SEARCHLIST в соответствующий символ из списка замены REPLACEMENTLIST и возвращает число преобразованных символов. По умолчанию преобразования осуществляются в строке, задаваемой переменной $_. Как и в рассмотренных выше операциях поиска и замены, при помощи операций связывания =~ и ! ~ можно задать для преобразования строку, отличную от принятой по умолчанию

$str =~ tr/SEARCHLIST/REPLACEMENTLIST/cds

Списки SEARCHLIST и REPLACEMENTLIST задаются перечислением символов, могут содержать диапазоны — два символа, соединенных знаком "-", и иметь собственные символы-ограничители, например, tr(a-j) /0-9/. Операция tr/// имеет синонимичную форму, используемую в потоковом редакторе sed:

y/SEARCHLIST/REPLACEMENTLIST/cds

Флаги cds имеют следующий смысл.

с — вместо списка поиска SEARCHLIST использовать его дополнение до основного множества символов (обычно расширенное множество ASCII).

d — удалить все символы, входящие в список поиска SEARCHLIST, для которых нет соответствия в списке замены REPLACEMENTLIST. Если флаг d не установлен и список замены REPLACEMENTLIST короче, чем список поиска SEARCHLIST, то вместо недостающих символов в списке замены используется последний символ этого списка. Если список замены пуст, то символы из списка поиска SEARCHLIST преобразуются сами в себя, что удобно использовать для подсчета числа символов в некотором классе. П s — все последовательности символов, которые были преобразованы в один и тот же символ, заменяются одним экземпляром этого символа.

$str =~ tr/A-Z/a-z/; # преобразование прописных букв в строчные $count=$str=~tr/\000//c; # подсчет числа символов в строке $str $str =~ tr/\200-\377/ /cs; # любая последовательность символов

с ASCII-кодами от 128 до 255 преобразуется

в единственный пробел

Следующий скрипт преобразует русский текст в DOS-кодировке 866, содержащийся в файле "866.txt", в русский текст в Windows-кодировке 1251, и записывает преобразованный текст в файл "1251. txt".

open(IN866, "866.txt"); open(OUT1251,">125I.txt"); while ($line=<IN866>) { .

$line=~tr/\200-\257\340-\361/\300-\377\250\270/; print OUT1251 $line;

>

close(IN866); close(OUT1251);



Операция заключения в кавычки qr//



Операция заключения в кавычки qr//

qr/STRING/imosx

Операция qr// по своему синтаксису похожа^наГдругие операции заключения в кавычки, такие как q//, qq//, qx//, qw//. Она обсуждается в данном разделе, так как имеет непосредственное отношение к регулярным выражениям. Регулярное выражение, содержащее переменные, метасимволы, мета-последовательности, расширенный синтаксис, перед использованием должно быть обработано компилятором. Операция qr// осуществляет предварительную компиляцию регулярного выражения STRING, преобразуя его в некоторое внутреннее представление, с тем, чтобы сохранить скомпилированное регулярное выражение в переменной, которую затем можно использовать без повторной компиляции самостоятельно или в составе других регулярных выражений.

Преимущества от применения операции qr// проявляются, например, в следующей ситуации. Допустим, что мы собираемся многократно использовать в качестве образца достаточно сложное регулярное выражение, например, / ^ ([ ^ ]*) *([ ^ ]*)/. Его можно использовать непосредственно в операции сопоставления с образцом

if ($line =~ / ^ (Г ]*) *([ ^ ]*)/) {...},

или сохранить в переменной $pattern= ll/^ ([ ^ ]*) *([ ^ ]*) и обращаться к переменной:

if ($line =~ /$pattern/) (...},

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

$pattn = qr/~(r ]*) *<Г ]*)/,

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

Флаги imosx имеют тот же смысл, что и в операции замены т//. Например, в следующем тексте операция qr// применяется с флагами ох:

$s="aA!Bb2cC3Dd45Ee";

@pattns=("\\d+ # последовательность цифр",

"[A-Z]+ t последовательность прописных букв", "[a-z]+ # последовательность строчных букв"); foreach $pattn Opattns) { my $pattern=qr/$pattn/ox; while ($s=~/$pattern/g) { $p=$p.$&; . } } print "s=$s p=$p\n";


В данном примере определен массив @pattns, состоящий из регулярных выражений. В цикле по элементам массива проверяется наличие в заданной строке $з фрагмента, соответствующего текущему образцу. Найденный фрагмент добавляется в конец строки $р. Флаг х в операции qr// позволяет использовать образцы в том виде, в каком они хранятся в массиве — с пробелами и комментариями. Если в операции qr// флаг о не установлен, то в результате выполнения скрипта строка $р будет состоять из символов строки $s, расположенных в следующем порядке: сначала все цифры, затем все прописные буквы, затем все строчные буквы. Если, как в данном ^тексте, флаг о установлен, то в операции $pattern=qr/$pattn/ox подстановка переменной $pattn произойдет только один раз, и строка $s будет три раза проверяться на наличие фрагмента, удовлетворяющего первому образцу $pattns[i]. В результате строка $р будет состоять только из цифр, входящих в строку $s, повторенных три раза.




Операция замены



Операция замены

s/PATTERN/REPLACEMENT/egimosx

Операция замены S/PATTERN/REPLACEMENT/ осуществляет поиск образца PATTERN и, в случае успеха, замену найденного фрагмента текстом REPLACEMENT. Возвращаемым значением является число сделанных замен или пустая строка (ЛОЖЬ), если замен не былоТПо умолчанию поиск и замена осуществляются в специальной переменной $_. Ее можно заменить другой строкой при помощи операций связывания =~ или ! ~:

$var =~ s/PATTERN/REPLACEMENT/egimosx

Флаг $ задает глобальную замену всех фрагментов, удовлетворяющих образцу PATTERN,TeKCTOM REPLACEMENT.

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

$str="abaabbaaabbbaaaabbbb"; $result=$str =~s[ (a+b+)]<length($l)>ge; print "result=$result new str=$str\n";

будет выведено число сделанных замен $ result и новое значение строки $str, в которой каждый найденный фрагмент, соответствующий образцу [а+b+], заменен числом, равным его длине:

result=4 new str=2468

Флаги imosx имеют тот же смысл, что для операции поиска m//.

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

s(pattern)<replacement>.



Регулярные выражения



Регулярные выражения

Язык, созданный первоначально с главной целью — облегчить обработку большого количества отчетов, просто обязан располагать развитыми средствами для работы с текстом. Напомним, что в среде UNIX, из которой вышел язык Perl, средства для обработки текстовых строк имеются в различных утилитах: sed, awk, grep, cut. Командный интерпретатор shell, также обладающий некоторыми средствами для обработки строк, позволяет организовать совместную работу этих утилит, передавая выход одной программы на вход другой через механизм, называемый конвейером. Такой подход требует написания достаточно изощренных скриптов на языке shell в сочетании с обращением к внутренним командам утилит обработки текста sed или awk. Язык Perl, являясь средством создания программ-сценариев, в то же время один обладает всеми возможностями перечисленных утилит и даже их превосходит.

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

Регулярное выражение, по сути, представляет собой набор правил для описания текстовых строк. Сами правила записываются в виде последовательности обычных символов и метасимволов, которая затем в качестве образца используется в операциях поиска и замены текста. Метасимволы — это символы, имеющие в регулярном выражении специальное значение. Пользователи DOS/Windows хорошо знают метасимвол *, используемый для порождения имен файлов и обозначающий любую допустимую последовательность. Регулярные выражения применяются многими программами UNIX, в том числе интерпретатором shell. Каждая из них использует свое множество метасимволов. В большинстве случаев они совпадают.


Метасимволы

В языке Perl к Метасимволам относятся следующие символы:

"\", ".", "^", "$", "|", "[", "(", ")", "*", "+", "?", "}".

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

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

Простейшее регулярное выражение состоит из одного обычного символа. Обычный символ в регулярном выражении представляет самого себя. Соответственно, последовательность обычных символов представляет саму себя и не нуждается в дополнительной интерпретации. Для использования в операциях в качестве образца регулярное выражение заключается между двумя одинаковыми символами-ограничителями. Часто в качестве ограничителя используется символ "/" (косая черта). Например, образцу /Peri/ будут соответствовать все строки, содержащие слово Perl.

Если, в регулярном выражении какой-либо метасимвол требуется использовать в буквальном, а не специальном значении, его нужно экранировать или маскировать при помощи другого метасимвола — "\". Например, образцу /\\\*/ соответствует фрагмент текста \*. Здесь первый метасимвол "\" экранирует второй метасимвол "\", а третий метасимвол "\" экранирует метасимвол "*".

Метасимвол "." представляет любой одиночный символ, кроме символа новой строки. Так, образцу /./ будет соответствовать любая непустая строка. Если в операциях сопоставления с образцом установлен флаг s, то метасимволу "." соответствует также и символ новой строки.

Метасимвол "[" используется в конструкции [...] для представления любого одиночного символа из числа заключенных в скобки, т. е. он представляет класс символов. Два символа, соединенные знаком минус, задают диапазон значений, например, [A-za-z] задает все прописные и строчные буквы английского алфавита. Если первым символом в скобках является символ " ^ ", вся конструкция обозначает любой символ, не перечисленный в скобках. Например, [ ^ о-9] обозначает все нецифровые символы. Ниже мы рассмотрим и другие способы представления классов символов.



Метасимволы " ^ " и "$" используются для задания привязки к определенному месту строки. Метасимвол " ^ " в качестве первого символа регулярного выражения обозначает начало строки. Метасимвол "$" в качестве последнего символа регулярного выражения обозначает конец строки. Например, следующим образцам соответствуют:

/ ^ $/ — пустая строка (начало и конец, между которыми пусто); / ^ рег1/ — слово Perl в начале строки; /Per 1$/ —слово Perl в конце строки.

Метасимвол "|" можно рассматривать как символ операции, задающей выбор из нескольких вариантов (подобно логической операции ИЛИ). Например, образцу /а|Ь|с/ соответствует фрагмент текста, содержащий любой из символов а, Ь, с, Если вариантами выбора являются одиночные символы, как в данном примере, то лучше использовать конструкцию, определяющую класс символов, в данном случае [abc]. Но в отличие от конструкции [...] операция "|" применима и тогда, когда вариантами выбора являются последовательности символов. Например, образцу /Word|Excel!windows/ соответствует фрагмент^текста^содержащий любое из слов Word, Excel, Windows.

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

r* — нуль и более повторений

r+— одно и более повторений

r? — нуль или одно повторение

r{n} — ровно п повторений

r{n}, — n и более повторений

r{n,m} — минимум n, максимум m повторений r

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

Образец Соответствие

/•*/ любая строка;

/• + / любая непустая строка;

/ [ о-9 ] {з} / любая последовательность из трех цифр;

/\ [ +/ последовательность, состоящая из любого числа символов [.

В первых двух примерах атомом является метасимвол ".". В третьем образце в качестве атома выступает конструкция [0-9], определяющая класс цифровых символов. В четвертом образце атом — это пара символов "\[", включающая метасимвол "\", отменяющий специальное значение следующего за ним метасимвола "[". Полный список атомов мы приведем после изучения всех необходимых синтаксических конструкций.



Алгоритм, применяемый в операциях поиска и замены (см. ниже) для обработки регулярных выражений, содержащих множители, является "жадным": он пытается найти для образца, снабженного множителем, максимальный сопоставимый фрагмент текста. Рассмотрим, например, что происходит при поиске в строке

"Скроен колпак не по-колпаковски, надо колпак переколпаковать" фрагмента, удовлетворяющего образцу /. * колпак/.

Алгоритм найдет максимальный фрагмент, удовлетворяющий выражению . * (вся строка без завершающего символа новой строки), затем начнет двигаться назад, отбрасывая в найденном фрагменте по одному символу, до тех пор, пока не будет достигнуто соответствие с образцом. Найденный фрагмент будет иметь вид "Скроен колпак не по-колпаковски, надо колпак переколпак".

Можно заставить алгоритм работать иначе, снабдив множитель "*" модификатором "?". В этом случае алгоритм из "жадного" превращается в "ленивый" и будет для образца, снабженного множителем, искать минимальный соответствующий фрагмент. "Ленивый" алгоритм для множителя "*?" начнет поиск в строке с пустого фрагмента "", добавляя к нему по одному символу из строки до тех пор, пока не достигнет соответствия с образцом. В этом случае найденный фрагмент будет иметь вид "Скроен колпак". Все сказанное справедливо и для других множителей. Например, в строке "1234567" будет найден:

для образца /\d*/ — максимальный фрагмент "1234567";

для образца /\d+/ — максимальный фрагмент "1234567";

для образца /\d?/ — максимальный фрагмент "1";

для образца /\d{2,5}/ — максимальный фрагмент "12345";

для образца /\d*?/ — минимальный фрагмент "";

для образца /\d+?/ — минимальный фрагмент "1";

для образца /\d??/ — минимальный фрагмент "";

для образца /\d{2,5}?/ — минимальный фрагмент "12".

Метапоследовательности

Символ "\", непосредственно предшествующий одному из метасимволов, отменяет специальное значение последнего. Если же "\" непосредственно предшествует обычному символу, то, напротив, такая последовательность во многих случаях приобретает специальное значение. Подобного рода последовательности будем называть метапоследователъностями. Метапоследова-тельности в регулярном выражении служат, в основном, для представления отдельных символов, их классов или определенного места в строке, дополняя и иногда дублируя/функции метасимволов. Рассмотрим существующие метапосл едовател ьности.



\nnrt — представляет\символ, восьмеричный код которого равен лил. Например, последовательность \120\145\162\154 представляет слово Perl (\120 — восьмеричный код буквы Р, \145 — буквы е, \1б2 — буквы г, \154- буквы!);

\хпп — представляет символ, шестнадцатеричный код которого равен пп. Слово Perl, например, представляется последовательностью \x50\x65\х72\хбс;

\сп — представляет управляющий символ, который генерируется при нажатии комбинации клавиш <Ctrl>+<N>, где N — символ, например, \со соответствует <Ctrl>+<D>;

\$ — символ "$"; G \@ — символ "@"; О \% — символ "%";

\а — представляет символ с десятичным ASCII-кодом 7 (звонок). При выводе производит звуковой сигнал;

\е — символ Esc, десятичный ASCII-код 27;

\f — символ перевода страницы, десятичный ASCII-код 12;

\п — символ новой строки, десятичный ASCII-код 10;

\г — символ "возврат каретки", десятичный ASCII-код 13;

\t — символ горизонтальной табуляции, десятичный ASCII-код 9;

\v — символ вертикальной табуляции, десятичный ASCII-код 11;

\з — представляет класс пробельных символов. К пробельным символам относятся: пробел, имвол табуляции, возврат каретки, символ новой строки и символ перевода страницы. То же самое, что и [\t,\r,\n,\fj;

\s — представляет класс непробельных символов, то же самое, что и класс [ л \t,\r,\n,\f];

\d — класс цифровых символов, то же, что и [0-9]; П \о — класс нецифровых символов, то же, что и [ Л о-9];

\w — представляет класс буквенно-цифровых символов, состоящий из букв, цифр и символа подчеркивания "_". То же самое, что и [a-zA-z_ o-9j. Обратите внимание, что в этот класс входят только буквы английского алфавита;

\w — представляет класс небуквенно-цифровых символов. То же, что и Pa-zA-Z_0-9];

\д — обозначает начало строки;

\z — обозначает конец строки;

Замечание
Последовательность \А эквивалентна метасимволу ^ в начале регулярного выражения, а последовательность \z— метасимволу $ в конце регулярного выражения, за исключением одного случая. Назовем строку, содержащую внутри себя символы новой строки (ASCII 10), мульти-строкой. Фактически муль-ти-строка состоит из отдельных строк, разделенных ограничителями — символами новой строки. При выводе мульти-строка отображается в виде нескольких строк. Если к мульти-строке применяется операция поиска или замены с опцией /т (см. ниже), то последовательности \А и \z обозначают соответственно начало и конец всей мульти-строки, а метасимволам ^ и $ соответствуют еще и границы внутренних строк, образующих мульти-строку.
<


\b — обозначает границы слова. Под словом понимается последовательность символов из класса \w. Граница слова определяется как точка между символами из класса \w и символами из класса \w;

\B — обозначает не-границы слова, т. е. класс символов [ Л \Ь];

\1 — обозначает, что следующий символ регулярного выражения преобразуется к нижнему регистру. Например, запись /\ip/ означает, что символ Р будет преобразован к нижнему регистру, после чего новый образец /р/ может быть использован в соответствующей операции поиска или замены;

\n — обозначает, что следующий символ регулярного выражения преобразуется к верхнему регистру;

\b.. .\Е — обозначает, что все символы в регулярном выражении между \ь и \Е преобразуются к нижнему регистру;

\n.. .\Е — обозначает, что все символы в регулярном выражении между \U и \Е преобразуются к верхнему регистру;

\Q...\E— обозначает, что все метасимволы в регулярном выражении между \Q и \Е экранируются при помощи символа "\". Например, запись /\0 Л *?+\Е/ эквивалентна записи /\ Л \*\?\+У;

\с — обозначает точку, в которой закончился предыдущий поиск m//g (см. описание операции поиска т//).

Атомы

Из всех метасимволов, перечисленных в начале раздела, нам осталось рассмотреть "(" и "). Эти метасимволы служат для группирования ряда элементов, входящих в состав образца, в один элемент. Например, образцу /<abc)+/ соответствует строка, состоящая из одного или более повторений последовательности abc, в то время, как образцу /abc+/ — строка, состоящая из начальных символов аb, за которыми следует один или более символов с.

Теперь мы можем перечислить "атомы", из которых строится регулярное выражение.

Регулярное выражение в скобках, представляющее несколько элементов, сгруппированных в один.

Любой обычный символ (не метасимвол).

Символ ".", представляющий любой одиночный символ, кроме символа новой строки.

Конструкция [...], представляющая класс символов, перечисленных в квадратных скобках.

Метапоследовательность, представляющая символ или класс символов: \&, \n, \r, \t, \f, \e, \d, \D, \w, \w, \s, \s.



Метапоследовательность вида \nnn, определяющая символ при помощи его восьмеричного ASCII-кода nnn

Метапоследовательность вида \хnn, определяющая (символ при помощи его шестнадцатеричного ASCII-кода nn .

Метапоследовательность вида \сn, представляющая управляющий символ ctri-n .

Конструкция вида \number, представляющая обратную ссылку.

Любая конструкция вида \character, не имеющая специального значения, а представляющая собственно символ character, например: \*, \ у , \ь.

Напомним, что в регулярном выражении множители *, +, ?, {n,m} применяются именно к атому, расположенному непосредственно слева.

Обратные ссылки

Ранее мы установили, что группу элементов регулярного выражения можно заключить в скобки и рассматривать как один элемент. Заключение группы элементов в скобки имеет дополнительный и весьма полезный эффект. Если в результате поиска будет найден фрагмент текста, соответствующий образцу, заключенному в скобки, то этот фрагмент сохранится в специальной переменной. Внутри регулярного выражения к нему можно будет обратиться, используя запись \number, где number—номер конструкции () в исходном регулярном выражении. Запись \number, указывающую на найденный по образцу фрагмент текста, будем называть обратной ссылкой. Можно задать любое количество конструкций вида () и ссылаться на соответствующие найденные фрагменты текста, как на \i, \2 и т. д. Например, образцу /(. + ) -\1/ соответствуют слова "ха-ха", "хи-хи", "ку-ку" и т. п., а образцу /{.)(.). ?\2\1/ — все палиндромы из четырех или пяти букв. (Палиндром — слово или предложение, которое одинаково читается слева направо и справа налево.)

Внутри образца конструкция \n (n=i,...,9) всегда обозначает обратную ссылку. Запись вида \пп также интерпретируется как обратная ссылка, но только в том случае, если в исходном выражении задано не менее, чем пп скобочных конструкций вида (). Иначе запись \пп обозначает символ с восьмеричным кодом nn.

Для ссылки на найденный фрагмент текста за пределами регулярного выражения, например, при задании замещающего текста в операции замены, вместо записи \number используется запись $пшпЬег. Например, операция замены



$str=~s/(\S+)\s+(\S+)/$2 $1/

меняет местами первые два слова в строке $str.

Область действия переменных $1, $2 и т. д, распространяется до наступления одного из следующих событий: конец текущего блока; конец строки, являющейся аргументом функции evai; следующее совпадение с образцом. Аналогичным образом определяется область действия и для следующих предопределенных переменных, используемых в операциях сопоставления с образцом.

$& — часть строки, найденная при последней операции сопоставления с образцом.

$' — часть строки, стоящая перед совпавшей частью при последней успешной операции сопоставления с образцом.

$' — часть строки, стоящая после совпавшей части при последней успешной операции сопоставления с образцом.

Например, в результате выполнения операции поиска

$str=~m/two/

в строке $str="one two three" образца /two/ переменным будут присвоены следующие значения:

$& - "two"; $* - "one"; $' - "three".

Эти значения будут сохраняться до наступления одного из перечисленных выше событий, и их можно использовать, например, для формирования строки с обратным порядком следования слов: $rstr=$'.$&.$". Строка $rstr будет иметь ВИД "three two one".

Следует отметить, что, если обращение к одной из переменных $&, $', $• встречается где-либо в программе, то интерпретатор peri будет вычислять и запоминать их для каждой операции сопоставления с образцом, что, в свою очередь, замедляет выполнение всей программы. Поэтому не следует использовать данные переменные без особой необходимости.

Расширенный синтаксис регулярных выражений

Выше мы использовали скобки для группирования нескольких элементов регулярного выражения в один элемент. Побочным эффектом данной операции является запоминание найденного фрагмента текста, соответствующего образцу, заключенному в скобки, в специальной переменной. Если скобки используются только для группирования элементов регулярного выражения, то найденный фрагмент текста можно не запоминать. Для этого после открывающей скобки "(" следует поместить конструкцию "?:", например, в случае задания альтернативы / (?: pattern) /.



?:pattern

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

(?#text)

Текст после символа tt и до закрывающей скобки ), комментарий, игнорируется интерпретатором и используется для добавления непосредственно в регулярное выражение.

(?:pattern)

(?imsx-imsx:pattern)

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

(?=pattern)

Следующий фрагмент в тексте должен соответствовать образцу pattern. Обычно образец для операций поиска или замены задается при помощи регулярного выражения. Результатом операции поиска является фрагмент, соответствующий образцу, который сохраняется в специальной переменной $&. Конструкция (?=pattern) в составе регулярного выражения позволяет задать условие поиска, не включая найденный фрагмент, соответствующий образцу pattern, в результат, сохраняемый в переменной $&. Конструкция (?=pattern) в регулярном выражении задает условие, что следующий фрагмент текста должен удовлетворять образцу pattern. Обращаем внимание на слово следующий. Данная конструкция неприменима для задания условия, что предыдущий фрагмент текста должен соответствовать заданному образцу. Например, образцу /b+(?=с+)/ соответствует часть строки, состоящая из одной или более литер ь, за которыми следуют одна или более литер с, причем найденный фрагмент текста будет содержать только последовательность литер ь без последовательности литер с.



Рассмотрим строку

$str = "aaabbbcccddd";

В результате операции поиска

$str =~ m/b+(?=c+)/;

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

$ ч — ааа $& - bbb $' — cccddd

Если в операции поиска указать образец /b+с+/, то значения специальных переменных будут следующими:

$" — ааа $& — bbbссс $' - ddd

В свою очередь, операция поиска по образцу / (?=b+) с+/ в нашем примере не даст результата. Данный образец задает условие, что следующий фрагмент текста должен содержать непустую последовательность литер ь. В нашей строке такой -фрагмент будет найден (это фрагмент bbb>, но не будет включен в результат поиска. Следующий фрагмент, в соответствии с образцом, должен представлять непустую последовательность литер с, но в нашем случае этого соответствия не будет, так как мы остановились перед фрагментом ььь, не включив его в результат, и поэтому следующим фрагментом будет bbb, а не ссс.

Конструкцию (?=pattern) будем называть регулярным выражением с положительным постусловием.

(?!pattern)

Конструкция (? [pattern) в регулярном выражении задает условие, что следующий фрагмент текста не должен удовлетворять образцу pattern. Найденный фрагмент не запоминается в переменной $&. Например, результат операции поиска

$str =~ m/b+(?!c+)/;

в рассмотренной выше строке $str будет зафиксирован в следующих значениях специальных переменных:

$' — ааа

$& - bb

$' — bcccddd

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

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

(?<=pattern)

Конструкция (?<=pattern) в регулярном выражении задает условие, что предыдущий фрагмент текста должен удовлетворять образцу pattern. Найденный фрагмент не запоминается в переменной $&. Образец pattern должен иметь фиксированную длину, т. е. не содержать множителей.

В нашем примере в результате операции поиска

$str =~ m/(?<=b)b+/;



значения специальных переменных будут распределены следующим образом:

$' — aaab

$& - bb

$' - cccddd I

Данную конструкцию назовем регулярным выражением с положительным предусловием. _/

(?<!pattern)

Конструкция (?<! pattern) в регулярном выражении задает условие, что предыдущий фрагмент текста не должен удовлетворять образцу pattern. Найденный фрагмент не запоминается в переменной $&. Образец pattern должен иметь фиксированную длину, т. е. не содержать множителей.

В нашем примере в результате операции поиска

$str =~ m/(?<!b)c+/;

специальные переменные получат следующие значения:

$' — aaabbbc $& — ее $' - ddd

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

(?imsx-imsx)

Задание флагов операции сопоставления с образцом осуществляется в самом образце. Флаги модифицируют выполнение операции и обычно являются частью синтаксиса самой операции. Расширенная конструкция (?imsx-imsx) позволяет задать флаги операции внутри самого образца. Эта возможность может быть полезной, например, в таблицах, когда разные элементы таблицы требуется по-разному сопоставлять с заданным образцом, например, некоторые элементы — с учетом регистра, другие — без учета. Допустимыми являются следующие флаги.

i — поиск без учета регистра;

dm — строка трактуется как мульти-строка, состоящая из нескольких строк, разделенных символом новой строки;

s — строка трактуется как одна строка, в этом случае метасимволу "." соответствует любой одиночный символ, включая символ новой строки;

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

Одна из литер i, m, s, x после знака "-" обозначает отмену соответствующего флага.

При помощи данной расширенной конструкции можно задать, например, следующий образец

/(?ix) peri # игнорирование регистра при поиске/

Флаг i предписывает не учитывать регистр в операциях сопоставления с образцом, так что образцу будет соответствовать и слово peri, и слово Perl. Флаг х позволяет выделить слово "peri" пробелами и использовать в образце комментарий. И пробелы, и комментарий не будут учитываться в операции сопоставления с образцом.




Сводка результатов



Сводка результатов

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

Любой одиночный символ, не являющийся метасимволом, представляет самого себя.

Специальное значение метасимвола можно отменить, поместив перед ним специальный экранирующий метасимвол "\".

Можно определить класс символов, заключив их в квадратные скобки. Если первым после открывающей скобки "[" является символ " ^ ", то вся конструкция обозначает символы, не перечисленные в скобках. Внутри скобок два символа, соединенные знаком "-", определяют диапазон. Чтобы включить в состав класса символ "-", его следует поместить в начале или в конце списка, или экранировать при помощи символа "\".

Символы можно задавать при помощи метапоследовательностей, состоящих из символа "\", за оторым следует обычный символ или последовательность символов.

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

Внутри регулярного выражения можно выделить подобразец, заключив его в круглые скобки. На и-ю конструкцию в скобках можно затем сослаться, используя нотацию \п внутри и $п — вне регулярного выражения.

В заключение раздела приведем в табл. 10.1 и 10.2 сводку метасимволов и метапоследовательностей, рассмотренных в данной главе.

Таблица 10.1. Символы, имеющие специальное значение у в регулярных выражениях Perl

Метасимвол

Интерпретация

\

Отменяет (экранирует) специальное значение следующего за ним метасимвола

-

Любой одиночный символ, кроме символа новой строки

Любой одиночный символ, включая символ новой строки, если в операции сопоставления с образцом задан флаг s

л

Обозначает начало строки, если является первым символом образца

$

Обозначает конец строки, если является последним символом образца

I

Разделяет альтернативные варианты

[...]

Любой одиночный символ из тех, которые перечислены в квадратных скобках. Пара символов, разделенных знаком минус, задает диапазон символов. Например, [A-Za-z] задает все прописные и строчные буквы английского алфавита. Если первым символом в скобках является символ " ^ ", то вся конструкция обозначает любой символ, не перечисленный в скобках. Внутри скобок символы ".", "*", "[" и "\" теряют свое специальное значение

(...)

Группирование элементов образца в один элемент

*

Нуль и более повторений регулярного выражения, стоящего непосредственно перед *

+

Одно или более повторений регулярного выражения, стоящего непосредственно перед +

9

Одно или ни одного повторения регулярного выражения, стоящего непосредственно перед ?

{ n, m }

Минимальное n и максимальное m число повторений регулярного выражения, стоящего перед {n, m). Конструкция {n} означает ровно n повторений, (m } — минимум n повторений

\0nn

Символ, восьмеричный код которого равен nn

При выводе производит звуковой сигнал

Обозначает начало строки

\b

Обозначает границы слова.


Под словом понимается последовательность символов из класса \w (см. ниже). Граница слова определяется как точка между символами из класса \w и символами из класса \w (см. ниже)

Обозначает не-границы слова

\сп

Управляющий символ, который генерируется при нажатии комбинации клавиш <Ctrl>+<N>

\d

Любой цифровой символ, то же, что и [0-9]

\D

Любой нецифровой символ, то же, что и [ л О-9]

Символ Esc, ASCII 27

Ограничитель последовательностей \L, \u, \Q

\f

Символ перевода страницы, ASCI1 1 2

\G

Обозначает точку, в которой закончился предыдущий поиск m/ /g

\1

Преобразует следующий символ регулярного выражения к нижнему регистру

<
Таблица 10.2. Метапоследовательности в регулярных выражениях Perl

Метапоследовательность

Значение

\L

Преобразует все последующие символы в регулярном выражении к нижнему регистру до тех пор, пока не встретится последовательность \Е

\n

Символ новой строки, ASCII 10

\Q

Эквивалентно экранированию всех последующих метасимволов в регулярном выражении при помощи символа "\" до тех пор, пока не встретится последовательность \Е

\r

Символ "возврат каретки", ASCII 1 3

\s

Класс пробельных символов: пробел (space), символ табуляции (tab), возврат каретки (carriage return), символ перевода строки (line feed) и символ перевода страницы {form feed); эквивалентное \t,\r,\n, \f]

\S

Класс непробельных символов

\t

Символ табуляции, ASCII 9

\u

Преобразует следующий символ к верхнему регистру

\U

Преобразует все последующие символы в регулярном выражении к верхнему регистру до тех пор, пока не встретится последовательность \Е

\v

Символ вертикальной табуляции, ASCII i 1

\w

Любая буква, цифра или символ подчеркивания

\W

Любой символ, не являющийся буквой, цифрой или символом подчеркивания

\xnn

Символ, шестнадцатеричный код которого равен пп

\z

Обозначает конец строки


Напишите программу, которая читает стандартный



Упражнения

1. Напишите программу, которая читает стандартный ввод, умножает каждое встретившееся число на 2 и выводит результирующую строку.
2. Напишите программу, которая читает стандартный ввод, удваивает каждую букву и выводит результирующую строку.
3. Напишите программу, подсчитывающую, сколько раз каждый алфавитно-цифровой символ встретился во вхбдном файле.
4. Напишите программу, которая считывает строку из стандартного файла ввода, меняет в ней порядок следования символов на обратный и выводит результат.
5. Напишите программу, которая выполняет преобразование русского текста из одной системы кодировки в другую:
(Dos 866, Windows 1251, UNIX KOI8} <=> (Dos 866, Windows 1251, UNIX, KOI8}
Для выполнения задания можно воспользоваться табл. 10.3, содержащей шестнадцатеричные коды символов русского алфавита.
Таблица 10.3. Таблицы кодов русского алфавита

Символ


866


1251


KOI8


Символ


866


1251


KOI8


А

80

СО

Е1

а

АО

ЕО

С1

Б

81

С1

Е2

б

А1

Е1

С2

В

82

С2

F7

в

А2

Е2

D7

Г

83

СЗ

Е7

г

A3

ЕЗ

С7

Д
84
С4
Е4
Д
А4
Е4
С4
Е

85

С5

Е5

е

А5

Е5

С5

Ё

FO

А8

ВЗ

е

F1

В8

A3

Ж

86

С6

F6

ж

А6

Е6

D6

3

87

С7

FA

3

А7

Е7

DA

И

88

С8

Е9

и

А8

Е8

С9

И

89

С9

EA

Й

А9

Е9

СА

К



СА

EB

к

АА

ЕА

СВ

Л



СВ

EC

л

АВ

ЕВ

СС

М



СС

ED

M

АС

ЕС

CD

Н

8D

CD

ЕЕ

Н

AD

ED

СЕ

О



СЕ

EF

0

АЕ

ЕЕ

CF

П

8F

CF

FO

П

AF

EF

DO

Р

90

DO

F2

Р

ЕО

FO

D2

С

91

D1

F3

с

Е1

F1

D3

Т

92

D2

F4

т

Е2

F2

D4

У

93

D3

F5

У

ЕЗ

F3

D5

ф

94

D4

E6

ф

Е4

F4

С6

X

95

D5

E8

X

Е5

F5

С8

Ц

96

D6

E3

ц

Е6

F6

СЗ

ч

97

D7

FE

Ц

Е7

F7

DE

ш

98

D8

FB

ш

Е8

F8

DB

Щ

99

D9

FD

Щ

Е9

F9

DD

ъ



DA

FF

ъ

ЕА

FA

DF

ы



DB

F9

ы

ЕВ

FB

D9

ь



DC

F8

ь

ЕС

FC

D8

э

9D

DD

FC

э

ED

FD

DC

ю



DE

EO

ю

ЕЕ

FE

СО

я

9F

DF

F1

я

EF

FF

D1

в регулярном выражении Perl специальное



Вопросы для самоконтроля

1. Что такое регулярное выражение?
2. Какие символы имеют в регулярном выражении Perl специальное значение?
3. Что такое метапоследовательность, как она образуется?
4. Что такое обратная ссылка?
5. Какая переменная используется в операции подстановки по умолчанию?
6. Какой смысл имеет символ "$" в следующих регулярных выражениях:
/abc*$/
/[аbс*$]/
/$abc/
7. Какой смысл имеет символ " ^ " в следующих регулярных выражениях:
/ ^ аbс/
/[аbс]/
/аbс ^ /
8. Объясните, какие множества строк соответствуют следующим образцам. Приведите пример.
/a.out/
/a\.out/
/\d{2,3}-\d{2}-\d{2}/
/(.)(.).\2\1/ /(.) (.).\02\01/
9. Напишите образец, задающий палиндром из шести букв.
10. Напишите команду замены, которая:
- заменяет все символы новой строки пробелами;
- выделяет из полного маршрутного имени файла имя файла;
- выделяет из полного маршрутного имени файла имя каталога.
11. Каково значение следующих выражений, если значение переменной
$var равно "123qwerty"? $var =~ /./ $var =- /[A-Z]*/ $var =~ /\w{4-9}/ $var =~ /(\d)2(\D/ $var =~ /qwerty$/ $var =~ /123?/
11. Какое значение будет иметь переменная $var после следующих операций подстановки, если ее начальное значение равно "qwertyi23qwerty"?
$var =~ s/qwerty/XYZ/; $var =~ s/[a-zJ+/X/g; $var =~ s/B/W/i; $var =~ s/(.)\d.*\l/d/; $var =~ s/(\d+)/$l*2/e;
12. Начальное значение переменной $var равно "qwertyi23qwerty". Каким оно будет после выполнения операций транслитерации?
$var =~ tr/a-z/A-Z/; $var =~ tr/a-z/0-9/; $var =~ tr/a-z/O-9/d; $var =~ tr/231/564/; $var =~ tr/123/ /s; . $var =~ tr/123//cd;
13. Переменная $var имеет значение "qwertyqwerty". Каково значение, возвращаемое функцией?
substr ($var, 0, 3);
substr ($var, 4);
substr ($var, -2, 2);
substr ($var, 2, 0) ;
index ($var, "rt"); index ($var, "rtyu"); index ($var, "er", 1); index ($var, "er", 7); rindex ($var, "er");


Функция lосаl ()



Функция lосаl ()

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

local EXPR;

local ($myvar, @mylist, %myhash);

local $pi = 3.14159;

local ($pi, $exp) = (3.14159, 2.71828);

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

подпрограммы;

заключенного в фигурные скобки блока операторов;

выражения, переданного на выполнение функции eval ();

файла;

в зависимости от того, в каком месте вызвана для объявления переменных сама функция local (). Если функция local () применяется для описания нескольких переменных, они должны быть заключены в скобки. Если глобальная переменная, объявленная при помощи этой функции, ранее встречалась до объявления и имела некоторое значение, то это значение сохраняется в скрытом стеке и восстанавливается после выхода соответственно из подпрограммы, блока, функции eval (} или файла. Переменная, объявленная при помощи функции local (), или, точнее, ее временное значение, доступна для' любой функции, вызванной внутри подпрограммы, блока, функции eval о или файла, в которых сделано объявление. Такую переменную называют динамической, а ее область видимости — динамической областью видимости. В • названии отражается тот факт, что область видимости переменной динамически изменяется с каждым вызовом функции, получающей доступ к этой переменной.

Функция ту о является относительно новой, она появилась в версии Perl 5. Для создания действительно локальных переменных рекомендуется использовать именно функцию ту о, а не функцию local о. Впрочем, есть несколько исключений. О них мы расскажем ниже.

В следующем примере показано, чем отличаются переменные, объявленные

При ПОМОЩИ функций mу () и local () .

sub fl{

local ($x) = "aaaa";

my($y) = "bbbb";

print("fl: x = $x\n");

print("fl: y='$y\n\n");

f 2 (} ;

print("fl: x = $x\n");

print("fl: у = $y\n\n");

} • ' ' ' sub f2{

print("f2: x = $x\n");

print("f2: y=$y\n\n");

$x = "cccc";

$y = "dddd";

print("f2: x = $x\n");

print("f2: y=$y\n\n");

I

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

II л = aaaa

f. у = bbbb ,

2: x — aaaa с2: у =

f2: x = cccc £2: у = dddd

fl: x = cccc fl: у = bbbb

Как видно из приведенного результата, функция f2 () не имеет доступа к переменной $у, объявленной при помощи функции ту о внутри функции fi () , и, напротив, имеет доступ к переменной $х, объявленной внутри fl ()

при ПОМОЩИ функции local () .



Функция ту()



Функция ту()

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

my EXPR

и ограничивает их область действия:

подпрограммой;

заключенным в фигурные скобки блоком операторов;

выражением, переданным на выполнение функции eval ();

файлом, в зависимости от того, в каком месте вызвана для объявления переменных сама функция my ().

Если выражение EXPR содержит список переменных, то он должен быть заключен в скобки:

my ($myvar, @mylist, %myhash);

Одновременно с объявлением переменные могут быть инициализированы:

my $pi = 3.14159;

ту ($pi, $exp) = (3.14159, 2.71828);

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



Использование типа typeglob Первый



Использование ссылок

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

sub doublparms {

ray ($listref, $hashref) = @_;

foreach $item (@$listref} { $item *= 2;

} .

foreach $key (keys %$hashref) { $$hashref{$key} *= 2;

} }

@somelist=(1,2,3) ;

%somehash=("one"=>5, "two"=>15, "three"=>20); print "начальные значения:\@somelist=@somelist\n"; foreach $key (keys %somehash) { .

print "\$somehash{$key}=$somehash{$key} "; }

print "\n";

doublparms(\@somelist,\%somehash); print "итоговые значения:\n\@somelist=@somelist\n"; foreach $key (keys %somehash) {

print "\$somehash{$key}=$somehash($key} "; } print "\n";

Здесь для описания локальных переменных использована функция ту. Как мы выяснили ранее в этой главе, применение функции ту в подобном случае реализует передачу параметров по значению. Другими словами, их изменение внутри подпрограммы не влияет на фактические параметры. Каким же образом в данном случае осуществляется передача массива и хеш-массива по ссылке? Дело в том, что по значению передаются только ссылки, указывающие на фактические параметры: массив @someiist и хеш-массив %somehash. Используя операции разыменования внутри подпрограммы, мы получаем доступ непосредственно к массиву @someiist и хеш-массиву %somehash, и изменяем их элементы. В результате выполнения данного сценария будет выведено:

начальные значения:

@somelist=1 2 3

$somehash{one}=5 $somehash{three}=20 $somehash{two}=15

итоговые значения:

@somelist=2 4 6

$somehash{one}=10 $somehash{three)=40 $somehash{two}=30



Локальные переменные в подпрограммах



Локальные переменные в подпрограммах

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

(Пакеты рассматриваются , а специальные переменные — )

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

ПОМОЩИ ФУНКЦИЙ my() И local () .



Определение подпрограммы



Определение подпрограммы

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

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

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

Подпрограмма может быть определена в любом месте основной программы при помощи описания

sub name [(proto)] ({block}};

Здесь name имя подпрограммы;

(proto) прототип, конструкция, используемая для описания передаваемых подпрограмме параметров;

{block} блок операторов, являющийся определением подпрограммы и выполняющийся при каждом ее вызове.

Форма

sub name [ (proto)];

представляет собой предварительное объявление подпрограммы без ее определения. Пользователь, предпочитающий помещать описания всех подпрограмм в конце основной программы, должен при вызове еще не определенной фуНКЦИИ ИСПОЛЬЗОВаТЬ Специальный СИНТаКСИС &лагае ИЛИ name. Если же некоторое имя предварительно объявить в качестве имени функции, то сразу после объявления к этой функции можно обращаться просто по имени без применения специального синтаксиса.

#!/usr/bin/perl sub max {

my $maximum = shift @$_;

my $x;

foreach $x (@_) {

$maximum=$x if ($x > $maximum) ;

}

return $maximum } print "Наибольший аргумент=", max(3,5,17,9), "\n";

В данном примере функция max () возвращает наибольший из своих аргументов. Об использовании функции ту() и массива @_ будет рассказано ниже.

Данный способ определения подпрограмм не является единственным. Существуют и другие варианты:

текст подпрограммы может храниться в отдельном файле и загружаться в основную программу при ПОМОЩИ КЛЮЧеВЫХ СЛОВ do, require, use;

П строка, содержащая текст подпрограммы, может быть передана в качестве аргумента функции eval о (см. ); в этом случае компиляция кода подпрограммы осуществляется при каждом вызове функции eval ();


анонимную подпрограмму можно определить при помощи ссылки на нее (см. ).

Применение функции eval () и ссылки на анонимную подпрограмму были рассмотрены ранее.

Конструкция do filename вызывает выполнение Peri-программы, содержащейся в файле filename. Если файл filename недоступен для чтения, функ-ция do возвращает неопределенное значение и присваивает соответствующее значение специальной переменной $!. Если файл filename может быть прочитан, но возникают ошибки при его компиляции или выполнении, то функция do возвращает неопределенное значение и помещает в переменную $@ сообщение с указанием строки, содержащей ошибку. Если компиляция прошла успешно, функция do возвращает значение последнего выражения,

Вычисленного В файле filename.

Замечание
Специальная переменная $! служит для хранения сообщения о последней системной ошибке. Такая ошибка возникает при обращении „к операционной системе с запросом на предоставление некоторой услуги, как, например, создание файла, чтение или запись в него.
Специальная переменная $@ 'используется для хранения сообщения, генерируемого при последнем обращении к функциям eval () или do filename,

# файл "l.pl":

#!/usr/bin/perl \ do "2.pi"; \ print "ошибка: $@\n" if $@; do "3.pl"; ' . j print "системная ошибка: $!\n" if $!;

# файл "2.pi":

$x=l;

$y=0;

$z=$x/$y;

print "z= $z\n";

Peri-программа "i.pi", используя конструкцию do filename, пытается выполнить сценарии, содержащиеся в файлах "2.pi" и "З.р1". Первый из них содержит в третьей строке операцию деления на 0, вызывающую появление ошибки во время выполнения программы, а второй вообще не существует. В результате выполнения файла "i .pi" будут выведены следующие сообщения:

ошибка: Illegal division by zero at 2.pi line 3. системная ошибка: No such file or directory

Ключевые слова use и require используются для включения в текущую программу подпрограмм из других модулей.

(Директивы компилятора use и require рассмотрены )


Передача параметров



Передача параметров

Информация в подпрограмму и обратно передается через параметры (аргументы). Для передачи параметров в подпрограмму используется специальный массив @_. Все параметры запоминаются в элементах массива $_ [ 0 ], $_ [ 1 ] и т. д. Такой механизм позволяет передавать в подпрограмму произвольное количество параметров.

Массив @_ является локальным для данной подпрограммы, но его элементы — это псевдонимы действительных скалярных параметров. Изменение элемента массива @_ вызывает изменение соответствующего действительного параметра.

В языках программирования различают передачу параметров по ссылке и по значению. При передаче параметров по значению подпрограмма получает копию переменной. Изменение копии внутри подпрограммы не влияет на ее оригинал. При передаче параметров по ссылке подпрограмма получает доступ к самой переменной и может ее изменять.

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

#!/usr/bin/perl

# Передача в подпрограмму параметров по значению sub f {

my($x, $y) = @_; return (++$х * —$у); }

$val = f ^lib-print "Значение (9+1) * (11-1) равно $val.\n"; $х = 9; $У = 11;

$val = f($x,$y);

print "Значение ($х+1) * ($у-1) равно $val.\n"; print "Значение \$х остается равным $х, а \$у равным $у.\п";

Результат выполнения:

Значение (9+1) * (11-1) равно 100.

Значение (9+1) * (11-1) равно 100.

Значение $х остается равным 9, а $у равным 11.



Передача по ссылке параметров-массивов



Передача по ссылке параметров-массивов

Итак, подпрограмма получает и возвращает параметры через специальный массив @_. Если параметр является массивом или хеш-массивом, его элементы также сохраняются в массиве параметров @_. При передаче в подпрограмму нескольких параметров-массивов или хеш-массивов они утрачивают свою целостность. Иными словами, после записи параметров-массивов (хеш-массивов) в массив @_ из него невозможно выделить отдельный параметр-массив (хеш-массив): все параметры в массиве @_ хранятся единой "кучей". Для сохранения при передаче в подпрограмму целостности массива или хеш-массива существуют два основных подхода.  



Прототипы



Прототипы

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

sub func ($$) {

1

определяет функцию func о с двумя скалярными аргументами. Символы

для обозначения типа аргумента приведены в табл. 11.1.

Таблица 11.1. Символы, используемые в прототипах для задания типа аргумента

Символ

Тип данных

$

Скаляр

@

Массив

%

Ассоциативный массив

&

Анонимная подпрограмма

*

Тип typeglob

Запись вида \char, где char — один из символов табл. 11.1, обозначает что при вызове подпрограммы имя фактического параметра должно обязательно начинаться с символа char. В этом случае в подпрограмму через массив параметров @_ передается ссылка на фактический параметр, указанный при ее вызове. Обязательные параметры в прототипе отделяются от необязательных точкой с запятой.

В табл. 11.2 в качестве примера приведены объявления пользовательских функции nybud,itin(), синтаксис которых соответствует синтаксису встроенных функций buil tin ().

Таблица 11.2. Примеры прототипов

Объявление

Обращение к функции

sub mylink ($$)

mylink $old, $new

sub myvec ($$$)

myvec $var, $offset, 1

sub myindex ($$;$)

myindex Sgetstring, "substr"

sub mysyswrite ($$$;$)

mysyswrite $buf, 0, length ($buf) - $off, vOf f

sub myreverse (@)

myreverse $a, $b, $c

sub my join ($@j

myjoin ":", $a, $b, $c

sub mypop (\@)

mypop garray

sub mysplice (\@$$@)

mysplice Sarray, @array, 0, @pushme

sub mykeys (\%)

mykeys %{$hashref}

sub myopen (*;$)

myopen HANDLE, $name

sub mypipe (**)

mypipe READER, WRITER

sub mygrep (s@)

mygrep { /pattern/ } $a, $b, $c

sub myrand ($)

myrand 42

sub mytime ()

mytime

Следует иметь в виду, что проверка синтаксиса, задаваемого при помощи прототипа, не осуществляется, если подпрограмма вызвана с использованием префикса &: ssubname.



Рекурсивные подпрограммы



Рекурсивные подпрограммы

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

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

sub tree {

local (*ROOT);

my ($root)=$_[01;

opendi.r ROOT, $root;

my (@filelist) = readdir ROOT;

closedir ROOT;

for $x (gfilelist) {

if ($x ne "." and $x ne ".."){ $x=$root."/".$x; print " $x\n" if (-f $x); if (-d $x) {

print "$x:\n";

tree($x); } } } }

Здесь использованы встроенные подпрограммы Perl opendiro, ciosediro, readdiro, применяемые соответственно для открытия каталога, его закрытия и чтения содержимого. Подпрограмма tree о рекурсивно просматривает каталог, переданный ей в качестве параметра, и выводит имена вложенных подкаталогов и содержащихся в них файлов в следующем виде:

/home/httpd/cgi-bin: /home/httpd/html:

/home/httpd/html/index.html /home/httpd/html/manual:

/home/httpd/html/manual/LICENSE

/home/httpd/html/manual/bind. html

/home/httpd/html/manual/cgi_path.html



Напишите подпрограмму, которая выводит пронумерованный



Упражнения

1. Напишите подпрограмму, которая выводит пронумерованный список своих аргументов.
2. Напишите подпрограмму, которая выводит пронумерованный список своих аргументов в обратном порядке.
3. Напишите подпрограмму, которая подсчитывает число символов из стандартного ввода и выводит результат.
4. Напишите подпрограмму, которая выводит свои параметры-массивы в обратном порядке по элементам.
5. Напишите подпрограмму, которая для двух своих параметров-массивов осуществляет взаимный обмен элементов с одинаковыми индексами.
6. Одной из известных задач, для решения которых применяется рекурсия, является задача о Ханойских башнях.
7. На плоскости установлены три стержня: а, b, с (Рисунок 11.1).
На стержень а нанизаны n дисков, расположенных по возрастанию диаметра. Необходимо переместить диски со стержня а на стержень с, используя стержень b и соблюдая следующие ограничения: можно перемещать только один диск одновременно, диск большего диаметра никогда не может находиться на диске меньшего диаметра.
Напишите подпрограмму, которая описывает последовательность переноса дисков в ходе решения задачи, выводя сообщения вида:
Перенос диска со стержня а на стержень с.

Рис 11.1. Задача о Ханойских башнях

В каких случаях функцию local нельзя заменить функцией ту



В каких случаях функцию local нельзя заменить функцией ту

В следующих случаях функция local () является незаменимой. П Присваивание временного значения глобальной переменной. В первую очередь это относится к некоторым предопределенным глобальным переменным, таким как $ARGV, $_ и т. д. Рассмотрим пример.

#!/usr/bin/perl $/ = under"; @ARGV = ("а"); $_ = <>;

print "Первое значение области ввода \$_= ", split,"\п"; {

local 0ARGV = ("аа"); local $_ = <>;

print "Второе значение области ввода \$_= ", split,"\п"; }

{

local 8ARGV = ("ааа"); local $_ = <>;

@fields = split;

print "Третье значение области ввода \$_= ", split, "\n";

}

print "Восстановленное значение области ввода \$_= ", split,"\n";

Пусть имеются три файла

"а": "аа": "ааа":

1111 1111 1111 2222 2222 2222 3333 3333 3333

аааа bbbb cccc dddd eeee ffff gggg hhhh iiii

В приведенной программе используются специальные глобальные переменные $/, $_ И OARGV.

Специальная переменная $/ содержит значение разделителя входных записей, которым по умолчанию является символ новой строки. Присваивание этой переменной неопределенного значения позволяет при помощи одной операции ввода <> считать весь файл, а не только первую строку.

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

Массив @ARGV содержит аргументы командной строки самой программы. Если при вызове программы ей будет передано имя файла, то оно будет сохранено в массиве @ARGV. Операция ввода о применяется к файлам, переданным в программу в качестве аргументов командной строки, т. е. к файлам, имена которых хранятся в массиве @ARGV. В нашем примере программа вызывается без аргументов, поэтому имя входного файла "а" задается внутри программы прямой записью в массив @ARGV. Первая операция ввода о, следовательно, осуществляется из файла "а". Далее следуют два блока операторов, заключенных в фигурные скобки. В каждом из них при помощи функции local () создаются временные значения для глобальных переменных @ARGV и @_. В первом блоке данные считываются из файла "аа" и сохраняются в качестве временного значения глобальной переменной $_, во втором — из файла "ааа" и также сохраняются в качестве следующего временного значения переменной $_. По выходе из второго блока глобальная переменная $_ восстанавливает свое первоначальное значение. В результате выполнения данной программы будет напечатано:


Первое значение области ввода $_= 11111111llllaaaabbbbcccc Второе значение области ввода $_= 222222222222ddddeeeeffff Третье значение области ввода $_= 3333333333 ; 33gggghhhhiiii Восстановленное значение области ввода $_= lilllllllllllaaaabbbbcccc

Создание локального дескриптора файла, каталога или локального псевдонима для функции.

В следующем примере функция local о используется для создания локального дескриптора файла внутри блока операторов.

#!/usr/bin/perl

open(FILEHANDLE,">b");

print FILEHANDLE "Новая строка в файл 'b'\n";

{

local *FILEHANDLE;

open(FILEHANDLE,">bb");

print FILEHANDLE "Новая строка в файл 'bb'\n";

close FILEHANDLE; }

{

local *FILEHANDLE;

open(FILEHANDLE,">bbb");

print FILEHANDLE "Новая строка в файл 'bbb'\n" ;

close FILEHANDLE; > J

print FILEHANDLE "Еще одна строка в файл 'b'\n"; close FILEHANDLE;

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

"b":

Новая строка в файл 'b'

Еще одна строка в файл 'b'

"bb":

Новая строка в файл 'bb'

"bbb":

Новая строка в файл 'bbb'

Заметьте, что во время выполнения операций с файлами "bb" и "bbb" файл "Ь" остается открытым.

Аналогичным образом может быть определено локальное имя для функции.

#!/usr/bin/perl

# функция NumberOfArgs() возвращает число своих параметров sub NumberOfArgs {

return $#_ + 1;

) ' . . ' print "NumberOfArgs: число параметров=", NumberOfArgs(1,2,3,4),"\n"; {

local *Numbers = *NumberOf Args;

print "Numbers: число параметров=", Numbers (1, 2, 3} , "\n"; } {

local *N = \SNumberOfArgs;

print "N: число параметров=", N(1,2), "\n"; }

Результат выполнения:

NumberOfArgs: число параметров=4 Numbers: число параметров=3 N: число параметров=2

Временное изменение элемента массива или хеш-массива.

В следующем примере внутри блока операторов временно изменяется значение одного элемента глобального хеш-массива %ENV, содержащего значение переменной $РАТН, входящей в состав среды интерпретатора UNIX shell.

tt!/usr/bin/perl

print "значение переменной среды \$РАТН:\n$ENV{PATH}\n"; {

local $ENV{PATH} = "/home/mike/bin"; I -print "временное значение переменной среды \$РАТН: $ENV{PATH}\n";

}

print "прежнее значение переменной среды \$РАТН:\n$ENV{PATH}\n";

Результат будет выведен в следующем виде:

значение переменной среды $РАТН:

/sbin: /usr/sbin: /usr/bin: /bin: /usr/XHR6/bin: /usr/local/ bin: /opt/bin

временное значение переменной среды $РАТН: /home/mike/bin

прежнее значение переменной среды $РАТН:

/sbin: /usr/sbin: /usr/bin: /bin: /usr/XHR6/bin: /usr/local/bin: /opt/bin




Вопросы для самоконтроля

1. Какие формы обращения к подпрограмме вы знаете?
2. Что такое область видимости переменной?
3. Как ограничить область видимости переменной?
4. Чем отличаются переменные, объявленные при помощи функции ту о от переменных, объявленных при помощи функции local () ?
5. Каким образом данные передаются в подпрограмму и из подпрограммы?
6. Что такое передача параметров по ссылке и по значению?
7. Какой тип данных называется typegiob?
8. Как осуществить передачу по ссылке параметра-массива?
9. В каких случаях функция local о не может быть заменена функцией my ()?
10. Что такое прототип?
11. Какие значения будут иметь переменные $х, @list1, @list2 после выполнения программы
#!/usr/bin/perl
$х = 0;
@list1 = (1, 2, 3);
@list2 = funcO ; sub func {
local ($x);
$x = 1;
@list1 = (4, 5, 6); ___
} "X
\


Вызов подпрограммы



Вызов подпрограммы

Мы знаем, что принадлежность к тому или иному типу определяется префиксом имени: $х — скалярная переменная, @х — массив, %х — ассоциативный массив. Префиксом функции является символ "&". К любой подпрограмме можно обратиться, указав ее имя с префиксом &:

Sname args; Sname(args); Sname;

Здесь args обозначает список аргументов подпрограммы. Если список аргументов отсутствует, вместо него используется специальный массив @_.

Если после имени подпрограммы следуют скобки, префикс & можно опустить:

name (args); name();

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

sub name {. . .}; name args;

name ;

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

$subref = sub (...}; Ssubref(args) ;

ssubref;

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



Автозагрузка



Автозагрузка

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

#!/usr/bin/perl sub AUTOLOAD {

print "Функция $AUTOLOAD не определена\п"; } print "Начало работы\п";

f();

print "Конец работы\n";

Функция f (), в отличие от функции AUTOLOAD, не определена в пакете main, поэтому в результате выполнения данной программы будут выведены сообщения:

Начало работы

Функция main::f не определена

Конец работы

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



Библиотеки



Библиотеки

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

Исторически первым средством являются библиотеки, появившиеся в версии Perl 4. Библиотека представляет собой пакет, областью действия которого является отдельный файл. Иными словами, библиотека — это файл, содержащий в качестве первой строки объявление пакета.

package package_name;

Имя файла библиотеки обычно имеет расширение pi.

Замечание

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

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

Ключевое слово require служит для обозначения встроенной функции Perl. Фактически обращение к функции require () используется в качестве директивы компилятора. Поэтому дальше мы будем использовать применительно к ключевому слову require оба термина: "функция" и "директива-компилятора". Выясним, какой смысл имеет эта директива.



Функция no ()



Функция no ()

Существует функция no (), противоположная по своему назначению функции use о и также выполняющая роль директивы компилятора

no Module LIST

Директива по отменяет действия, связанные с импортом, осуществленные ранее директивой use, вызывая метод unimport () Module LIST.



Функция require()



Функция require()

require [EXPR]

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

Если параметр EXPR отсутствует, вместо него используется специальная переменная $_.

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

require 5.005;

Более ранние версии Perl вызовут немедленное завершение интерпретатора с выдачей сообщения об ошибке.

Если параметр является строкой, функция require включает в основную программу библиотечный файл, задаваемый параметром EXPR. Логика работы функции require соответствует следующему коду:

sub require {

my($filename) = @_; return 1 if $INC{$filename}; . my($realfilename,$result); ITER: {

foreach $prefix (@INC) {

$realfilename = "$prefix/$filename"; if (-f $realfilename) {

$result = do $realfilename; last ITER; } }

die "Can't find $filename in \@INC"; }

die $@ if $@;

die "$filename did not return true value" unless $result; $INC{$filename} = $realfilename; return $result; }

Замечание

Специальный встроенный массив OINC содержит имена каталогов, в которых следует искать сценарии Peri, подлежащие выполнению в конструкциях do filename, require или use. Первоначально содержит:

• имена каталогов, переданные при запуске интерпретатору peri в качестве параметра ключа -i;

• имена библиотечных каталогов по умолчанию (зависят от операционной системы);

• символическое обозначение текущего каталога ".".

Специальный встроенный хеш-массив % INC содержит по одному элементу для каждого файла, включенного при помощи do или require. Ключом является имя файла в том виде, как оно указано в качестве аргумента функций do или require, а значением — его полное маршрутное имя.


Встретив директиву require myfile, интерпретатор peri просматривает специальный хеш-массив %INC, проверяя, не был ли файл (rayfile уже включен ранее при помощи функций require или do. Если да, то выполнение функции require завершается. Таким образом файл под одним именем может быть включен только один раз. В противном случае интерпретатор просматривает каталоги, имена которых содержатся в специальном массиве @INC, в поисках файла ту file. Если файл найден, он выполняется, иначе директива require завершает выполнение с выдачей сообщения об ошибке:

Can't find myfile in @INC



Замечание
Замечание об использовании имени файла В Директиве require EXPR. Обычно имена библиотечных файлов имеют суффикс ".pl", например, myfile.pl. Интерпретатор peri воспринимает точку"." в качестве знака операции конкатенации двух строк "myfile" и "pi" и пытается найти файл myfiiepl. Во избежание подобных ошибок имя файла в директиве require следует заключать в кавычки:
require "myfile.pl";

Если аргумент EXPR является словом без суффиксов, не заключенным в кавычки, то директива require предполагает, что оно имеет суффикс ".рl", чтобы облегчить загрузку стандартных модулей, имеющих расширение " .рl".




Функция use ()



Функция use ()

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

ФУНКЦИЯ use () use Module [LIST] use VERSION

служит для загрузки модуля во время компиляции.

Директива use автоматически экспортирует имена функций и переменных в основное пространство имен текущего пакета. Для этого она вызывает метод import () импортируемого модуля. Механизм экспорта имен устроен таким образом, что каждый экспортирующий модуль должен иметь свой метод import (), который используется программой, импортирующей имена. Метод import () должен быть определен в самом экспортирующем модуле или наследован у модуля Exporter. Большинство модулей не имеют своих собственных методов import (), вместо этого они экспортируют его из модуля

Exporter.

Логику работы директивы use можно описать одной строкой:

BEGIN { require Module; import Module LIST; }

Здесь значением параметра Module должно быть слово без суффиксов, не заключенное в кавычки.

Если первый аргумент директивы use является числом, он обозначает номер версии интерпретатора peri. Если номер версии текущего интерпретатора peri меньше, чем значение VERSION, интерпретатор выводит сообщение об ошибке и завершает работу.

Конструктор пакета BEGIN вызывает немедленное выполнение подпрограммы require {) и метода import о до завершения компиляции оставшейся части файла.

Выше мы рассмотрели логику работы функции require(). Она загружаете память файл Module.pm, выполняя его при помощи функции dot). Затем метод import() модуля Module.pm импортирует в вызывающую программу имена, определенные в Module.pm, в соответствии со списком LIST.

Если список импорта LIST отсутствует, из Module будут импортированы те имена, которые перечислены в специальном массиве @EXPORT, определенном В самом Module.

Если список импорта задан, то в вызывающую программу из модуля Module будут импортированы только имена, содержащиеся в списке LIST.  



Конструктор и деструктор пакета ВEGIN и END



Конструктор и деструктор пакета ВEGIN и END

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

Для создания пакета, как мы знаем, требуется только его объявление (в том числе, предполагаемое по умолчанию объявление package main). Вместе с тем, существуют специальные подпрограммы, выполняющие функции инициализации и завершения пакета. По аналогии их можно назвать конструкторами и деструкторами пакета, хотя никаких пакетов они не создают и не удаляют. Это подпрограммы BEGIN и END. При описании этих подпрограмм ключевое слово sub, необходимое при объявлении обычной подпрограммы, можно опустить. Таким образом, /синтаксис, подпрограмм BEGIN, END имеет вид:

BEGIN (block) END {block}

Подпрограмма BEGIN выполняется сразу после своего определения до завершения компиляции оставшейся части программы. Попробуйте запустить интерпретатор peri в интерактивном режиме. Если ему передать строку

print "Привет!";

то он напечатает ее только после того, как обнаружит во входном потоке признак конца файла (например, комбинацию <Ctrl>+<D>). Если же в интерактивном режиме определить конструктор пакета

BEGIN {print "Привет!"};

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

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

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

END {

print "Завершаем работу, до свидания\п";

} . . BEGIN {

print "Привет, начинаем работу\n"; }

print "Это тело программы\n"; BEGIN {

print "Еще один блок BEGIN после блока END\n"; }

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

Привет, начинаем работу

Еще один блок BEGIN после блока END

Это тело программы

Завершаем работу, до свидания



Модули



Модули

Дальнейшим развитием понятия библиотеки явилось понятие модуля, возникшее в версии Perl 5. Модуль представляет собой библиотеку подпрограмм, обладающую дополнительными свойствами по сравнению с библиотеками Perl 4. Он позволяет управлять экспортом своих имен в другие программы, объявляя, какие из них экспортируются по умолчанию, а какие должны быть явно указаны в соответствующем операторе вызывающей программы.

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

Для целей управления экспортом каждый модуль должен располагать методом import о и определить специальные массивы @EXPORT и @EXPORT_OK.

(Понятие "метод" используется в объектно-ориентированном программировании, которое обсуждается )

Вызывающая программа обращается для импорта символов^ к методу import () экспортирующего модуля.

Специальный массив @ EXPORT содержит идентификаторы, экспортируемые по умолчанию.

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

С появлением модулей появилась новая директива для их подключения к основной программе. Эта директива реализуется функцией use ().



Пакеты



Пакеты

В части 11 мы упомянули о том, что область действия переменных ограничена пакетом. Рассмотрим этот вопрос более подробно.

Итак, пакет — это способ создания собственного изолированного пространства имен для отдельного отрезка программы. Каждый фрагмент кода Peri-программы относится к некоторому пакету. Объявление

package NAMESPACE;

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

Подпрограммы;

блока операторов, заключенного в фигурные скобки;

строки, переданной на выполнение функции eval ();

файла.

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

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

package main;

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

К переменной из другого пакета можно обратиться, указав перед ее именем префикс, состоящий из имени этого пакета, за которым следуют два двоеточия: $PackageName: :name. Такие имена условимся называть квалифицированными именами. Если имя пакета отсутствует, предполагается имя main, т. е. записи $: :var и $main: :var обозначают одну и ту же переменную.


Специальная лексема _PACKAGE_ служит для обозначения имени текущего пакета.

#!/usr/bin/perl

$x=_PACKAGE_;

print "package $x:\n";

print "\$x= $x\n";

print "\$two::x= $two::x\n";

print "\$three::x= $three::x\n";

eval 'package two; $x=_PACKAGE_; print " package $x:\n"; print "\$x= $x\n"; print "\$main::x= $main::x\n"; print "\$three::x= $three::x\n";';

print "package $x:\n"; print "\$x= $x\n";

package three;

$x=_PACKAGE_;

print "package $x:\n";

print "\$x= $x\n";

print "\$main::x= $main::x\n";

print "\$two::x= $two::x\n";

package main;

print "package $x:\n";

print "\$x= $x\n";

print "\$two::x= $two::x\n";

print "\$three::x= $three::x\n";

В результате выполнения будут выведены следующие значения:

package main: $х= main

$two::x= $three::x=

package two: $x= two

$main::x= main $three::x=

package main: $x= main

package three: $x= three $main: :x= main $two::x= two

package main: $x= main $two::x= two $three::x= three

В данном примере используются три пакета, каждый со своим пространством имен: main, two, three. В каждом пакете определена переменная $х, значение которой совпадает с именем пакета. С пакетом main связаны следующие отрезки программы:

от начала программы до вызова функции evai ();

после вызова функции evai о до объявления пакета package three;

после явного объявления пакета package main до конца файла, содержащего данную программу.

Для выражения, выполняемого функцией evai (), определено собственное пространство имен two. Оно действует только в пределах этого выражения. Если бы внутри функции evai о не был определен собственный пакет two, все действия внутри нее были связаны с пакетом, в котором функция evai () была скомпилирована, т. е. с пакетом main.

С пакетом three связана часть программы от объявления package three до объявления package main.

В каждом пакете происходит обращение к переменным из двух других пакетов при помощи указания соответствующего префикса имени переменной.

Компилятор создает для каждого пакета отдельное пространство имен. Переменным $х из разных пакетов присваиваются их значения по мере выполнения соответствующего кода программы. Вот почему при первом обращении из пакета main к переменным two: :$x и $three: :x их значения еще не определены.




Прагма-библиотеки



Прагма-библиотеки

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

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

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

#!/usr/bin/perl

print "Арифметика с плавающей точкой: 2/3= ", 2/3, "\n";

use integer;

print "Целочисленная арифметика: 2/3= ",2/3, "\n";

no integer;

print "Возврат к арифметике с плавающей точкой: 2/3= ", 2/3, "\n";

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

Арифметика с плавающей точкой: 2/3= 0.666666666666667

Целочисленная арифметика: 2/3=0

Возврат к арифметике с плавающей точкой: 2/3= 0.666666666666667

В дистрибутивный комплект Perl входит стандартный набор прагма-библиотек. Некоторые из них представлены в табл. 12.1.

Таблица 12.1. Некоторые прагма-библиотеки


Прагма-библиотека

Назначение

diagnostics

Включает режим диагностики с выдачей подробных сообщений

integer

Применение целочисленной арифметики вместо арифметики с плавающей точкой

lib

Позволяет добавлять элементы в специальный массив @INC во время компиляции

overload

Режим переопределения операций Perl, например, директива

package Number; use overload "+" => \&add;

определяет функцию Number : : add ( ) в качестве операции сложения

sigtrap

Директива, позволяющая управлять обработкой сигналов в UNIX

strict

Режим ограниченного использования "опасных" конструкций Perl

use strict "refs";

генерирует ошибку выполнения при использовании символических ссылок

use strict "vars";

генерирует ошибку компиляции при попытке обращения к переменной, которая не была объявлена при помощи директивы use vars, локализована при помощи функции ту() или не является квалифицированным именем

use strict "subs";

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

use strict;

эквивалентно заданию всех трех рассмотренных выше ограничений

subs

Служит для предварительного объявления подпрограмм, указанных в списке:

use subs qw(subl sub2 sub3);

vars

Служит для предварительного объявления переменных, указанных в списке

use vars qw($scal @list %hash) ;

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


Создание и подключение библиотечного файла



Создание и подключение библиотечного файла

Для создания собственной библиотеки следует выполнить следующие шаги. П Создать каталог для хранения библиотечных файлов.

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

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

$result = do $realfilename;

Значение $resuit, возвращаемое функцией require, должно быть ненулевым, что является признаком успешного выполнения кода инициализации. Простейший способ получить ненулевое значение — добавление в конец каждого библиотечного файла строки "1,-".

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

Добавить в массив @INC имя каталога, содержащего библиотечные файлы, либо при запуске основной программы передать это имя интерпретатору peri при помощи ключа -i. \ '

Создадим библиотечный файл myiib.pl и поместим его в каталог /usr/temp/periiib. Файл myiib.pl содержит единственную подпрограмму Numof Args (), которая выводит число аргументов, переданных ей при вызове.

# библиотечный файл /usr/temp/perllib/mylib.pl sub NumOfArgs {

return $#_+!; }

I;

Создадим файл основной программы mymain.pi:

#!/usr/bin/perl

unshift (@INC, "/usr/temp/perllib");

require "mylib.pl";

print "Число аргументов=", NumOfArgs(1,2,3,4), "\n";

В результате выполнения файла mymain.pi будет выведена строка

Число аргументов=4

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



Создание и подключение модуля



Создание и подключение модуля

Для создания модуля MyModuie следует создать пакет и сохранить его в файле MyModuie. pm. Расширение .pm является признаком того, что данный файл является модулем Perl.

В следующем примере мы создадим собственный модуль MyModuie, содержащий одну функцию MyArgs о , одну скалярную переменную $MyArgs, один массив емуАгдз и один хеш-массив $MуАrgs. Затем создадим файл основной программы MyMain.pi, экспортирующий модуль MyModuie, используя директиву use.

Файл МОДУЛЯ MyModuie. pm:

package MyModuie;

require Exporter;

@ISA = 'qw(Exporter) ;

SEXPORT = qw(MyArgs) ;

@EXPORT_OK = qw($MyArgs @MyArgs %MyArgs);

sub MyArgs {

my ($x, $i);

SMyArgs = @_;

$MyArgs = $#MyArgs + 1;

foreach $x (@MyArgs) {

$MyArgs{$x}=++$i ; }

}

Файл основной вызывающей программы MyMain.pi: ^

tt!/usr/bin/perl

use MyModuie qw(:DEFAULT $MyArgs @MyArgs %MyArgs);

MyArgs one, two, three, four;

print "число аргументов=$МуАгдз\n";

print "массив аргументов: @MyArgs\n";

print "хеш-массив аргументов:\n";

foreach $k (keys %MyArgs) {

print "\$MyArgs{$k}=$MyArgs{$k} "; }

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

Первая строка служит определением пакета.

Вторая строка осуществляет включение встроенного модуля Exporter. Так предоставляется возможность наследовать метод import, реализованный в этом модуле, и использовать стандартные соглашения для задания списка импорта в вызывающей программе.

Третья строка определяет массив @ISA, состоящий из одного элемента, содержащего название пакета Exporter. С каждым пакетом ассоциируется свой массив @ISA, включающий имена других пакетов, представляющих классы. Иногда интерпретатор встречает обращение к методу, не определенному в текущем пакете. Он ищет этот метод, просматривая пакеты, определенные в массиве @ISA текущего пакета. Таким образом в языке Perl реализован механизм наследования.


В четвертой и пятой строках определяются имена, которые будут экспортироваться за пределы модуля. Специальный массив OEXPORT содержит имена, экспортируемые по умолчанию. В четвертой строке указывается, что из данного МОДУЛЯ ПО умолчанию будет Экспортировано ИМЯ фуНКЦИИ MyArgs.

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

$MyArgs, Массива @MyArgs из ассоциативного массива %MyArgs.

Функция MyArgs подсчитывает число своих аргументов и запоминает его в переменной $MyASrgs. Затем она помещает аргументы в массив @MyArgs и формирует ассоциативный массив %MyArgs, в котором ключами являются имена аргументов функции MyArgs, а значениями — их порядковые номера.

К основной программе MyMain.pl модуль MyModuie подключается при помощи директивы use. Директива use содержит список импорта

qw(:DEFAULT $MyArgs @MyArgs %MyArgs)

Обычно список импорта включает в себя имена переменных и функций. Кроме того, он может содержать некоторые управляющие им спецификации. Спецификация : DEFAULT означает включение в список импорта всех элементов специального массива @EXPORT. В нашем случае это значит, что в список импорта будет добавлено имя функции MyArgs, содержащееся в списке @EXPORT. Кроме того, список импорта явно содержит имена $MyArgs, @MyArgs и %MyArgs. Экспорт этих имен по явному запросу вызывающей программы разрешен модулем MyModuie путем их включения в список

@EXPORT_OK.

В результате выполнения основной программы MyMain.pi будет получен вывод:

число аргументов=4

массив аргументов: one two three four хеш-массив аргументов: . $MyArgs{one}=1 $MyArgs{three}=3 $MyArgs{two}=2 $MyArgs{four}=4




Стандартные модули Perl



Стандартные модули Perl

В данном разделе мы рассмотрели вопросы, связанные с созданием модулей. В состав дистрибутива Perl входит большой набор стандартных модулей, предназначенных для выполнения определенных функций) Помимо них, существует огромная коллекция модулей, разработанных программистами всего мира, известная как коллекция модулей CPAN (Cbmprehensive Perl Archive Network). Ее копия поддерживается в сети Internet на многих анонимных ftp-серверах. В качестве отправной точки можно обратиться по адресу . Список стандартных модулей и категорий модулей CPAN приведен в приложении 2. Здесь же мы в заключение рассмотрим специальный вид стандартных модулей — прагма-библиотеки.



Таблицы символов.



Таблицы символов.

С каждым пакетом связана таблица символов. Она представляет собой хеш-массив, имя которого образовано из имени пакета, за которым следуют два двоеточия. Например, таблица символов пакета main хранится в хеш-массиве %main::. Ключами этого хеш-массива являются идентификаторы переменных, определенных в пакете, значениями — значения типа typeglob, указывающие на гнездо, состоящее из одноименных переменных разных типов: скаляр, массив, хеш-массив, функция, дескриптор файла или каталога.

Тип typeglob, с которым мы уже сталкивались в главе И — это внутренний тип данных языка Perl, который используется для того, чтобы при помощи одной переменной типа typeglob сослаться на все одноименные переменные разных типов. Признаком типа typeglob является символ "*". Если переменной типа typeglob присвоить значение другой переменной типа

typeglob:

*у = *х;

то для всех переменных с именем х: $х, @х, %х, &х, будут созданы псевдонимы $у, @у, %у, &у соответственно. Можно создать псевдоним только для переменной определенного типа, например, для скалярной:

*у = \$х;

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

* !/usr/bin/perl

my ($key, $item) ;

print "Таблица символов пакета main:\n";

for $key (sort keys %main::) {

local *myglob = $main::{$key};

print "определен скаляр \$$key = $myglob\n" if defined $myglob; i^'.Med @myglob) {

опт "определен массив \@$key :\n"; for $item (0..$#myglob) {

p> ••••••• \$$key [$item] = $myglob[$item] \n";

} }

if (defined %myglob) {

print "определен хеш-массив \%$key :\n";

for $item (sort keys %myglob) {

print "\$$key {$item} = $myglob{$item}\n";

} } print "определена функция $key()\n" if defined Smyglob;


}
При помощи типа typegiob можно создавать скалярные псевдоконстанты. Например, после присваивания
*Р1 = \3.14159265358979;
выражение $PI обозначает операцию разыменования ссылки на константу. Его значением является значение самой константы 3.14159265358979. Значение $PI нельзя изменить, так как это означало бы попытку изменить константу.
В таблицу символов пакета, отличного от main, входят только идентификаторы, начинающиеся с буквы или символа подчеркивания. Все остальные идентификаторы относятся к пакету main. Кроме того, к нему относятся следующие начинающиеся с буквы идентификаторы: STDIN, STDOUT, STDERR, ARGV, ARGVOUT, ENV, INC, sic. Например, при обращении внутри некоторого пакета pack к хеш-массиву %ENV подразумевается специальный хеш-массив %ENV основного пакета main, даже если имя main не используется в качестве префикса для обозначения принадлежности идентификатора ENV.


Напишите программу, которая выводит таблицу



Упражнения

1. Напишите программу, которая выводит таблицу символов для пакета, заданного ее аргументом.
2. Создайте два модуля. Модуль Modi должен содержать подпрограмму reverselist о, которая переставляет в обратном порядке элементы в массиве, переданном ей в качестве параметра. Модуль Mod2 должен содержать массив @listt. Используйте оба модуля в основной программе, которая при помощи подпрограммы reverseiist о переставляет элементы в массиве @list.



Вопросы для самоконтроля

1. Что такое пакет?
2. Верно ли, что пакет должен всегда занимать отдельный файл?
3. Что такое таблица символов?
4. Сколько таблиц символов могут быть связаны с одним файлом, функцией, блоком операторов, заключенным в фигурные скобки?
5. Какие функции выполняют конструктор и деструктор пакета BEGIN и END?
6. Как определить имя текущего пакета?
7. Для чего нужна функция AUTOLOAD?
8. Что такое библиотека?
9. Назовите действия, необходимые для создания библиотечного файла.
10. Что такое модуль? В чем разница между модулем и библиотекой?
11. Объясните назначение массивов SEXPORT и @EXPORT_OK.
12. Чем похожи и чем отличаются функции use () и require () ?
13. Объясните, как создать модуль и подключить его к вызывающей программе.
14. Объясните назначение функции по ().
15. Что такое прагма-библиотека?