perlreftut
НАЗВАНИЕ
perlreftut - очень короткое руководство по ссылкам, которое написал Марк
ОПИСАНИЕ
One of the most important new features in Perl 5 was the capability to manage complicated data structures like multidimensional arrays and nested hashes. To enable these, Perl 5 introduced a feature called 'references', and using references is the key to managing complicated, structured data in Perl. Unfortunately, there's a lot of funny syntax to learn, and the main manual page can be hard to follow. The manual is quite complete, and sometimes people find that a problem, because it can be hard to tell what is important and what isn't.
К счастью, вам нужно знать только 10% того, что дает руководство, чтобы иметь 90% возможностей. На этой странице даны именно важные 10%.
Зачем нужны сложные структуры данных?
В Perl 4 постоянно вставала проблема представления данных в виде хэша, значениями которого являются списки. Конечно, в Perl 4 были хэши, но его значения должны были быть скалярами и не могли быть списками.
Для чего может понадобиться хэш списков? Возьмем простой пример: у вас есть файл с названиями городов и стран:
Chicago, USA Frankfurt, Germany Berlin, Germany Washington, USA Helsinki, Finland New York, USA
и вы хотите получить на выходе следующее: название страны, затем в алфавитном порядке список городов в этой стране:
Finland: Helsinki. Germany: Berlin, Frankfurt. USA: Chicago, New York, Washington.
Естественный путь сделать это - создать хэш, ключи которого - названия стран. Значением каждого ключа будет список городов этой страны. При чтении каждой входящей строки надо разделить ее на страну и город, затем добавить город в список городов данной страны, если такого города в списке еще не существует. После завершения чтения пройтись по хэшу в цикле, сортируя каждый список городов перед печатью.
Если значения хэша не могут быть списками, вы многое теряете. В Perl 4 значения хэша не могут быть списками, они могут быть только строками. Вы прогадали. Возможно, вам придется объединить названия всех городов в одну строку, затем, перед печатью разбить строку в список, который нужно отсортировать и преобразовать снова в строку. Это добавляет беспорядка и ошибок. И это нас расстраивает, потому что в Perl'е уже есть превосходные списки, которые могли бы решить данную проблему...
Решение
Ко времени обкатки Perl 5 мы уже завязли в этой концепции: Значения хэша могут быть только скалярами. Решение пришло в виде ссылок.
Ссылка — это скалярная величина, которая ссылается на весь массив или на весь хэш (или еще на что-нибудь). Имена — один из видов ссылок, которые вам уже известны. Подумайте о президенте США: грязный, неудобный мешок с кровью и костями. Но чтобы говорить о нем, или представить его в компьютерной программе, все что для этого нужно - удобная скалярная строка "Барак Обама".
Ссылки в Perl похожи на имена для списков или хэшей. Это частные, внутренние имена perl программы, поэтому можно быть уверенным, что они однозначны. В отличие от "Барак Обама", ссылка ссылается только на один объект, и вы всегда знаете, на какой. Если у вас есть ссылка на массив, вы можете получить весь массив. Если у вас есть сслыка на хэш, вы можете получить весь хэш. Но ссылка все еще легкая, компактная скалярная величина.
Не может быть хэша, значения которого - массивы; значения хэша могут быть только скалярами. Мы опять застряли. Но одна ссылка может указывать на целый массив, и при этом ссылка остается скаляром, так что можно создать хэш ссылок на массивы, и это будет работать, как хэш массивов, и будет таким же полезным, как хэш массивов.
Мы вернемся к проблеме городов и стран позже, после того, как рассмотрим синтаксис управления ссылками.
Синтаксис
Существует два способа создания ссылок, и два способа их использования.
Создание ссылок
Правило создания 1
Поместив перед переменной обратный слеш \
, вы получите
ссылку на эту переменную.
$aref = \@array; # $aref получает ссылку на @array $href = \%hash; # $href содержит ссылку на %hash $sref = \$scalar; # $sref присваивает ссылку на $scalar
После того, как ссылка сохранена в переменной, ее можно копировать или просто хранить, как любую другую скалярную величину:
$xy = $aref; # $xy теперь содержит ссылку на @array $p[3] = $href; # $p[3] содержит ссылку на %hash $z = $p[3]; # $z содержит ссылку на %hash
В этих примерах показано, как создавать ссылки на именованные
объекты. Может быть, вы захотите создать массив или хэш, у
которого нет имени, по аналогии с возможностью использовать
строку "\n"
или число 80, не сохраняя их в именованной переменной.
Правило создания 2
Конструкция [ элемент1, элемент2, ... ] создает новый анонимный массив и возвращает ссылку на этот массив. Конструкция { элемент1 => элемент2, ... } создает новый анонимный хэш и возвращает ссылку на этот хэш.
$aref = [ 1, "foo", undef, 13 ]; # $aref содержит ссылку на массив $href = { APR => 4, AUG => 8 }; # $href содержит ссылку на хэш
Ссылка, которую вы получаете по правилу 2, такая же, как и та, которая создана по правилу 1:
# Это: $aref = [ 1, 2, 3 ]; # Делает то же самое, что и это: @array = (1, 2, 3); $aref = \@array;
Первая строка — сокращение последующих двух строк, кроме того, что
она не создает излишней переменной массива @array
.
Если вы напишете просто []
, вы получите новый пустой анонимный массив.
Если вы напишите просто {}
, вы получите новый пустой анонимный хэш.
Использование ссылок
Что можно сделать со ссылкой после её создания ? Мы уже видели, что её можно хранить как скалярную величину и получить её значение. Есть еще два способа её использования:
Правило использования 1
Можно использовать ссылку на массив в фигурных скобках, вместо имени
массива. Например, @{$aref}
вместо @array
.
Несколько примеров:
Массивы:
@a @{$aref} Массив reverse @a reverse @{$aref} Массив в обратном порядк $a[3] ${$aref}[3] Элемент массива $a[3] = 17; ${$aref}[3] = 17 Присваивание значения элементу массива
В каждой строке два выражения, которые равносильны друг другу.
Выражения слева работают с массивом @a
. Справа — работают с
массивом посредством ссылающейся на него ссылки $aref
.
После нахождения массива оба варианта выполняют одинаковые операции.
Ссылки на хэш используются точно так же:
%h %{$href} Хэш keys %h keys %{$href} Получение ключей хэша $h{'red'} ${$href}{'red'} Элемент хэша $h{'red'} = 17 ${$href}{'red'} = 17 Присвоение значения элементу
Все что можно сделать со ссылкой, делается по Правилу использования 1.
Вы просто пишите код на Perl, который делает то, что нужно с обычным
массивом или хэшем, а затем заменяете имя массива или хэша {$ссылкой}
.
"Как перебрать элементы массива, если у меня есть ссылка ?" Чтобы
перебрать элементы массива, нужно написать
for my $element (@array) { ... }
затем замените имя массива @array
, ссылкой:
for my $element (@{$aref}) { ... }
"Как распечатать содержимое хэша, если у меня есть только ссылка?" Сначала напишем код для распечатки хэша:
for my $key (keys %hash) { print "$key => $hash{$key}\n"; }
И затем заменяем имя хэша ссылкой:
for my $key (keys %{$href}) { print "$key => ${$href}{$key}\n"; }
Правило использования 2
Правило использования 1 — это все, что вам действительно нужно, потому что оно описывает абсолютно все действия со ссылкой. Но чаще всего приходится извлекать единственный элемент массива или хэша, и запись по Правилу использования 1 громоздка. Поэтому есть несколько сокращений.
${$aref}[3]
трудно читать, поэтому можно писать $aref->[3]
.
${$href}{red}
нечитабельна, лучше писать $href->{red}
.
Если $aref
содержит ссылку на массив, тогда $aref->[3]
—
четвертый элемент массива. Не перепутайте его с $aref[3]
, что
есть четвертый элемент совершенно другого массива, обманчиво
названного @aref
.
Так же, $href->{'red'}
— часть хэша, возможно даже безымянного
на который указывает скалярная переменная $href
. $href{'red'}
—
часть обманчиво названного хэша %ref
. Легко забыть вставить ->
,
в этом случае вы получите странные результаты, когда программа будет
извлекать элементы совершенно неожиданных массивов и хэшей, которые
вы совсем не хотели использовать.
Пример
Небольшой пример того, как все это можно использовать.
Во-первых, следует помнить, что [1, 2, 3]
создает анонимный массив,
содержащий (1, 2, 3)
, и возвращает ссылку на этот массив.
Тогда:
@a = ( [1, 2, 3], [4, 5, 6], [7, 8, 9] );
@a представляет собой массив из трех элементов, каждый из которых является ссылкой на другой массив.
$a[1]
одна из этих ссылок. Она ссылается на массив, массив
содержащий (4, 5, 6)
, и, так как это ссылка на массив, по
Правилу использования 2 мы можем написать $a[1]->[2]
,
чтобы получить третий элемент этого массива. $a[1]->[2]
равно 6. Аналогично, $a[0]->[1]
равно 2. То, что мы здесь
имеем, похоже на двумерный массив, можно писать
$a[СТРОКА]->[КОЛОНКА]
, чтобы получить или внести элемент
в любую строку и любого столбца массива.
Но эта нотация все еще тяжеловата, поэтому есть еще одно сокращение:
Правило стрелки
Между двумя индексами стрелка не обязательна.
Вместо $a[1]->[2]
, мы можем написать $a[1][2]
, результат тот же.
Аналогично, вместо $a[0]->[1] = 23
, мы можем написать $a[0][1] = 23
.
Теперь это в самом деле выглядит, как двумерный массив!
Из этого видно, почему важны стрелки. Без них пришлось бы писать
${$a[1]}[2]
вместо $a[1][2]
. Для трехмерных массивов можно
писать $x[2][3][5]
вместо нечитаемого ${${$x[2]}[3]}[5]
.
Решение
Теперь — решение поставленнного в начале вопроса о переформатировании названий городов и стран.
1 my %table; 2 while (<>) { 3 chomp; 4 my ($city, $country) = split /, /; 5 $table{$country} = [] unless exists $table{$country}; 6 push @{$table{$country}}, $city; 7 } 8 foreach $country (sort keys %table) { 9 print "$country: "; 10 my @cities = @{$table{$country}}; 11 print join ', ', sort @cities; 12 print ".\n"; 13 }
В программе две части: Строки 2—7 считывают данные и создают структуры данных,
а строки 8—13 анализируют данные и распечатывают отчет. Мы получаем хэш
%table
, ключи которого — названия стран, а значения — ссылки на массивы
названий городов. Эта структура выглядит примерно так:
%table +-------+---+ | | | +-----------+--------+ |Germany| *---->| Frankfurt | Berlin | | | | +-----------+--------+ +-------+---+ | | | +----------+ |Finland| *---->| Helsinki | | | | +----------+ +-------+---+ | | | +---------+------------+----------+ | USA | *---->| Chicago | Washington | New York | | | | +---------+------------+----------+ +-------+---+
Рассмотрим сначала вывод. Предположим, что у нас уже есть эта структура, как ее распечатать?
8 foreach $country (sort keys %table) { 9 print "$country: "; 10 my @cities = @{$table{$country}}; 11 print join ', ', sort @cities; 12 print ".\n"; 13 }
%table
— обычный хэш, и мы получаем список его ключей, которые, как
обычно, сортируем и перебираем. Единственное использование ссылки в строке 10.
$table{$country}
ищет в хэше ключ $country и получает значение — ссылку
на массив городов этой страны. По Правилу использования 1 мы можем получить
массив обратно используя конструкцию @{$table{$country}}
. Строка 10
равносильна такой конструкции:
@cities = @array;
кроме того, что имя array заменено ссылкой {$table{$country}}
.
Знак @
указывает интерпретатору Perl получить весь массив.
Получив список городов, как обычно, сортируем его соединяем и распечатываем.
Строки 2-7 отвечают в первую очередь за построение структуры. Приводим их еще раз:
2 while (<>) { 3 chomp; 4 my ($city, $country) = split /, /; 5 $table{$country} = [] unless exists $table{$country}; 6 push @{$table{$country}}, $city; 7 }
В строках 2-4 мы получаем название города и страны. Строка 5 проверяет
наличие такой страны в ключах хэша. Если ее нет, программа использует
запись []
(Правило создания 2), чтобы получить новый пустой
анонимный массив городов, и установить ссылку на него в качестве
значения для соответствующего ключа хэша.
Строка 6 добавляет название города в соответствующий массив.
$table{$country}
теперь содержит ссылку на массив городов в
этой стране, которые внесены до данного момента. Строка 6 подобна
push @array, $city;
кроме того, что имя array заменено ссылкой $table{$country}}
.
push
добавляет название города в конец указываемого массива.
Есть один интересный момент, который я упустил. Строка 5 не является необходимой, и мы можем избавиться от неё.
2 while (<>) { 3 chomp; 4 my ($city, $country) = split /, /; 5 #### $table{$country} = [] unless exists $table{$country}; 6 push @{$table{$country}}, $city; 7 }
Если в %table
уже есть запись для текущей страны $country
, то
ничего не изменится. Строка 6 найдет значение $table{$country}
,
которое указывает на массив, и добавит город $city
в массив.
Но что произойдет, если $country
содержит ключ, которого еще нет
в %table
, например, Greece
?
This is Perl, so it does the exact right thing. It sees that you want
to push Athens
onto an array that doesn't exist, so it helpfully
makes a new, empty, anonymous array for you, installs it into
%table
, and then pushes Athens
onto it. This is called
'autovivification'--bringing things to life automatically. Perl saw
that they key wasn't in the hash, so it created a new hash entry
automatically. Perl saw that you wanted to use the hash value as an
array, so it created a new empty array and installed a reference to it
in the hash automatically. And as usual, Perl made the array one
element longer to hold the new city name.
Остальное
Я обещал дать 90% возможностей, используя 10% подробностей, и это значит, что 90% подробностей я упустил. Теперь, после обзора самого важного, вам будет легче читать perlref, в котором даны 100% информации.
Некоторые из самых значимых моментов perlref:
-
Можно создавать ссылки на все объекты, включая скаляры, функции, и другие ссылки.
-
В Правилу использования 1 можно опустить фигурные скобки, когда в них атомарная скалярная переменная, как
$aref
. Например,@$aref
— то же самое, что@{$aref}
, а$$aref[1]
— то же, что${$aref}[1]
. Пока вы не освоитесь, вам лучше привыкнуть всегда писать фигурные скобки. -
Так массив не скопировать:
$aref2 = $aref1;
Вы получаете две ссылки на один и тот же массив. Если вы измените
$aref1->[23]
и затем посмотрите на$aref2->[23]
, вы увидите, что они не равны.Чтобы скопировать массив, используйте
$aref2 = [@{$aref1}];
Здесь используется запись
[...]
для создания нового анонимного массива. Переменной$aref2
присваивается ссылка на новый массив. Новый массив инициализируется содержимым массива, на который указывает$aref1
.Похожим способом можно скопировать анонимный хэш:
$href2 = {%{$href1}};
-
Чтобы узнать, содержит ли переменная ссылку, используйте функцию
ref
. Она возвращает истину, если ее аргумент является ссылкой. На самом деле она делает даже лучше: возвращаетHASH
для ссылки на хэш иARRAY
для ссылки на массив. -
Если попробуете использовать ссылку как строку, вы получите строку вроде таких
ARRAY(0x80f5dec) or HASH(0x826afc0)
Если вы увидите строку, которая выглядят подобным образом, знайте — вы по ошибке распечатали ссылку.
Побочный эффект такого представления в том, что вы можете использовать
eq
для проверки того, на один ли и тот же объект указывают ссылки. (Но обычно лучше использовать==
, потому что так работает намного быстрее). -
Вы можете использовать строку, как если бы она была ссылкой. Если вы используете строку
"foo"
как ссылку на массив, она считается как ссылка на массив@foo
. Это так называемая мягкая ссылка или символическая ссылка. Объявлениеuse strict 'refs'
отключает эту возможность, которая может вызвать всевозможные неприятности, если вы используете её случайно.
Возможно, вместо perlref
, вам стоит сначала прочесть perllol
,
в ней детально обсуждаются списки списков и многомерные массивы.
После этого ознакомьтесь с perldsc
— справочник структур данных,
в котором даны рецепты по использованию и распечатки массива хэшей,
хэшей массивов, и других типов данных.
Итого
Всем нужны сложные структуры данных, и Perl позволяет создавать их с помощью ссылок. Есть четыре правила управления ссылками: два правила создания и два правила использования. Когда вы усвоите эти правила, вы сможете делать почти все важное, что можно сделать с ссылками.
АВТОРЫ
Автор: Mark Jason Dominus, Plover Systems (mjd-perl-ref+@plover.com ) Перевод: Иван Рак (rak@tut.by)
Впервые статья появилась в The Perl Journal ( http://www.tpj.com/ ), том 3, #2. Перепечатывается с разрешения.
Первоначально статья называлась Пойми ссылки сегодня (анг. Understand References Today).
Условия распространения
Copyright 1998 The Perl Journal.
Этот документ распространяется свободно, и вы можете свободно распространять и/или изменять его на тех же условиях, как и сам Perl.
Независимо от распространения, все примеры кода в этом файле являются публичным достоянием. Разрешается и поощряется использование этого кода в ваших собственных программах для развлечения или для получения прибыли. Простой комментарий с указанием авторства приятен, но не обязателен.