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

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

20. Хэши, массивы и списки в Perl,  или уже чуть посложнее

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

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

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

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

Скаляры

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

Итак, скалярная переменная Perl может хранить одно из трех значений: строку, вещественное число или же ссылку на произвольный объект. Точка. В отличие от PHP, в котором по имени переменной нельзя узнать, какого она типа ($i может быть и массивом, и скаляром, да вообще чем угодно), Perl придерживается довольно четких правил на этот счет. А посему скалярные переменные всегда начинаются с доллара, хотя для всех остальных сущностей это не так.

Вернее будет сказать несколько иначе: любой скалярный объект, который может стоять в левой части оператора присваивания, всегда начинается со знака $. Собственно, именно по этому признаку Perl и определяет, что объект — скалярного типа. Чудес не бывает, а Perl не станет думать за вас о типе переменной.

Лирическое отступление 
К сожалению, из этого правила существуют исключения — в левой части оператора присваивания, как это ни странно, может стоять вызов функции специального рода. Это — так называемые LVALUE-функции, и их обсуждение выходит за рамки настоящей наблы. Может, как-нибудь в другой раз.

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

print $n;    # число или строка
print $#A;   # номер последнего элемента в массиве @A
print $A[1]; # элемент массива с индексом 1
print $$r;   # разыменование ссылки на скаляр

Забегая вперед, скажу, что даже обращение к скалярному элементу массива происходит при помощи вездесущего доллара. А вы говорите — инфляция...

Списки

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

Чайник 

Список задает в программе некоторую последовательность скаляров, массив же — хранит их в памяти.

Список в программе на Perl задается в круглых скобках любого уровня вложенности:

(1,2,3)
(((1,2,3)))
((1),(2),(3))

Таким образом, все три приведенные выше строчки задают один и тот же список, содержащий числа 1, 2 и 3. Вложенность скобок не играет никакой роли — хоть всю программу ими обвешайте. Итак, список — это набор скалярных значений, заключенных в круглые скобки, и ничего более.

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

Лирическое отступление 
Как ни странно, но один и тот же список в программе может в итоге принять весьма причудливую форму в зависимости от того, какой переменной его присвоили.

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

$a = (10,20,30);

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

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

Имеет место более общее утверждение. Как известно, присвоить можно только значение — высказывание «вчера я присвоил переменной другую переменную и пошел домой» следует понимать как «присвоил переменной значение другой переменной». То есть, когда в программе вы пишете A=B, перед присваиванием Perl переводит B либо в список, либо в скалярное значение (в зависимости от вида A).

Лирическое отступление 
Этот момент иногда оказывается весьма существенным. Например, функции могут возвращать либо скаляр, либо список, и ничто иное. Они не могут вернуть ни массив, ни хэш.

Раз существует всего два типа «обменных» данных, должны существовать и какие-то правила перевода одного типа в другой. Эти правила довольно просты. Когда список нужно перевести в скаляр, просто берется его последний элемент, остальные откидываются. Когда скаляр нужно перевести в список, Perl «мысленно» обрамляет его скобками. Что может быть проще?..

Массивы

Вернувшись к нашим баранам, можно легко догадаться, что будет, если присвоить список переменной типа массив (такие переменные всегда предваряются знаком @):

@A = (10,20,30);

Конечно, Ларри Уолл — не Осама бин Ладен и понимал, что в такой ситуации в массив должно быть помещено 3 элемента, перечисленных в списке. Так оно, в сущности, и происходит.

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

Итак, если Perl видит слева нечто, начинающееся с @, а справа — список, то он понимает, что происходит присваивание значения массиву и поступает соответственно. Таким образом, само обозначение «с собакой» используется только в том случае, когда имеется в виду массив целиком. Для обращения к конкретному элементу массива по-прежнему необходимо применять доллар — деньги не пахнут.

Вот несколько примеров:

# присваиваем значение всему массиву
@A = (10,20);
# присваиваем один массив другому
@B = @A;
# то же самое - все равно в первом случае
# массив разворачивается в список...
@B = ((@A))

Я уже говорил о том, как преобразуются между собой скалярные и списковые величины. Настало время посмотреть, как массивы преобразуются в список и в скаляр. Ну со списком все ясно — его элементами просто становятся элементы массива, как это показано в последней строчке примера. Что же до скаляра, то тут происходит весьма интересная вещь. Можно было бы подумать, что, как и в случае со списком, в переменную попадает лишь последний элемент массива, однако это не так: при преобразовании массива в скаляр на выходе получается число, величина которого — количество элементов в массиве. В этом заключается одно из фундаментальных отличий массива от списка (помимо всего сказанного выше). Теперь ясно, что делает следующий код:

# помещаем в $a число элементов @A
$a = @A; 
# помещаем в @B все, что было в @A, 
# и еще парочку элементов
@B = (1, @A, 2);

В первом случае массив @A преобразуется в скаляр (как говорят, трактуется в скалярном контексте), и получается число элементов в нем. Во втором случае массив трактуется в списковом контексте, а потому превращается в список; как мы знаем, ему все равно, сколько там вложенных скобок, поэтому на выходе массиву @B присваивается вновь составленный список, дополненный элементами 1 и 2.

Лирическое отступление 
Ну вот, наконец-то удалось описать понятие скалярного и спискового контекстов. Далее я буду пользоваться только ими, избегая фраз типа «преобразование массива в скаляр».

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

# вывести число элементов хэша
@K = keys %data;
print scalar(@K);

Функция print подразумевает для своих аргументов списковый контекст, поэтому, написав print @K, мы бы вывели на экран значения ключей, но никак не их количество. Давайте теперь упростим этот код — избавимся от временной переменной @K. Итак, вот программа, которая выдаст нам тот же самый результат:

# вывести число элементов хэша
print scalar(keys %data);

Хотя о хэшах мы еще не говорили, нетрудно понять, что делает данный код: вначале он при помощи функции keys получает список всех ключей массива %data, а затем трактует этот список в скалярном контексте и получает... так, м-ммм... листаем наши конспекты... последний элемент списка??? Но это же совсем не то, что мы хотели!

На самом деле, все в порядке, и программа работает как надо. Все дело в том, что в данном примере функция keys возвращает не список, а прямо сразу число — количество элементов. Как же она узнает, когда нужно возвращать число, а когда — список ключей?.. Именно об этом и заботится оператор scalar: он передает вызываемой функции специальный флажок, который говорит, вызвана она в скалярном или в списковом контексте.

Лирическое отступление 
По-правде говоря, этот флажок передается даже и без слова scalar, в случае, если контекст очевиден (например, когда мы присваиваем возвращаемое значение скалярной переменной).

Итак, функция keys проверяет флажок и, если требуется скаляр, возвращает число элементов массива, а если нет — то список. Это можно было бы записать на Perl вот так:

sub keys
{   ... работаем ...
    return wantarray? @K : scalar(@K);
}

Скалярный контекст для получения числа элементов массива (но не списка!) удобно применять, например, в цикле for:

for(my $i=0; $i<@A; $i++) {...}

Это выглядит несколько лучше, чем такой код:

for(my $i=0; $i<=$#A; $i++) {...}

На всякий случай я приведу еще пару примеров работы с массивами.

# получаем элемент массива
$a = $A[$i*2-1];
# последний элемент - вот так просто
$b = $A[-1];
# предпоследний и последний элементы
($a, $b) = ($A[-2], $A[-1]);

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

# перебор массива
foreach my $elt (@A) {...}
# или по-другому
for(my $i=0; $i<@A; $i++) {...}
# добавление элемента в конец массива
push @A, 10;
# добавление трех элементов (можно без скобок)
push @A, (10, 20, 30)
# добавление массива @B в начало @A
unshift @A, @B;

Хэши

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

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

%H = (
    'a', 'aaa', # первая пара "ключ-значение"
    'b', 'bbb', # вторая пара
    'c', 'ccc', # третья
);

Чайник 

Как было написано в предыдущей набле, запятая в конце последней пары допускается, это не ошибка.

Обилие апострофов несколько раздражает, а потому в Perl был придуман новый знак пунктуации — стрелочка =>, которая ведет себя идентично запятой, однако позволяет указывать слева от себя слово без апострофов, не выдавая предупреждений:

%H = (
    a => 'aaa', # первая пара "ключ-значение"
    b => 'bbb', # вторая пара
    c => 'ccc', # третья
);

Во всем остальном этот значок полностью аналогичен запятой, поэтому не смущайтесь, когда увидите такой вызов функции:

print 10=>" "=>20=>" "=>30;

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

Когда хэш трактуется в списковом контексте, он превращается в 2*N значений, где N — число пар ключ-значение в хэше. Однако учите, что порядок следования элементов в полученном списке будет непредсказуемым — это специфика работы с хэшами в Perl. То есть, получая доступ по ключу, вы за это расплачиваетесь «порядочностью» значений.

Лирическое отступление 
Нельзя сказать, что в PHP хэши удобнее. Здесь просто другая идеология. Везде, где требуется предсказуемый порядок элементов, используйте массивы, а где поиск по ключу — хэши. Это даже дисциплинирует.

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

# присваиваем значение хэшу целиком
%H = (a=>'aaa', b=>'bbb');
# получаем доступ по ключу...
my $a = $H{'a'};
# а можно и так
$H{b} = 100;

Чайник 

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

Наконец, когда хэш трактуется в скалярном контексте, он возвращает довольно интересную строку, которая выглядит вот так: M/N. Здесь M — число ключей в хэше, а N — это, грубо говоря, количество места, которое еще есть в памяти данного хэша для размещения элементов.

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

Ну и несколько примеров работы с хэшами.

# создаем хэш из списка
%H = (a=>10, b=>20, c=>30);
# перебираем все элементы
while(my ($k,$v)=each(%H)) {...}
# перебираем ключи в алфавитном порядке
foreach my $k (sort keys %H) {...}
# добавляем элемент в хэш
$H{d} = 40;
# удаляем элемент из хэша
delete $H{a};
# проверяем существование ключа
if(exists $H{b}) {...}

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

Что преобразуем Тип В скаляр В список
$a скаляр значение (значение)
(3,4,5) список последний элемент (3,4,5)
@A массив число элементов (элементы)
%A хэш M/N (k1,v1, k2,v2, ...)

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

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

На странице:
    20. Хэши, массивы и списки в Perl,  или уже чуть посложнее
Скаляры
Списки
Массивы
Хэши

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

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

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

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

Ссылки от спонсоров
    заказ цветов цветы


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