Spec-Zone.ru › Perl 5.34

perlreftut

СОДЕРЖАНИЕ

  • ИМЯ
  • ОПИСАНИЕ
  • Кому нужны сложные структуры данных?
  • Решение
  • Синтаксис
    • Создание ссылок
      • Правило 1 создания
      • Правило 2 создания
    • Использование ссылок
      • Правило 1 использования
      • Правило 2 использования
    • Пример
    • Правило стрелки
  • Решение
  • Остальное
  • Резюме
  • Благодарности
    • Условия распространения

ИМЯ

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
$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. Перепечатано с разрешения.

Оригинальное название — Понимание ссылок сегодня.

Условия распространения

Авторское право 1998 The Perl Journal.

Данная документация бесплатна; вы можете перераспределять и/или изменять ее на тех же условиях, что и Perl сам по себе.

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

© 1993–2021 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.34.0/perlreftut

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API