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

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

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

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

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

$a++;

echo "$a\n";

}

 

....

 

$num=10;

// $num=10

Test($num);

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

echo $num;

// выводит 11!

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

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

Переменное число параметров

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

Листинг 11.6. Переменное число параметров функции

function myecho()

{ for($i=0; $i<func_num_args(); $i++) {

for($j=0; $j<$i; $j++) echo " ";

// выводим отступ

echo func_get_arg($i)."<br>\n";

// выводим элемент

}

 

}

// отображаем строки "лесенкой" myecho("Меркурий", "Венера", "Земля", "Марс");

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

189

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

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

rint func_num_args()

Возвращает общее число аргументов, переданных функции при вызове.

rmixed func_get_arg(int $num)

Возвращает значение аргумента с номером $num, заданного при вызове функции. Нумерация, как всегда, отсчитывается с нуля.

rlist func_get_args()

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

Перепишем наш пример с применением последней функции (листинг 11.7):

Листинг 11.7. Использование fuct_get_args()

function myecho()

{foreach(func_get_args() as $v) {

for($j=0; $j<@$i; $j++) echo " "; echo "$v<br>\n";

@$i++;

}

}

// выводим строки "лесенкой" myecho("Меркурий", "Венера", "Земля", "Марс");

Мы используем здесь цикл foreach для перебора аргументов, а также оператор отключения ошибок @, чтобы PHP не "ругался" на то, что переменная $i не определена при первом "обороте" цикла.

190

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

Локальные переменные

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

(листинг 11.8):

Листинг 11.8. Локальные переменные (параметры)

$a=100; // Глобальная переменная, равная 100 function Test($a)

{echo $a; // выводим значение параметра $a

// Этот параметр не имеет к глобальной $a никакого отношения!

$a++; // изменяется только локальная копия значения, переданного в $a

}

Test(1); // выводит 1

echo $a; // выводит 100 — глобальная $a, конечно, не изменилась

В действительности такими же свойствами будут обладать не только аргументы, но и все другие переменные, инициализируемые или используемые внутри функции. Вот пример (листинг 11.9):

Листинг 11.9. Локальные переменные

function Silly()

{ $i=rand();

// записывает в $i случайное число

echo $i;

// выводит его на экран

// Эта $i не

имеет к $i никакого отношения!

}

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

Здесь переменная $i в функции будет не той переменной $i, которая используется в программе для организации цикла. Поэтому, собственно, цикл и проработает только 10 "витков", напечатав 10 случайных чисел (а не будет крутиться долго и упорно, пока "в рулетке" функции rand() не выпадет 10.

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