Next Previous Contents

Unix Review Column 7 -- Простые ссылки

Randal Schwartz

Март 1996

Перевод Anton Petrusevich <casus@mail.ru> и Alex Ott <ott@phtd.tpu.edu.ru>

Одна из наиболее мощных возможностей появившихся в последней версии Perl (версия 5.0

Март 1996г. -- Прим. переводчика.
) это ``ссылка''. Ссылка подобна указателю в языке Си, предоставляя средство ссылаться на структуры данных ``косвенно''. Эта косвенность может быть использована двояко: вы можете иметь секцию кода действующую на различные структуры данных в разное время, или вы можете иметь структуры данных, косвенно содержащие другие структуры данных, предоставляя вам возможности вложенных структур данных (списки списков, списки ассоциативных массивов, и так далее).

Ссылку можно сделать на скаляры, массивы (списки), ассоциативные массивы (хеши), или подпрограммы. Эти ссылки подходят практически в любое место, где подходят скалярные значения, таким образом, они могут содержатся в скалярных переменных, быть значением списка или хеша, и могут быть переданы в или из подпрограммы. (Почти единственное место, где может подойти скаляр и не может ссылка, это ключ хеша).

Ссылка на именованую переменную создаётся с помощью оператора ``\''.


        $ref_to_a = \$a;

Значение в $ref_to_a теперь ссылка на переменную $a. Мы можем использовать эту ссылку чтобы изменить значение $a косвенно, используя раскрытие ссылки до скаляра:


        ${ $ref_to_a } = 35;

Чтобы понять этот синтаксис, представьте замещение имени скалярной переменной (вроде b в $b) блоком, возвращающим значение ссылки на скалярную переменную (в этом случае $ref_to_a). Тогда выражение выше становится таким же как:


        $a = 35;

И следующие два выражения так же эквивалентны:


        $a++;
        ${ $ref_to_a }++;

Я даже могу сократить эту запись немного, чтобы поменьше набирать. Если единственное, что есть внутри блока, то это просто скалярная переменная, и я могу опустить фигурные скобки, вот как здесь:


        $$ref_to_a++; # increment $a
        $$ref_to_a = 35; # set $a = 35

Теперь это выглядит просто как более сложный путь написать $a, но давайте посмотрим следующий текст:


        $the_ref = \$a;
        $$the_ref = 35; # set $a to 35
        $the_ref = \$b;
        $$the_ref = 35; # set $b to 35

Заметьте, что тот же самый кусок текста ($$the_ref = 35) изменил $a в первый раз и $b во второй раз. Действительно, мы можем это немного автоматизировать используя цикл foreach:


        foreach $the_ref (\$a, \$b) {
                $$the_ref = 35;
        }

Возможно теперь вы начинаете видеть, что я могу использовать один и тот же текст программы применительно к различным переменным, спасибо добавочному уровню косвенности. Хорошо!

Так же, как я могу создать ссылку на скаляр, я могу создать ссылку на массив (список).


        $list_ref = \@fred;

В этом случае, конструкция {$list_ref} (или эквивалентная $list_ref) может быть использована везде, где я мог бы использовать ``fred'' как списковую переменную:


        @$list_ref = (3,4,5); # @fred = (3,4,5)
        $$list_ref[2] = 6; # $fred[2] = 6
        print $#$list_ref; # print $#fred;
        push(@$list_ref,8,9); # push(@fred, 8, 9)

И ещё раз, я могу использовать ссылки для исполнения действия косвенно:


        foreach $list_ref (\@fred, \@barney) {
                @$list_ref = (); # clear out the array
        }

которое устанавливает оба @fred и @barney в пустое значение (присваивает пустой список весьма нелёгким образом).

Всё это время я создавал ссылки на именованные списковые переменные, но так же можно создавать список, который не соответствует ни одной списковой переменной. Это называется ``анонимный список''. Анонимный список может быть создан конструктором анонимных списков, который строит список и возвращает ссылку на него:


        $list_ref = [3, 4, 6, 8, 9];

Значение $list_ref может быть использовано точно так же, как я использовал ссылку на именованный список. Единственная разница в том, что не будет реального имени переменной --- только ссылка. Например, я могу добавить в массив:


        push(@$list_ref, 10, 12);

и выяснить его размер:


        $len = @$list_ref; # $len = 7

или увеличить его первый элемент:


        $$list_ref[0]++; # first element is now 4

Ссылки на списки и ссылки на анонимные списки можно использовать для передачи списка в подпрограмму без копирования целого списка в аргументы подпрограммы.


        @a = 1..1000; # create 1000 element list
        $ref_to_a = \@a; # reference to @a
        &brack_it($ref_to_a); # pass reference to subroutine
        sub brack_it {
                my($list_ref) = @_; # name the first parameter
                foreach (@$list_ref) {
                        print "[$_]"; # print element in brackets
                }
                print "\n";
        }

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

На самом деле, мы можем пропустить шаг-другой:


        &brack_it(\@a); # call sub on @a

Нам нет необходимости хранить промежуточную ссылку в скалярной переменной --- достаточно просто передать её первым аргументом.

Так же, мы можем передать первым аргументом ссылку на анонимный массив:


        &brack_it(
                [10, 20, 30]
        );

Здесь создаётся анонимный список из трёх значений 10, 20, 30, и ссылка на этот список передаётся в подпрограмму.

Что происходит с анонимным списком, когда подпрограмма завершается? Анонимный список (подобно всем анонимным вещам) учитывается ``счётчиком ссылок'', это означает, что Perl отслеживает количество ссылок на этот блок данных. Когда данные передаются в подпрограмму, на блок появляется первая ссылка, затем, появляется вторая (список аргументов и локальная копия ``$list_ref''). Когда подпрограмма завершается, обе ссылки исчезают, оставляя блок, на который никто не ссылается. Когда это происходит, Perl автоматически освобождает память, занимаемую списком, точно так же, как будто подпрограмма завершалась и её локальные переменные больше не были действительны.

Так же, ссылки можно копировать:


        $a = [ 20, 30, 40 ]; # $a is a listref
        $b = $a;

Теперь, обе переменные $a и $b указывают на одни и те же данные. Если я добавлю элемент к @$a, я буду иметь доступ к тем же данным через @$b. Я могу даже удалить $a.


        undef $a;

и $b будет указывать на оригинальные данные! Хотя, когда я окончательно удалю $b,


        undef $b;

блок, содержащий анонимный список, будет возвращён обратно в пул свободных блоков, так как на него никто не ссылается.

Я могу создавать ссылки на ассоциативные массивы (хеши):


        $hash_ref = \%score;
        ${ $hash_ref }{"fred"} = 205; # $score{"fred"} = 205
        $$hash_ref{"barney"} = 195; # $score{"barney"} = 195
        @$hash_ref{"wilma","betty"} = (170,180);
                # @score{"wilma","betty"} = (170,180);
        @the_keys = keys %$hash_ref; # keys %score

Опять, если я использую {$hash_ref} (или даже просто $hash_ref) в любом месте, где я бы использовал имя ассоциативного массива (score для %score), то я получаю свойственный хешам синтаксис для получения косвенного доступа к %score.

Я могу создать обобщённую подпрограмму для вывода ключей и соответствующих значений (отсортированных по ключам), и передавать ссылку на хеш.


        sub show_hash {
                my($hash_ref) = @_;
                foreach (sort keys %$hash_ref) {
                        print "$_ => $$hash_ref{$_}\n";
                }
        }

        &show_hash(\%score);

Преимущество снова в том, что здесь передаётся только один скаляр (ссылка на хеш) в подпрограмму, а не полностью анонимный список (выложенный в плоский список).

Списки и хеши можно возвращать из подпрограммы используя похожий синтаксис. Приведу пример подпрограммы, возвращающей ссылку на список:


        sub return_it {
                my(@list) = 1..100;
                \@list;
        }

        $list_ref = &return_it();
        print $$list_ref[4]; # prints 5, or $list[4]

Каждый раз, когда эта подпрограмма вызывается, создаётся новый список от 1 до 100. Подпрограмма, затем, возвращает ссылку на этот список. Даже хотя список обычно был бы уничтожен на выходе из подпрограммы, (так как он локальный для подпрограммы), Perl замечает, что есть ещё внешняя ссылка на список (возвращаемое значение), и превращает его в анонимный список. Эта ссылка затем копируется в переменную $list_ref, которая затем может быть использована как любая ссылка на список. Пока хотя бы одна ссылка на список существует, он будет храниться в памяти.

Каждый вызов \&return_it() будет создавать различные @list, и поэтому возвращать ссылки на различные структуры данных. Таким образом мы можем превращать именованные переменные подпрограммы в неименованые переменные вне подпрограммы довольно тривиально. Также, заметьте, что мы возвращаем не значения списка, а только скаляр, ссылающийся на значения, и таким образом уменьшаем избыточность передачи всех значений из функции на Perl ``стек''.

Подобно анонимным спискам, я могу создавать анонимный ``хеш'':


        $hash_ref = {
                "fred", 205,
                "barney", 195,
                "dino", 30,
        };

        print ${$hash_ref}{"fred"}; # prints 205
        print $$hash_ref{"dino"}; # prints 30

И снова, эта структура данных, которая действует как если бы я создал переменную (ассоциативный массив) и затем получил ссылку на него, но переменная не имеет реального имени. Эти данные доступны только косвенно через $hash_ref. Я мог бы передать такую ссылку в \&show_hash (см. выше) вот так:


        &show_hash( {
                "fred", 205,
                "barney", 195,
                "dino", 30,
        } );

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

Надеюсь, эта экскурсия в ссылочные переменные оказалась вам полезной. В следующем выпуске, я постараюсь проиллюстрировать ссылки на подпрограммы, и даже изменение ``таблицы символов'' Perl используя ссылки во время исполнения программы.


Next Previous Contents