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

Котеров Д. В., Костарев А. Ф. - PHP 5. 2-е издание (В подлиннике) - 2008

.pdf
Скачиваний:
6114
Добавлен:
29.02.2016
Размер:
11.36 Mб
Скачать

224

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

 

 

Таким образом, мы по одному добавляем в массив $namesList элементы, например,

пронумерованные от 0. PHP узнает, что мы хотим создать массив по квадратным скобкам (нужно заметить, что для этого переменная $namesList в начале не должна

еще быть инициализирована). Мы будем в дальнейшем называть массивы, ключи

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

вскоре увидим), списками.

Некоторые стандартные функции PHP, обрабатывающие массивы, требуют переда-

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

Давайте теперь посмотрим, как можно распечатать наш список (листинг 13.1).

Листинг 13.1. Файл list.php

<?php ## Демонстрация работы со списками. $namesList[0] = "Yuen Wo Ping"; $namesList[1] = "Geofrey Darrow"; $namesList[2] = "Hugo Weaving";

echo "А вот первый элемент массива: ".$namesList[0]."<hr>"; // Печатаем в цикле все элементы массива.

for ($i=0; $i<count($namesList); $i++) echo $namesList[$i]."<br>";

?>

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

ноним sizeof().

Создание массива "на лету". Автомассивы

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

этого избежать, можно написать те же команды так:

$namesList[] = "Yuen Wo Ping";

$namesList[] = "Geofrey Darrow";

$namesList[] = "Hugo Weaving";

В этом случае PHP сам начнет (конечно, если переменная $namesList еще не существует) нумерацию с нуля и каждый раз будет прибавлять к счетчику по единичке,

создавая список. Согласитесь, довольно удобно. Разумеется, можно использовать скобки [] и не только в таком простом контексте, очень часто они применяются для

более общего действия — добавления элемента в конец массива, например:

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

225

 

 

unset($FNames); // на всякий случай стираем массив while ($f = очередное_имя_файла_в_текущем каталоге)

if (расширение_$f_есть_txt) $FNames[]=$f;

// теперь $FNames содержит список файлов с расширением txt

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

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

$names["Anderson"] = "Thomas";

$names["Weaving"] = "Hugo";

$names["Darrow"] = "Geofrey";

Далее, мы можем распечатать имя любого абонента командой:

$f = "Anderson";

echo $names["Weaving"]." said: Hmmm, mr. ".$names[$f]."...";

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

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

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

Оператор list()

Пусть у нас есть некоторый массив-список $list с тремя элементами: имя человека,

его фамилия и возраст. Нам бы хотелось присвоить переменным $name, $surname и $age эти величины. Это, конечно, можно сделать так:

$name = $list[0];

$surname =

$list[1];

$age

=

$list[2];

Но гораздо изящнее будет воспользоваться оператором list(), предназначенным как

раз для таких целей:

list ($name, $surname, $age) = $list;

Согласитесь, выглядит несколько приятнее. Конечно, list() можно задействовать

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

226

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

 

 

Что, если нам нужны только второй и третий элементы массива $list? В этом слу-

чае имеет смысл пропустить первый параметр в операторе list(), вот так:

list (, $surname, $age) = $list;

Таким образом, мы получаем в переменных $surname и $age фамилию и возраст человека, не обращая внимания на его имя в первом аргументе.

Разумеется, можно пропускать любое количество элементов, как слева или справа, так и посередине списка. Главное — не забыть проставить нужное количество запятых.

Списки и ассоциативные массивы: путаница?..

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

Операция [] всегда добавляет элемент в конец массива, присваивая ему при этом

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

Оператор array() и многомерные массивы

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

$names["Weaving"] = "Hugo";

$names["Chong"]

= "Marcus";

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

echo

$names["Weaving"];

//

выведет

Hugo

echo

$names["ложка"];

//

ошибка:

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

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

227

 

 

Идем дальше. Прежде всего, обратим внимание: приведенным выше механизмом мы

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

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

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

//создает такой же массив, как в предыдущем примере с именами $names = array("Weaving"=>"Hugo", "Chong"=>"Marcus");

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

$namesList = array("Yuen Wo Ping", "Geofrey Darrow", "Hugo Weaving");

Теперь займемся вопросом формирования двумерных (и вообще многомерных) мас-

сивов. Это довольно просто. В самом деле, мы уже говорили, что значениями переменных (и значениями элементов массива тоже, поскольку PHP не делает никаких

различий между переменными и элементами массива) может быть все, что угодно,

в частности — опять

же массив. Так,

можно создавать ассоциативные массивы

(а можно — списки)

с любым числом

измерений. Например, если кроме имени

о человеке известен также его возраст, то можно инициировать массив $names так:

$dossier["Anderson"] = array("name"=>"Thomas", "born"=>"1962-03-11"); $dossier["Reeves"] = array("name"=>"Keanu", "born"=>"1962-09-02");

или даже так:

$dossier = array(

"Anderson" => array("name"=>"Thomas", "born"=>"1962-03-11"), "Reeves" => array("name"=>"Keanu", "born"=>"1962-09-02"),

);

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

echo $dossier["Anderson"]["name"]; // напечатает "Thomas"

echo $dossier["Reeves"]["diff"]; // ошибка: нет элемента "diff"

Кстати, мы можем видеть, что ассоциативные массивы в PHP удобно использовать

как некие структуры, хранящие данные. Это похоже на конструкцию struct в языке

C (или record в Pascal).

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

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

заодно и подытожим все сказанное выше.

228

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

 

 

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

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

echo

$names["Weaving"];

//

выводит элемент массива с ключом "Weaving"

echo

$dossier["Anderson"]["name"];

//

так используются двумерные массивы

echo (SomeFuncThatReturnsArray())[5]; // ОШИБКА! Так нельзя! // Вот так правильно:

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

Последний пример показывает, что PHP сильно отличается от языка C с точки зре-

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

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

$names["Davis"] = "Don";

// присваиваем элементу массива строку "Don"

$ref = &$dossier["Reeves"]["name"];

// $ref — синоним элемента массива

$namesList[] = "Paul Doyle";

// добавляем новый элемент

Функция count()

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

$num = count($namesList); // в $num — количество элементов массива

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

что по ошибке.

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

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

$good = array("Arahanga"=>"Julian ", "Doran"=>"Matt"); $bad = array("Goddard"=>"Paul", "Taylor"=>"Robert"); $all = $good + $bad;

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

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

229

 

 

вы сливаются. Видите, как проявляется направленность массивов? Она заставляет оператор + стать некоммутативным, т. е. $good + $bad не равно $bad + $good.

Слияние списков

Никогда не сливайте списки при помощи оператора +! Будьте особенно вниматель-

ны. Рассмотрим, например, программу, представленную в листинге 13.2.

Листинг 13.2. Файл badplus.php

<?php ## Неправильное слияние списков.

$good = array("Julian Arahanga", "Matt Doran", "Belinda McClory"); $bad = array("Paul Goddard", "Robert Taylor");

$ugly = array("Clint Eastwood"); $all = $good + $bad + $ugly; print_r($all);

?>

Возможно, вы рассчитываете, что в $all будет массив, состоящий из 3 + 2 + 1 = 6 эле-

ментов? Это неверно! Вызов print_r() напечатает лишь следующий результат:

Array ( [0] => Julian Arahanga [1] => Matt Doran [2] => Belinda McClory)

Как видите, все произошло так, будто бы $bad и $ugly вообще не упоминались. Вот почему так происходит. При конкатенации массивов с некоторыми одинаковыми элементами (т. е. элементами с одинаковыми ключами) в результирующем массиве

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

Обновление элементов

Последний факт может слегка озадачить. Казалось бы, элементы массива $bad по

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

$a = array('a'=>10, 'b'=>20);

$b = array('b'=>'new?');

$a += $b;

Мы-то ожидали, что оператор += обновит элементы $a при помощи элементов $b.

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

Так как же нам все-таки обновить элементы в массиве $a? Например, при помощи стандартной функции array_merge(), лишенной указанного недостатка (о ней мы

поговорим позже):

$a = array_merge($a, $b);

Или же воспользуйтесь циклом:

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

230

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

 

 

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

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

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

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

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

численные в правом операнде-массиве, если они еще не содержатся в массиве слева.

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

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

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

Перебор списка

Если наш массив — список, то эта задача, как мы уже знаем, не будет особенно

обременительной:

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

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

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

for ($i=0; $i<count($namesList); $i++)

echo "$namesList[$i]\n";

Дело в том, что это, пожалуй, единственный способ, который совместим с PHP 3.

А что касается четвертой версии, то мы спокойно можем помещать массивы в строки, заключив их в фигурные скобки вместе с символом $ (листинг 13.3).

Листинг 13.3. Файл for.php

<?php ## Перебор списка.

$dossier = array(

array("name"=>"Thomas Anderson", "born"=>"1962-03-11"),

array("name"=>"Keanu Reeves",

"born"=>"1962-09-02"),

);

for($i=0; $i<count($dossier); $i++)

echo "{$dossier[$i]['name']} was born {$dossier[$i]['born']}<br>";

?>

Обратите внимание, что мы используем апострофы внутри скобок {}. Если бы мы этого не сделали, PHP выдал бы предупреждение: "Use of undefined constant name — assumed 'name'".

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

231

 

 

Перебор ассоциативного массива

Давайте теперь предположим, что у нас есть ассоциативный массив $birth: его клю-

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

Листинг 13.4. Файл forkeys.php

<?php ## Перебор ассоциативного массива. $birth = array(

"Thomas Anderson" => "1962-03-11", "Keanu Reeves" => "1962-09-02",

);

for (reset($birth); ($k=key($birth)); next($birth)) echo "$k родился {$birth[$k]}<br>";

?>

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

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

На самом деле, две простейшие функции, — reset() и next(), — помимо выполне-

ния своей основной задачи, еще и возвращают некоторые значения, а именно:

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

массив пуст);

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

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

for (end($birth); ($k=key($birth)); prev($birth))

echo "$k родился {$birth[$k]}<br>";

По контексту несложно сообразить, как это работает. Функция end() устанавливает

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

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

вращает не ключ, а величину текущего элемента (если он не указывает на конец массива).

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

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

232

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

 

 

массив мы можем перебрать как в одну, так и в другую сторону. Однако существуют и недостатки.

Вложенные циклы

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

пию массива, и во внутреннем цикле работать с ней, но это не очень-то красиво). Однако практика показывает, что такие переборы встречаются крайне редко.

Нулевой ключ

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

for (reset($birth); ($k=key($birth)); next($birth))

echo "$k родился {$birth[$k]}<br>";

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

Нам придется писать так:

for(reset($birth); ($k=key($birth))!==false; next($birth))

echo "$k родился {$birth[$k]}<br>";

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

ный метод перебора массивов, о котором сейчас и пойдет речь.

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

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

дом "витке" цикла одновременно получать и ключ, и значение текущего элемента.

Старый способ перебора

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

for (reset($birth); list ($k,$v)=each($birth); /*пусто*/)

echo "$k родился $v<br>";

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

Что делает функция each()? Во-первых, возвращает небольшой массив (мы бы даже

сказали, список), нулевой элемент которого хранит величину ключа текущего эле-

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

233

 

 

мента массива $birth, а первый — значение текущего элемента. Во-вторых, она про-

двигает указатель текущего элемента к следующей позиции. Следует заметить, что если следующего элемента в массиве нет, то функция возвращает не список, а false. Именно поэтому она и размещена в условии цикла for. Становится ясно, почему

мы не указали третий блок операторов в цикле for: он просто не нужен, ведь указа-

тель на текущий элемент и так смещается функцией each().

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

Прямой перебор массивов применялся столь часто, что разработчики PHP решили

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

foreach ($birth as $k=>$v) echo "$k родился $v<br>";

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

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

Ссылочный синтаксис foreach

В предыдущей главе мы говорили, что цикл foreach перед началом своей работы

выполняет копирование массива. Это позволяет, например, использовать вместо

переменной-массива результат работы некоторой функции или даже сложное выражение:

foreach (array(101, 314, 606) as $magic)

echo "На стене было написано: $magic.<br>";

Работа с копиями в большинстве случаев оказывается удобной, однако она не по-

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

цу, в действительности не изменяет переменную.

Листинг 13.5. Файл foreach_copy.php

<?php ## Цикл перебирает копию массива, а не оригинал. $numbers = array(100, 313, 605);

foreach ($numbers as $v) $v++; echo "Элементы массива: ";

foreach ($numbers as $elt) echo "$elt "; ?>

Тут вы можете оставить комментарий к выбранному абзацу или сообщить об ошибке.

Оставленные комментарии видны всем.