Некоторые сведения о Perl 5/Ссылки

← Форматированный вывод Глава Функции и процедуры →
Ссылки


Данные программы размещаются в оперативной памяти компьютера. Обычно мы обращаемся к значениям в памяти через переменные. Каждая переменная имеет свой адрес в памяти.

Ссылка в Perl — это скалярная переменная, которая хранит адрес другой переменной в памяти. Основной областью применения ссылок является создание сложных структур, способных изменяться во время исполнения программы. В языках C и C++ существуют понятия указателя и ссылки, которые в общем не тождественны друг другу, однако, в Perl это одно и то же.

В этой главе мы рассмотрим как работать с ссылками в Perl и как ими пользоваться.

Виды ссылок

править

Ссылки в Perl появились с 5-й версии из-за необходимости создания структур произвольной сложности, которые невозможно определить через простые переменные, например многомерные массивы. Помимо создания сложных структур, ссылки активно используются для создания объектов ООП.

В Perl можно создать ссылку на любые данные, таким образом, можно выделить следующие виды ссылок:

  • ссылка на скаляр;
  • ссылка на массив скаляров;
  • ссылка на хеш-массив;
  • ссылка на функцию;
  • ссылка на ссылку (так как ссылка по сути является скаляром, то на саму ссылку также можно создать ссылку);
  • typeglob ссылка.

Ссылка в Perl хранит тип данных, на которые она ссылается. Для получения типа ссылки используется функция ref(), аргументом которой должна быть ссылка. Эта функция возвращает пустую строку, если аргумент не является ссылкой, либо один из идентификаторов:

  • REF – ссылка на ссылку;
  • SCALAR – ссылка на скаляр;
  • ARRAY – ссылка на массив скаляров;
  • HASH – ссылка на хеш-массив;
  • CODE – ссылка на функцию/процедуру;
  • GLOB – ссылка на typeglob объект.

Ссылки в Perl бывают жесткие и символические. Эти термины не являются изобретением Perl, а перешли в него из Unix. Жесткая ссылка в Perl — это скалярная величина, которая является адресом в памяти, при этом сами данные называются субъектом этой ссылки. Жесткие ссылки — это истинные ссылки. Таким образом, с самого начала главы мы говорили именно о жестких ссылках.

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

Символическая ссылка в Perl — это скалярная переменная, которая хранит имя другой скалярной переменной.

Определение ссылок

править

Обычным способом определения ссылки является операция \:

$scalar_ref = \3;            # ссылка на константу
$scalar_ref_1 = $scalar_ref; # ссылка на скаляр
$array_ref = \@array;        # ссылка на массив
$hash_ref = \%hash;          # ссылка на хеш-массив
$func_ref = \&some_func;     # ссылка на функцию

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

Конструирование анонимного массива скаляров

править

Имя переменной также выражает имя программного объекта. Perl позволяет конструировать объекты минуя создания переменной, т.е. к таким данным можно обратиться исключительно по ссылке. Такие объекты мы называем анонимными, т.е. к таким объектам нельзя обратиться через переменную (не имеют имени).

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

$array_ref = [1, 2, 3];
# Анонимный массив с ссылкой, сохраняемой в $array_ref

# Предыдущий оператор аналогичен следующим двум
$array = (1, 2, 3);
$array_ref = \@array;

Данный способ является единственным для создания анонимного массива.

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

\($a, $b, $c)
# НЕ ОЗНАЧАЕТ анонимный массив с тремя переменными, а является краткой записью
(\$a, \$b, \$c)

Конструирование анонимного ассоциативного массива

править

Чтобы создать анонимный хеш-массив, нужно использовать фигурные скобки:

$hash_ref = {
   'one' => 1,
   'two' => 2,
};
# Создает анонимный хеш-массив с ссылкой на него в $hash_ref

# Предыдущий оператор аналогичен двум
%hash = ('one' => 1, 'two' => 2);
$hash_ref = \%hash;

Конструирование анонимной функции

править

Ссылка на анонимную функцию создается, когда используется ключевое слово sub следющим образом:

$func_ref = sub { print "Hello", "\n" };

Конструирование ссылки на объект

править

Начиная с Perl 5, в язык была добавлена поддержка ООП, основой которого являются классы и объекты.

В Perl доступ к объекту реализован исключительно по ссылке, для создания которой используется специальная функция-конструктор, в которой применяется встроенная функция bless(). Функция-конструктор должна всегда возвращать ссылку.

Об ООП в Perl мы поговорим в отдельной главе (см. Объектно-ориентированное программирование в Perl).

Конструирование typeglob ссылок

править

Имена всех переменных программы компилятор Perl заносит в специальную таблицу символов. Единицей хранения этой таблицы является так называемое гнездо идентификатора, в которое сохраняются адреса программных объектов, сохраненных под этим идентификатором.

Например следующие объекты

$a = 5;
@a = (1, 2, 3);
%a = ("one" => 1);
sub a { return 0 };

будут занимать одно гнездо с идентификатором a.

В Perl есть возможность получить ссылку на некоторый элемент гнезда с помощью typeglob-ссылки, признаком которой является символ *. Так, имея гнездо, объявленное выше, можно получить ссылки на все программные объекты в нем через такие ссылки:

#                            Аналогично
$scalar_ref = *a{SCALAR};  # \$a
$array_ref = *a{ARRAY};    # \@a
$hash_ref = *a{HASH};      # \%a
$code_ref = *a{CODE};      # \&a
$glob_ref = *a{GLOB};      # \*a

typeglob-ссылки могут использоваться для создания псевдоконстант. Например, следующим образом может быть введена константа Пи:

*PI = \3.14159265358979;

# Тогда
$PI; # это разыменовывание ссылки на константу
# При этом $PI нельзя изменить.

Разыменовывание ссылок

править

Разыменованием ссылки называется получение данных, хранящихся в памяти по этой ссылке. Для разыменовывания используются различные синтаксические конструкции, подчас достаточно сложные для визуального восприятия, и к ним нужно просто привыкнуть. Разыменовывание в Perl привязано к типу данных, поэтому для разных типов оно выглядит по разному.

Если ссылка является простой скалярной переменной, то при разыменовании нужно использовать соответствующую по типу переменную для операции присваивания, а для ссылки нужно использовать соответствующий идентификатор типа:

$var = $$scalar_ref;   # Разыменовывание ссылки на скаляр
@arr = @$array_ref;    # Разыменовывание ссылки на массив
%hash = %$hash_ref;    # Разыменовывание ссылки на хеш-массив
&func = &$code_ref;    # Разыменовывание ссылки на функцию

# Разыменовывание анонимного массива в lvalue выражении с одновременным порождением ссылки
$$array_ref_1[0] = 5;
# Разыменовывание анонимного хеш-массива в lvalue выражении с одновременным порождением ссылки
$$hash_ref_1{"one"} = 1;

Первые четыре разыменовывания выглядят понятно и логично, а последние два немного сложны для понимания, поэтому мы рассмотрим их подробнее. Так как ссылка это скаляр, то сначала произойдет разыменовывание ($$array_ref_1, $$hash_ref_1), и только после этого будет использована индексация (в соответствии с правилами для простых и хеш-массивов). При этом если ссылки до этого не существовали (как в примере), то они будут созданы при присваивании, так как эта операция предполагает их существование. Этот побочный эффект при разыменовывании в lvalue-выражении нужно запомнить.

Если ссылка не является простым скаляром (например ссылка хранится в элементе массива), то для ее разыменования нужно использовать блок:

${$array[0]} = 5;
${$hash{"one"}} = 2;
${&f()}[1] = 6;

В первом примере предполагается, что в $array[0] массива @array хранится ссылка. Для ее разыменования мы помещаем обращение к ней в массиве в блок, а затем разыменовываем как ссылку из скаляра через $ перед блоком. Аналогично мы поступаем, если ссылка хранится в хеш-массиве.

В последнем примере мы вызываем функцию f() в блоке, которая возвращает ссылку на массив. Затем мы ее разыменовываем и присваиваем второму элементу массива значение 6.

Отметим, что блоки можно использовать и с ссылками, хранящимися в скалярах, например

$$array_ref_1[0] = 5;
# и
${$array_ref_1}[0] = 5;

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

Операция ->

править

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

${$a[0]}[1] = 5;
${$b[0]}{"one"} = 1;

По этой причине в Perl есть отдельная операция разыменования для массивов в виде стрелки ->. Похожую операцию вы могли видеть при разыменовывании указателей в языке Си.

Данная операция является бинарной. В левом операнде она ожидает ссылку на массив (простой или хеш-массив), а в правом операнде ожидается индекс массива (в зависимости от типа ссылки). Результатом такого разыменовывания является значение элемента массива или хеш-массива. Таким образом, предыдущий пример может быть компактно записан так:

$a[0]->[1] = 5;
$b[0]->{"one"} = 1;

Благодаря такой записи становится очевидней то, что первая ссылка хранится в массиве @a в первом элементе и является ссылкой на простой массив, а в массиве @b в первом элементе хранится ссылка на хеш-массив.

Если ссылки на некоторые массивы хранились бы в скалярных переменных, то запись становится следующей:

$array_ref->[0]->[1] = 5;
$hash_ref->[0]->{"one"} = 1;

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

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

${${$a[$i]}[$j]}[$k]
# то же самое
$a[$i]->[$j]->[$k]
# то же самое
$a[$i][$j][$k]

# И для хеш-массива
${${$b[$i]}{"key"}}[$j]
# то же самое
$b[$i]->{"key"}->[$j]
# то же самое
$b[$i]{"key"}[$j]

Другими словами, знак операции -> можно опускать в контексте разыменовывания ссылок на массивы.

Этой операцией также разыменовываются ссылки на процедуры/функции. В этом случае правый операнд операции ожидает список аргументов функции в круглых скобках. Сравните

$procedure = sub { print "Called: @_\n"; };   # Анонимная функция

$procedure->('one', 'two', 'three') ;         # Разыменовывание через ->

&$procedure ('four', 'five');                 # Обычным способом

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

Символические ссылки

править

Символической ссылкой называется переменная, которая хранит имя другой переменной. Разыменовывание символической ссылки происходит аналогично жесткой ссылке:

$ref_var = "var"; # Символическая ссылка на переменную 'var'

# Разыменовывание ссылки на скаляр $var с присваиванием. Аналогично $var = 5;
$$ref_var = 5;

# Разыменовывание ссылки на массив @var с присваиванием.
@$ref_var = (1, 2, 3);
# Операция -> может использоваться с символическими ссылками
$ref_var->[3] = 4;  # Аналогично $var[3] = 4;

# Разыменовывание ссылки на хеш-массив %var
%$ref_var = ("one" => 1, "two" => 2);

sub var { print "hello" }
# Разыменовывание ссылки на функцию
&$ref_var();

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

$ref_a = "a";
$$ref_a = 1;
{
    my $a = "2";
    print $$ref_a;  # Напечатается 1, так как my $a не видна этому механизму
}
print $$ref_a;  # Напечатается 1

Символические ссылки в большинстве случаев нежелательны, так как они могут запутывать код и создавать неявные ошибки. В больших программах их использование следует запрещать с помощью директивы use strict 'refs';.

На практике по символическим ссылкам обычно передаются и разыменовываются только функции.

Примеры использования ссылок

править

Многомерные массивы

править

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

@matrix = (
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
);

# Цикл печати матрицы
foreach (@matrix) {
    # Конструкция $#$_ вернет последний индекс массива по ссылке
    for (my $i=0; $i <= $#$_; $i++) {
        print $$_[$i], " ";
    }
    print "\n";
}

Замыкания

править

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

Замыкания в Perl создаются через две функции, одна из которых (внешняя) конструирует анонимную функцию, передавая ей свободные переменные.

sub build_closure {
    my $free = shift;   # Свободная переменная
    return sub {
         my $closure_var = shift;        # Аргумент замыкания
         return $free + $closure_var;   # Переменная $free для замыкания статична
    }
}

У замыканий бывают самые разные применения: например, если парсится некая структура, в которой есть разные блоки, но при этом принцип парсинга для каждого из них одинаковый, можно было бы генерировать замыкание для каждого типа блока, передавая в свободных переменных только особенности каждого блока, оставив алгоритм парсинга в функции замыкания; функции-колбеки хорошо ложатся под замыкания; различные hook-функции и другие примеры.

Ниже приведен пример замыкания, которое печатает элементы массивов столбиком в зависимости от форматной строки, которая передавалась при конструировании замыкания.

sub formatter {
    my $format = shift;
    return sub {
        foreach $element (@_) {
            printf STDOUT $format, $element;
        }
    }
}

@arr = ("apple", "pear", "grapes");

$print_left = formatter("|%-30s|\n");
$print_right = formatter("|%30s|\n");

$print_left->(@arr);
$print_right->(@arr);
|apple                         |
|pear                          |
|grapes                        |
|                         apple|
|                          pear|
|                        grapes|



← Форматированный вывод Функции и процедуры →