Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Самоучитель по PHP 4

.pdf
Скачиваний:
82
Добавлен:
02.05.2014
Размер:
4.36 Mб
Скачать

Глава 10. Ассоциативные массивы

171

Операция [] всегда добавляет элемент в конец массива, присваивая ему при этом такой числовой индекс, который бы не конфликтовал с уже имеющимися в массиве (точнее, выбирается номер, превосходящий все имеющиеся цифровые ключи в массиве). Вообще говоря, любая операция $Array[ключ]=значение всегда добавляет элемент в конец массива, конечно, за исключением тех случаев, когда ключ уже присутствует в массиве. Если вы захотите изменить порядок следования элементов в ассоциативном массиве, не изменяя в то же время их ключей, это можно сделать одним из двух способов: воспользоваться функциями сортировки, либо же создать новый пустой массив и заполнить его в нужном порядке, пройдясь по элементам исходного массива.

Инструкция array()

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

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

$Names["Ivanov"] ="Dmitry"; $Names["Petrova"]="Helen";

Теперь можно, как мы знаем, написать:

echo

$Names["Petrova"];

//

выведет

Helen

echo

$Names["Oshibkov"];

//

ошибка:

в массиве нет такого элемента!

Идем дальше. Прежде всего обратим внимание: приведенным выше механизмом мы никак не смогли бы создать пустой массив. Однако он очень часто может нам понадобиться, например, если мы не знаем, что раньше было в массиве $Names, но хотим его проинициализировать указанным путем. Кроме того, каждый раз задавать массив указанным выше образом не очень-то удобно — приходится все время однообразно повторять строку $Names...

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

//создает пустой массив $Names $Names=array();

//создает такой же массив, как в предыдущем примере с именами $Names=array("Ivanov"=>"Dmitry", "Petrova"=>"Helen");

//создает список с именами (нумерация 0,1,2)

$NamesList=array("Dmitry","Helen","Sergey");

172

Часть III. Основы языка PHP

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

$Names["Ivanov"] = array("name"=>"Dmitry","age"=>25); $Names["Petrova"] = array("name"=>"Helen", "age"=>23);

или даже так:

$Names=array(

"Ivanov" => array("name"=>"Dmitry","age"=>25), "Petrova"=> array("name"=>"Helen", "age"=>23)

);

Как же добраться до нужного нам элемента в нашем массиве? Нетрудно догадаться по аналогии с другими языками:

echo $Names["Ivanov"]["age"]; // напечатает "25"

echo $Names["Petrova"]["bad"]; // ошибка: нет такого элемента "bad"

Довольно несложно, не правда ли? Кстати, мы можем видеть, что ассоциативные массивы в PHP удобно использовать как некие структуры, хранящие данные. Это похоже на конструкцию struct в Си (или record в Паскале). Пожалуй, это единственный возможный способ организации структур, но он очень гибок.

Операции над массивами

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

Доступ по ключу

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

echo $Arr["anykey"]; // выводит элемент массива $Arr с ключом anykey echo $Arr["first"]["second"]; // так используются двумерные массивы echo (SomeFuncThatReturnsArray())[5]; // ОШИБКА! Так нельзя!

Глава 10. Ассоциативные массивы

173

// Вот так правильно:

$Arr= SomeFuncThatReturnsArray(); echo $Arr[5];

Последний пример показывает, что PHP сильно отличается от Си с точки зрения работы с массивами: в нем нет такого понятия, как "контекст массива", а значит, мы не можем применить [] непосредственно к значению, возвращенному функцией.

Величина $Arr[ключ] является полноценным "левым значением", т. е. может стоять в левой части оператора присваивания, от нее можно брать ссылку с помощью оператора &, и т. д. Например:

$Arr["anykey"]=array(100,200); // присваиваем элементу массива 100 $ref=&$Arr["first"]["second"]; // $ref — синоним элемента массива $Arr[]="for add"; // добавляем новый элемент

Функция count()

Мы можем определить размер (число элементов) в массиве при помощи стандартной функции count():

$num=count($Names); // теперь в $num — число элементов в массиве

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

Слияние массивов

Еще одна фундаментальная операция — слияние массивов, т. е. создание массива, содержащего как элементы одного, так и другого массива. Реализуется это при помощи оператора +. Например:

$a=array("a"=>"aa", "b"=>"bb"); $b=array("c"=>"cc", "d"=>"dd"); $c=$a+$b;

В результате в $c окажется ассоциативный массив, содержащий все 4 элемента, а

именно: array("a"=>"aa", "b"=>"bb", "c"=>"cc", "d"=>"dd"), причем именно в указанном порядке. Если бы мы написали $c=$b+$a, результат бы был не-

много другой, а именно: array("c"=>"cc", "d"=>"dd", "a"=>"aa", "b"=>"bb"), т. е. элементы расположены в другом порядке. Видите, как проявляется направленность массивов? Она заставляет оператор + стать некоммутативным, т. е. $a+$b не равно $b+$a, если $a и $b — массивы.

Будьте особенно внимательны при слиянии таким образом списков. Рассмотрим следующие операторы:

174

Часть III. Основы языка PHP

$a=array(10,20,30); $b=array(100,200); $c=$a+$b;

Возможно, вы рассчитываете, что в $c будет array(10,20,30,100,200)? Это неверно: там окажется array(10,20,30). Вот почему так происходит. При конкатенации массивов с некоторыми одинаковыми элементами (то есть, элементами с одинаковыми ключами) в результирующем массиве останется только один элемент с таким же ключом — тот, который был в первом массиве, и на том же самом месте.

Последний факт может слегка озадачить. Казалось бы, элементы массива $b по логике должны заменить элементы из $a. Однако все происходит наоборот. Окончательно выбивает из колеи следующий пример:

$a=array('a'=>10, 'b'=>20); $b=array('c'=>30, 'b'=>'new?'); $a+=$b;

Мы-то ожидали, что оператор += обновит элементы $a при помощи элементов $b. А напрасно. В результате этих операций значение $a не изменится! Если вы не верите своим глазам, можете проверить.

Так как же нам все-таки обновить элементы в массиве $a? Получается, только прямым способом — с помощью цикла:

foreach ($b as $k=>$v) $a[$k]=$v;

Что поделать, так уж распорядились разработчики PHP.

Еще несколько слов насчет операции слияния массивов. Цепочка

$z=$a+$b+$c+...и т. д.;

эквивалентна

$z=$a; $z+=$b; $z+=$c; ...и т. д.

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

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

Глава 10. Ассоциативные массивы

175

Так как списки являются тоже ассоциативными массивами, оператор + будет работать с ними неправильно! Например, в результате слияния списков array(10,20) и array(100,200,300) получится список array(10,20,300)

всего из трех элементов! Согласитесь, ведь это совсем не то, что вы ожидали увидеть, не правда ли?..

Косвенный перебор элементов массива

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

// Пусть $Names — список имен. Распечатаем их в столбик for($i=0; $i<count($Names); $i++)

echo $Names[$i]."\n";

Я стараюсь везде, где можно, избегать помещения имени переменной-массива в кавычки — например, предыдущий пример я не пишу вот так:

for($i=0; $i<count($Names); $i++) echo "$Names[$i]\n";

Дело в том, что это, пожалуй, единственный способ, который совместим с PHP версии 3. А что касается четвертой версии, то мы спокойно можем помещать массивы в строки, заключив их в фигурные скобки вместе с символом $:

$Names=array( array(’name’=>’Вася’, ’age’=>20), array(’name’=>’Билл’, ’age’=>40)

);

for($i=0; $i<count($Names); $i++) echo "{$Names[$i][’age’]}\n";

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

for(Reset($Names); ($k=key($Names)); Next($Names)) echo "Возраст $k — {$Names[$k]} лет\n";

Эта конструкция опирается на еще одно свойство ассоциативных массивов в PHP. А именно, мало того, что массивы являются направленными, в них есть еще и такое понятие, как текущий элемент. Функция Reset() просто устанавливает этот элемент на первую позицию в массиве. Функция key() возвращает ключ, который имеет текущий элемент (если он указывает на конец массива, возвращается пустая строка, что

176

Часть III. Основы языка PHP

позволяет использовать вызов key() в контексте второго выражения for). Ну а функция Next() просто перемещает текущий элемент на одну позицию вперед.

На самом деле, две простейшие функции, — Reset() и Next(), — помимо выполнения своей основной задачи, еще и возвращают некоторые значения, а именно:

r функция Reset() возвращает значение первого элемента массива (или пустую строку, если массив пуст);

r функция Next() возвращает значение элемента, следующего за текущим (или пустую строку, если такого элемента нет).

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

for(End($Names); ($k=key($Names)); Prev($Names)) echo "Возраст $k — {$Names[$k]} лет\n";

По контексту несложно сообразить, как это работает. Функция End() устанавливает позицию текущего элемента в конец массива, а Prev() передвигает ее на один элемент назад.

И еще. В PHP имеется функция current(). Она очень напоминает key(), только возвращает не ключ, а величину текущего элемента (если он не указывает на конец массива).

Недостатки косвенного перебора

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

Одинаковые ключи

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

Нулевой ключ

А что, если в массиве встретится ключ 0 (хотя для массивов имен это, согласитесь, маловероятно)? Давайте еще раз посмотрим на первый цикл перебора:

for(Reset($Names); ($k=key($Names)); Next($Names)) echo "Возраст $k — {$Names[$k]} лет\n";

Глава 10. Ассоциативные массивы

177

В этом случае выражение ($k=key($Names)), естественно, будет равно нулю, и цикл оборвется, чего бы нам совсем не хотелось.

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

Прямой перебор массива

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

Классический перебор

Давайте опять вернемся к нашему примеру, в котором массив $Names хранил связь имен людей и их возрастов. Вот как можно перебрать этот массив при помощи прямого перебора:

for(Reset($Names); list($k,$v)=each($Names); /*пусто*/) echo "Возраст $k — $v\n";

В самом начале заголовка цикла мы видим нашу старую знакомую Reset(). Дальше переменным $k и $v присваивается результат работы функции each(). Третье условие цикла попросту отсутствует (чтобы это подчеркнуть, я включил на его место комментарий).

Что делает функция each()? Во-первых, возвращает небольшой массив (я бы даже сказал, список), нулевой элемент которого хранит величину ключа текущего элемента массива $Names, а первый — значение текущего элемента. Во-вторых, она продвигает указатель текущего элемента к следующей позиции. Следует заметить, что если следующего элемента в массиве нет, то функция возвращает не список, а false. Именно поэтому она и размещена в условии цикла for. Становится ясно, почему мы не указали третий блок операторов в цикле for: он просто не нужен, ведь указатель на текущий элемент и так смещается функцией each().

Перебор в стиле PHP 4

Прямой перебор массивов применялся столь часто, что разработчики PHP решили в четвертой версии языка добавить специальную инструкцию перебора массива — foreach. Мы уже рассматривали ее ранее. Вот как с ее помощью можно перебрать и распечатать наш массив людей:

foreach($Names as $k=>$v) echo "Возраст $k — $v\n";

178

Часть III. Основы языка PHP

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

Есть и еще одна причина предпочесть этот вид перебора "связке" цикла for с eaсh(). Дело в том, что при применении foreach мы указываем имя переби- раемого массива $Names только в одном месте, так что когда вдруг потребу- ется это имя изменить, нам достаточно будет поменять его только один раз. Наоборот, использование Reset() и each() заставит нас в таком случае из- менять название переменной в двух местах, что потенциально может привести к ошибке. Представьте, что произойдет, если мы случайно изменим операнд each(), но сохраним параметр Reset()!

Списки и строки

Есть несколько функций, которые чрезвычайно часто используются при программировании сценариев. Среди них — функции для разбиения какой-либо строки на более мелкие части (например, эти части разделяются в строке каким-то специфическим символом типа |), и, наоборот, слияния нескольких небольших строк в одну большую, причем не впритык, а вставляя между ними разделитель. Первую из этих возможностей реализует стандартная функция explode(), а вторую — implode(). Рекомендую обратить особое внимание на указанные функции, т. к. они применяются очень часто.

Функция explode() имеет следующий синтаксис:

list explode(string $token, string $Str [, int $limit])

Она получает строку, заданную в ее втором аргументе, и пытается найти в ней подстроки, равные первому аргументу. Затем по месту вхождения этих подстрок строка "разрезается" на части, помещаемые в массив-список, который и возвращается. Если задан параметр $limit, то учитываются только первые ($limit-1) участков "разреза". Таким образом, возвращается список из не более чем $limit элементов. Это позволяет нам проигнорировать возможное наличие разделителя в тексте последнего поля, если мы знаем, что всего полей, скажем, 6 штук. Вот пример:

$st="4597219361|Иванов|Иван|40|ivan@ivanov.com|Текст, содержащий (|)!"; $A=explode("|",$st,6); // Мы знаем, что там только 6 полей!

// теперь $A[0]="Иванов", ... $A[5]= "Текст, содержащий (|)!" list($Surname,$Name,$Age,$Email,$Tel)=$A; // распределили по переменным

Глава 10. Ассоциативные массивы

179

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

Функция implode() и ее синоним join() производят действие, в точности обратное вызову explode().

string implode(string $glue, list $List) или string join(string $glue, list $List)

Они берут ассоциативный массив (обычно это список) $List, заданный в ее первом параметре, и "склеивают" его значения при помощи "строки-клея" $glue во втором параметре. Примечательно, что вместо списка во втором аргументе можно передавать любой ассоциативный массив — в этом случае будут рассматриваться только его значения.

Рекомендую вам чаще применять функции implode() и explode(), а не писать самостоятельно их аналоги. Работают они очень быстро.

Сериализация

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

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

Рекомендую проделать это в качестве упражнения, заодно постарайтесь до- биться, чтобы упакованные данные занимали минимум объема. Это пригодит- ся вам в будущем, при работе с Cookies.

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

И тут нам на помощь опять приходят разработчики PHP. Оказывается, обе функции давным-давно реализованы, причем весьма эффективно со стороны быстродействия (но, к сожалению, непроизводительно с точки зрения объема упакованных данных). Называются они, соответственно, Serialize() и Unserialize().

180

Часть III. Основы языка PHP

Функция Serialize() возвращает строку, являющуюся упакованным эквивалентом некоего объекта $Obj, переданного во втором параметре.

string Serialize(mixed $Obj)

При этом совершенно не важно, что это за объект: массив, целое число…. Да что угодно. Например:

$A=array("a"=>"aa", "b"=>"bb", "c"=>array("x"=>"xx")); $st=Serialize($A);

echo $st;

// выведется что-то типа нечто:

//

a:2:{s:1:"a";s:2:"aa";s:1:"b";s:2:"bb";s:1:"c";a:1:{s:1:"x";s:2:"xx";}}

Вообще-то, я не уверен, что в будущих версиях PHP такой формат "упаковки" сохранится неизменным, хотя это очень и очень вероятно.

Функция Unserialize(), наоборот, принимает в лице своего параметра $st строку, ранее созданную при помощи Serialize(), и возвращает целиком объект, который был упакован.

mixed Unserialize(string $st)

Например:

$a=array(1,2,3);

 

$s=Serialize($a);

 

$a="bogus";

 

echo count($a);

// выводит 1

$a=Unserialize($s);

 

echo count($a);

// выводит 3

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