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 мы уже столкнулись с этим ограничением: Значения хешей должны быть скалярами. Решением этой проблемы являются ссылки.
Ссылка — это скалярное значение, которое указывает на весь массив или весь хеш (или практически на что-либо ещё). Имена — это один из видов ссылок, с которыми вы уже знакомы. Каждый человек — это сложный, неудобный набор ячеек. Но чтобы сослаться на конкретного человека, например, на первого программиста, нет необходимости описывать каждую из его ячеек; всё, что вам нужно — это лёгкое и удобное скалярное строковое значение "Ada Lovelace".
Ссылки в Perl похожи на имена для массивов и хешей. Это внутренние, частные имена Perl, поэтому вы можете быть уверены в их однозначности. В отличие от имени человека, ссылка указывает только на одну вещь, и вы всегда знаете, на что она указывает. Если у вас есть ссылка на массив, вы можете восстановить весь массив из неё. Если у вас есть ссылка на хеш, вы можете восстановить весь хеш. Но ссылка по-прежнему является удобным, компактным скалярным значением.
Вы не можете иметь хеш, значениями которого являются массивы; значения хешей могут быть только скалярами. Мы привязаны к этому. Но одна ссылка может ссылаться на весь массив, а ссылки — это скаляры, поэтому вы можете иметь хеш ссылок на массивы, и он будет работать очень похоже на хеш массивов, и будет столь же полезен, как хеш массивов.
Мы вернёмся к этой проблеме с городами и странами позже, после того, как увидим некоторый синтаксис для управления ссылками.
Синтаксис
Существует всего два способа создать ссылку и два способа использовать её, когда вы её получите.
Создание ссылок
Правило 1 создания
Если вы поместите \ перед переменной, вы получите ссылку на эту переменную.
$aref = \@array; # $aref now holds a reference to @array
$href = \%hash; # $href now holds a reference to %hash После того, как ссылка сохранена в переменной, такой как $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. Перепечатано с разрешения.
Оригинальное название было Понимание ссылок сегодня.
Условия распространения
Авторское право 1998 The Perl Journal.
Данная документация бесплатна; вы можете перераспространять и/или изменять ее на тех же условиях, что и Perl сам по себе.
Независимо от его распространения, все примеры кода в этих файлах объявляются общественным достоянием. Вам разрешается и рекомендуется использовать этот код в своих программах для развлечения или с коммерческими целями, как вам заблагорассудится. Простая ссылка на исходный код в комментариях будет вежливой, но не обязательной.
© 1993–2023 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.38.0/perlreftut