1. 定义
函数是一个被命名的,独立的代码段,它执行特定的任务,并可能给调用它的程序一份返回值。
PHP的模块化程序结构都是通过函数或对象来实现的,函数则是将复杂的PHP程序分为若干功能模块,每个模块都写成一个PHP函数,然后通过在脚本中调用函数,以及在函数中调用函数来实现一些大型问题的PHP脚本编写。使用函数有以下好处:
- 提高程序的重用性;
- 提高软件的可维护性;
- 提高软件的开发效率;
- 提高软件的可靠性;
- 控制程序设计的复杂性
2. 自定义函数
2. 1函数的声明
在PHP中声明一个自定义的函数可以使用下面的语法格式:
function 函数名(参数1,参数2,…,参数n)
{
函数体;
return 返回值;
}
其中,参数和返回值都可以省略。
2.2. 函数的调用
- 通过函数名调用,每调用一次执行一次;
- 如果函数有参数列表,还可以通过函数后面的小括号传入对应的参数,在函数体中使用参数改变内部代码的执行行为;
- 如果函数有返回值,当函数执行完毕时就会return后面的值返回到调用函数的位置出,这样就可以把函数名称当作函数的返回值使用。
只要声明的函数在脚本中可见,就可以通过函数名在脚本的任意位置调用。
2.3. 函数的参数
参数列表时由0个、一个或多个参数组成的。对于有参函数,在PHP程序中和被调用程序之间由数据传递关系。定义函数时,函数名后面括号内的表达式称为形式参数(简称“形参”),被调用函数后面括号中的表达式称为实际参数(简称“实参”),形参和实参要按顺序对应传递数据。
2.4 . 函数的返回值
在函数中,通过return将函数的返回值传递给参数。return在函数体中使用时,由以下两个作用:
- return语句可以向函数调用者返回函数体中任意确定的数值。
- 将程序控制权返回到调用者的作用于,即退出函数。在函数体中如果执行了return语句,它后面的语句就不会被执行。
3 函数的工作原理和结构化编程
通过在PHP程序中使用函数,可以实现结构化编程。在结构化编程中,各个任务是由对立的程序代码完成的。而函数正是实现“独立的程序代码段”最理想的方式,所以函数和结构化编程的关系十分密切。结构化编程之所以卓越,有如下两个重要原因:
- 结构化程序更容易编写,以为复杂的程序被划分为多个更小、更简单的任务。
- 结构化程序更容易调试。程序中的错误很容易定位到特定的代码段(如特定的函数)。
结构化编程的另一个优点是节省时间。因为可以实现代码的重复利用。
4 PHP变量的范围
变量的范围也就是它的使用范围。大部分PHP变量肢有一个单独的使用范围,也包括include和require的文件。当一个变量执行赋值动作以后,会随着声明区域位置的差异有不同的使用范围。大致上来说,变量会依据声明的位置分为局部变量和全局变量两种。
4.1 局部变量
局部变量也称内部变量,是在函数内部声明的变量,作用域仅限于函数内部。
4.2 全局变量
全局变量也称为外部变量,是在函数的外部定义的,它的作用域为从本函数开始,到本程序文件的末尾。
和其它编程语言不同,PHP全局变量不是自动设置为可用的。在PHP中,由于函数可以视为单独的程序片段,因此局部变量会覆盖全局变量的能见度,因此在函数中无法直接调用全局变量。
在函数中若要使用全局变量,必须使用global关键字定义目标变量,以告诉函数主题该变量为全局变量。
<?php
$one=100;
$two=200;
function demo(){
global $one,$two; //加载全局变量
echo "运算结果:".($one+$two)."<br>";
}
demo(); //输出:运行结果:300
在函数中使用全局变量,除了使用关键字global,还可以用特殊的PHP自定义$GLOBALS数组。
<?php
$one=100;
$two=200;
function demo(){
$GLOBALS['two']=$GLOBALS['one']+$GLOBALS['two'];
}
demo();
echo $two; //输出结果300,说明全局变量被访问到,重新赋值。
*****4.3 静态变量
局部变量从存储方式上可分为动态存储类型和静态存储类型。
函数中的局部变量,如不专门声明为static存储类别,默认都是动态的分配存储空间的。其中的内部动态变量在函数调用结束后自动释放。如果希望函数执行后,变量依然保存在内存中,应使用静态变量。静态变量在函数使用完毕后不会消失,而会在所有对该函数的调用之间共享,即在函数再次执行时,静态变量将接续前次的结果继续运算,并且仅在脚本执行期间函数第一次被调用时初始化。
要声明静态变量,需要使用关键字static。
<?php
funcition test(){
static $a=0; //声明静态变量
echo $a;
$a++;
}
test(); //第一次调用输出0;
test(); //第二次调用输出1;
test(); //第三次调用输出2;
5 声明及应用各种形式的PHP函数
编写PHP程序时,除了自己定义函数之外,也可以直接使用PHP系统中可直接使用的自带函数。
PHP中有很多标准函数可供直接使用,但有一些函数必须与特定的PHP扩展模块一起编译,否则使用时就会得到一个致命的“未定义函数”错误。例如,使用imagecreatetruecolor()需要GD库的支持;使用mysql_connect()需要mysql的支持。
有很多核心函数已经包含在每个版本的PHP中,如字符串和变量函数。
调用phpinfo()或者get_loaded_extensions()函数可以得知PHP加载了哪些扩展库。同时还要注意,很多扩展库时默认就有效的。
调用系统函数和调用自定义函数的方法相同。系统中的每一个函数都有详细的帮助文档,所以很多时候没必要深究函数的内部执行,只需参考帮助文档完成函数的调用即可。
如果声明一个函数让别人去使用,也应提供详细的使用说明文档,介绍函数的帮助文档必须包括以下几点:
- 函数的功能
- 参数说明(决定怎么使用这个函数)
- 返回值(调用后如何处理)
5.1 常规参数的函数
所谓常规参数的函数,就是形参和实参应该个数相等、类型一致。常规参数的函数格式如下所示:
string example(string name,int age,double height)
该函数有三个参数,调用时传递的参数个数和顺序必须一致,且第一参数必须时字符串,第二个必须时整型,第三个必须是双精度浮点数。
5.2 伪类型参数的函数
伪类型参数的函数格式说明如下:
mixed funName(mixed $args)
number funName(number $args)
PHP是弱类型的语言,不仅在声明变量时不需要指定类型,在声明函数时参数也不需要指定类型,所以PHP中的每个参数都可以为其传递任意类型的值。
伪类型参数的函数时PHP中最常见的函数应用形式。PHP的伪类型,抱愧mixed、number、callback三种。声明函数时,如果参数能接受多种不同但不必须时所有类型的值,在函数的说明文档中就可以用mixed标记这个参数类型。如果一个参数可以是integer或float,就可以使用number标记参数。除了参数可以传递伪类型的参数,函数的返回值也可以根据参数类型的不同返回不同类型的值。在PHP中,像empty(),pow()等都是这样的函数。
5.3 引用参数的函数
引用参数的函数格式说明如下:
void funName(array &arg) //在参数列表中出现使用&描述的参数
在PHP中默认时按制定传递参数,而且函数的参数也属于局部变量,所以在函数内部改变参数的值并不会影响到函数外部的值。如果允许函数修改它的参数值,就可以通过在定义函数时参数前添加&引用传递参数。
<?php
function test(&$arg){
$arg=200;
}
$var=100;
test($var);
echo $var;
注意:如果函数的形参中有“&”修饰的参数,则调用该参数时就必须传入一个变量给这个参数,而不能传递一个值。
在PHP中有很多这样的函数,都需要传递一个变量给引用参数。如数组处理中的next()、sort()、shuffle()、key()。
5.4 默认参数的函数
默认参数的函数格式说明如下:
mixed funName(string name[,string name[,int expire]]) //在参数列表中出现使用[ ]描述的参数
在定义函数时生命了参数,而在调用函数时没有指定或是少指定了参数,就会出现缺少参数的警告。在PHP中,支持函数的默认方式调用,即为参数指定一个默认值。在调用函数时如果没有指定参数的值,就会使用参数的默认值。
默认值必须是常量表达式,不能是变量、类成员或者函数调用。
PHP中还允许使用数组和特殊类型NULL作为默认参数。
<?php
function person($name="张三",$age=20,$sex="20"){
echo"我的名字是:{$name},我的年龄为:{$age}"
}
person();
person("李四");
person(''王五",''22");
person("贾六","18","女");
上例声明了一个名为person并带有三个参数的函数。其中的三个数值都有默认值。在调用该函数时,如果不传或少传参数,函数将使用默认值。
当调用函数传递参数时,实参和形参是按顺序对应传递数据的。
定义一个有默认参数的函数时,默认参数必须放在形参的最右侧。否则程序无法正常运行。
在PHP中有很多这样的函数,前面时必须传值的参数,后面时可选参数。例如printf()、explode()、mysql_query()、setCookie()等。
5.5 可变参数个数的函数
可变参数的函数格式说明如下:
mixed funName(string arg1[,string…]) #在参数列表中出现使用“…”描述的参数
使用默认参数适合实参少于形参的情况,而可变参数列表则适合实参多于形参的情况。
通常,用户在定义函数时,设置的参数数量是有限的。如果希望函数可以接受任意的参数,则需要在函数中使用PHP系统提供的func_get_args()函数,它将所有传递给脚本函数的参数当作一个数组返回。如下所示:
<?php
/**
声明一个函数more_args(),用于打印参数列表的值
虽然没有声明参数列表,但可以传入任意类型、任意个数的参数值
*/
function more_args(){
$args=func_get_args();
for($i=0;$i<count($args);$i++){
echo "第".$i."个参数是".$agrs[$i]."<br>"; //分别输出每个参数
}
}
more_args("one","two","three",1,2,3);
除此之外,也可以用func_num_args()函数返回参数的总数。func_num_args()函数接收一个数字参数,返回指定的参数。上面的例子可以改写为下面的形式:
<?php
/**
声明一个函数more_args(),用于打印参数列表的值
虽然没有声明参数列表,但可以传入任意类型、任意个数的参数值
*/
function more_args(){
for($i=0;$i<func_num_args();$i++){
echo "第".$i."个参数是".func_get_arg[$i]."<br>"; //分别输出每个参数
}
}
more_args("one","two","three",1,2,3);
上面的例子实现了相同的功能,都可以在函数中获取任意个数的参数列表,并在函数中使用。
在PHP的系统函数中,也有很多这样的可变参数的函数,例如array()、echo()、array_merge()等函数。
5.6 回调函数
回调函数的格式说明如下所示:
mixed funName(callback arg) #在参数列表中使用伪类型callback描述
所谓的回调函数,就是指调用函数时并不是传递一个标准的变量作为参数,而是将另一个函数作为参数传递到调用的函数中。如果在函数的格式说明中出现“callback”类型的参数,就说明该函数是回调函数。callback也属于PHP中伪类型的一种,说明函数的参数需要接受另一个函数作为实参。
一个很重要的问题是为什么要使用函数作为参数呢?前面说过,通过参数的传递可以改变调用函数的执行行为,但有时将一个值传递给函数能力还是有限的。如果可以将一个用户定义的“执行过程”传递到函数中使用,就大大增加了用户对函数功能的扩展。而如何声明和使用回调函数也是比较关键的问题,如果需要声明回调函数,就需要先了解以下变量函数。
1.变量函数
变量函数也称可变函数。如果一个变量名后有圆括号,PHP将寻找与变量的值同名的函数,并且将尝试执行它。这样就可以将不同的函数名称赋值给同一个变量,再通过变量去调用这个函数,类似于面向对象中多态特征的应用。如下所示:
<?php
function one($a,$b){
return $a+$b;
}
function two($a,$b){
return $a*$a+$b*$b;
}
function three($a,$b){
return $a*$a*$a+$b*$b*$b;
}
$result="one";
//$result="two";
//$result="three";
echo "运算结果是:".$result(2,3); //变量$result接收到哪个函数名的值,就调用哪个函数。
在上例中声明了one()、two()、three()三个函数,分别用于计算和、平方和及立方和,并且将三个函数的函数名(不带圆括号)以字符串的方式赋值给变量$result,然后用变量名$result后面加上圆括号并传入两个整型参数,就会寻找与变量$result的值同名的函数执行。大多数函数都可以将函数名赋值给变量,形成变量函数。
但变量函数不能用于语言结构,例如echo()、print()、unset()、isset()、empty()、include()、require()及类似的语句。
2 使用变量函数声明和应用回调函数
如果要自定义一个可以回调的函数,可以选择使用变量函数帮助实现。在定义回调函数时,函数的声明结构是没有变化的,只要声明的参数是一个变量即可。但在函数的内部应用这个参数变量时,如果加上圆括号就可以调用到和这个参数值同名的函数了,所以其传递的参数一定要是另一个函数名称字符串才行。
使用回调函数的目的是可以将一段自己定义的功能传递到函数内部使用。
<?php
//声明回调函数filter, 在0-10的整数中通过自定义条件过滤不要的数字
function filter($fun){
for($i=0; $i<=10; $i++){
//将参数变量$fun加上一个圆括号$fun(),则为调用和变量$fun值同名的函数
if($fun($i))
continue;
echo $i.'<br>';
}
}
//声明一个函数one,如果参数是3的倍数就返回ture,否则返回false
function one($num){
return $num%3 == 0;
}
//声明一个函数two,如果参数是一个回文数返回ture,否则返回false
function two($num){
return $num == strrev($num);
}
filter("one"); //打印出10以内非3的倍数,参数one是函数one()的名称字符串,是一个回调
echo "---------------------<br>";
filter("two"); //打印出10以内非回文数,参数two是函数two()的名称字符串,是一个回调
?>
3 借助call_user-func_array()函数自定义回调函数
虽然可以使用变量函数去声明自己的回调灌输,但最多的还是通过call_user_func_array()函数去实现。虽然该函数是PHP中的内置函数,其实它也是一个回调函数,格式说明如下:
mixed call_user_func_array(callback function,array param_arr)
该函数有两个参数:第一个参数因为使用伪类型callback,所以这个参数需要是一个字符串,表示要调用的函数名;第二个参数则是一个数组类型的参数,表示参数列表,按照顺序依次会传递给要调用的函数。该函数的应用示例如下:
<?php
// 声明一个函数fun(),功能是输出两个字符串,目的是作为call_user_func_array()函数的回调函数
function fun($str1, $str2){
echo "$str1 = ".$str1;
echo "<br>";
echo "$str2 = ".$str2;
}
//通过系统函数call_user_func_array()调用函数fun()
call_user_func_array("fun",array("楠哥哥","很帅!"));
?>
在上例中通过call_user-func_array调用了自己定义的函数fun(),将fun()的名称字符串传递给了第一个参数,第二个参数则需要一个数组,数组的元素个数必须和fun()的参数列表个数相同。
我们可以将前面通过变量函数实现的自定义回调函数,改成借助call_user_func_array()函数的形式实现。代码如下:
<?php
//声明回调函数filter, 在0-10的整数中通过自定义条件过滤不要的数字
function filter($fun){
for($i=0; $i<=10; $i++){
//使用系统函数call_user_func_array(),调用和变量$fun值相同的函数
if(call_user_func_array($fun,array($i)))
continue;
echo $i.'<br>';
}
}
//声明一个函数one,如果参数是3的倍数就返回ture,否则返回false
function one($num){
return $num%3 == 0;
}
//声明一个函数two,如果参数是一个回文数返回ture,否则返回false
function two($num){
return $num == strrev($num);
}
filter("one"); //打印出10以内非3的倍数,参数one是函数one()的名称字符串,是一个回调
echo "---------------------<br>";
filter("two"); //打印出10以内非回文数,参数two是函数two()的名称字符串,是一个回调
?>
4 类静态函数和对象的方法调用
前面说的都是通过全局函数(没有在任何对象或类中定义的函数)声明和应用的回调函数,但如果遇到回调类中的静态方法或是对象中的普通方法,则会有所不同。面向对象技术将在后面介绍,此处只做简单了解。
回调的方法,如果是一个类的静态方法或对象中的一个成员方法,怎么办呢?我们再来看一下call_user_func_array()函数的应用。可以将第一个参数“函数名称字符串”改为“数组类型的参数”。
下面通过分别申明一个类,类中在申明一个静态成员方法。使用系统函数调用类中的静态成员方法和实例对象成员方法。
<?php
//声明一个类demo,类中声明一个静态成员方法fun()
class demo{
static function fun($str1,$str2){
echo "str1 = ".$str1;
echo "<br>";
echo "str2 = ".$str2;
}
}
//声明一个类test,类中声明一个静态成员方法fun()
class test{
function fun($str1,$str2){
echo "str1 = ".$str1;
echo "<br>";
echo "str2 = ".$str2;
}
}
//使用系统函数call_user_func_array(),调用demo类中的静态成员方法fun()
call_user_func_array( array("demo","fun"), array("楠哥哥", "很帅!"));
echo "<br>------------------------<br>";
//使用系统函数call_user_func_array(),调用test类中的实例对象成员方法fun()
call_user_func_array( array(new test(),"fun"), array("楠哥哥","是大好人!"))
?>
说明:所有使用 call_user_func_array() 函数实现的自定义回调函数,或者php系统中为我们提供的所有回调函数,都可以像该函数一样,在第一个参数中使用数组类型值,而且数组中必须使用两个元素:如果调用类中的成员方法,就需要在这个数组参数中指定第一个元素为类名称字符串,第二个元素则为该类中的静态方法名称字符串;如果调用对象中的成员方法名称,则这个数组中的第一个元素为对象的引用,第二个元素则是该对象中的成员名称字符串。 call_user_func_array() 函数的第二个参数使用没有变化。
6 递归函数
递归函数即自调用函数,在函数体内部直接或间接的自己调用自己,即函数的嵌套调用是函数本身。通常在此类型的函数体之中会附加一个条件判断叙述,以判断是否需要执行递归调用,并且在特定条件下终止函数的递归调用动作,把目前流程的主控权交回上一层函数执行。因此,当某个执行递归调用的函数没有附加条件判断叙述时,可能会造成无限循环的错误情形。
递归函数调用最大的好处在于可以精简程序中的繁杂重复调用程序,并且能能以这种特性来执行一些较为复杂的运算操作。例如,列表、动态树形菜单及遍历目录等操作。相应的非递归函数虽然效率高,但却比较难编程,而且相对来说可读性差。现代程序设计的目标主要是可读性好。随着计算机硬件性能的不断提高,程序在更多的场合优先考虑可读而不是高效,所以,鼓励用递归函数实现程序思想。一个简单的递归调用如下所示:
<?php
//声明一个函数,用于测试递归
function test($n){
echo $n." "; //在函数开始输出参数的值
if($n>0){ //判断参数是否大于0
test($n-1); //如果参数大于0则调用自己,并将参数减1后再次传入
}else{ //判断参数是不大于0
echo "<--------> ";
}
echo $n." ";
}
test(10);
//输出结果:10 9 8 7 6 5 4 3 2 1 0 <--------> 0 1 2 3 4 5 6 7 8 9 10
7 使用自定义函数库
函数库并不是函数的PHP语法,而是编程时的一种设计模式。函数是结构化程序设计的模块,能够实现代码的反复利用。为了更好的组织代码使其能够在同一项目的多个文件中使用,通常将多个自定义的函数组织到同一个或多个文件中。这些收集函数定义的文件就是创建的PHP函数库。如果在PHP的脚本中想使用这些文件中自定义的函数,就需要使用include()、include_once()、require()或require_once()中的一个函数,将这些函数库文件载入脚本程序中。
require()语句的性能()与include()类似,都是包括并运行指定文件。不同之处在于,对include()语句来说,在执行文件时每次都要进行读取和评估;而对于require()语句来说,文件只处理一次(实际上,文件内容替换了require()语句)。这就意味着如果可能多次执行的代码,使用require()效率比较高。另外,如果每次执行代码时读取不同的文件,或者有通过一组文件迭代的循环,则使用require()效率比较高。
require语句的使用方法如require("myfile.php"),这条语句通常放在PHP程序的最前面。PHP程序在执行前就会先读入require()语句所引入的文件,使之称为PHP脚本文件的一部分。
include()语句的使用方法和require()语句一样,如include(''myfile.php")。而这条语句一般放在流程控制的处理区段中。PHP脚本文件在读到include()语句时,才将它包含的文件读进来。
<?php
require "config.php"; //使用 require 语句包含执行 config.php文件
if($condition){
include "file.txt"; //使用include 语句包含并执行 file.txt文件
}else{
include ("other.php"); // 使用include 语句包含并执行 other.php文件
}
require("somefile.txt"); // 使用require 语句包含执行 somefile.php文件
?>
require()和include()语句时语言结构,不是真正的函数,可以和PHP中其它的语言结构一样,不加圆括号而直接加参数。如include "myfile.php"。
include_once()和require()_once()语句也是在脚本执行期间包括并运行指定文件。使用方法和require()和include()一样。区别是如果该文件中的代码如果已经被包括了,则不会再次包括。这两条语句应该用于在脚本执行期间,同一个文件有可能被包括超过一次的情况下,确保它只被包括一次,以避免函数重定义及变量重新赋值等问题。
8 PHP匿名函数和闭包
在PHP5.3以后,可以创建匿名函数(Anonymous functions),也叫闭包函数(closures),它允许临时创建一个没有指定名称的函数,常用作回调函数参数的值。当然,也有其他应用的情况。匿名函数的示例代码如下:
<?php
$fun=function($param){
echo $param;
};
$fun('www.ydma.com');
匿名函数也可以作为变量的值来使用。直接将匿名函数作为参数传给回调函数,是匿名函数最常见的用法。
<?php
function callback($callback){
$callback()
}
callback(function(){
echo "闭包函数测试"
});
闭包函数的一个重要概念就是在内部函数中可以使用外部变量,需要通过关键字use连接闭包函数和外界变量,这些变量都必须在函数或类的头部声明。闭包函数是从父作用域中继承变量,与使用全局变量时不同的。全局变量存在于一个全局的范围,无论当前正在执行的是哪个函数。而闭包的父作用域是定义该闭包的函数,不一定是调用它的函数。关键字use的使用代码如下:
<php
/**
声明函数callback,需要传递一个匿名函数作为参数
*/
function callback($callback){
$callback();
}
$var='字符串';
//闭包的一个重要概念就是内部函数中可以使用外部变量,通过use关键字实现
//use引用的变量时$var的副本,如果要完全引用,像上面一样,加上&
callback(function()use($var){
echo "闭包函数传参测试($var)";
});