perlreftut
СОДЕРЖАНИЕ
- ИМЯ
- ОПИСАНИЕ
- Кому нужны сложные структуры данных?
- Решение
- Синтаксис
- Решение
- Остальное
- Заключение
- Благодарности
ИМЯ
perlreftut - Краткий учебник Марка о ссылках
ОПИСАНИЕ
Одной из самых важных новых функций в Perl 5 была возможность управлять сложными структурами данных, такими как многомерные массивы и вложенные хэши. Для этого в Perl 5 была введена функция под названием ссылки, и использование ссылок является ключом к управлению сложными, структурированными данными в Perl. К сожалению, нужно изучить много странного синтаксиса, а основная страница справки может быть трудночитаемой. Справка довольно полная, и иногда это является проблемой, потому что бывает трудно определить, что важно, а что нет.
К счастью, вам нужно знать только 10% из того, что содержится на главной странице, чтобы получить 90% пользы. Эта страница покажет вам эти 10%.
Кому нужны сложные структуры данных?
Одна проблема, которая постоянно возникает, заключается в необходимости хэша, значениями которого являются списки. Perl, конечно, имеет хэши, но значения должны быть скалярами; они не могут быть списками.
Зачем нужен хэш списков? Давайте рассмотрим простой пример: у вас есть файл с названиями городов и стран, например:
Chicago, USA
Frankfurt, Germany
Berlin, Germany
Washington, USA
Helsinki, Finland
New York, USA и вы хотите получить вывод, подобный этому, с каждой страной, упомянутой один раз, а затем алфавитным списком городов в этой стране:
Finland: Helsinki.
Germany: Berlin, Frankfurt.
USA: Chicago, New York, Washington. Естественный способ сделать это — создать хэш, ключами которого являются имена стран. Каждому ключу имени страны соответствует список городов в этой стране. Каждый раз, когда вы читаете строку входных данных, разделяйте ее на страну и город, найдите список городов, уже известных в этой стране, и добавьте новый город в этот список. Когда вы закончите чтение входных данных, итерируйтесь по хэшу как обычно, сортируя каждый список городов перед его печатью.
Если значения хэша не могли бы быть списками, вы бы потеряли. Вам, вероятно, пришлось бы объединить все города в одну строку каким-то образом, а затем, когда пришло время выводить результат, вам пришлось бы разбить строку на список, отсортировать список и снова преобразовать его в строку. Это некрасиво и подвержено ошибкам. И это раздражает, потому что Perl уже имеет идеально подходящие списки, которые решили бы проблему, если бы только вы могли их использовать.
Решение
К моменту появления Perl 5 мы уже столкнулись с этой проблемой: значения хэша должны быть скалярами. Решением этой проблемы являются ссылки.
Ссылка — это скалярное значение, которое ссылается на весь массив или весь хэш (или на практически что угодно). Имена — это один из типов ссылок, с которыми вы уже знакомы. Каждый человек — это неудобное скопление клеток. Но для ссылки на конкретного человека, например, первого программиста-компьютерщика, не нужно описывать каждую его клетку; все, что вам нужно, это удобная скалярная строка «Ада Лавлейс».
Ссылки в Perl похожи на имена для массивов и хэшей. Это внутренние, частные имена Perl, поэтому вы можете быть уверены в их однозначности. В отличие от имени человека, ссылка ссылается только на одну вещь, и вы всегда знаете, на что она ссылается. Если у вас есть ссылка на массив, вы можете восстановить весь массив из нее. Если у вас есть ссылка на хэш, вы можете восстановить весь хэш. Но ссылка по-прежнему является удобным компактным скалярным значением.
Вы не можете иметь хэш, значениями которого являются массивы; значения хэша могут быть только скалярами. Мы с этим столкнулись. Но одна ссылка может ссылаться на весь массив, а ссылки — это скаляры, поэтому вы можете иметь хэш ссылок на массивы, и это будет работать очень похоже на хэш массивов, и это будет так же полезно, как хэш массивов.
Мы вернемся к этой проблеме с городами и странами позже, после того, как увидим синтаксис для работы со ссылками.
Синтаксис
Есть только два способа создать ссылку и только два способа использовать ее после создания.
Создание ссылок
Правило создания 1
Если вы поместите \ перед переменной, вы получите ссылку на эту переменную.
$aref = \@array; # $aref now holds a reference to @array
$href = \%hash; # $href now holds a reference to %hash
$sref = \$scalar; # $sref now holds a reference to $scalar После того, как ссылка сохранена в переменной, такой как $aref или $href, вы можете скопировать ее или сохранить ее так же, как любое другое скалярное значение:
$xy = $aref; # $xy now holds a reference to @array
$p[3] = $href; # $p[3] now holds a reference to %hash
$z = $p[3]; # $z now holds a reference to %hash Эти примеры показывают, как создавать ссылки на переменные с именами. Иногда вы хотите создать массив или хэш, у которого нет имени. Это аналогично тому, как вы хотели бы использовать строку "\n" или число 80 без необходимости сначала сохранить его в именованной переменной.
Правило создания 2
[ ITEMS ] создает новый анонимный массив и возвращает ссылку на этот массив. { ITEMS } создает новый анонимный хэш и возвращает ссылку на этот хэш.
$aref = [ 1, "foo", undef, 13 ];
# $aref now holds a reference to an array
$href = { APR => 4, AUG => 8 };
# $href now holds a reference to a hash Ссылки, получаемые по правилу 2, являются тем же типом ссылок, что и ссылки, получаемые по правилу 1:
# This:
$aref = [ 1, 2, 3 ];
# Does the same as this:
@array = (1, 2, 3);
$aref = \@array; Первая строка — это сокращение следующих двух строк, за исключением того, что она не создает излишнюю переменную массива @array.
Если вы напишете только [], вы получите новый пустой анонимный массив. Если вы напишете только {}, вы получите новый пустой анонимный хэш.
Использование ссылок
Что вы можете сделать со ссылкой, когда у вас есть ссылка? Это скалярное значение, и мы видели, что вы можете сохранить его как скаляр и получить его обратно, как любой скаляр. Есть еще два способа использовать ее:
Правило использования 1
Вы всегда можете использовать ссылку на массив в фигурных скобках вместо имени массива. Например, @{$aref} вместо @array.
Вот несколько примеров:
Массивы:
@a @{$aref} An array
reverse @a reverse @{$aref} Reverse the array
$a[3] ${$aref}[3] An element of the array
$a[3] = 17; ${$aref}[3] = 17 Assigning an element В каждой строке находятся два выражения, которые делают одно и то же. Версии слева работают с массивом @a. Версии справа работают с массивом, на который ссылается $aref. Как только они найдут массив, с которым работают, обе версии делают с массивами то же самое.
Использование ссылки на хэш точно такое же:
%h %{$href} A hash
keys %h keys %{$href} Get the keys from the hash
$h{'red'} ${$href}{'red'} An element of the hash
$h{'red'} = 17 ${$href}{'red'} = 17 Assigning an element Все, что вы хотите сделать со ссылкой, Правило использования 1 показывает, как это сделать. Вы просто пишете код Perl, который вы бы написали для выполнения того же действия с обычным массивом или хэшем, а затем заменяете имя массива или хэша на {$reference}. «Как мне перебрать массив, если у меня есть только ссылка?» Ну, чтобы перебрать массив, вы бы написали
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. $aref и @aref не связаны так же, как $item и @item.
Аналогично, $href->{'red'} является частью хэша, на который ссылается скалярная переменная $href, возможно, даже без имени. $href{'red'} — часть хэша, искусно названного %href. Легко забыть указать ->, и если вы это сделаете, вы получите странные результаты, когда ваша программа извлечет элементы массивов и хэшей из совершенно неожиданных хэшей и массивов, которые вы не хотели использовать.
Пример
Давайте рассмотрим быстрый пример того, как все это полезно.
Во-первых, помните, что [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[ROW]->[COLUMN] для получения или задания элемента в любом ряду и любом столбце массива.
Обозначение все еще немного громоздко, поэтому есть еще одно сокращение:
Правило стрелки
Между двумя индексами стрелка является необязательной.
Вместо $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 for my $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 for my $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 содержит ключ, например Greece, который ещё не существует в %table?
Это Perl, поэтому он делает ровно то, что нужно. Он видит, что вы хотите добавить Athens в массив, которого нет, поэтому он создаёт новый пустой анонимный массив, устанавливает ссылку на него в %table и добавляет Athens в него. Это называется автовивификация — придание жизни вещам автоматически. Perl увидел, что ключа нет в хеше, поэтому автоматически создал новую запись в хеше. Perl увидел, что вы хотите использовать значение хеша как массив, поэтому он автоматически создал новый пустой массив и установил ссылку на него в хеш. И как обычно, Perl увеличил массив на один элемент, чтобы вместить новое имя города.
Остальное
Я обещал дать вам 90% выгоды с 10% деталей, а это означает, что я оставил 90% деталей. Теперь, когда у вас есть общее представление о важных частях, вам должно быть легче читать страницу справки perlref, в которой обсуждаются все детали.
Некоторые из ключевых моментов perlref:
-
Вы можете создавать ссылки на что угодно, включая скаляры, функции и другие ссылки.
-
В Правиле использования 1 можно опустить фигурные скобки, когда то, что в них находится, является атомной скалярной переменной, например
$aref. Например,@$arefэквивалентно@{$aref}, а$$aref[1]эквивалентно${$aref}[1]. Если вы только начинаете, вы можете привыкнуть всегда включать фигурные скобки. -
Это не копирует базовый массив:
$aref2 = $aref1;Вы получаете две ссылки на один и тот же массив. Если вы измените
$aref1->[23], а затем посмотрите на$aref2->[23], вы увидите изменения.Для копирования массива используйте
$aref2 = [@{$aref1}];Это использует обозначение
[...]для создания нового анонимного массива, и$aref2получает ссылку на новый массив. Новый массив инициализируется содержимым массива, на который ссылается$aref1.Аналогично, для копирования анонимного хеша можно использовать
$href2 = {%{$href1}}; -
Чтобы проверить, содержит ли переменная ссылку, используйте функцию
ref. Она возвращает true, если её аргумент является ссылкой. На самом деле это немного лучше: она возвращаетHASHдля ссылок на хеши иARRAYдля ссылок на массивы. -
Если вы попытаетесь использовать ссылку как строку, вы получите строки, подобные
ARRAY(0x80f5dec) or HASH(0x826afc0)Если вы когда-нибудь увидите строку, похожую на эту, вы будете знать, что случайно напечатали ссылку.
Побочным эффектом этого представления является то, что вы можете использовать
eq, чтобы узнать, ссылаются ли две ссылки на одно и то же. (Но вы обычно должны использовать==, потому что это намного быстрее.) -
Вы можете использовать строку как ссылку. Если вы используете строку
"foo"как ссылку на массив, она интерпретируется как ссылка на массив@foo. Это называется символической ссылкой. Декларацияuse strict 'refs'отключает эту возможность, которая может привести к различным проблемам, если вы её используете случайно.
Возможно, вам захочется перейти к perllol вместо perlref; в нём подробно рассматриваются списки списков и многомерные массивы. После этого вы должны перейти к perldsc; это сборник рецептов по структуре данных, в котором показаны примеры использования и вывода массивов хешей, хешей массивов и других видов данных.
Резюме
Всем необходимы сложные структуры данных, и в Perl вы их получаете с помощью ссылок. Есть четыре важных правила для работы со ссылками: два для создания ссылок и два для их использования. Узнав эти правила, вы сможете выполнить большинство важных операций со ссылками.
Авторы
Автор: Марк Джейсон Доминус, Plover Systems (mjd-perl-ref+@plover.com)
Эта статья первоначально была опубликована в журнале The Perl Journal ( http://www.tpj.com/ ) том 3, #2. Перепечатано с разрешения.
Оригинальное название — Understand References Today.
Условия распространения
Авторское право 1998 The Perl Journal.
Данная документация бесплатна; вы можете перераспределять и/или изменять её в соответствии с теми же условиями, что и Perl сам по себе.
Независимо от его распространения, все примеры кода в этих файлах объявляются общественным достоянием. Вы разрешаете и можете использовать этот код в своих программах для развлечения или получения прибыли, как вам заблагорассудится. Простая ссылка в коде на автора была бы вежливой, но не обязательной.
© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.28.3/perlreftut