7.3 自定义函数
如果说一个WEB系统是一个加工工厂,那么一个函数可以比作是一个“加工作坊”,这个“加工作坊”接收上一个“作坊”传递过来的“原料”(其实是参数),并对这些“原料”进行加工处理产生“产品”,再把“产品”传递给下一个“作坊”。典型地,函数有一个或多个参数,函数定义了一系列的操作对这些参数进行处理,然后将处理结果返回。对于自定义函数而言,其使用过程为:程序员定义函数的参数、函数体(一系列的操作)及返回值,声明函数后对函数进行调用。
7.3.1 自定义函数的定义
在PHP中,定义自定义函数的语法格式为:
function functionName($param1, $param2, $param3,…$paramn=defaultValue){
函数体
return 返回值;
}
自定义函数的语法格式说明如下。
(1)function:定义自定义函数的关键字,关键字function大小写不敏感。
(2)functionName:自定义函数的函数名。函数名由程序员指定,在函数被调用时使用。
(3)$param:定义函数的参数,函数通过参数接收“外部”数据,从而实现对外部数据的处理。一个函数可以没有参数,也可以存在多个参数,参数之间用逗号隔开。
( 4 ) defaultValue:函数参数的默认值。必要时可以为函数中的个别参数指定默认值defaultValue,默认值通常是一个常数表达式。调用函数时,如果不给带有默认值的参数传递值,此时默认值自动赋予该参数。
自定义函数中默认值参数尽量放在参数列表的末尾,这样做的好处是默认值参数可以作为可选参数,即在函数被调用时,不必为默认值参数提供数值。
(5)函数体:函数的功能实现。函数体是在函数被调用时执行的语句块。
(6)return:如果函数有执行结果,使用return语言结构返回函数的执行结果,该执行结果可以是任意类型的数据。调用函数时,当程序运行到return语句时,立即结束此函数的执行,将执行结果作为函数的值返回给调用者,并将控制权转交给调用者。
为了便于管理自定义函数,通常将自定义函数放置到一个“专门”存放自定义函数的目录(例如functions)下,且该目录中存放的PHP文件名通常是自定义函数名。
下面的3个程序定义了3个函数:makeNine()函数、makeNineWithParams()函数和maxValue()函数。
(1)在目录“C:\wamp\www\7\”下创建“functions”目录,在该目录下创建程序makeNine.php,该程序定义了一个无参函数makeNine(),其功能是制作九九乘法表。
<?php
function makeNine(){
echo "<table border='1'>";
for ($c=1;$c<=9;$c++){
echo "<tr>";
for ($d=$c;$d<=9;$d++){
echo "<td align='right'>";
echo $c."×".$d."=".$c*$d." ";
echo "</td>";
}
echo "</tr>";
echo "<tr/><tr/>";
}
echo "</table>";
}
?>
(2)在“functions”目录下创建程序 makeNineWithParams.php,该程序定义了一个有参函数makeNineWithParams(),该函数的功能是制作九九乘法或加法表,并可以指定表格的边框宽度,边框宽度没有指定时,宽度为1。
<?php
function makeNineWithParams($method, $border=1){
echo "<table border='$border'>";
for ($c=1;$c<=9;$c++){
echo "<tr>";
for ($d=$c;$d<=9;$d++){
echo "<td align='right'>";
if($method==="+"){
echo $c."+".$d."=".($c+$d)." ";
}else if($method==="*"){
echo $c."×".$d."=".($c*$d)." ";
}
echo "</td>";
}
echo "</tr>";
echo "<tr/><tr/>";
}
echo "</table>";
}
?>
说明:makeNineWithParams()函数的参数$border 有默认值,且放在参数列表的末尾,因此$border为可选参数。
( 3 )在“functions”目录下创建程序 maxValue.php ,该程序定义了一个有返回值的函数maxValue(),该函数的功能是计算两个数的最大值。
<?php
function maxValue($a=0,$b=0){
$c = $a>$b?$a:$b;
return $c;
}
?>
说明:程序maxValue.php定义的maxValue ()函数的参数$a和$b都是可选参数。
如果将maxValue()函数名修改为max(),程序将产生错误。这是由于max()是PHP的内置函数,自定义函数名不能和内置函数名相同。
7.3.2 自定义函数的声明和调用
调用自定义函数时需要注意,应该先声明自定义函数,然后才可以在调用处使用如下方式调用自定义函数:
functionName(param1Value, param2Value,param3Value,…paramnValue)
说明如下。
(1)functionName:调用自定义函数的函数名,函数名大小写不敏感。
(2)paramValue:传递给函数的参数值。注意参数值的顺序和自定义函数参数的顺序需保持一致。当函数的定义和函数的调用位于不同的PHP文件时,需要使用include(或include_once)或require(或 require_once)语言结构引用函数定义所在的 PHP 文件,这个过程称为函数的声明。当函数的定义和函数的调用位于同一个PHP文件时,此时无须函数的声明即可直接调用自定义函数。例如如下程序call.php实现了对函数makeNine()、maxValue()和makeNineWithParams()的调用,程序call.php中使用“include_once("相对路径");”语句引用了函数定义所在的程序文件。
<?php
//函数的声明
include_once("functions/makeNine.php");
include_once("functions/makeNineWithParams.php");
include_once("functions/maxValue.php");
//函数的调用
makeNine();
echo "<hr/>";
makeNineWithParams('+');
echo "<hr/>";
makeNineWithParams('*',2);
echo "<hr/>";
echo maxValue();
echo "<hr/>";
echo maxValue(200,100);
?>
7.3.3 自定义函数的参数赋值
和变量赋值方法相同,自定义函数的参数赋值有两种方法:传值赋值和传地址赋值。
1.传值赋值
默认情况下,自定义函数的参数是按传值赋值的方式为函数参数赋值,即将一个值的“拷贝”赋值给函数的参数(例如程序byValue.php)。
程序byValue.php代码如下,该程序的运行结果如图7-6所示。
<?php
function addAge($value){
$value = $value + 1;
echo $value;
}
$age = 18;
addAge($age); //输出:19
echo "<br/>";
echo $age; //输出:18
?>
程序byValue.php运行过程中的内存分配图如图7-7所示。
程序byValue.php运行过程说明如下。
(1)函数只有被调用时,才占用WEB服务器的CPU资源和内存资源,否则函数仅仅是保存到外存空间(即硬盘)的一个PHP文件。
(2)程序执行语句“$age = 18;”,PHP预处理器为程序分配了第一个内存空间,这个过程称为过程1。
(3)程序执行到语句“addAge($age);”,此时自定义函数 addAge()被调用,PHP 预处理器为函数的参数$value分配了内存空间,这个过程称为过程2。由于PHP采用的是“写时拷贝”的原理,并没有为参数值分配新的内存空间。
(4)当$value的值发生变化时,PHP预处理器为$value的值分配新的内存空间,这个过程称为过程3。
(5)函数体执行完毕后,意味着函数调用的结束。PHP预处理器回收函数调用期间分配的所有内存,这个过程为过程4。
(6)所有程序执行完毕,内存又恢复到初始状态,这个过程为过程5。
从图7-7可以看出,使用传值赋值时,函数参数$value在内存中的生存周期是从函数addAge()被调用到函数addAge()运行完毕结束调用的这段时间,$value的作用域为函数内有效。读者可以自己分析下面的程序byValue2.php的内存分配图,推断byValue2.php程序的运行结果。
<?php
function addAge($age){
$age = $age + 1;
echo $age;
}
$age = 18;
addAge($age);
echo "<br/>";
echo $age;
?>
说明:由于生存周期和作用域的不同,程序byValue2.php中函数的参数$age和程序中的变量$age是两个不同的变量。
2.传地址赋值
自定义函数的参数也可使用传地址赋值,即将一个变量的“引用”传递给函数的参数。和变量传地址赋值的方式一样,在函数的参数名前追加一个“&”符实现传地址赋值(例如程序byReference.php)。
程序byReference.php代码如下,该程序运行结果如图7-8所示。
<?php
function addAge(&$value){
$value = $value + 1;
echo $value;
}
$age = 18;
addAge($age); //输出:19
echo "<br/>";
echo $age; //输出:19
?>
程序byReference.php运行过程中的内存分配图如图7-9所示。
程序byReference.php运行过程部分说明如下。
(1)程序执行到语句“addAge(&$age);”时,此时自定义函数addAge()被调用,PHP预处理器为函数的参数$value 分配了内存空间,这个过程称为过程 2。由于这里是传地址赋值,函数参数$value和程序byReference.php的变量$age指向同一个变量值18。
(2)程序执行到函数体内的语句“$value = $value + 1;”时,$value的值变为19,此时变量$age的值也跟着发生了变化,这个过程称为过程3。
(3)函数体执行完毕后,意味着函数的调用结束。PHP预处理器回收函数调用期间分配的所有内存,这个过程为过程4。
(4)所有程序执行完毕,内存又恢复到初始状态,这个过程为过程5。
从图7-9可以看出,使用传地址赋值时,函数参数$value在内存中的生存周期是从函数addAge()被调用到函数addAge()运行完毕结束调用的这段时间,$value的作用域为函数内有效。读者可以自己分析下面的程序byReference2.php的内存分配图,推断byReference2.php程序的运行结果。
<?php
function addAge(&$age){
$age = $age + 1;
echo $age;
}
$age = 18;
addAge($age);
echo "<br/>";
echo $age;
?>
说明:由于生存周期和作用域的不同,程序byReference2.php中函数的参数$age和程序中的变量$age是两个不同的变量。
通过对比程序byValue.php和程序byReference.php的运行结果可以得知,函数调用时,若使用传值赋值方式为函数参数赋值,函数无法修改函数体外的变量值;若使用传地址赋值方式为函数参数赋值,函数可以修改函数体外的变量值。但不管使用传值赋值还是传地址赋值,函数参数(或函数体内变量)的生存周期是函数运行期间,函数参数(或函数体内变量)的作用域为函数体内有效。若要延长函数体内变量的生存周期,需使用关键字static;若要扩大函数体内变量的作用域,需使用关键字global。
使用传地址赋值方式时,传递给函数的值不能是常量,否则程序将产生 Fatal error错误终止程序的运行(例如程序byConstant.php)。
程序byConstant.php代码如下,该程序的运行结果如图7-10所示。
<?php
function addAge(&$age){
$age = $age + 1;
echo $age;
}
$age = 18;
addAge(20); //给出Fatal error 错误信息
?>
细心的读者可能会有一个疑问:调用函数过程中,在使用传值赋值的方式为函数参数赋值时,能不能将一个变量的引用(例如&$age)传递给函数?例如程序 byValue3.php 如下,该程序的运行结果如图7-11所示。
<?php
function addAge($value){
$value = $value + 1;
echo $value;
}
$age = 18;
addAge(&$age); //输出:19
echo "<br/>";
echo $age; //输出:19
?>
从图7-11中可以看出,PHP鼓励在函数的定义中指定哪些参数应该用引用传递,不建议直接给函数传递一个引用参数。当然这仅仅是一种建议,通过修改 php.ini 配置文件的选项allow_call_time_pass_reference(默认值为Off)决定是否开启函数调用时强制参数按照引用传递。
7.3.4 变量的作用域和global关键字
变量的作用域决定了PHP程序在何地能访问到该变量,根据变量的作用域可将变量分为全局变量和局部变量。变量的作用域取决于变量在PHP程序中的位置。
(1)在函数内定义的变量(包括函数的参数)为局部变量,局部变量在调用函数结束后被自动回收。
(2)在函数外定义的变量为全局变量,声明后的全局变量可以被PHP程序中所有语句访问(函数内的PHP语句除外),当程序执行到程序末尾的时候,全局变量才被自动回收。全局变量也可应用于 include语句和require语句所引用的PHP程序文件。
例如程序 byValue2.php,该程序的运行结果如图7-12所示,程序运行过程中的内存动态分配图如图7-13所示。
如果函数中的PHP语句要访问全局变量,需要在函数内定义的变量名前加关键字global,此时函数内局部变量变为全局变量。例如程序global.php如下,该程序的内存动态分配图如图7-14所示。
<?php
function addAge($age){
global $age;
$age = $age + 1;
echo $age;
}
$age = 18;
addAge($age); //输出:19
echo "<br/>";
echo $age; //输出:19
?>
程序global.php运行过程部分说明如下。
(1)程序执行到语句“addAge($age);”时,此时自定义函数addAge()被调用,PHP预处理器为程序global.php创建一个局部变量$age,该过程为过程2。
(2)程序执行到语句“global $age;”时,将局部变量$age声明为全局变量,此后函数内的变量$age和函数外的变量$age为同一个变量,该过程为过程3。
(3)当程序执行到语句“$age = $age + 1;”时,将全局变量$age的值修改为19,该过程为过程4。
global关键字用法的注意事项如下。
不能使用global定义函数的参数。
在函数内使用global定义全局变量的同时,不能使用赋值语句给该变量赋值。
global可以一次性地定义多个全局变量,例如“global $a,$b;”。
在函数内使用global语句定义全局变量时,若程序中已经存在该全局变量,则直接“拿来”使用,否则将创建该全局变量。
经global定义的全局变量,PHP会将该变量的定义放到$_GLOBALS数组中,数组的键为该全局变量的变量名,数组的值为该全局变量的变量值。
常量作用域不同于变量的作用域,常量在其定义处开始到程序运行结束期间(包括被调用函数的PHP语句执行期间)一直有效。
7.3.5 变量的生存周期和static关键字
函数体内定义的变量生存周期是短暂的:每一次函数调用的开始到这一次函数调用的结束。有时希望函数体内的变量能够从这次调用一直存活到下次调用,此时需要在该变量前加上 static关键字。static关键字一般在函数定义中使用,用于修饰局部变量。例如程序static.php如下,该程序运行过程中的内存分配图如图7-15所示。
<?php
function plus(){
static $sum = 0;
$sum++;
echo $sum;
echo "<br/>";
}
plus(); //输出:1
plus(); //输出:2
?>
程序static.php运行过程说明如下。
(1)程序第一次执行到语句“plus();”,开始调用plus()函数,然后执行函数体内的第一条语句“static $sum = 0;”。此时内存创建了一个$sum的静态变量,该过程为过程1。
(2)程序执行到语句“$sum++;”时,该静态变量的值加1,该过程为过程2。
(3)函数第一次调用结束后,由于$sum变量为静态变量,因此该变量将一直存在于内存中,该过程为过程3。
(4)第二次执行语句“plus();”,再次调用plus()函数时,由于内存中已经存在静态变量$sum,程序将跳过语句“static $sum = 0;”,此时静态变量$sum能够保持前一次的值,不再进行初始化,该过程为过程4。
(5)程序执行到语句“$sum++;”时,该静态变量的值加1,该过程为过程5。
(6)所有代码执行结束后,内存中的所有变量被回收,该过程为过程6。
static关键字用法的注意事项如下。
static主要用于修饰函数体内的变量,不能使用static定义函数的参数。
静态变量只在PHP程序的当前执行中有效,如果刷新了页面,一切又将从头开始。
经static修饰的变量一般要进行初始化。
static可以一次性地定义多个全局变量,例如“static $a,$b;”。
例如如下程序trColor.php使用静态变量制作一个表格,表格中的每一行颜色交替以示醒目。
<?php
function trColor() {
static $color;
if($color=="#FE2E9A"){
$color = "#E6E6E6";
}else{
$color = "#FE2E9A";
}
return($color);
}
?>
<table border=1>
<?php
for ($i=0;$i<10;$i++){
$color = trColor();
echo "<tr bgcolor='$color'><td>第".$i."行</td></tr>";
}
?>
</table>
程序trColor.php的运行结果如图7-16所示。
借助静态变量可以实现递归函数。递归函数是一种调用自身的函数,为了防止递归函数无休止地“调用”自身,必须为递归函数提供一个函数出口,这个出口可以使用静态变量实现。例如程序recursion.php如下。
<?php
function recursion()
{
static $count = 0;
$count++;
echo $count." ";
if ($count < 10) {
recursion();
}
echo $count." ";
$count--;
}
recursion();
?>
程序recursion.php的运行结果如图7-17所示。
7.3.6 变量函数
变量函数类似于可变变量,变量函数的函数名为变量。使用变量函数可以实现通过改变变量的值的方法调用不同的函数。变量函数的调用方法如下:
$varName(param1Value, param2Value,param3Value,…paramnValue)
例如可以将程序call.php的代码修改为如下代码。
<?php
//函数的声明
include_once("functions/makeNine.php");
include_once("functions/makeNineWithParams.php");
include_once("functions/maxValue.php");
//函数的调用
$functionName = "makeNine";
$functionName();
echo "<hr/>";
$functionName = "makeNineWithParams";
$functionName('+');
echo "<hr/>";
makeNineWithParams('*',2);
echo "<hr/>";
$functionName = "maxValue";
echo $functionName();
echo "<hr/>";
echo $functionName(200,100);
?>