Приглашаем посетить
Техника (find-info.ru)

11.1. Ссылки на массивы

Назад
Глава 11 Ссылки и записи
Вперед

11.1. Ссылки на массивы

Проблема

Требуется работать с массивом через ссылку.

Решение

Ссылка на массив создается следующим образом:
$aref = \@аrrау;
$anon_array = [1, 3, 5, 7, 9];
$anon_copy = [ @аrrау ];
@$implicit_creation = (2, 4, 6, 8, 10);

Чтобы разыменовать ссылку на массив, поставьте перед ней символ @: push(@$anon_array, 11); Или воспользуйтесь стрелкой с указанием индекса конкретного элемента в квадратных скобках:
$two = $implicit_creation->[0];

Для получения индекса последнего элемента массива по ссылке или определения количества элементов в массиве применяется следующая запись:
$last_idx = $#$aref;
$num_items = @$aref;

Дополнительные фигурные скобки повышают надежность и форсируют ну/к ный контекст:
$last_idx = $#{ $aref };
$num_items = scalar @{ $aref };

Комментарий

Рассмотрим примеры использования ссылок на массивы: # Проверить, содержит ли $someref ссылку на массив
if (ref($someref) ne 'ARRAY') {
die "Expected an array reference, not $someref\n";
}
print "@{$array_ref}\n"; # Вывести исходные данные
@order = sort @{ $array_ref }; # Отсортировать их
push @{ $array_ref }, $item; # Добавить в массив новый элемент

Если вы не можете выбрать между использованием ссылки на именованпыи массив и созданием нового массива, существует простое правило, которое в большинстве случаев оказывается верным. Получение ссылки на существующий массив используется либо для возврата ссылки за пределы области действия, либо при передаче массива функции по ссылке. Практически во всех остальных случаях используется [@аrrау], что приводит к созданию ссылки на новый массив с копиями старых значений. Автоматический подсчет ссылок в сочетании с оператором \ обладает большими возможностями:
sub array_ref {
my @array;
return \@array:
$aref1 = array_ref();
$aref2 = array_ref();

При каждом вызове array_ref функция выделяет для array новый блок памяти. Если бы мы не вернули ссылку на @аrrау, то занимаемая массивом память была бы возвращена при выходе из блока, то есть при завершении подпрограммы. Однако ссылка на @аrrау продолжает существовать, поэтому Perl не освобождает намять, и мы получаем ссылку на блок памяти, недоступный через таблицу символов. Такие блоки памяти называются анонимными, поскольку с ними не связано никакое имя. К определенному элементу массива, на который указывает ссылка $aref, можно обратиться в форме $$aref[4], но $aref->[4] делает то же самое и обладает большей наглядностью.
print $array_ref->[$N], # Обращение к М-му элементу (лучший вариант)
print $$array_ref[$N]; # To же, но менее наглядно
print ${$array_ref}[$N]; # To же, но непонятно и уродливо

Имея ссылку на массив, можно получить срез субъектного массива:
(@$pie[3. .5]; # Срез массива, но читается плохо
@{$pie}[3..5]; # Срез массива, читается лучше (?)

Срезы массивов, даже при обращении через ссылки, допускают присваивание. В следующей строке сначала происходит разыменование массива, после чего элементам среза присваиваются значения:
@{$pie}[3..5] = ("blackberry", "blueberry", "pumpkin");
Срез массива полностью идентичен списку отдельных элементов. Поскольку o сылку на список получить нельзя, вам не удастся получить ссылку на срез массива:
$sliceref = \@{$pie}[3.,5]; # НЕВЕРНО! Для перебора в массиве применяется цикл
foreach или for:
foreach $item ( @{$array_ref} ) { # Данные в $item
}

for ($idx = 0; $idx <= $@{ $array_ref }; $idx++) {# Данные в $array_ref->[$idx]
}


> Смотри также
perlref{1) и perllol(1); рецепты 2.14; 4.5.

11.2. Создание хэшей массивов

Проблема

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

Решение

Сохраните в элементе хэша ссылку на массив. Используйте push для присоединения новых элементов:
push(@{ $hash{"KEYNAME"} }, "new value");
Затем при выводе хэша разыменуйте значение как ссылку на массив:
foreach $string (keys %hash) {
print "$string: @{$hash{$string}}\n";

Комментарий

В хэше могут храниться только скалярные величины. Впрочем, ссылки и являются скалярными величинами. Они помогают решить проблему сохранения не- скольких ассоциированных значений с одним ключом - в $hash{$key} помещается ссылка на массив, содержащий значения $key. Все стандартные операции с хэшами (вставка, удаление, перебор и проверка существования) могут комбинироваться с операциями массивов (push, splice и foreach). Присвоение ключу нескольких значений осуществляется следующим образом:
$hash{"a key"} = [ 3, 4, 5 ];

# Анонимный массив Ключ с ассоциированным массивом используется так:
lvalues = @{ $hash{"a key"} };

Для присоединения новых значений к массиву, ассоциированному с конкретным ключом, используется функция push:
push @{ $hash{"a key"} }, $value;

Классическое применение этой структуры данных - инвертирование хэша, в котором одно значение ассоциируется с несколькими ключами. В хэше, полученном после инвертирования, один ключ ассоциирован с несколькими значениями. Эта проблема рассматривается в рецепте 5.8. Учтите, что запись вида:
Presidents = @{ $phone2name{$number} };

при действующей директиве use st rict вызовет исключение, поскольку вы пы та-етесь разыменовать неопределенную ссылку без автоматического создания. Приходится использовать другую формулировку:
@residents = exists( $phone2name{$number} ) ? @{ $phone2name{$number} } : О;}


> Смотри также ------------------------------
Раздел "Hashes of Arrays" perldsc(1); рецепт 5.8; пример "Хэш с автоматическим дополнением" из рецепта 13.15.

11.3. Получение ссылок на хэши

Проблема

Требуется работать с хэшем по ссылке. Например, ссылка может передаваться ф} ции или входить во внешнюю структуру данных.

Решение

Получение ссылки на хэш:
$href = \%hash;
$anon_hash = { "key1" => "valuel", "key2" => "value2 ..." };
$anon_hash_copy = { %hash };
Разыменование ссылки на хэш:
%hash = %$href;
$value = $href->{$key};
@slice = @$href{$key1, $key2, $key3}; # Обратите внимание: стрелки нет!
@keys = keys %$hash;

Проверка того, является ли переменная ссылкой на хэш:
if (ref($someref) ne 'HASH') {
die "Expected a hash reference, not $someref\n";
}

Комментарий

Следующий пример выводит все ключи и значения двух заранее определенных хэшей:
foreach $href ( \%ENV, \%INC ) { # ИЛИ:
for $href ( \(%ENV,%INC) ) { foreach $key ( keys %$href ) {
print "$key => $href->{$key}\n";
}
}

Операции со срезами хэшей по ссылке выполняются так же, как со срезами массивов. Например:
@values = @$hash_ref{"key1", "key2", "key3"};
for $val (@$hash_ref{"key1", "key2", "key3"}) {
$val += 7; # Прибавить 7 к каждому значению в срезе хэша
}


Смотри также: Глава 5 "Хэши"; perlref(1), рецепт 11.9.

11.4. Получение ссылок на функции

Проблема

Требуется создать ссылку для вызова подпрограммы. Такая задача возникает при создании обработчиков сигналов, косвенно-вызываемых функций Tk и указателей па хэши функций.

Решение

Получение ссылки на функцию:
$cref = \&func;
$cref = sub { ... };

Вызов функции по ссылке:
@returned = $cref->(@arguments);
@oreturned = &$cref(@arguments);

Комментарий

Чтобы получить ссылку на функцию, достаточно снабдить ее имя префиксом Кроме того, формулировка sub {} позволяет создавать анонимные функции. Ссылка на анонимную функцию может быть сохранена так же, как и любая другая. В Perl 5.004 появилась постфиксная запись для разыменования ссылок на функции. Чтобы вызвать функцию по ссылке, раньше приходилось писать &$funcname (@ARGS), где $funcname - имя функции. Возможность сохранить имя функции в переменной осталась и сейчас:
$funcname = "thefunc";
&$funcname();

однако подобное решение нежелательно по нескольким причинам. Во-первых, в нем используются символические, а не настоящие (жесткие) ссылки, поэтому при действующей директиве use st rict ' refs' оно отпадает. Символические ссылки обычно не рекомендуются, поскольку они не могут обращаться к лексическим, а только к глобальным переменным, и для них не ведется подсчет ссылок. Во-вторых, оно не содержит данных о пакете, поэтому выполнение фрагмента в другом пакете может привести к вызову неверной функции. Наконец, если функция была в какой-то момент переопределена (хотя это происходит нечасто), символическая ссылка будет обращаться к текущему определению функции, а жесткая ссылка сохранит старое определение. Вместо того чтобы сохранять имя функции в переменной, создайте ссылку на нее с помощью оператора \. Именно так следует сохранять функцию в переменной или передавать ее другой функции. Ссылки на именованные функции можно комбинировать со ссылками на анонимные функции:
my %commands = (
"happy" => \&joy,
"sad" => \&sullen,
"done" => sub { die "See ya!" },
"mad" => \&angry, );
print "How are you? ";
chomp($string = );
if ($commands{$string}) {
$comroands{$string}->();
} else {
print "No such command: $string\n";
}


Если вы создаете анонимную функцию, коюрая ссьыапся на лскшчесмю ( у) переменную из вмещающей области действия, схема подсчета ссылок гарантирует, что память лексической переменной не будет освобождена при наличии ссылок на нее:
sub counter_maker { my $start = 0;
return sub {
return $start++;
# Замыкание
# Лексическая переменная
# из вмещающей области действия
};
}
$counter = counter_maker();
for ($i = 0; $i < 5; $i ++) { print &$counter, "\n";
}

Даже несмотря на то что функция counter_maker завершилась, а переменная $start вышла из области действия, Perl не освобождает ее, поскольку анонимная подпрограмма (на которую ссылается $counter) все еще содержит ссылку на $stan. Если повторно вызвать counter_maker, функция вернет ссылку на другую анонимную подпрограмму, использующую другое значение $start:
$counter1 = counter_maker();
$counter2 = counter_maker();
for ($i =0; $i < 5; $i ++) { print &$counter1, "\n":
}
print &$counter1, " ", &$counter2, "\n";
0
1
2
3
4
5 0

Замыкания часто используются в косвенно-вызываемых функциях (callbacks). В графических интерфейсах и вообще в программировании, основанном на событиях, определенные фрагменты кода связываются с событиями нажатий клавиш, щелчков мышью, отображения окон и т. д. Этот код вызывается много позже, возможно, из совсем другой области действия. Переменные, используемые л замыкании, должны быть доступными к моменту вызова. Для нормальной работы они должны быть лексическими, а не глобальными. Замыкания также используются в генераторах функций, то есть в функциях которые создают и возвращают другие функции. Функция counter_maker является генератором. Приведем еще один простой пример:
sub timestamp {
my $start_time = time();
return sub { return time() - $start_time };
}
$early = timestamp();
sleep 20;
$later = timestampo;
sleep 10;
printf "It's been %d seconds since early.\n", $early->();
printf "It's been %d seconds since later.\n", $later->();
It's been 30 seconds since early. It's been 10 seconds since later.

Каждый вызов timestamp генерирует и возвращает новую функцию. Функция timestamp создает лексическую переменную $start_time, которая содержит текущее время (в секундах с начала эпохи). При каждом вызове замыкания оно возвращает количество прошедших секунд, которое определяется вычитанием начального времени из текущего.

> Смотри также -------------------------------
Описание замыканий в perlref(1); рецепты 10.11; 11.4.

11.5. Получение ссылок на скаляры

Проблема

Требуется создать ссылку на скалярную величину и работать с ней.

Решение

Для создания ссылки на скалярную величину воспользуйтесь оператором \:
$scalar_ref = \$scalar; # Получение ссылки на именованный скаляр

Чтобы создать ссылку на анонимную скалярную величину (то есть скаляр, не являющийся переменной), присвойте нужное значение через разыменование неопределенной переменной:
undef $anon_scalar_ref;
$$anon_scalar_ref = 15;

Ссылка на скалярную константу создается следующим образом:
$anon_scalar_ref = \15;

Разыменование выполняется конструкцией ${...}:
print ${ $scalar_ref }; # Разыменовать
${ $scalar_ref } .= "string"; # Изменить значение субъекта

Комментарий

Если вам понадобилось создать много новых анонимных скаляров, воспользуйтесь функцией, возвращающей ссылку на лексическую переменную вне области действия, как объяснялось во введении:
sub new_anon_scalar {
my $temp;
return \$temp;
}

Perl почти никогда не выполняет косвенного разыменования. Исключение составляют ссылки на 4)айловые манипуляторы, программные ссылки на sort И ссылочный аргумент функции bless. Из-за этого для разыменования скалярнов переменной следует снабдить ее префиксом $, чтобы получить все ее содержимое;!
$sref = new_anon_scalar();
$$sref = 3;
print "Three = $$sref\n";
@array_of_srefs = ( new_anon_scalar(), new_anon_scalar() );
${ $array[0] } = 6,02е23;
${ $аrrау[1] } = "avocado";
print "\@аrrау contains: ", join(", ", map { $$_ } @аrrау ), "\n";
Обратите внимание на фигурные скобки вокруг $аrrау[0] и $аrrау[1]. Если бы мы попытались ограничиться простым $$аrrау[0], то в процессе разыменования получили бы $аrrау->[0]. Переменная $аrrау интерпретировалась бы как ссылка на массив, поэтому в результате был бы возвращен элемент с нулевым индексом. Приведем другие примеры, в которых фигурные скобки необязательны:
$var = 'uptime'; # $var содержит текст
$vref = \$var; # $vref "указывает на"
$var if ($$vref =~ /load/) {} # Косвенное обращение к
$var chomp $$vref; # Косвенное изменение $var
Как упоминалось во введении, для определения типа субъекта по ссылке применяется встроенная функция ref. При вызове ref для ссылки на скаляр возвращается строка "SCALAR": # Проверить, содержит ли $someref ссылку на скаляр
if (ref($someref) ne 'SCALAR') {
die "Expected a scalar reference, not $someref\n";


Смотри также: perlref(1).

11.6. Создание массивов ссылок на скаляры

Проблема

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

Решение

Чтобы создать массив, либо снабдите префиксом \ каждый скаляр в списке:
@array_of_scalar_refs = ( \$а, \$b );

либо просто поставьте \ перед всем списком, используя свойство дистрибутивности оператора \:
@array_of_scalar_refs = \( $а, $b );
Чтобы получить или задать значение элемента списка, воспользуйтесь конструкцией ${...}:
${ $array_of_scalar_refs[1] } = 12; # $b = 12

Комментарий

В следующих примерах предполагается, что @аrrау - простой массив, содержащий ссылки на скаляры (не путайте массив ссылок со ссылкой на массив). При косвенных обращениях к данным необходимы фигурные скобки.
($а, $b, $c, $d) = (1 .. 4); # Инициализировать
@аrrау = (\$а, \$b, \$c, \$d); # Ссылки на все скаляры
@array = \( $а, $b, $c, $d); # То же самое!
${ $аrrау[2] } += 9; # $c = 12
${ $array[ $#аrrау ] } *= 5; # $d = 20
${ $аrrау[-1] } *= 5; # То же; $d = 100
$tmp = $array[-1]; # Использование временной переменной
$$tmp *= 5; #$d = 500
Две формы присваивания @аrray эквивалентны - оператор \ обладает свойством дистрибутивности. Следовательно, \ перед списком (но не массивом!) эквивалентно применению \ к каждому элементу списка. Следующий фрагмент изменяет значения переменных, ссылки на которые хранятся в массиве. А вот как работать с массивом без явного индексирования.
use Math::Trig qw(pi); # Загрузить константу pi
foreach $sref (@array) { # Подготовиться к изменению $a,$b,$c,$d
($$sref **= 3) *= (4/3 * pi); # Заменить объемом сферы
}

В этом фрагменте используется формула вычисления объема сферы:
V = 4/3pir

Переменная цикла $s ref перебирает все ссылки @а г гау, а в $$s ref заносятся сами числа, то есть исходные переменные $а, $Ь, $с и $d. Изменение $$sref в цикле приводит к изменению этих переменных. Сначала мы возводим $$sref в куб, а затем умножаем полученный результат на 4/Зтс. При этом используется то обстоятельство, что присваивание в Perl возвращает левостороннее выражение. Это позволяет сцеплять операторы присваивания, как это делается с операторами **= и -. Вообще говоря, анонимные скаляры обычно бесполезны - ведь скалярная b( -личина занимает столько же места, что и ссылка на нее. По этой причине не предусмотрены и специальные конструкции для их создания. Скалярные ссылки существуют только для поддержки синонимов, которые могут быть реализованы и другими способами.

> Смотри также -------------------------------
Раздел "Assignment Operators" perlop(1).


Назад
Вперед