Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу).
Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Homepage Карта сайта Версия для печати

Джентльменский набор Web-разработчика   Ларри Уолл о Perl6   Наблы Система Orphus
 

21. Ссылки и многомерные структуры в Perl,  или просто о не очень сложном

[5 апреля 2002 г.]

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

Ссылки

Как известно, массивы и хэши в Perl могут хранить лишь скаляры, но не могут — более сложные объекты (в отличие от PHP). Это, конечно же, не значит, что мы не в состоянии создавать многомерные массивы и хэши (хотя в ранних версиях Perl так и было). Просто делается это по-особому — посредством механизма ссылок.

Ссылка в Perl — это скаляр, который содержит адрес некоторого объекта, как-то: массив, хэш или другой скаляр. В дальнейшем, используя этот адрес, мы можем к объекту обратиться — это так называемое разыменование ссылки (пишется с буквой «ы»).

Лирическое отступление 
Здесь уместна аналогия с гардеробом. Пусть ваш карман — это массив, а пальто и шляпа — некоторые объекты. Вы сдаете одежду в гардероб, а гардеробщик выдает номерок — то есть, ссылку. Предположим, вам выдали отдельный номерок для пальто и отдельный — для шляпы (у гардеробщика не все дома). Таким образом, теперь вы храните у себя в кармане (массиве) не сами объекты, а номерки (ссылки). Ясно, почему вы так поступаете: пальто бы в карман просто не поместилось (тут прослеживается тесная аналогия с массивами Perl). Обладание номерком дает вам право на доступ к объекту, на который номерок «ссылается». Теперь, чтобы достать деньги из кармана пальто, вам придется спуститься вниз, предъявить свою «ссылку» и получить доступ к объекту, что требует времени на хождение туда-сюда. В Perl все обстоит точно так же: доступ к объекту по ссылке занимает чуть больше времени, чем по имени.

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

Ранее было сказано, что ссылка имеет право ссылаться не только на массив и хэш, но также и на скаляр. Но ведь в этом скаляре вполне может содержаться другая ссылка. В результате получится ссылка на ссылку на объект?.. Именно так, и для доступа к нему потребуется выполнить операцию разыменования дважды. Конечно, вложенность ссылок может быть и больше — например, допускается создание ссылки на ссылку на ссылку на массив.

Лирическое отступление 
Применив «теорию гардероба» для многоуровневых ссылок, мы получим следующее. Вы сдали шляпу в гардероб и получили номерок («ссылка на шляпу»). Затем вы пошли в банк и положили номерок в камеру хранения, а взамен вам выдали ключ («ссылка на номерок», или «ссылка на ссылку на шляпу»). Имея ключ, вы можете получить доступ к шляпе с помощью двойного разыменования (пойти в банк и забрать ключ, пойти в гардероб и забрать шляпу). Естественно, проделывая трюк с камерой хранения несколько раз, вы получите ключ-ссылку любого уровня вложенности.

Любителям Си стоит обратить внимание на то, что ссылка — это не указатель. Поэтому с ней можно сделать лишь две вещи: разыменовать (то есть, получить доступ к объекту) и удалить (то есть, присвоить ссылке неопределенное значение — undef). Одной ссылке можно также присвоить значение другой (и мы получим две ссылки на один и тот же объект, а не две копии объекта!).

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

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

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

Значение Тип скаляра
"" скаляр не является ссылкой
SCALAR ссылка на скаляр
ARRAY ссылка на массив
HASH ссылка на хэш
CODE ссылка на функцию
GLOB ссылка на элемент таблицы символов

Вот пример использования ref:

# выводит тип ссылки
print ref($r);

Создание ссылок

Имея имя некоторого объекта, мы можем создать ссылку на него, используя оператор \ (обратный слэш — да-да, вы не ошиблись):

$r = \ ВЫРАЖЕНИЕ;

В качестве ВЫРАЖЕНИЯ может выступать все, что угодно, начиная от простого указания имени объекта и кончая арифметическим или другим выражением, которое будет вычислено. В последнем случае результат помещается во временную память, а затем возвращается ссылка на эту память. Вот несколько примеров.

# Получаем ссылку на массив @A
$r = \ @A;
# Ссылка на хэш %A (хотя имя у него и 
# похоже на имя @A, это разные объекты)
$r = \ %A;
# Ссылка на скаляр $a
$r = \ $a;
# Ссылка на константу - то же самое,
# что ссылка на скаляр, но объект read-only
$r = \ "test";
# Ссылка на ссылку на массив
$r = \ \ @A;
# Ссылка на ссылку на значение выражения
$r = \ (1+2*sin(10));
# Ссылка на функцию программы
$r = \ &myFunc;
# Ссылка на элемент таблицы символов
$r = \ *someElt;
# Ссылка сама на себя (лучше так не делать)
$r = \ $r;

Как видим, можно создать ссылку на любой объект Perl, будь то массив, хэш или даже функция. Можно также создать ссылку на саму себя (тут «гардеробная теория» пасует), однако это порождает некоторые проблемы при сборе мусора, о которых мы поговорим в следующих наблах.

Я не зря подчеркнул слова «любой объект», потому что только на объект Perl можно создать ссылку. Как вы знаете (или не знаете) из предыдущей наблы, список в Perl не является объектом (это — просто промежуточное представление данных), а потому создать ссылку на список в Perl нельзя. Однако давайте посмотрим, что же будет, если мы попробуем это сделать:

# неверный код!
$r = \ (1,2,3);

Напоминаю, что этим не создается ссылка на список (и уж тем более не создается ссылка на массив). Угадайте, что будет в $r?.. А будет там... \3. И вот почему.

# вот такой код...
@rA = \ (1,2,3);
# полностью эквивалентен следующему:
@rA = (\1, \2, \3);

То есть, выражение \(x,y,z,...) всегда интепретируется как список, состоящий из ссылок на x, y, z и т. д., то есть как (\x,\y,\z,...). Именно поэтому в $r из предыдущего примера попадает ссылка на последний элемент списка — ведь в скалярном контексте список трактуется как его последний элемент, а все предыдущие — отбрасываются.

На первый взгляд может показаться, что такое поведение несколько странновато. На самом же деле это не так. Более того, у Ларри Уолла просто не было выбора в этом вопросе, и вот почему. Давайте вернемся к одному из примеров выше:

$r = \ (1+2*sin(10));

Казалось бы, здесь возможно два толкования: берется ссылка на скаляр, а скобки указаны лишь для удобства чтения, либо же это ссылка на список из одного элемента, и скобки обозначают именно слово список. Легко видеть, что современная Perl-семантика \(x,y,z,...) сводит эти разночтения на нет: и в том, и в другом случае получается одинаковый результат. А именно, ссылка на скалярное значение (потому как список из одного элемента в скалярном контексте есть сам этот элемент).

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

Итак, как же создать ссылку на список?.. А никак. Потому что сам вопрос бессмысленен. Вероятнее всего, требуется сделать ссылку не на список, а на массив, но только вот этот массив еще не хранится ни в какой переменной — хочется создать его «на лету». Ну, так и поступим:

# правильный код
@temporaryA = (1,2,3);
$rA = \ @temporaryA;
# было бы неправильно писать (см. выше):
# $rA = \(1,2,3)

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

# создание нового массива и ссылки на него
$rA = [СПИСОК];

Здесь СПИСОК — это любое выражение, которое трактуется в списковом контексте. В частности, это может быть результат, который вернула некоторая функция. Вот несколько примеров:

# ссылка на список "на лету"
$rA = [(1,2,3)];
# то же самое, но лаконичнее
$rA = [1,2,3];
# создаем ссылку на пустой массив
$rA = [];
# ссылка на массив ссылок на массивы
$rA = [
  [1,2,3],
  [2,3,4],
  [3,4,5],
];
# ссылка на массив ключей хэша %H
$rK = [keys %H];

Обратите внимание на последний пример: было бы ошибкой написать вот так:

# неверный код!
$rK = \ (keys %H);

А все из-за того, что функция keys не может возвратить ничего, кроме списка, а при взятии \ от списка мы получаем список ссылок, в скалярном контексте — ссылку на последний элемент. Будьте внимательны!

Чайник 

Итак, существует всего 2 способа создать ссылку на массив: это использовать \@что_то, либо же [что-то]. Все остальные способы не работают в принципе.

Создание ссылок на хэш

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

# Если имеем переменную-хэш - то так:
%temporaryH = (a=>1, b=>2, c=>3);
$rH = \%temporaryH;
# А если не имеем, то вот так:
$rH = {a=>1, b=>2, c=>3};
# Ссылка на пустой хэш
$rA = {};

Чайник 

Для хэшей также существует только 2 способа создать ссылку: применить синтаксис \%что_то, либо же {что-то}. Без исключения.

Обратите внимание, что существует разница между следующими двумя строками:

# некоторый хэш
%H = (a=>1, b=>2);
# ссылка на существующий хэш
$r = \%H;
# ссылка на новый хэш
$r = {%H};

В первом случае мы создаем ссылку на объект, который уже в программе существует. Мы можем теперь обратиться к объекту двояко: либо прямо по имени ($H{a}=0), либо же через ссылку (разыменовав ее). Во втором случае происходит совершенно другое: вначале на основе хэша создается список его элементов (чередуются ключи и значения, см. предыдущую наблу), потом этот список преобразуется в «безымянный» хэш (по правилу преобразования: четные элементы становятся ключами, нечетные — значениями), и уж только потом возвращается ссылка на полученный хэш.

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

Разумеется, это правило справедливо и для массивов: конструкция [@A] создаст новый массив и вернет ссылку на него, а конструкция \@A — даст ссылку на уже имеющийся в программе массив.

Метод создания массива и хэша с использованием скобок [] и {} называется созданием анонимного массива или хэша. Вообще, «анонимным» называется объект, доступный исключительно посредством ссылок. То есть, добраться до него можно только в процессе разыменования, о чем вскоре и пойдет речь.

Создание ссылок на функцию

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

# Ссылка на функцию программы
$r = \ &myFunc;

Здесь можно заметить известную аналогию с массивами и хэшами, только вместо % и @ используется &. В тот раз нам удалось обойтись без создания временного объекта — мы использовали конструкции [] и {} для получения анонимных объектов. Можно ли применить этот прием для получения безымянных функций?.. Оказывается, да, и выглядит это следующим образом:

# создание ссылки на анонимную функцию
$rF = sub {
    print "Мы в функции!\n";
    foreach(1..2) { print "$_\n" }
    return 10;
};

Здесь нужно обратить внимание на два момента. Во-первых, не забудьте указать ; после определения тела функции — здесь ее пропускать нельзя, как нельзя пропускать, например, при объявлении анонимного хэша. Во-вторых, инструкция создания функции выглядит в общем виде как sub {...}, и слово sub является обязательным (есть, правда, исключение из этого правила, но о нем как-нибудь позже). Именно по слову sub Perl и определяет, что программист желает создать функцию, а не хэш.

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

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

 
Рекламный блок
   

На странице:
    21. Ссылки и многомерные структуры в Perl,  или просто о не очень сложном
Ссылки
Создание ссылок
     Создание ссылок на массив
     Создание ссылок на хэш
     Создание ссылок на функцию

Важное объявление:
    автор категорически против копирования и распространения в Интернете всех статей «Куроводства» с возрастом, меньшим 6 месяцев. Печальный опыт «расползания» чрезвычайно устаревших ошибочных версий статьи про Apache действительно объясняет такое решение.

Орфография на «Куроводстве»:
    если вы заметили орфографическую, стилистическую или другую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Выделенный текст будет немедленно отослан вебмастеру, а Вы даже ничего и не заметите — настолько быстро все произойдет.

На заметку:
    если вы уже вскипели насчет дизайна этой страницы, то присмотритесь повнимательнее к названию, почитайте FAQ, сходите по лебедевским местам, как это уже предлагалось выше. Можно ли считать пародию плагиатом? Надеюсь, что нет.

Параметры этой страницы
   
GZip

Ссылки от спонсоров
    Доставка цветов екатеринбург красивый букет цветов в екатеринбурге. | компании по производству пакетов с логотипом на заказ | Кардиологии Обследование сердца в СПб lokd.ru.


Дмитрий Котеров | 5 апреля 2002 г. ©1999-2016 | Генеральный спонсор: Хостинг «Джино» | Контакт Вернуться к оглавлению