第7章 函数
当程序代码多了以后,如何组织这些程序?PHP语言和其他一些编程语言一样,最初的设计原则是用函数来组织,这样可以让一段代码形成一个“程序模块”,不管在什么地方使用到相同功能时,即可调用该函数,省去了重复编写代码的麻烦,也方便了代码的审阅、修改和完善。
7.1 使用函数
有一个疯狂的程序员,非常喜欢吃红烧肉,但是又怕麻烦不想做,于是这个程序员就在想,要是有一个盒子能自动化做菜,放进土豆、醋和青椒就出来酸辣土豆丝;放进五花肉和红萝卜就出来红烧肉……这该有多好!其实这个程序员是将生活中的事程序化了,非常多的程序员都将函数看成黑盒子,即输入五花肉和红萝卜,输出红烧肉,至于在盒子里面具体怎么烧肉、烧萝卜的过程则不关心。
运行Zend Studio,打开项目php,新建文件“useFunction.php”,并输入代码7-1。
代码7-1 使用函数的例子
<?php // 打印文字为粗体函数 function printBold($text) { print("<span class="kindle-cn-bold">$text</span>"); } print("这一行不是粗体<br>\n"); printBold("这一行是粗体"); print("<br>\n"); print("这一行不是粗体<br>\n"); ?>
保存代码,按“F5”键运行,结果显示如图7.1所示。
图7.1 函数使用
7.2 系统(内置)函数
PHP有很多标准的函数,这些函数分为两部分,一部分是核心函数,例如字符串和变量函数,在各个版本的PHP安装后,默认就有。还有一些函数需要和特定的PHP扩展模块一起安装,否则在使用它们的时候就会得到一个致命的“未定义函数”错误。例如,要使用图像函数imagecreatetruecolor(),需要在安装PHP的时候加上GD(一个图像处理库)的支持。或者,要使用MySQL数据库的连接函数mysql_connect(),就需要在安装PHP的时候加上MySQL数据库的支持。要知道当前使用的PHP有哪些函数,可以调用phpinfo()函数或者get_loaded_extensions()函数得到PHP加载了哪些扩展库。
运行Zend Studio,打开项目php,新建文件“phpinfo.php”,并输入以下代码。
<?php phpinfo(); ?>
保存代码,按“F5”键运行,查看结果。
注意 对于默认就是有效的函数库,PHP手册中的函数参考章节按照不同的扩展库组织了它们的文档。
7.3 自定义函数
学会调用核心函数后,自己动手写一个函数,运行Zend Studio,打开项目php,新建文件“function.php”,并输入以下代码7-2。
代码7-2 自定义函数
<?php function hongShaoRou( $wuHuaRou, $hongLuoBu ) { Echo '洗红萝卜和五花肉'; Echo '<br>'; Echo '切红萝卜和五花肉'; Echo '<br>'; Echo '将原料和调料放入锅'; Echo '<br>‘; Echo '烧肉…’; Echo '<br>‘; Echo '起锅!’; return '红烧肉'; } ?>
代码分析:以上是一个函数,函数是以关键字“function”开头,然后是空格,紧接着是本函数的函数名称“hongShaoRou”,接着是一对括号,里面有两个变量一样的东西$wuHuaRou和$hongLuoBu,这叫函数参数,接下来是一对大括号和其中的内容,这是函数体,即实现函数功能的代码,大括号用来界定函数代码的范围,表明其内的代码属于此函数,注意最后一行是return语句,这表明函数返回结果。
注意 函数名和PHP中的其他标签命名规则相同。有效的函数名以字母或下画线打头,后面跟字母、数字或下画线。
在目前的PHP版本中,函数具有全局属性,也就是定义一个函数后,可以在程序的任何地方使用,定义函数的位置可以在程序的任何地方,甚至可以放在另一个函数的内部。运行Zend Studio,打开项目php,新建文件“defineFunction.php”,并输入代码7-3。
代码7-3 调用函数
<?php // 调用etc函数 etc(); // 定义foo函数 function foo() { echo "我是函数foo\n"; // 定义bar函数 function bar() { echo "我是函数bar\n"; } } // 定义etc函数 function etc() { echo "我是函数etc\n"; } // 调用foo函数 foo(); // 调用bar函数 bar(); ?>
保存代码,并按“F5”键,结果打印了3个函数的调用结果。
代码分析:代码第4行调用etc函数,而etc函数是在第19~22行定义的,由此可见在PHP中,函数可以定义在需要调用代码之后。第7~16行定义了foo函数,在foo函数里面的第12~15行又定义了bar函数,这叫函数中的函数,在实际项目中很少有这样定义的,最后第25行和第27行调用了foo函数和bar函数,运行正常,再次证明了PHP中函数可以定义在任何地方,且在任何地方均可调用。
由于PHP函数有全局特性,造成了函数不能重名,在大型项目开发中,往往会出现不同模块开发者定义了相同的函数名,造成维护成本的增加。好在PHP在5.3.0版本中加入了命名空间支持,这样只要是在不同名称空间下,同名也可以,相当于增加了一个管理维度。
Tips 使用逐步调试,查看程序运行时如何调用函数。
7.4 函数参数
函数的参数是用来和函数沟通的途径,参数以逗号作为分隔符,例如上面7.3节中的红烧肉函数。
<?php function hongShaoRou( $wuHuaRou, $hongLuoBu ) { //….. } ?>
代码中的“$wuHuaRou, $hongLuoBu”为函数hongShaoRou的参数,调用函数时,需要给参数传值,传值有3种方式。
- 按值传递:函数默认按值传递,相当于将值赋给了参数。下面是一个按值传递的例子,运行Zend Studio,打开项目php,新建文件“paramFunction.php”,并输入以下代码7-4。
代码7-4 函数默认按值传递例子
<?php $n = 3; // 初始化一个变量 // 定义函数 function byValue($num) { $num = $num + 1; // 改变参数的值 echo $num; } byValue($n); echo "\n"; // 换行 echo $n; ?>
保存代码,按“F5”键,结果中可以看到输出了4和3。
代码分析:第3行初始了变量$n的值为3,第6~9行定义了函数byValue,实现的功能是将接收到的参数值做加1操作,第11行调用函数byValue,给参数$num的值为$n(即3),函数byValue实现了$num+1并打印出结果4,完成函数调用,第12行输出了一个换行,第13行打印变量$n的值为3。
- 引用传递:引用传递和C语言的传址类似,传值是一个拷贝,引用传递是传递了一个别名,而不是拷贝一个值。继续看例子,运行Zend Studio,打开项目php,打开文件“paramFunction.php”,并修改代码如7-5所示。
代码7-5 传址
<?php $n = 3; // 初始化一个变量 // 定义函数 function byValue(&$num) // 参数前多了一个 & 符号 { $num = $num + 1; // 改变参数的值 echo $num; } byValue($n); echo "\n"; // 换行 echo $n; ?>
保存代码,按“F5”键,结果中$n的值不一样了,在按值传递中,$n的值为3,现在的结果是4,这就是引用传递。如果希望允许函数修改它的参数值,必须通过引用传递参数。
- 默认值:函数的参数可以设置一个默认值,修改paramFunction.php如代码7-6所示。
代码7-6 设置参数默认值
<?php $n = 3; // 初始化一个变量 // 定义函数 function byValue($num=1) { $num = $num + 1; // 改变参数的值 echo $num; } byValue($n); echo "\n"; // 换行 byValue(); ?>
保存代码,并按“F5”键,结果为4和2。
代码分析:这次修改是在参数$num后面添加了一个默认值,第12行和第14行各调用一次byValue函数,不同的是第12行调用给了参数值$n,第14行调用时没有给参数,结果显示给了参数$n($n的值是3)的结果是4,没有给参数时的值为2,这说明当函数参数有默认值的时候,调用时没有给参数,那么函数使用默认值。
注意 默认值必须是常量表达式,不是变量、类成员,或者函数调用等。
在函数参数使用默认值时,如果有多个参数,那么需要将有默认值参数放在任何非默认值参数的右边,注意下面的例子。
<?php // 定义函数 function hello($name = 'longyang', $time) { return '你好, ' . $name . '。 现在的时间是:' . $time; } echo hello('9点'); ?>
运行结果将会得到一个警告信息,下面是正确的代码片断。
<?php // 定义函数 function hello($time,$name = 'longyang') { return '你好, ' . $name . '。 现在的时间是:' . $time; } echo hello('9点'); ?>
这将得到正确的结果。
7.5 返回值
函数值通过使用可选的返回语句返回。通常使用return语句,任何类型都可以返回,其中包括列表和对象。在return语句中已经提到,如果return语句在函数中,那么执行后将立即停止该函数运行,并且将控制权传递回它被调用的行,如果忘了return语句的使用方法,可以回头复习。函数不能返回多个值,但可以返回一个数组来得到多个值,关于数组的知识将在后面的章节中讲到。
同样,函数也可以返回一个引用,这和变量的引用类似,只需要在函数前加上符号“&”,下面是一个例子。
<?php function &returnReference() { $someReference = 'alskdjfaskdjfkalsdjf'; return $someReference; } $reference =&returnReference(); ?>
变量$reference是函数returnReference返回的一个引用。
7.6 动态调用函数
假设一个状况,在一个项目中,客户要求自己设置字符串转换成大写或小写,按照之前学习到的知识,实现的代码如下。
<?php /** * 设置字符串转换方式,小写:strtolower,大写:strtoupper */ $functionName = 'strtolower'; // 假设用户设置为小写 $string = 'ABCDEFG'; // 要处理的字符串 // 判断用户设置 if ($functionName == 'strtolower') { // 调用函数处理 $string = strtolower($string); } ?>
实际上这个实现很简单了,但PHP还有一个更加简单的实现方法,请参考下面的实现代码。
<?php /** * 设置字符串转换方式,小写:strtolower,大写:strtoupper */ $functionName = 'strtolower'; // 假设用户设置为小写 $string = 'ABCDEFG'; // 要处理的字符串 $functionName($string); ?>
这就是PHP中的动态调用函数,注意第8行,变量后面有圆括号,这意味着PHP将寻找与变量$functionName的值同名的函数,并且将尝试执行它,结果为处理$string的函数是strtolower。如果用户设置为strtoupper,那么处理$string的函数则是strtoupper。
注意 动态调用函数不能用于语言结构,例如echo()、print()、unset()、isset()、empty()、include()、require()及类似的语句,除非将这些语言结构重新用自定义函数包装起来,然后使用包装的函数调用。
7.7 作用域
学到现在,从变量到运算,从流程到函数,一个PHP代码文件就越来越复杂了,这时候有一个问题,即这些变量和函数产生后,其有效期有多长,在哪些结构里面起作用,在这一节将详细说明变量和函数在文件结构中的作用域及一些改变其作用域的方法。
7.7.1 局部作用域
局部作用域即只是在某一个区间有效。首先来看一个例子,运行Zend Studio,打开项目php,新建文件“see.php”,并输入以下代码7-7。
代码7-7 局部作用域例子
<?php function test() { $abc = 123; } echo $abc; ?>
保存代码,并按“F5”键,结果有一条提示信息显示“Notice: Undefined variable: abc in……”。
代码分析:代码第3~6行定义了函数test,函数里面定义了一个变量$abc,值为整数123,第8行显示$abc变量,结果显示$abc变量未定义,这说明变量$abc在test函数外是无效的。
下面再看一个在函数外声明的变量,是否在函数内有效,将see.php的代码改为下列代码。
<?php $abc = 123; function test() { echo $abc; } test(); ?>
保存代码,并按“F5”键,结果还是显示了一条信息“Notice: Undefined variable: abc in……”。
代码分析:第3行在函数外定义了变量$abc,第4~7行定义函数test,第8行调用test函数,结果打印变量$abc时提示变量未定义。
这时候可能有人要问,如果函数内需要使用函数外定义的变量该如何做?修改see.php代码如下。
<?php $abc = 123; function test() { global $abc; echo $abc; } test(); ?>
保存代码,并按“F5”键,结果输出123。
代码分析:这次修改在函数中加入了关键字“global”,后面是空格和变量$abc,这样函数内就可以使用函数外的变量了,如果有多个变量,那么使用逗号(,)隔开,依然是分号结束,如下面代码片断所示。
<?php $abc = 123; $def = 456; function test() { global $abc, $def; echo $abc . $def; } test(); ?>
保存代码,并按“F5”键,结果输出123456。
还有一种方法是使用超全局变量来访问具有全局作用的变量,通常情况不这样做,相关知识请见下一节的学习。
7.7.2 全局作用域
在7.3节中提到,函数具有全局作用域,也就是定义一个函数,那么该函数可以在程序中的任何地方使用。在PHP中,虽然直接在文件开头定义的变量是全局变量,但在函数中却需要加关键字global才可以使用,但如果有必要可以将变量定义在预定义变量中,因为PHP预定义变量是超全局变量,超全局变量是可以在PHP中任何地方使用的,这个特性和函数的特性一样。下面看一个例子。
运行Zend Studio,打开项目php,新建文件“supperVar.php”,并输入代码7-8。
代码7-8 全局作用域例子
01 <?php 02 03 // 定义变量$test 04 $test = 'abcd'; 05 // 定义函数printVar 06 function printVar() 07 { 08 echo '在函数printVar内显示的$GLOBALS[\'test\']:' . $GLOBALS['test']; 09 echo "\n"; 10 } 11 12 printVar(); 13 echo '直接显示的$GLOBALS[\'test\']:' . $GLOBALS['test']; 14 echo "\n"; 15 echo '直接显示的$test: ' . $test; 16 ?>
保存代码,并按“F5”键,查看运行结果。
代码分析:第4行定义变量$test,值为abcd,第6~10行定义函数printVar,函数的作用是打印$GLOBAL[‘test’]这个变量,并输出了一个换行符“\n”,第12行调用函数printVar,此时得到第一个结果“在函数printVar内显示的$GLOBALS['test']:abcd”,第13行打印字符串“直接显示的$GLOBALS[\'test\']:”,后面是一个字符串运算符“.”,是将“直接显示的$GLOBALS[\'test\']:”加上变量“$GLOBALS['test']”的值,结果为“直接显示的$GLOBALS['test']:abcd”,细心的人可能发现了,字符串“直接显示的$GLOBALS[\'test\']:”中的“$GLOBALS[\'test\']”和其他的有点不一样,在单引号前多了一个反斜线,这是因为打印的这个字符串含有变量,如果使用双引号将不会显示“$GLOBAL[‘test’]”,而是显示“$GLOBAL[‘test’]”的值,使用单引号是为了让这个变量以字符串输出,但是使用单引号就会和“$GLOBAL[‘test’]”中的两个单引号冲突,此时使用反斜线来转义这两个单引号,这样就准确地输出了字符串“$GLOBAL[‘test’]”。第14行输出一个换行,第15行直接输出变量$test的值,结果为“直接显示的$test: abcd”。
这说明,只要定义的变量具有全局属性,那么该变量都可以用“$GLOBAL[‘定义的变量名字’]”来访问,不论在何时何地。具有全局属性的变量通常指没有在任何条件或一些结构下定义的变量,将supperVar.php的代码改为以下代码7-9。
代码7-9 全局属性例子
<?php // 定义变量$test $test = 'abcd'; // 定义函数printVar function printVar() { $wahaha = 'wahaha'; } echo '显示的$GLOBALS[\'test\']:' . $GLOBALS['test']; echo "\n"; echo '显示的$GLOBALS[\'wahaha\']:' . $GLOBALS['wahaha']; ?>
保存代码,并按“F5”键。结果发现一条消息,提示“Notice: Undefined index: wahaha in……”意思是未定义的索引“wahaha”,“$GLOBALS['wahaha']:”后面没有显示结果。
代码分析:定义$test变量没有在任何函数内或foreach等结构内,所以具有全局属性,这些变量可以用超全局变量$GLOBAL访问,反之,在函数printVar内定义的变量$wahaha则具有局部属性,只是在函数printVar中有效,所以不能用$GLOBAL访问到。
Tips 使用Zend Studio编写代码时,如果该变量具有全局属性,那么当在编辑器中输入“$GLOBALS['”时,编辑器自动出现下拉列表,可以在里面找到具有全局属性的变量。
7.8 生存期
变量和函数放在不同的地方会有不一样的生存期,变量生存期的内容放在函数的章节里面,是因为变量的生存期和函数及结构息息相关。
运行Zend Studio,打开项目php,新建文件“varLife.php”,并输入代码7-10。
代码7-10 变量生存期的例子
<?php // 定义函数test function test() { $a = 0; echo $a; $a++; } // 调用函数test两次 test(); test(); ?>
保存代码,并按“F5”键,结果输出0 0。函数内的变量$a是一个局部作用域变量,每次调用时都会将$a的值设为0并输出"0"。将变量自加1的$a++没有作用,因为一旦退出本函数变量$a就不存在了。
那么,要将函数内的变量保留下来,除了return返回值之外,还可以使用静态变量,修改varLife.php代码,在变量$a之前加上static关键字。
<?php // 定义函数test function test() { static $a = 0; echo $a; $a++; } // 调用函数test两次 test(); test(); ?>
保存代码,并按“F5”键,结果为0 1。这说明情况发生了变化,变量$a的生存期不仅仅因为第一次函数test的调用结束而结束,当第二次调用该函数时,变量$a的值还存在。
现在来看一下函数的生存期,先前提到函数是有全局域的,无论在哪都能使用,但下面这个例子会出现一些例外。
运行Zend Studio,打开项目php,新建文件“functionLife.php”,并输入代码7-11。
代码7-11 函数生存期的例子
<?php // 定义函数parent_fun和函数中的函数child_fun function parent_fun() { echo 'parent_fun'; function child_fun() { echo ' child_fun'; } } // 第一个注释 //parent_fun(); // 第二个注释 //parent_fun(); child_fun(); ?>
保存代码,并按“F5”键运行。结果出现一个致命错误“Call to undefined function child_fun()”,这是因为没有运行parent_fun函数,child_fun函数就没有定义,现在编辑functionLife.php去掉第一个注释,保存并运行,结果显示正常,再继续去掉第二个注释,保存并运行,结果出现一个致命错误“Cannot redeclare child_fun()(previously declared in……)”,意思是child_fun函数已经定义了,第一次调用parent_fun函数时,定义了函数child_fun,第二次调用就出现了错误,这就是为什么不推荐在函数中定义函数的原因,因为定义了函数中的函数,那么上层函数就只能被调用一次。
7.9 典型实例
【实例7-1】递归函数类似于一个循环,其在很多方面都有应用,例如无限分级的目录树或菜单等。在创建递归函数时也和循环一样,就是不能产生像无限循环一样的结果。递归调用函数最好在100至200层以内。本实例通过递归函数创建一个无限分级的目录树。
代码7-12 通过递归函数创建一个无限分线的目录树
<?php //定义一个保存目录树数据的数组 $tree = array( array("id"=>1,"pid"=>0,"t"=>"总公司"), array("id"=>2,"pid"=>1,"t"=>"分公司1"), array("id"=>3,"pid"=>1,"t"=>"分公司2"), array("id"=>4,"pid"=>2,"t"=>"分公司1部门"), array("id"=>5,"pid"=>3,"t"=>"分公司2部门"), array("id"=>6,"pid"=>4,"t"=>"分公司1部门"), array("id"=>7,"pid"=>6,"t"=>"分公司1部门"), array("id"=>8,"pid"=>7,"t"=>"分公司1部门"), array("id"=>9,"pid"=>8,"t"=>"分公司1部门") ); //定义一个查询数组内容函数 function getline($id){ //访问全局变量$tree global $tree; //定义一个空数组 $r = array(); //定义一个空的数组,用于保存获取的变量 foreach($tree as $v){ //使用foreach遍历数组$tree if($v["pid"]==$id){ //当数组中键名为pid的元素的值等于给出的值时,将这个值保存到新创建的数组中 $r[] = $v; //记录属于同一个上级结点的数组 } } //返回数组 return $r; } //定义一个递归函数 function comtree($pid=0,&$result="",$d=1){ //取得数组中相关的数据 $data = getline($pid); //遍历取回的数组,构建目录树 foreach($data as $v){ //处理目录树显示数据 $result.=str_repeat("-",$d-1).$v["t"]."<br>"; //根据部门的级别,设置部门前方显示的符号 //递归调用函数本身 comtree($v["id"], $result,$d+1); } //当上级结点为0时,才显示树结构 if($pid==0){ print $result; } } comtree(); ?>
运行该程序后,运行结果如图7.2所示。
图7.2 程序运行结果
【实例7-2】在PHP变量知识的介绍中,提到过可变变量。通过在字符型变量前添加“$”符号,可以产生一个新变量。而函数也可以通过改变其函数名称,达到类似可变变量的效果,这类函数称为变量函数。
在了解了变量函数的特点后,在实际应用中,可以使用变量函数的这一特点,实现函数调用。特别是在软件项目中,函数特别多的情况下,使用变量函数,可以根据用户的输入调用相关的函数,方便程序模块化。在使用变量函数时需要注意两点:
(1)检查目标函数是否存在,这样才能更有效地防止程序出现调用未知函数的错误。
(2)如果要使用变量函数动态地调用对应函数,一定要检查目标函数的安全性,防止非法用户调用到权限之外的函数,影响系统安全。
代码7-13 函数调用
<?php function u_1() { echo "这是函数u_1<br />"; } function u_2($title) { echo "这是函数u_2,其参数是".$title.".<br />\n"; } class runf{ function u_3(){ echo "这是运行于类中的函数u_3"; } } $function = "u_1"; //给变量$function赋值 $function(); //运行以$function变量值同名的函数 $function = "u_2"; //给变量$function赋值 $function("2"); //运行以$function变量值同名的函数,并为函数设置参数 $function = "u_3"; //给变量$function赋值 $nrunf = new runf(); //实例化类 $nrunf->$function(); //运行类中的函数 ?>
运行该程序后,运行结果如图7.3所示。
图7.3 程序运行结果
7.10 小结
本章介绍了函数的结构、函数的使用、系统函数和自定义函数,以及变量和函数的作用域、生命期等。从第1章到第7章是PHP的基础部分,其中的变量、流程结构和函数又是基础中的重点,对这些知识掌握的程度直接影响从第8章开始的高级部分,建议多复习几次第1~7章的内容,重新消化一遍。
7.11 习题
一、填空题
1. PHP自定义函数的关键字是_____。
2. 在主程序中定义的变量,不但在主程序中有效,函数内也能调用;同样在函数中定义的变量也能被函数以外的程序调用,这类变量称为_____。
3. 当函数执行完毕后,变量的存储空间将自动被释放出去,这类变量称为_____;在函数执行完毕后,仍能保留其存储空间的变量称为_____。
二、选择题
1. return语句可以返回()类型的数据。
A. 整型
B. 浮点型
C. 数组
D. 以上都有
2. 下面程序的运行结果是( )。
$a=2008; function add (&$a){ $a=$a+1; echo $a."<br>"; } add($a); echo $a;
A. 2008
2008
B. 2009
2008
C. 2009
2009
D. 编译有误
3. 下面程序的运行结果是( )。
$int=1; function num(){ $int=$int+1; echo "$int<br>"; } num();
A. 程序无输出
B. 1
C. 2
D. 以上都不对
共有条评论 网友评论