Статья
Версия для печати
Обсудить на форуме
Perl. Базовые типы и еще немного.


Статья ориентирована на новичков Perl, но не на новичков программирования.
При написании использован Perl 5.6.x и замечательная книга "Perl Programming" (ISBN 0-596-00027-8) в переводном русскоязычном издании - "Программирование на Perl" (ISBN 5-93286-020-0). Эта книга может быть использована как в качестве самоучителя, так и справочника, наравне со штатной документацией, поставляемой в комплекте с Perl.

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

Я наметил несколько статей для краткого ввода в понимание основ Perl-а. Четкого плана нет - объем и состав будет зависеть от отклика читателей и моего желания (ну и, конечно, наличия времени).
Если что-то в статье будет не понятно - спрашивайте на нашем форуме.
Для обсуждения статей: https://forum.shelek.ru/index.php?board=46.0
Для вопросов по Perl-у (но не по статьям): https://forum.shelek.ru/index.php?board=121.0


В этой статье я расскажу о типах данных.

Содержание:



1. Базовые типы данных.

Базовые типы часто называют встроенными, но это не совсем верно, так как в Perl встроены и более сложные типы. Базовых типов всего три: скаляр, массив скаляров и хеш скаляров. Переменные этих типов обозначаются префиксами "$", "@" и "%" соответственно.

1.1. Скаляр. Пробразование величин, константы, сравнение.

Скаляр - это одиночное значение. Скаляр внутренне может быть логическим значением, числовым (целым или действительным), строкой или ссылкой на объект любого другого типа. Любая из этих величин может быть преобразована в другую. Perl это делает автоматически, по необходимости (контекстно зависимо).
Исключением является только ссылка - можно автоматически преобразовать ссылку в строку или логическое значение, а другое значение в ссылку - нет. Ссылки же на объекты с перегруженными операторами могут вести себя иначе (как запрограммировано в классе), но в этой статье я этого касаться не буду. Это касается и преобразований - они тоже перегружаемы.
Кроме этого, возможно особое состояние значения - неопределенное. Оно может быть использовано как самостоятельное значение или быть преобразовано в другое, в зависимости от контекста. Для обозначения такой величины используется ключевое слово undef (от англ. undefined). Неопределенное значение соответствует пустой строке в строковом контексте, нолю в контексте арифметических операций и значению "ложь" в логическом выражении. Для работы с неопределенным значением используется функция defined.

1.1.1. Методы преобразования величин.

1.1.1.1. В строку.

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

Код: (Perl)
$number = 11; $value = "$number"; # интерполяция
$value = 11 . " рублей"; # конкатенация
$hash{11} = "нечто"; # ключ хеша
$value = "строка" . undef; # преобразование undef в пустую строку

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

1.1.1.2. В число.

Скаляр преобразуется в число при выполнении с ним арифметических действий (+ - * / % ** x neg).

Код: (Perl)
$value = 1.0 * "22";
$value = 0 - "-1E-3";
$value = 11 / "2.2"; # числовой контекст - получается целое или плавающее значение
$value = 1 + undef; # преобразование undef в ноль

Если строка не может быть распознана как число, она преобразуется в ноль.
Когда нужно принудительно преобразовать значение в число, его сладывают с числом (типично - с нолем).

1.1.1.3. В логическое значение.

Преобразование в логическое значение происходит в логических выражениях: логическими операторами (&& || ! or and not xor), в тернарном операторе ( ? : ) или в соответствующем контексте операторов условий и циклов. Также логическое значение получается в результате вычисления выражения с операторами сравнения (> < == >= <= != <=> gt lt eq ge le ne cmp).
Преобразование происходит следующим образом: ноль, пустая строка, строка, которая может быть преобразована в ноль (пример: "0"), и undef - преобразуются в ложь, а все остальные значения - истинны. Ссылки всегда истинны, так как являются указателями (перегрузка может изменить это).

Код: (Perl)
if (0) { ... } # false
if (1 != 2) { ... } # true
if ("0" || "") { ... } # false
if ("aaaa") { ... } # true
if (undef) { ... } # undef - всегда ложь


1.1.2. Константы.

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

1.1.2.1. Строковые.

Я уже упомянал интерполируемый контекст. Отличие его от неинтерполируемого в том, что некоторые символы имеют специальное значение (выполняют некоторые специфичные функции) и для их включения в строку перед ними нужно поставить символ обратного слеша (backslash - "\"). В обычных строках таких символов четыре - "\", "$", "@", "%". В регурялных выражениях тоже есть зарезервированные символы и их значительно больше.
Кавычки следует рассматиривать как операторы. Почти каждому оператору есть эквивалент.

Строковые константы:

ОператорОписаниеИнтерполяцияСиноним
''
Текст в одиночных кавычках.
Нет
q//
""
Текст в двойных кавычках.
Да
qq//
``
Выполнение команд в оболочке ОС.
Да
qx//
()
Список слов. В () слова разделяются запятыми. В qw// - пробелами.
Нет
qw//
//
Поиск по шаблону. Лучше всего пользоваться формой m// - она нагляднее.
Да (*)
m//
s///
Замена по шаблону.
Да
Нет
y///
Табличная замена символов.
Нет
tr///
""
Регулярное выражение в двойных кавычках.
Да (*)
qr//
(*) Правила интерполяции регулярных выражений рекомендую посмотреть в справочнике.

В перечисленных выше операторах слеши можно заменить на любой символ, кроме пробельных (перевод строки, пробел, табуляция и т.п.). Если в качестве ограничителя используются скобки или кавычки, то они обязательно должны быть парными. В одиночных кавычках интерполяция не происходит, даже если оператор ее подразумевает.
Чтобы не ухудшать вид программы и не наступить на грабли тонкостей некоторых конструкций, не следует заменять слеши на другие символы.  В тоже время, для регулярных выражений замена слеша на другой символ - частая практика (особенно если слеш часто встречается в регулярном выражении). При этом форма оператора поиска по шаблону "//" запрещает замену слешей (единственное исключение) для предотвращения неоднозначности.

Код: (Perl)
$value = 'строка1' . "строка2" . qw/два слова/ . (еще, три, слова) . q/строка3/ . qq/строка4/;
$value = `ls -l`; # получение результата выполнения внешней программы в оболочке ОС
$value =~ s/шаблон поиска/замена/;
$value =~ m/шаблон поиска/;
$value =~ tr/ABC/xyz/; # трансляция "A" в "x", "B" в "y" и "C" в "z"

Круглые скобки могут выступать в роли оператора принудительного создания списочного контекста (об этом ниже).
Для удобства задания строковых констант есть еще три формы: встроенные домументы (document here), последовательности кодов (v-строки) и специальные контанты Perl-а.
Встроенные документы. Применяется для удобного задания многострочного текста. Интерполяция текста выполняется. Задается как последовательность "<<" и строковая константа (идентификатор), которой будет завершен текст. Константа может быть голой или в двойных или одиночных кавычках. Документ начинается со строки, следующей за выражением. Терминирующая константа должна быть на строке в гордом одиночестве и в самом ее начале.

Код: (Perl)
$value = <<ТЕРМИНАТОР;
строка1
строка2
ТЕРМИНАТОР

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

Код: (Perl)
$value = "строка1" . <<СТРОКА2 . "строка3" . << 'СТРОКА4' . "строка5";
это строка 2
СТРОКА2
это строка 4
это продолжение строки 4
СТРОКА4

Добавлю еще, что если используется голый идентификатор, то между символами "<<" и идентификатором не должно быть пробелов.
V-строки используются для задания строки последовательностью десятичных кодов, разделенных точками.

Код: (Perl)
$value = v32.13.10; # последовательность: пробел, возврат каретки и перевод строки.

Специальные константы Perl-а используются для динамической подстановки таких строк, как имя исполняемого в данный момент файла (__FILE__), номер строки в файле(__LINE__), имя класса (__PACKAGE__) и других.
Есть несколько специальных констант, используемых не по назначению: __DATA__ и __END__. К текстовым константам они прямого отношения не имеют (но тоже полезны).
Имена всех этих констант начинаются и завершаются двумя символами подчеркивания.

Теперь кратко об интерполяции.

Интерполяция, как я уже замечал, это подмена одной последовательности символов другой. Это может быть замена имени переменной ее значением или вставкой различных символов (в том числе непечатных).
Интерполяция скалярных переменных (при этом скаляр преобразуется в строку).
Если в тексте исходной строки справа от имени переменной есть символы, которые могут быть интерпретированы как часть имени, то имя следует обернуть фигурными скобками (при этом префикс имени - $ - остается за скобками!).

Код: (Perl)
$value1 = "aaaa"; $value2 = "bbb$value1"; # результат: "bbbaaaa"
$value1 = "aaaa"; $value2 = "bbb${value1}ccc"; # результат: "bbbaaaaccc"

Интерполяция символов и последовательностей.
Такие конструкции начинаются с обратного слеша. Кстати, перед самим обратным слешом, если он не является началом интерполируемой последовательности, следует поставить еще один обратный слеш. Правила похожи на аналогичные в C/C++/Java и многих других языках, но значительно шире.

Код: (Perl)
$value = "строка, завершающаяся переводом строки\n";
$value = "вставка символа по его восьмеричному коду \012 или по шестнадцатеричному \x0a";
$value = "unicode: \{263a}";

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

Напоминаю еще раз: если в интерполируемом контексте встречается символ из нижеперечисленного набора, и он не является частью задуманной последовательности для интерполяции, то перед ним следует поставить символ обратного слеша! Таких символов всего четыре: "\", "$", "@", "%".
Кроме того, если текст заключен в кавычки, то для вставки символа кавычки того же вида нужно также поставить перед ней обратный слеш.

Код: (Perl)
$value = "\$var = $var";
$value = "Строка1: \"aaaa\"\nСтрока2: 'bbbb'\n";
$value = 'Строка1: "aaaa"' . "\n" . 'Строка2: \'bbbb\'' . "\n";
$value = "c:\\windows\\system32\\";


1.1.2.2. Числовые.

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

Код: (Perl)
$value = 123.456e-78; # научная нотация
$value = 123 + 0377 + 0xffff + 0b10101010; # 10-и, 8-и и 16-еричная запись, а также двоичная
$value = 123_456_789 + 0x1234_5678 + 0b1010_1010_1010; # форматированная версия

Напоминаю, что целая константа, похожая на десятичную, но начинающаяся с ноля, является восьмеричной!

1.1.2.3. Каких констант в Perl нет.

Логических констант нет. Вместо них обычно применяют числовые - 0 и 1 - в логическом контексте они сами преобразуются во что надо. Логические выражения возвращают "" и "1".
Также нет привычных для C/C++ символьных констант - в Perl это просто строка из одного символа.

1.1.2.4. Пользовательские константы.

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

Код: (Perl)
use constant CONST => 123; # константе CONST назначается значение 123

Такая константа не может использоваться в качестве ключа хеша, т.к. может быть интерпретирована как "голое слово". Следует или запретить использование голых слов директивой use strict "subs", или использовать константу в вычисляемом выражении.

Код: (Perl)
$CONST : constant = 567 * 890; # скалярная переменная $CONST объявляется константой


1.1.3. Сравнение скаляров.

В Perl, ввиду легкости преобразования строк и чисел друг в друга, есть два набора операторов сравнения - отдельно для чисел и для строк.

ЧисловыеСтроковые
>
gt
<
lt
==
eq
>=
ge
<=
le
!=
ne
<=>
cmp

Буквенные операторы используются для сравнения строк. Переводить, надеюсь, не надо? ;)
Операторы <=> и cmp возвращают не логическое значение, а число, как результат сравнения операндов: -1, 0 и +1. Соответственно: первый операнд меньше, равен или больше по отношению ко второму.
Следует быть внимательным и не сравнивать строки операторами сравнения чисел! В этом случае строки сперва преобразуются в числа - результат, быстрее всего, будет иной, чем при сравнении строк "правильными" операторами. То же самое относится и к сравнению чисел строковыми операторами. Самое опасное в том, что такая ошибка не является синтаксической и не может быть обнаружена при компиляции.

Код: (Perl)
"0" == "x" # true
"0" eq "x" # false

"1" == "x" # false
"1" eq "x" # false

"1.1" == "11e-1" # true
"1.1" eq "11e-1" # false


1.1.4. Ссылки.

Ссылка - это адрес объекта. Наглядно это можно продемонстрировать, распечатав строку, полученную преобразованием ссылки (только если объект не имеет перегруженного метода соответствующего преобразования) к тексту.

Код: (Perl)
@array = (1, 2, 3);
$ref = \@array;
print "" + $ref; # выводится "ARRAY(0x8e1ac18)"

Манипулировать адресами в Perl нельзя - адрес можно только скопировать. Поэтому в Perl отсутствуют обычные для C/C++ адресные операции "&" и "*", а также адресная арифметика. Явное управление памятью тоже отсутствует. В данном плане Perl похож на Java: пока на объект есть ссылки, он существует. Исчезновение последней ссылки приводит к немедленному уничтожению объекта.
Ссылка может указывать на скаляр, массив, хеш или объект другого типа. Так как ссылка - скаляр, то для ее имени используется тот же префикс - "$". Для получения ссылки на существующий объект используется оператор "\".

Код: (Perl)
$ref = \$value;
$ref = \@array;
$ref = \%hash;
$ref = \function; # ссылка на функцию

Для доступа к объекту ссылку надо разыменовать. Для этого используются дополнительные префиксы соответствующих типов.

Код: (Perl)
$value2 = $$ref;
@array = @$ref;
%hash = %$ref;
&$ref; # разыменование функции

В последней строке примера разыменовывается функция. Стоит упомянуть еще один вид префикса - "&". На практике он применяется редко. Обычно для разыменования функции используют стрелку и скобки. Следующие примеры эквивалентны:

Код: (Perl)
&$ref;
&$ref();
$ref->();

Можно напрямую обращаться к элементам объекта используя оператор разыменования ссылки "->".

Код: (Perl)
$ref->[11] = 22; # доступ к элементу массива
$ref->{'key'} = 'строка'; # доступ к элементу хеша

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


1.2 Массив скаляров.

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

Массивы в Perl динамические - могут увеличиваться и уменьшаться в процессе выполнения программы.
Массивы индексируются числом по номеру элемента, начиная с ноля (на самом деле начальным индексом управляет встроенная переменная "$[", но зачем нам это надо?). Также допускаются отрицательные индексы - в этом случае индекс складывается с размером массива и используется для указания элементов с конца.
При обращении к элементам массива индекс заключается в прямоугольные скобки. При создании массив пуст, но можно совместить определение с инициализацией. Тип значения каждого элемента массива не зависим от других элементов. Массивы могут содержать только скаляры, посему они одномерные по своей природе.

Код: (Perl)
@array = (1, "строка", 4.3333e-10); # контекст списочный

Если массив присваивается списку, содержащему другой массив, то значения присваиваются слева направо до достижения массива, а затем массиву присваиваются все оставшиеся элементы.

Код: (Perl)
@array = (1, 2, 3, 4, 5);
($val1, $val2, @array2, $val3) = @array;

В результате выполнения примера переменным $val1 и $val2 будут присвоены значения 1 и 2 соответственно, в массив @array2 будут помещены значения 3, 4 и 5, а значение переменной $val3 станет неопределенным.
Так как элементы массива - скаляры, то и обращение к ним идет как к скалярам - с префиксом "$".

Код: (Perl)
$array[0] = 11; # первый элемент
$array[-1] = 11; # последний элемент

Определить текущий размер массива можно двумя способами: конструкцией "$#" или использовав массив в скалярном контексте. Первый метод возвращает индекс последнего элемента массива. Так как индексация начинается с ноля, то это значение всегда будет на единицу меньше размера массива. Второй метод возвращает размер массива.

Код: (Perl)
$size = $#array + 1; # $size = 3
$size = @array; # $size = 3

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

Код: (Perl)
@array2 = (1, 2, @array + 0, 3); # операция сложения дает местный скалярный контекст
@array2 = (1, 2, $#array + 1, 3);
@array2 = (1, 2, scalar(@array), 3); # прямо заявляем скалярный контекст для массива

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

Код: (Perl)
$#array = 9; # теперь массив имеет размер 10
$array[200] = "abcd"; # Теперь элементы 10..199 равны undef

В Perl есть набор встроенных функций работы с массивами. Вот некоторые из них:

push ARRAY, LIST

Добавляет все значения из списка в конец массива (создает новые элементы).

shift ARRAY

Возвращает значение первого ([0]) элемента массива. При этом элемент удаляется, а индексы всех оставшихся элементов уменьшаются на единицу (сдвигаются к началу).

unshift ARRAY, LIST
pop ARRAY

Аналогичны push и shift соответственно, но элементы добавляются в начало, а удаляются с конца.

splice ARRAY [, OFFSET [, LENGTH [, LIST ] ] ]

Возвращает список значений элементов. При этом элементы удалаются из массива, а на их место помещаются значения из списка (если он задан). Причем, размер списка может отличаться от числа удаленных элементов - массив сдвигается или раздвигается. С помощью splice можно выполнить операции, эквивалентные push, pop, shift, unshift, присвоению, очистке массива или выполнить удаление его части.

Можно управлять целыми диапазонами индексов - это называется срезом.

Код: (Perl)
@array2 = @array[2..5, 10, 15..99, 101, 105]; # копирование части массива
@array[5..7] = ("aaa", "bbb", "cccc"); # присвоение элементам среза

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

Код: (Perl)
@array2 = @array; # здесь списочный контекст создается левым значением
print @array; # списочный контекст создает функция print
($val1, $val2, $val3) = @array; # первые три элемента списка присваиваются переменным
$value = (@array); # переменной присваивается значение последнего элемента

Массивы можно сортировать (функция sort), перебирать (в цикле или функцией map), преобразовывать в строку (интерполяцией или функцией join).

LIST = sort LIST;
LIST = sort { .... } LIST;
LIST = sort SUB, LIST;

LIST = map EXPR, LIST;
LIST = map { .... } LIST;

$value = "@array"; # в качестве сепаратора используется значение переменной $"
$value = join SEPARATOR, LIST;

Для перебора циклом есть несколько способов. Можно классически поступить - индексация скаляром с последовательной инкрементацией, а можно оператором цикла foreach (foreach - более понятный синоним для for). Последняя форма объединения - join - позволяет работать со срезами.

Код: (Perl)
for ($i = 0; $i < scalar(@array); $i++ ) { .... $array[$i] ..... }
foreach $value (@array) { ... $i .... }
foreach $value (@array[2...7, 12..13]) { ... $i .... }

Массивы годятся для создания любых типов стуктур со строго последовательным расположением элементов. Все, что напридумывали в STL для C++ (векторы, очереди и т.п.), в Perl уже есть в базовом комплекте, и пользоваться этим очень легко и удобно.


1.3. Хеш скаляров.

Хеш - это ассоциативный массив, индексируемый строкой. В отличие от массива хеш не сохраняет порядок элементов, так как внутренне использует хеш-таблицы для ускорения поиска. В скалярном контексте хеш возвращает строку, содержащую информацию об эффективности работы алгоритма: пустая строка для пустого хеша или строка формата "число/число" для заполненного. Эти числа означают, сколько блоков памяти использованных и выделенных соответственно. Единственная реальная польза от этого значения - использовать ее в качестве логического значения "хеш не пуст".
Для имени хеша выбран префикс "%". Доступ к элементам хеша аналогичен массиву, но используются фигурные скобки. В списках присваивания хешу можно ипользовать пары ключ/значение, разделенные "=>" или запятой - это равноценно.

Код: (Perl)
%hash = ("табак" => "забавный", "вес", 30);
$hash{"вес"} -= 5;

Если ключ состоит из одного слова, то можно не оборачивать его кавычками, но при использовании директивы use strict 'subs' это не допускается.
Проверить существование элемента можно логической функцией exists.

Код: (Perl)
if (exists $hash{'табак'}) { print "$hash{'табак'}\n"; }

Не следует путать exists и defined: элемент может существовать, но быть неопределенным (в то же время, несуществующий элемент всегда неопределен). Функция exists может также проверять наличие элемента в массиве - правила те же, что и для хеша.
При использовании exists (как, в общем, и любой другой функции, но для exists эта ошибка типична) следует помнить об эффекте "самооживления".
Рассмотрим следующий пример:

Код: (Perl)
my $ref;
exists $ref->{'aaa'};

Сначала объявляется скаляр $ref с неопределенным значением. Функция exists проверяет наличие элемента 'aaa' в хеше, на который ссылается ссылка. НО хеша не было, так как скаляр был неопределен, и ссылка самооживляется: создается пустой анонимный хеш, а скаляру присваивается ссылка на него. При этом элемент 'aaa' не создается - ведь exists лишь проверяет его наличие.
Забегая вперед, рассмотрим более сложный пример:

Код: (Perl)
my $ref;
exists $ref->{'aaa'}{'bbb'}{'ccc'};

Здесь exists проверяет наличие элемента 'ccc' в сложной трехуровневой структуре. НО exists всегда проверяет только последний индекс (!), а остальные самооживляются: скаляру $ref присваивается ссылка на хеш, в котором создается элемент 'aaa', которому, в свою очередь, присваивается ссылка на хеш, в котором создается элемент 'bbb', которому, опять-таки, присваивается ссылка на хеш, а в нем уже ищется (не создается!) элемент 'ccc'.
Это может быть не критичным, а может привести к сбою программы. Чтобы избежать этого, нужно проверять ступенчато:

Код: (Perl)
if (ref($ref) eq 'HASH' # скаляр является ссылкой на хеш
        && exists($ref->{'aaa'}) # элемент 'aaa' существует
        && ref($ref->{'aaa']) eq 'HASH' # ... и является ссылкой на хеш
        && exists($ref->{'aaa'}{'bbb'}) # элемент 'bbb' существует
        && ref($ref->{'aaa']) eq 'HASH' # ... и является ссылкой на хеш
        && exists($ref->{'aaa'}{'bbb'}{'ccc'}) # элемент 'ccc' существует
        )
{
......
}

Это может показаться сложным, но расскажите мне, в каком языке можно сделать проще?! Обычно проверка "размазывается" по логике программы и не выглядит громоздкой.
Упомянутая функция ref проверяет параметер и сообщает, является ли он ссылкой, и если является, то возвращает имя типа.

Как и у массивов, у хешей можно использовать срезы.

Код: (Perl)
%{"табак", "вес"} = %{"вес", "табак"};

Для перебора элементов хеша в Perl обычно используют функции keys и values. Они, соответственно, возвращают список ключей и значений.

Код: (Perl)
foreach $key (keys %hash) { .... $hash{$key} .... }
foreach $value (values %hash) { ... $value ..... }

Эти списки можно упорядочить сортировкой. В общем, тут применимы любые принципы работы со списками.

Код: (Perl)
foreach $key (sort keys %hash) { ... }

Хеши годятся для создания структур с именованными полями. В известной библиотеке Perl-модулей CPAN есть различные классы для создания хешей с разным внутренним алгоритмом. В том числе и упорядоченные ассоциативные массивы, аналогичные массивам в PHP.


2. Таблицы имен.

Хотя Perl не является интерпретируемым языком (Perl - язык с динамической компиляцией и исполнением), идентификаторы (имена переменных, функций, констант, пакетов) остаются актуальными и во время выполнения программы. Они хранятся в таблицах имен. У каждого пакета есть своя таблица имен. Это можно назвать пространствами имен (как в C++), но это будет не точным ответом. Пакеты в этой статье я не описываю.
В таблицах имен хранятся имена переменных, функций и пакетов более низкой иерархии. Одному имени может соответствовать произвольное число переменных разных типов, по одному для каждого типа.
Можно просмотреть содержимое таблицы через специальный хеш формата "%имя_пакета::". Элементы этого хеша можно рассматривать как универсальные ссылки на все типы сразу.
Пакетом по умолчанию является "main".

Код: (Perl)
for my $name (sort keys %main::)
{
    print "$name\n";
}

Значения переменных - объекты - хранятся отдельно от имен, и элементы таблицы имен ссылаются на эти значения. Когда переменная удаляется, счетчик ссылок объекта уменьшается на единицу.
Пример: можно в коде функции получить ссылку на объект и сохранить ее в глобальной переменной или вернуть через return. При выходе из функции имя переменной уничтожается, но ссылка на объект остается, и он продолжает существовать. Благодаря такому принципу можно возвращать из функций ссылки на внутренние переменные (чего нельзя делать в C/C++) и динамически создавать массивы объектов (точнее, массивы ссылок на объекты). Можно сказать больше: для существования объекта имя не обязательно.

Код: (Perl)
sub func
{
        my $value = 10;

        print "$value\n";
        return \$value;
}

$value2 = func();
$value3 = func();
print "$value2 $value3\n"; # результат: SCALAR(0x9b03c3c) SCALAR(0x9aecd48) - это разные объекты!

Прямая работа с таблицами имен, как правило, не нужна. Если кого-нибудь интересуют подробности, то рекомендую обратиться к справочникам.


3. Составные типы.

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

Код: (Perl)
$array_ref = [ 1, 2, 3 ]; # скаляру присвоена ссылка на анонимный массив
@array = ( [ 1, 2, 3 ], [ 4, 5, 6 ] ); # присваивается список ссылок на массивы

$array_ref->[0] = 11;
$array[0]->[0] = 22;
$array[0][0] = 22; # допускается сокращенная форма обращения к массиву

$array_ref = \@array; # для наглядности получим ссылку на двухмерный массив
$array_ref->[0]->[0] = 33; # формальный подход
$array_ref->[0][0] = 44; # допустимая форма сокращения

Анонимный массив не имеет имени, и потому при каждом выполнении оператора "[ ]" будет создан новый объект.
Пример создания двухмерного массива 3х10 в цикле:

Код: (Perl)
@array = (); # инициализация
for (1..10)
{
        push @array, [ 1, 2, 3 ];
}

Многомерные массивы Perl не аналогичны массивам C/C++ - их нельзя просто объявить как неинициализированную N-мерную матрицу.
Можно сделать так:

Код: (Perl)
@array = ();
for $i (0..9)
{
        $#{$array[$i]} = 9; # или такой вариант: $array[$i][9] = undef;
}

В этом примере конструкция "$#{}" разыменовывает ссылку в массив. Элемент с номером $i на момент выполнения выражения не существовал, и он "самооживляется" - создается ссылка на массив, для которого операцией "$#" устанавливается номер последнего элемента равный 9.

Опять сложно?
Благодаря эффекту самооживления такие извращения не нужны.

Код: (Perl)
$array[9][9] = undef;

В этом примере "из ничего" создается массив @array из десяти элементов. Первые девять - неинициализированны, а десятый содержит ссылку на анонимный массив из десяти элементов (последний элемент можно проинициализировать, если заменить undef на другое значение).
Аналогично можно поступить со ссылкой:

Код: (Perl)
$ref->[9][9] = 11;

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

Аналогично анонимным массивам существуют анонимные хеши. Для их создания используется пара фигурных скобок.

Можно создать структуру любой степени сложности. Как динамически, так и таким вот статичным объявлением:

Код: (Perl)
%struct =
(
        'имя' => 'Вася',
        'фамилия' => 'Пупкин',
        'рост' => 180,
        'собственность' => [ 'корзина', 'картина', 'картонка' ],
        'покупки' =>
                [
                        {
                                'наименование' => 'маленькая собачонка',
                                'рост' => 12.5
                        },
                        {
                                'наименование' => 'огромный пес',
                                'рост' => 100
                        }
                ]
);

Возможно существование анонимных функций.

Код: (Perl)
$ref = sub { print $_[0]; };
$ref->("параметер");


4. Встроенные переменные.

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

"$_" - переменная по умолчанию для ввода и поиска по шаблону. Эта переменная подразумевается, если переменная не задана явно, в следующих случаях (здесь мне просто не остается ничего, кроме как цитировать книгу, но я постараюсь рассказать своими словами):

  • Некоторые встроенные списковые и унарные функции и все операции проверки файлов, кроме "-t". Иначе говоря, это применимо только ко встроенным функциям, и в документации следует проверить, работает ли та или иная функция с этой переменной.
  • Операции поиска и замены по шаблону - m// и s///, а также транслятор tr///.
  • Переменная итерации для цикла forforeach).
  • Неявная переменная итерации в функциях grep и map. (Задать для них другую переменную невозможно.)
  • Буфер ввода для операций <>, readline и glob. Работает только в контексте условия оператора while и нигде более.

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

"@_" - входные параметры функции. В Perl не предусмотрены именованные параметры функций, но это легко решается присвоением отдельных элементов @_ локальным переменным, или, что порой удобнее, присвоение списку переменных.

Код: (Perl)
sub
{
        my ($parm1, $parm2) = @_; # присвоение массива списку
        .....
}

Так как во входном массиве может быть произвольное число параметров, то следует следить за числом входных параметров или использовать прототипы функций для жесткого контроля.
Есть еще одно применение массива @_ - приемный массив функции split при использовании ее в скалярном контексте. Это применение устаревшее, но еще поддерживается. Пользоваться им не рекомендуется, но о подводных камнях нужно знать.

@ARGV - аргументы командной строки. В отличие от параметра argv функции main в C/C++, этот массив не содержит имени программы в первом элементе.

%ENV - переменные окружения.

Остальные встроенные переменные смотрите в справочнике.


5. Встроенные типы.

Наиболее употребимый встроенный тип - дескриптор файла. В ранних версиях Perl этот тип был базовым, а позже, при наведении порядка, оформился как класс IO::Handle, но остался встроенным. Для этого класса есть специальные операторы и функции.
Хотя дескрипторы файлов являются ссылками, для них сделано отступление от синтаксиса (обратная совместимость с ранними версиями) - они могут не иметь префикса.
Пример открытия файла:

Код: (Perl)
open FILE, "<filename"; # без префикса
open $file, "<filename"; # желательный синтаксис

Три стандартных потока ввода-вывода имеют имена без префикса: STDIN, STDOUT и STDERR. После запуска программы они уже открыты.
Для ввода исторически используется оператор ввода "<дескриптор>". Для STDIN имя дескриптора можно опускать. Оператор "<>" считывает и возвращает очередную строку из открытого файла.

Код: (Perl)
while (<>) { print; }
while (defined($_ = <STDIN>)) { print $_; }

Строки примера функционально совершенно идентичны.
Оператор "<>" также используется для поиска файлов по шаблону. Это тоже устаревшая фича - следует пользоваться функцией glob.

Еще одним встроенным типом является typeglob. Я считаю, что для начинающих это не нужно, я лишь упомяну его. Для этого типа выделен персональный префикс - "*". Посредством этого типа можно осуществлять доступ к таблице имен.


Для начала хватит!
Думаю, я уже достаточно запутал (и запугал) ленивых и осторожных? :) Остальных прошу на форум!


Чернышов Роман (RXL) 18.02.2007.
Версия для печати
Обсудить на форуме