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

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

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

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

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

Глава 11

Функции и области видимости

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

rвы можете использовать параметры по умолчанию (а значит, функции с переменным числом параметров);

rобласти видимости переменных внутри функций представляются в древовидной форме, как и в других языках программирования;

rсуществует удобная инструкция return, которой так не хватает в Паскале;

rтип возвращаемого значения может быть любым;

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

К сожалению, разработчики PHP не предусмотрели возможность создания локальных функций (то есть одной внутри другой), как это сделано, скажем, в Паскале или в Watcom C++. Однако кое-какая эмуляция локальных функций все же есть: если функцию B() определить в теле функции A(), то она, хоть и не став локальной, все же будет "видна" для программы ниже своего определения. Замечу для сравнения, что похожая схема существует и в языке Perl. Впрочем, как показывает практика программирования на Си (вот уже 30 лет), это не такой уж серьезный недостаток.

В системе определения функций в PHP есть и еще один небольшой недочет, который особенно неприятен тем, кто до этого программировал на других языках. Дело в том, что все переменные, которые объявляются и используются в функции, по умолчанию локальны для этой функции. При этом существует только один (и при том довольно некрасивый) способ объявления глобальных переменных — инструкция global (на самом деле есть и еще один, через массив $GLOBALS, но об этом чуть позже). С одной стороны, это повышает надежность функций в смысле их независимости от основной программы, а также гарантирует, что они случайно не изменят и не создадут глобальных переменных. С другой стороны, разработчики PHP вполне могли бы предугадать нужность инструкции, по которой все переменные функции становились бы по умол-

182

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

чанию глобальными — это существенно упростило бы программирование сложных сценариев.

Пример функции

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

Листинг 11.1. Пример функции

function GetMaxNum($arr, $max="")

{// проходимся по всем элементам массива for($i=0,$n=-1; $i<count($arr); $i++) {

//если этот элемент нам пока подходит, запоминаем его if((!Isset($m) || $arr[$i]>$m) && ($max==="" || $arr[$i]<$max)) {

//сюда мы попадаем, когда очередной элемент больше текущего,

//либо же текущего элемента еще не существует (первый проход) $m=$arr[$i]; // запоминаем текущий элемент

$n=$i;

// запоминаем его номер

}

 

}

 

return $n;

 

}

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

echo "Программа..."; function GetMaxNum($arr,$max) { ... тело функции ...

}

echo "Программа продолжается!";

При таком подходе транслятор, дойдя до определения функции, просто прове- рит его корректность и оттранслирует во внутреннее представление, но не бу-

Глава 11. Функции и области видимости

183

дет генерировать код для выполнения, а сразу переключится на следующие за телом функции команды. Только потом, при вызове функции, интерпретатор начнет исполнять ее команды...

Итак, мы создали функцию с именем GetMaxNum() и двумя параметрами, первый из которых рассматривается ей как массив, а второй — как вещественное число.

На самом деле на этапе создания функции еще никаких предположений о ти- пах параметров не строится. Однако попробуйте нашей функции вместо мас- сива в первом аргументе передать число интерпретатор "заругается", как только выполнение дойдет до строчки с $arr[$i], и скажет, что "переменная не является массивом".

Алгоритм работы функции таков: в цикле анализируем очередной элемент на предмет "максимальности": если он больше текущего максимального элемента, но меньше $max, он сам становится текущим максимумом, а его положение запоминается в $n. (Обратите внимание, что в описании функции параметр $max задается в виде $max="". Это означает, что если при вызове он будет опущен, то функция получит пустую строку в $max.) После окончания цикла в $n окажется номер такого элемента (либо число −1, которое мы присвоили $n в начале). Его-то мы и возвращаем в качестве значения функции оператором return.

Ну вот, теперь в программе ниже описания функции можно написать:

$a=array(10,20,80,35,22,57);

$m=GetMaxNum($a,50); // теперь $m=3, т. е. $a[$m]=35

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

Зачем может понадобиться функция GetMaxNum() в реальной жизни? Например, для сортировки массива в порядке убывания с одновременным получением уникальных элементов. Конечно, это будет очень неоптимальный алгоритм, но для тренировочных целей он нам вполне подойдет (листинг 11.2):

Листинг 11.2. Сортировка с применением GetMaxNum()

function MySort($Arr)

184

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

{$m= GetMaxNum($Arr)+1; // число, на 1 большее максимума в массиве while(($n=GetMaxNum($Arr,$m))!=-1)

$New[]=$m=$Arr[$n]; // добавляем очередной максимальный элемент return $New;

}

// Пример вызова: $Sorted=MySort(array(1,2,5,2,4,7,3,7,8)); // Теперь $Sorted===array(8,7,5,4,3,2,1)

Приведенная функция не изменяет исходный массив, а возвращает новый. В силу устройства функции GetMaxNum() в результирующий массив будут помещены только уникальные элементы из $Arr, отсортированные в порядке убывания.

Функцию MySort() можно ускорить примерно в 2 раза, если после каждой

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

Общий синтаксис определения функции

В общем виде синтаксис определения функции таков:

function имя_функции(арг1[=зн1], арг2[=зн2], ... аргN[=знN]) { операторы_тела_функции;

}

Имя функции должно быть уникальным с точностью до регистра букв. Это означает, что, во-первых, имена MyFunction, myfunction и даже MyFuNcTiOn будут считаться одинаковыми, и, во-вторых, мы не можем переопределить уже определенную функцию (стандартную или нет — не важно), но зато можем давать функциям такие же имена, как и переменным в программе (конечно, без знака $ в начале). Список аргументов, как легко увидеть, состоит из нескольких перечисленных через запятую переменных, каждую из которых мы должны будем задать при вызове функции (впрочем, когда для этой переменной присвоено через знак равенства значение по умолчанию (обозначенное =знM), ее можно будет опустить; см. об этом чуть ниже). Конечно, если у функции не должно быть аргументов вовсе (как это сделано у функции time()), то следует оставить пустые скобки после ее имени, например:

function SimpleFunction() { ... }

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

Глава 11. Функции и области видимости

185

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

Инструкция return

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

function MySqrt($n) { return $n*$n;

}

echo MySqrt(4); // выводит 16

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

Листинг 11.3. Возвращение массива

function Silly()

{ return array(1,2,3);

}

//присваивает массиву значение array(1,2,3) $arr=Silly();

//присваивает переменным $a, $b, $c первые значения из списка list($a,$b,$c)=Silly();

В этом примере использован оператор list(), который мы уже рассматривали.

Если функция не возвращает никакого значения, т. е. инструкции return в ней нет, то считается, что функция возвратила ложь (то есть, false). Все же часто лучше вернуть false явно (если только функция не объявлена как процедура, или void-функция по Си-терминологии), например, задействуя return false, потому что это несколько яснее.

186

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

Параметры по умолчанию

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

function MySort(&$Arr, $NeedLoOrder=1)

{ ... сортируем в зависимости от $NeedLoOrder...

}

Теперь, имея такую функцию, можно написать в программе:

MySort($my_array,0);

//

сортирует в порядке возрастания

MySort($my_array);

//

второй аргумент задается по умолчанию!

То есть, мы можем уже вообще опустить второй параметр у нашей функции, что будет выглядеть так, как будто мы его задали равным 1. Как видно, значение по умолчанию для какого-то аргумента указывается справа от него через знак равенства. Заметьте, что значения аргументов по умолчанию должны определяться справа налево, причем недопустимо, чтобы после любого из таких аргументов шел обычный "неумолчальный" аргумент. Вот, например, неверное описание:

// Ошибка!

function MySort($NeedLoOrder=1, &$Arr)

{

... сортируем в зависимости от $NeedLoOrder...

}

MySort(,$my_array); // Ошибка! Это вам не Бейсик!

Передача параметров по ссылке

Давайте рассмотрим механизм, при помощи которого функции передаются ее аргументы. Пусть, например, у нас есть такая программа:

function Test($a)

{echo "$a\n"; $a++;

echo "$a\n";

}

. . .

$num=10; Test($num);

Глава 11. Функции и области видимости

187

echo $num;

Что происходит перед началом работы функции Test() (которая, кстати, не возвращает никакого значения, т. е. является в чистом виде подпрограммой или процедурой) — как выражаются программисты на Паскале? Все начинается с того, что создается переменная $a, локальная для данной функции (про локальные переменные мы поговорим позже), и ей присваивается значение 10 (то, что было в $num). После этого значение 10 выводится на экран, величина $a инкрементируется, и новое значение (11) опять печатается. Так как тело функции закончилось, происходит возврат в вызвавшую программу. А теперь вопрос: что будет напечатано при последующем выводе переменной $num? А напечатано будет 10 (и это несмотря на то, что в переменной $a до возврата из функции было 11!) Ясно, почему это происходит: ведь $a — лишь копия $num, а изменение копии, конечно, никак не отражается на оригинале.

В то же время, если мы хотим, чтобы функция имела доступ не к величине, а именно к самой переменной (переданной ей в параметрах), достаточно при передаче аргумента функции перед его именем поставить & (листинг 11.4):

Листинг 11.4. Передача параметров по ссылке (первый способ)

function Test($a)

{echo "$a\n"; $a++;

echo "$a\n";

}

 

$num=10;

// $num=10

Test(&$num);

// а теперь $num=11!

echo $num;

// выводит 11!

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

Чтобы не забывать каждый раз писать & перед переменной, передавая ее функции, существует и другой, более привычный для программистов на Си++ синтаксис передачи по ссылке. А именно, можно символ & перенести прямо в заголовок функции, вот так (листинг 11.5):

Листинг 11.5. Передача параметров по ссылке (второй способ)

function Test(&$a) { echo "$a\n";