Документация Perl 5

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.

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

 
Разделы документации
Внешние ссылки