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

Самоучитель PHP 4 - Котеров Д. В

..pdf
Скачиваний:
92
Добавлен:
24.05.2014
Размер:
4.38 Mб
Скачать

168

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

Давайте теперь начнем с самого начала. Пусть у нас в программе нужно описать список из нескольких человеческих имен. Можно сделать это так (листинг 10.1):

Листинг 10.1. Инициализация массива

$NamesList[0]="Dmitry"; $NamesList[1]="Helen"; $NamesList[2]="Sergey";

. . .

Таким образом, мы по одному добавляем в массив $NamesList элементы, например, пронумерованные от 0. PHP узнает, что мы хотим создать массив, по квадратным скобкам (нужно заметить, что для этого переменная $NamesList в начале не должна еще быть инициализирована). Я буду в дальнейшем называть массивы, ключи (или, как их часто называют, индексы — то, что стоит в квадратных скобках) которых нумеруются с нуля и идут без пропусков (а это далеко не всегда так, как мы вскоре увидим), списками.

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

Давайте теперь посмотрим, как можно распечатать наш список. Самый простой способ — воспользоваться циклом for:

echo "А вот первый элемент массива: ".$NamesList[0]."<hr>"; for($i=0; $i<кол-во_элементов; $i++)

echo $NamesList[$i]."<br>";

Количество элементов в массиве легко можно определить, задействуя функцию count() или ее синоним sizeof():

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

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

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

$NamesList[]="Dmitry"; $NamesList[]="Helen"; $NamesList[]="Sergey";

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

169

В этом случае PHP сам начнет (конечно, если переменная $NamesList еще не существует) нумерацию с нуля и каждый раз будет прибавлять к счетчику по единичке, создавая список. Согласитесь, довольно удобно. Разумеется, можно использовать [] и не только в таком простом контексте, очень часто они применяются для более общего действия — добавления элемента в конец массива, например:

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

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

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

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

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

$Names["Koteroff"] = "Dmitry"; $Names["Ivanov"] = "Ivan"; $Names["Petrov"] = "Peter";

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

echo $Names["Ivanov"]; $f="Koteroff";

echo $Names[$f];

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

170

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

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

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

$name=$List[0]; $surname=$List[1]; $age=$List[2];

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

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

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

Что, если нам нужны только второй и третий элемент массива $List? В этом случае имеет смысл пропустить первый параметр в инструкции list(), вот так:

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

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

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

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

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

Глава 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";