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

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

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

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

191

Глобальные переменные

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

Листинг 11.10. Использование global

function Silly()

{global $i; $i=rand(); echo $i;

}

for($i=0; $i!=10; $i++) Silly();

Вот теперь-то переменная $i будет везде едина: что в функции, что во внешнем цикле (для последнего это приведет к немедленному его "зацикливанию", во всяком случае, на ближайшие несколько минут, пока rand() не выкинет 10). А вот еще один пример, который показывает удобство использования глобальных переменных внутри функции (листинг 11.11):

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

$Monthes[1]="Январь"; $Monthes[1]="Февраль";

... и т. д. $Monthes[12]="Декабрь";

. . .

// Возвращает название месяца по его номеру. Нумерация начинается с 1! function GetMonthName($n)

{ global $Monthes; return $Monthes[$n];

}

. . .

echo GetMonthName(2); // выводит "Февраль"

192

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

Согласитесь, массив $Monthes, содержащий названия месяцев, довольно объемист. Поэтому описывать его прямо в функции было бы, мягко говоря, неудобно. В то же время функция GetMonthName() представляет собой довольно преемлемое средство для приведения номера месяца к его словесному эквиваленту (что может потребоваться во многих программах). Она имеет единственный и понятный параметр: это номер месяца. Как бы мы это сделали без глобальных переменных?

Массив $GLOBALS

В принципе, есть и второй способ добраться до глобальных переменных. Это — использование встроенного в язык массива $GLOBALS. Последний представляет собой хэш, ключи которого есть имена глобальных переменных, а значения — их величины.

Этот массив доступен из любого места в программе — в том числе и из тела функции, и его не нужно никак дополнительно объявлять. Итак, приведенный выше пример можно переписать более лаконично:

// Возвращает название месяца по его номеру. Нумерация начинается с 1! function GetMonthName($n) { return $GLOBALS["Monthes"][$n]; }

Кстати, тут мы опять сталкиваемся с тем, что не только переменные, но даже и массивы могут иметь совершенно любую структуру, какой бы сложной она ни была. Например, предположим, что у нас в программе есть ассоциативный массив $A, элементы которого — двумерные массивы чисел. Тогда доступ к какой-нибудь ячейке этого массива с использованием $GLOBALS мог бы выглядеть так:

$GLOBALS["A"][First"][10][20];

То есть получился четырехмерный массив!

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

rприсвоить этот массив какой-либо переменной целиком, используя оператор =;

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

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

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

193

операции Unset() для него равносильно уничтожению соответствующей переменной.

Атеперь я скажу нечто весьма интересное все о том же массиве $GLOBALS. Как вы думаете, какой элемент (то есть, глобальная переменная) всегда в нем присутствует? Это — элемент GLOBALS, "которая" также является массивом, и в "которой" также есть элемент GLOBALS... Так что же было первей — курица или яйцо (только не надо мне говорить, что первым был петух)?

Асобственно, почему бы и нет? С чего это мы все привыкли, что в большом содержится малое, а не, скажем, наоборот? Почему множество не может содержать себя же в качестве элемента? Очень даже может, и $GLOBALS — тому наглядный пример.

В PHP версии 3 такая ситуация была чистой воды шаманством. Однако с появлением в четвертой версии PHP ссылок все вернулось на круги своя. На самом-то деле элемент с ключом GLOBALS является не обычным массивом, а лишь ссылкой на $GLOBALS. Вот поэтому все и работает так, как было описано.

Вооружившись механизмом создания ссылок, мы можем теперь наглядно продемонстрировать, как работает инструкция global, а также заметить один ее интересный нюанс. Как мы знаем, global $a говорит о том, что переменная $a является глобальной, т. е., является синонимом глобальной $a. Синоним в терминах PHP — это ссылка. Выходит, что global создает ссылку? Да, никак не иначе. А вот как это воспринимается транслятором:

function Test()

{global $a; $a=10;

}

Приведенное описание функции Test() полностью эквивалентно следующему описанию:

function Test()

{$a=&$GLOBALS[’a’]; $a=10;

}

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

$a=100; function Test()

{global $a; Unset($a);

}

Test();

echo $a; // выводит 100, т. е. настоящая $a не была удалена в Test()!

194

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

Эта особенность инструкции global появилась только в PHP версии 4, т. е. когда начали поддерживаться ссылки! Если вы запустите приведенный только что при- мер на PHP версии 3, то при исполнении echo увидите предупреждение: $a не оп- ределена. Помните это при переносе старых сценариев на новый PHP версии 4.

Как же нам удалить глобальную $a из функции? Существует только один способ: использовать для этой цели $GLOBALS['a']. Вот как это делается:

function Test() { unset($GLOBALS['a']); } $a=100;

Test();

echo $a; // Ошибка! Переменная $a не определена!

Статические переменные

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

Листинг 11.12. Статические переменные

function Silly() { static $a=0;

echo $a; $a++;

}

for($i=0; $i<10; $i++) Silly();

После запуска будет выведена строка 0123456789, как мы и хотели. Давайте теперь уберем слово static. Мы увидим: 0000000000. Это и понятно, ведь переменная $a стала локальной, и ей при каждом вызове функции присваивается одно и то же значение — 0.

Итак, конструкция static говорит компилятору о том, что уничтожать указанную переменную для нашей функции между вызовами не надо. В то же время присваивание $a=0 сработает только один раз, а именно — при самом первом обращении к функции (так уж устроен static).

Рекурсия

Конечно, в PHP поддерживаются рекурсивные вызовы функций, т. е. вызовы функцией самой себя (разумеется, не до бесконечности, а в соответствии с определенным условием). Это бывает чрезвычайно удобно для таких задач, как, например, обход

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

195

всего дерева каталогов вашего сервера (с целью подсчитать суммарный объем, который занимают все файлы), или для других задач. Рассмотрим для примера функцию, которая рекурсивно вычисляет факториал из некоторого числа n (обозначается n!). Алгоритм стандартный: если n=0, то n!=1, а иначе n!=n*((n-1)!).

function Factor($n) { if($n<=0) return 1;

else return $n*Factor($n-1);

}

echo Factor(20);

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

function Factor($n)

{for($f=1; $n>1; $n--) $f*=$n; return $f;

}

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

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

Итак, "вложенные" функции выглядят следующим образом (листинг 11.13):

Листинг 11.13. Вложенные функции

function Parent($a) { echo $a;

function Child($b)

{echo $b+1; return $b*$b;

}

return $a*$a*Child($a); // фактически возвращает $a*$a*($a+1)*($a+1)

}

// Вызываем функции

Parent(10);

196

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

Child(30);

//Попробуйте теперь ВМЕСТО этих двух вызовов поставить такие

//же, но только в обратном порядке. Что, выдает ошибку?

//Почему, спрашиваете? Читайте дальше!

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

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

Давайте теперь попробуем запустить другой пример. Вызовем Parent() два раза подряд:

Parent(10);

Parent(20);

Последний вызов породит ошибку: функция Child() уже определена. Это произошло потому, что Child() определяется внутри Parent(), и до ее определения управление программы фактически доходит дважды (при первом и втором вызовах Parent()). Поэтому-то интерпретатор и "протестует": он не может второй раз добавить Child() в таблицу функций.

Для тех, кто раньше программировал на Perl, этот факт может показаться ужа- сающим. Что ж, действительно, мы не должны использовать вложенные функ- ции PHP так же, как делали это в Perl.

Условно определяемые функции

Предположим, у нас в программе где-то устанавливается переменная $OS_TYPE в значение win, если сценарий запущен под Windows 9x, и в unix, если под Unix. Как известно, в отличие от Unix, в Windows нет такого понятия, как владелец файла, а значит, стандартная функция chown() (которая как раз и назначает владельца для указанного файла) там просто не имеет смысла. В некоторых версиях PHP для Windows ее может в этой связи вообще не быть. Однако, чтобы улучшить переносимость сценариев с одной платформы на другую (без изменения их кода!) можно написать следующую простую "обертку" для функции chown() (листинг 11.14):

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

197

Листинг 11.14. Условно определяемые функции

if($OS_TYPE=="win")

{// Функция-заглушка

function MyChOwn($fname,$attr)

{// ничего не делает return 1;

}

}

else

{// Передаем вызов настоящей chown() function MyChOwn($fname,$attr)

{ return chown($fname,$attr);

}

}

Это — один из примеров условно определяемых функций. Если мы работаем под Windows, функция MyChOwn() ничего не делает и возвращает 1 как индикатор успеха, в то время как для Unix она просто вызывает оригинальную chown(). Важно то, что проверка, какую функцию использовать, производится только один раз (в момент прохождения точки определения функции), т. е. здесь нет ни малейшей потери производительности. Теперь в сценарии мы должны всюду отказаться от chown() и использовать MyChOwn() (можно даже провести поиск/замену этого имени в редакторе) — это обеспечит переносимость.

Если вам совсем не нравится идея поиска/замены (а мне она не нравится категорически), то существует гораздо более элегантный способ, но только в том случае, если chown() еще не была нигде определена — в том числе и среди стандартных функций:

if(!function_exists("chown"))

{function chown($fname,$mode)

{// не делаем ничего return 1;

}

}

Этот способ работает независимо от того, появится ли вдруг в будущих версиях PHP для Windows "заглушка" для функции chown(), или же нет. (Нужно сказать для справедливости, что в PHP версии 4 такая заглушка уже существует.)

Знатоки Си могут заметить в приеме условно определяемых функций разительное сходство с директивами условной компиляции этого языка: #ifndef, #else и #endif. Действительно, аналогия почти полная, за исключением того факта, что в Си

198

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

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

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

Передача функций "по ссылке"

Я отнюдь не случайно заключил последние два слова названия этого раздела в кавычки — дело в том, что как таковая, передача функции по ссылке в PHP не поддерживается. Однако, т. к. это слишком часто может быть полезным, в PHP есть понятие "функциональной переменной". Легче всего его можно рассмотреть на

примерах:

function A($i) { echo "a $i\n"; } function B($i) { echo "b $i\n"; } function C($i) { echo "c $i\n"; } $F="A"; // или $F="B" или $F="C"

$F(10); // вызов функции, имя которой хранится в $F

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

// Сравнение без учета регистра символов строк function FCmp($a,$b)

{ return strcmp(tolower($a),tolower($b))

}

$a=array("b"=>"bbb", "a"=>"Aaa", "d"=>"ddd); uasort($a,"FCmp"); // Сортировка без учета регистра символов

Здесь функция, имя которой получено со вторым параметром uasort(), должна иметь два аргумента, которые являются сравниваемыми значениями в массиве.

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

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

199

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

Возврат функцией ссылки

До сих пор я рассматривал лишь функции, которые возвращают определенные значения — а именно, копии величин, использованных в инструкции return. Заметьте, это были именно копии, а не сами объекты. Например:

$a=100; function R()

{ global $a; // объявляет $a глобальной

return $a; // возвращает значение, а не ссылку!

}

$b=R();

$b=0; // присваивает $b, а не $a! echo $a; // выводит 100

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

Как же нам добиться нужного результата? Использование оператора $b=&R(), к сожалению, не подходит, т. к. при этом мы получим в $b ссылку не на $a, а на ее копию. Если задействовать return &$a, то появится сообщение о синтаксической ошибке (PHP воспринимает & только в правой части оператора присваивания сразу после знака =). Но выход есть. Воспользуемся специальным синтаксисом описания функции, возвращающей ссылку (листинг 11.15):

Листинг 11.15. Возвращение ссылки

$a=100;

function &R() // & — возвращает ссылку

{ global $a; // объявляет $a глобальной

return $a; // возвращает значение, а не ссылку!

}

$b=&R(); // не забудьте & !!!

$b=0; // присваивает переменной $a!

echo $a; // выводит 0. Это значит, что теперь $b — синоним $a

200

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

Как видим, нужно поставить & в двух местах: перед определением имени функции, а также в правой части оператора присваивания при вызове функции. Использовать амперсанд в инструкции return не нужно.

Лично я не нахожу такой синтаксис удобным. Достаточно по-ошибке всего один раз пропустить & при вызове функции, как переменной $b будет присвоена не ссылка на $a, а только ее копия со всеми вытекающими из этого последствия- ми. При использовании объектно-ориентированного программирования это может породить логические ошибки, выглядящие крайне странно. Поэтому я рекомендую применять возврат ссылки как можно реже, и только в тех случа- ях, когда это действительно необходимо.

Пример функции: Dump()

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

Функция выводит содержимое любой, сколь угодно сложной, переменной, будь то массив, объект или простая переменная. Как уже говорилось, приведенная функция исключительно полезна при отладке сценариев (которая в PHP пока еще не особенно развита).

В PHP версии 4 для аналогичных целей существуют две стандартных функ- ции print_r() и var_dump(), но листинг, который они выводят, довольно неудобен для восприятия человеком.

Листинг 11.16. Функция Dump()

// Вспомогательная функция, делающая всю "грязную" работу function TextDump(&$Var,$Level=0)

{ if(is_array($Var)) $Type="Array[".count($Var)."]"; else if(is_object($Var)) $Type="Object";

else $Type=""; if($Type) {

echo "$Type\n";

for(Reset($Var),$Level++; list($k,$v)=each($Var);) { if(is_array($v) && $k==="GLOBALS") continue;