第八章 - 函式的定义及应用
本章重点
2 使用内建的PHP函式
2 浏览线上函式手册
2 使用者定义函式
2 进价技巧:可变参数、参引呼叫和可变函式
任何程序语言都有某种程序抽象处理能力(procedural
abstraction,就是帮程序码某段区块命名,以便在编写其它程序码区段时能够取用)。某些script语言缺乏这种能力,而根据我们的经验,这会使复杂的伺服端程序码很快变得无法管理。
PHP用来提供这种抽象处理的机制就是函式(function)。PHP中实际上有两种类型的函式,一种是已经由PHP开发者内建在语言中,另一种是由每个PHP程序设计师自己定义的。
本章中,我们来看看如何使用PHP已提供的大量函式,然后再学会如何定义属于自己的函式。很幸运地,使用内建函式和自己定义的函式并没有什么区别。
使用函式
使用(或呼叫)函式的基本语法如下:
function_name(expression_1, expression_2,…,expression_n)
它是函式名称后带有括号并以逗号分隔输入运算式,(称为函式参数)列表的型式出现。根据具体定义,呼叫函式可以不带参数,也可以同时带入多个参数。
当PHP遇到有函式呼叫,它首先对每个参数运算式评算求值,然后使用这些值做为该函式的输入。函式执行后,回传值(如果有回传值)就是整个函式运算式的结果。
下面所列出的例子都是PHP内建函式的有效呼叫方式:
sqtr(9) //平方根函式,结果是「3」
rand(10,10+10) //回传介于「10」和「20」之间的数值
strlen(“This has 22 characters”) //回传数值「22」
pi() //回传p的近似值
这些函式分别用1,2,1和0个参数进行呼叫。
回传值与附带作用
每个函式呼叫都算是一个PHP运算式(与其它运算式一样),在程序码中包含函式只有两个原因:为了取得回传值,或者为了得到附带作用。
函式的回传值是函式运算的值。使用这个值,可以像使用其它任何运算式的求值结果一样,例如,可以把它指定到某个变数,如下所示:
$my_pi = pi();
或者,可以把它嵌入复杂的运算式中,如下所示:
$approx = sprt($approx)* sqrt($approx)
函式还可以用来实现各种不同的附带作用,其中包括将资料写入档案、资料库操作以及把内容输出示显示在浏览器视窗上。同时使用回传与附带作用出是可行的,例如,我们常常使用一个附带作用的函数并同时回传值来检查这个函数是否执行正常
。
函式的结果可以是任何型别,通常都把array型别用来当成传回多个值的函式。
函式手册
PHP的架构设计得很聪明,让它很容易由其它开发人员进行扩展延伸。其本的PHP语言本身很容易懂,出很有弹性,大多数PHP功能都存在大量的内建函式中。这是指开发人员可以自由添加新的内建函式,并且能够为PHP有所贡献,这种方式非常好,因为它不会改变PHP使用者可能依赖的任何部份。
虽然本书涵盍了许多内建函式,对其中某些函式的讲解比线上手册的讲解更详细,但http://www.php.net/所提供的手册还是函式资讯的重要参考资源。在本书中,我们会在某种程度上摘选我们想要的主题来延伸介绍,而PHP手册的内容仍旧是PHP在每个方面上最完整的参考。虽然我们希望跟随PHP的未来版本即时更新本书,但线上说明还是会关于PHP新功能的最新功能的最新资讯,包括那些还处于正在开发状态的功能。所以有空看看PHP官方网站和线上提供的不同资源可是非常有帮助的。
虽然下面的资讯在本书结稿时是正确的(第二版再版的时间是2002年)。但可能有些细节部份已过时,或者由于线上手册有了更新版本或重新组织而不再适用。
想要查寻线上手册,可进入http://www.php.net/网站,并选中右上方导航列中的「documentaion」选项标签,这样会转到一个含有各种格式且包含民其使用手册资讯的链接。我们想要看的是线是可注解的使用手册(目前连接位置在Documentation网页的View
Online选项上),该手册允许使用者把自己的注解资料张贴到每一页中。[请注意:手册注解系统不是张贴问题的地方!如果有使用上的疑问,可参看邮寄清单(mailing
list)部份,它位于http://www.php.net/的「support」选项标签中,或者参阅本书有关PHP资源的附录D。手册注解系统是在使用者自己理解后要用自己的话进行解释和提出意见的地方,不过确是我们能做出贡献的途径,这也是有可能就会变成未来的正式版本,这的确是我们能做出贡献的好途径,这也是指出易混淆与可能有问题的好地方,不过这里都以「英文」来沟通哦!]
使用手册中的绝大部份是函式的考次料,其中每个内建函式都有单独一份说明页面。每页从函式名称和单行的说明开始,然后是属于C语言风格的函式标头宣告(下一节会讲解到),后面是个稍长的说明,可能还有一两个例子,然后(在注解手册中)是来自使用者的注解和意见报告。
在手册中的标头
对于那些不熟悉C语言函式标头的人,函式手册的最开头部份可能会看不懂。基格式是:
return-type function-name(typle argl,type2 arg2,…);
它指示了期望的函式回传值型值,函式名称和期望的参数型别等。
下面是个基本标头描述:
string substr (string string,int startl [,int length]);
这就表示函式substr会传回一个字符串,并期望取得一个字符串和两个整数为参数。实际上,length两边的方括号表示了这个参数是选择性的,因此呼叫substr时,可带一个string字符串和一个int整数,或是带一个string字符串和两个int整数。
与在C语言中的情况不同,这些手册标头中宣告的参数型别并非是绝对必要的。如果用一个数字当成一个参数来呼叫substr,也不会有错误出现,它是由 PHP在开始执行该函式之前,把第一个参数转换成字符串。不过参数型别的确表现了函式创造者的初衷,另外最好是按照手册中注明的型别来使用函式,或者对型别转换的有关事项要有足够的了解,这样才能够保得到预期的结果。
一般来说,函式手册中用的型别名称是六种基本型别之一,或者是用它们的别名:integet(或int)、double(或float、real)、 Boolean、string、array、object、resource和NULI。另外,可以看到void和mixed的类型。Viod型态的函式根本不传回任何值,而mixed参数型别意味着参数可以是任何型别。
查询函式手册
在手册中查询关于某函式的资讯的最佳途径是什么呢?这主要取决于你的好奇心和有兴趣的部份是那种?关于函式的最常见问题是:
我要用X函式,它又该如何使用呢?
我需要完成某项Y任务,有函式能帮我完成这一任务吗?
对于第一种情况,完整版本的线上手册提供民根据函式名称的自动查询。在右上方导航列下面,「search for」文字框预设搜寻的模式是函数的名称,并且会在找到该函数后显示此函数显示此函数的网页(你也可以选取取其它搜寻对象,包含整个网站的 mailing list,这里是当你不知道所需函数名称时,但是可以猜出所需函数的概略字样时搜寻的好地方)。
以第二种情况来寻找的话,最好是使用函式参考文献(Funcrion Reference ,PHP手册中的其中一篇)的层次组织结构,该函式参考文献分成108章。例如,前面提到的substr函式在「Headers in Documentation」部分内的「String functions」部分。读者可以浏览函式参考文献的章节列表,以选择最适合你要完成的任务所属的那一章。或者,如果恰好知道一个函式的名字,该函式可能与完成的工作处于同一范围领域,此时就可以直接按下Quick Ref按钮连接到相关章节。
使用者定义的函式
使用者定义的函式在PHP中并非必要的。你也可以只用基本的语言结构和大量内建函式就能够构建出有用的Web网站了。不过如果发现自己的程序码内容变得越来越长了,而且愈来愈难了解和管理时,这表示要想一想应该把自己的某些程序码转成函式了。
什么是函式?
函式就中一种包裹某段程序码区块并给它取个名字的程序写法,这方便于以后只用一行程序码就能够使用该程序码区块。假如在写程序时,在多处都要使用到该程序码区块时,函式是最有效的方式,不过即使只使用一次,函式的建立与编写也会对程序结构有所帮助,因为可以使你的程序码更具可读性。
函式定义语法
函式可定义为以下形式:
function function-name($argument1, $argument2,..)
{
statement1;
statement2;
…
}
函式定义有四个部份:
专用的「function」一词
帮函式取的名称
函式的参数列表(以逗号分隔的$符号变数)
函式本体(大括号内的一系列叙述语句)
和变数名称一样,函式名称也必须由英文字母、数字和底线(_)组成,并且它不能以数字当开头。与变数名称不同的是,函式名称在储存前会被转换成小写,因此可将函式名称视为不区分大小写。
当呼叫使用定义的函式时发生的情况简述如下:
1. PHP会按照名字找寻该函式(如果函式还没有被定义过,则会显示错误讯息)。
2. PHP把参数(或称「实际参数」)的值替代函式定义中参数列表里的变数(或称「形式参数」)。
3. 执行函式本体的叙述语句。如果执行到叙述中「return」语句,则函式会停止执行并传回指定的值。否则,函式会一直执行到最后一道,并且不传回任何值。
请小心,有经验的程序设计师也许会注意到,前面的说明暗示了其为按值呼叫(call-by-value),而不是按参引呼叫(call-by-reference)。在本章最后一节会说明它们的差别,并示范如何进行按参引来呼叫。
函式定义的范例
举一个虚构的例子,请设想下面的这段程序码,目的是帮助我们决定购卖多少瓶装饮料(也许未来的什么时候,超商老品就已在使用可摧式的无线Web浏览器查看比较采买的价格了)。
$liters_1 = 1.0;
$price_1 = 1.59;
$liters_2 = 1.5;
$price_2 = 2.09;
$per_liter_1 = $price_1 / $liters_1;
$per_liter_2 = $price_2 / $liters_2;
if($per_literl<$per_liter2)
print(“The first deal is better! < BR >”);
else
print(“The second deal is better!< BR >”);
因为这种类型的比较在我们的Web网页程序码中随处可见,我们很想把它做成可重复使用的函式。这样做的一种途径是按照范例来重写:
function better_deal ($amount_1,$price_1,
$amount_2,$price_2)
{
$per_amount_1 = $price_1/$amount_1;
$per_amount_2 = $price_2/$amount_2;
return($per_amount_1<$per_amount_2);
}
$liters_1 = 1.0;
$price_1 = 1.59;
$liters_2 = 1.5;
$price_2 = 2.09;
if(better_deal($liters_1,$price_1,
$liters_2,$price_2))
print(“The first deal is better!< BR >”);
else
price(“The second deal is better!< BR >”);
better_deal函式抽取了以前程序码中进行算术运算和比较的那三行程序码,它带有四个数字当作参数,并传回一个Boolean 运算式的值。和任何其它Boolean值一样,可以把它嵌入到if叙述的测试部份。虽然这个函式比原来的程序码还长,但这样重写后有两个优点:可以在程序其它多个位置使用这个函式(节省了整体程序行数),如果决定改变计算方式,只需要修改一个地方就好了。
还有一种替代方案,如果价格比较就是为了印出哪笔交易更划算,那可以直接把输出显示叙述句放到函式里,如下所示:
function print_better_deal($amount_1,$price_1,
$amount_2,$price_2)
{
$per_amount_1 = $price_1 / $amount_1;
$per_amount_2 = $price_2 / $amount_2;
if($per_amout_1<$per_amount_2)
print(“The first deal is better!< BR >”);
else
print(“The second deal is better!< BR >”);
}
$liters_1 = 1.0;
$price_1 = 1.59;
$liters_2 = 1.5;
$price_2 = 2,09;
print_better_deal($liters_1,$price_1,
$liters_2,$price_2);
第一个函式使用return语句传送一个Boolean结果,然后该结果用于在一个if叙述的测试中。第二个函式没有return语句,因为它完成了其附带作用:在使用者浏览器是显示文字。当这个函式的最后一个叙述
语句执行完毕后,PHP接着执行函式呼叫后的下一条叙述语句。
形式参数与实际参数的对比
在前面的例子中,传递给函式的参数是「变数」,但并不是必须都如此处理。实际参数(即函式呼叫中的参数)可以是能够评算求值的任何运算式。在我们的例子中,可以给函式呼叫传入数字,而不用变数,如下所示:
Print_bettet_deal(1.0,1.59,1.5,2.09);
还要注意,在这个例子中有两处实际参数变数与形式参数同名的情况(如$price_1还有实际参数与形式参数名称不同的(如$liters_1与$ amount_1不同)。下一节介绍的内容中会提到两种方式都没有问题,函式的形式参数名称完全独立存在于函式外部,即使函数呼叫自己也是同样的。
参数数目不匹配
如果呼叫函式时带有的参数个数比定义中的参数个数少时,会怎样呢?或者,如果附带的参数比较多,又会怎样呢?正如读者所期待的,PHP可以容忍这些情况,而它会根据正常PHP4的错误回报设定,你会在浏览器看到所印出的警告讯息。
PHP4的预设错误回报设定会回报每一种除了执行期提醒之外类型的所有错误讯息,因为执行期提醒是所检查到较不重要的问题,而你所看到函数除入参数不足的警告被视为一种执行期的警告问题(较重要的错误种类),如果你真的需要有时使用函数定传入较少的参数时不想看到这种警告讯息时,你有两种选择来抑制这种警告:
你可以在程序中暂时更改错误回报,使用下列语法
errot_reporting(E_ALL – (E_NOTICE + E_WARNING));
这会将程序在出现下一个error_reporting()之间(如果有出现的话)关闭执行期提醒以及执行期警告(请注意这样做是危险的,因为其它你有兴趣的错误讯息可能也会被隐藏起来)。
你可以针对任何单一叙述使用错误控制符号@来抑制错误讯息的出现,你可以在任何叙述之前使用它来关闭针对这个叙述产生的错误讯息,例如,如果函数呼叫 my_function()会产生一个警告,那么@ my_function()将不会产生,请注意这样做也是危险的,因为除了语法解析错误之外的所有类型错误都会被隐藏起来。
我们并不建议使用这些变相的选择,但是因为我们并无法保证永不出错所以偶尔会使用这些技巧,事实PHP提供一种方法来预测参数的撰写函数方式(请参阅本章稍后的「进阶函式技巧」内的「可变参数个数」),使用它们是比隐藏错误讯息呈现更好的主意。
不要将PHP的回报水准降低,我们比较赞同当你在开发新的程序码时将它调为最高水准,你可以在php.ini档案内设定这个选项(请参阅第37章)或是只要在程序的一开始加入error_reporting(E_ALL);语法,除此之外,调升回报水准意谓着当你忘记指事实上变数的内容时将会出现警告,这也是最易发生的费时错误。
太多参数个数
如果你传入太多的函数参数,那么额外的参数将单纯的被省略,即使错误回报被设定为E_ALL亦然,我们在「进阶函式技巧」部分将会介绍,这种弹性将会使我们能够将函数的参数部分传入变动的参数。
函式和变数作用域
正如我们在第五章中所提到的,函式外面关于变数作用域(作用范围)的规则很简单;在PHP程序码档案执行过程中的任何时候指定变数,该值将在档案的后续执行过程中一直存在。在函式定义本体中,这个规则得稍微有一点复杂要多留意。
管理函式本体中变数的基本原理是:「每个函式皆具有自己的独立世界」。也就是说,除非进行特殊宣告,否则函式内部的变数名称与其它地方使用到的名称就算相同也没有冲突。[这是属于PHP的特质,而不是错误。函式是要在不同本文内容中重复呼叫取用的,因此其行为应该独立于本文内容之外。如果不这样限定作用域(作用范围),会浪费很多时间来解决由于程序码中不同部份使用了同名变数而产生的问题。]
自从要存取的变数值只有形式参数变数(它们的值是从实际参数复制来的),还有那些在函式内定义的所有为数。这表示可以在函式内使用区域变数,你不必担心它们会对外面的世界有所影响。例如,请思考下面的函式以及随后的使用:
function SayMyABCs()
$count = 0 ;
while ($count< 10 >
{
print(chr(ord(ˋAˊ)+ $count));
$count = $count + 1;
}
$count = 0;
sayMyABCs();
$count = $count + 1;
ptint(“Now I′ve made $count function call(s).< BR >”);
sayMyABCs()
$count = $count + 1;
ptint(“Now I′ve made $count function call(s).< BR >”);
SayMyABCs()的用途是印出一个字母序列(函式chr()和ord()在字母和相对应的ASCII码之间进行转换,这里使用它们只不是为了产生字母序列而已),程序码的输出结果如下:
ABCDEFGHIJ
NOW I know 10 letters
NOW I′ve made 1 function call(s).
ABCDEFGHIJ
NOW I know 10 letters
NOW I′ve made 2 function call(s).
函式定义和函式外的程序码都用了名称为$count的变数,但它们实际不指不同的变数,彼此不会产生问题。
在函式内部指定变数的预设行为是不与外界有所交互影响的,每次呼叫函式时就好像新建立了些变数一样。不过这些行为都可以用特殊的宣告方式来覆盖。
全域变数(global宣告)vs. 区域变数
在函式内定义变数的作用域,在预设情况下是局限于该函式之内,属于区域性的,意谓(我们在前面部分解释过)它信与函数外面的变数没有任何关系。透过使用 global宣告,就可以让PHP知道,现在需要让这个变数名称和它在函式外的环境中拥有同样的含义的含义与作用。这种宣告的语法就是用global,后面带有以逗号分隔的变数列表,并用分号来结尾。为了看到效果,可考虑前一个例子的新版本写法。唯一的不同是把$count 宣告为plobal型式,在函式内去掉了把它指定为「0」的语句:
Function SayMyABCs2()
{
Global $count;
While($count< 10)
}
Print(chr(ord(ˋAˊ) + $count));
$count = $count + 1;
}
Print(“< BR >Now I know $count letters< BR >”);
}
$count = 0;
SayMyABCs2()
$count = $count + 1;
Print(“Now I′ve made $count function call(s).< BR >”);
SayMyABCs2();
$count = $count + 1;
Print(“Now I′ve made $count function call(s).< BR >”);
这个修订后的版本在浏览器输出:
ABCDEFGHIJ
NOW I know 10 letters
NOW I′ve made 11 function call(s).
NOW I know 11 letters
NOW I′ve made 12 function call(s).
这种方式是有问题的,global宣告就是问题的来源。现在只有一个$cuont变数,它在函式内和函式外都是递增值,当第二次呼叫SayMyABCs()时,$cuont已经变成是「ll」了因此根本不会进入印出字母的回圈。
虽然这个例子显示了使用global所带来的问题,但只要使用得当,它还是可以非常有用的,尤其是因为PHP提供了一些变数可以其它程序码执行之前捆绑到每个页面上(在第九章将进行讲解)。它能够让函式看到这些变数,而省去了每次在呼叫中把它们作为参数再传入函式的麻烦。
静态变数(static宣告)
在预设情况下,函式对自己的执行并没有保留任何记忆,对于每个函式呼叫,区域变数都会认定成是新建的。Static宣告可以帮某些特定变数覆盖这一行为,使得正在多次叫同一个函式时能够保留变数的「值」。利用这一点,我们可以修改前面的函式,使它保留一些记忆:
Finction SayMyABCs3()
{
Static $count = 0; //只在第一次呼叫时指派
$limit = $count + 10;
While($count<$limit)
{
Print(chr(ord(ˋAˊ)+$count));
$count = $count + 1;
}
Print(“< BR >Now I know $count letters< BR >”);
}
$count = 0;
SayMyABCs3()
$count = $count + 1;
Print(“Now I′ve made $count function call(s).< BR >”);
SayMyABCs3()
$count = $count + 1;
Print(“Now I′ve made $count function call(s).< BR >”);
这种记忆增强版本可以得到下面的输出:
ABCDEFGHIJ
NOW I know 10 letters
NOW I′ve made 1 function call(s).
KLMNOPQRST
NOW I know 20 letters
NOW I′ve made 2 function call(s).
Static关键字允许有初始的指定,这个指定只有在该函式还没有被呼叫过的时候才有效。第一次执行SayMyABCs时,$count变数的区域内版本被设为「0」。第二次呼叫这个函式时,它的值就是上次执行结束时具有的值,因此能够继续前面的工作。请注意,在函式外对$count所做的更改对区域内、的值没有作用。
函式作用域
关于变数名称作用域(作用范围)的规则相当简单,而函式名称的作用域规则就更简单了。在PHP中只有一条规则:函式必须在使有用它的中某处定义一次(并且只能定义一次,请参考下面的新功能部份就可以了解PHP4与PHP3中这一功能之间的差别)。函式名称的作用域暗指为全域的,因此在script中定义的函式可在script中其它任何地方便用。但为了清楚起见,最好是在使用函式程序码之前对所有的函式进行定义。
在PHP3 中,函式只能在定义后使用。这意味着嗫安全的方法是,在使用函式前,在一个给定的script中先定义所有函式(或将它们的定义含括入内)。PHP4在执行script前对其进行了预先编译,这样做的效果是能够在实际执行程序码之前就发现所有函式的定义。因此,函式和程序码能够以任何顺序出现在 script中,只要所有函式被定义了一次(并且只有一次)就行。
档案的含括(include)的请求(require)
在一系列Web网站页面中使用同样的函式集是很常见的,通常的处理方式是用include或require,这两种方法都会把其它档案的内容导入正要执行的档案中。使用任何一种形式都比复制函式定义(即在需要使用它们的每页天头重复写一次)要好得多,因为将来有需要修改函式时,只需要修改一次。
例如,在PHP档案的最上方,可能有下面这样的程序码:
include “basic-functions.inc”
include “advanced-function.inc”;
(..code that uses basic and advanced functions..)
这些程序码含括了两个不同的函式定义档案(请注意,对于include()和require(),其后方括号都是可选择性的)。只要这些档案内容只有函式定义,在含括它们的次序上就无关紧要了。
Include和require都具有在被呼叫的该处接合入档案内容的效果。两者唯一不同的就是如果含括号档案找不到会产生何种错误,include()会显示一个警告讯息并仍会继续执行程序,但是require()会在找不到档案时产生严重的错误。
请注意现在include()与require()在网页载入时拥有静态的档案片段,但是include()会在网页执行时动态的载入程序,除此之外,这会使得程序根据include/require会产生不同的效果,然而现在include与require两者皆会动态载入,这意谓如果有一个 include/requires格式在回圈中执行十次,那么将会含括十次。
只要含括一次
有时候你真的只要含括一个档案一次,但是不可以超过一次,这是在定义函数时常出现的问题,例如,有两个不同函数定义的档案可能同样含括一个相同内含括工具函数的档案,这会使得程序反应重复定制函数的错误讯息。
我们可以使用include_once()与requires_once来解决这种问题,它们不同的是将已经含括一次给定档案名称时不再第二次含括进档案内,通常含括函数或类别定义档案时使用_once版本的含括是较理想的。
include_once()与require_once()于PHP 4.0.1p12新增。
含括路径
当你含括一个档案名称,PHP会在你的include_path(在php.ini档案内设定)指定目录内搜此档案,预设的路径会含括目前网页所在最上层的相同目录,你可以参阅第37章更详细的如何在你的含括路径上加入位置。
Warning
请记住included(和required)档是以预设的HTML模式来解析,而不是以PHP模式。这意谓着任代号included档中要有PHP标签在开始和结尾的位置来包住让PHP直译的内容。
递回
某些类似C和C++的编译语言对如何定义函式给予了一些复杂的次序限制。为了知道如何编译一个函式,编译器必须了解该函式呼叫的所有函式,这意味着必须先定义被呼叫的函式。因此,如果两个函式互相呼叫,或者一个函式呼叫自己本身,那该怎么办呢?这样的情况会导致像C语言的设计者把函式宣告(或称原型)与函式定义(或称实作)分离开来。这种思考方式是,使用宣告把计划使用函式的参数类型和回传值型别提前告知编译器,编译器有了这些资讯就可以完全按照任何次序来处理实际的定义。
在PHP中就没有这外问题,因此不再需要单独分离的函式原型。在PHP3中,函需在使用前先定义,但定义某个函式呼叫另一个函式,并不算真正使用了这个函式。当PHP3遇到函式A定义,如果函式A本体内包含对函式B的呼叫,而函式B还未定义,PHP3并不会阻止这样做。函式B要在函式A真正执行前进行定义就可以了。不过这在PHP4中,顺序根本就不是问题。
这意味着递回函式(呼叫自己本身的函式)没什么使用上的问题。举例来说,我们可以定义一个递回函式,然后立即呼叫它:
function countdown ($num_arg)
{
if($num_arg >0)
{
print(“Counting down from $num_arg < BR >”);
countdown($num_arg – 1);
}
}
Countdown(10);
浏览器产生的输出为:
Counting down from 10
Counting down from 9
Counting down from 8
Counting down from 7
Counting down from 6
Counting down from 5
Counting down from 4
Counting down from 3
Counting down from 2
Counting down from 1
与所有递回函式相同,除了递回情形之外,确保函式有一个基本方案(非递回分支)是很重要的,基本方案最终还是要发生的。如果基本方案从未被启动,就像是个测试条件永远为真的while回圈,函式呼叫的回圈就会无限地进行下去。对于前面的这个函式,我们知道基本情形将会发生,因为每次启动递回都会把数字减小,最后就会到「0」。当然,这里先假设输入的是正整数,而不能是负数或浮点数。请注意,「大于0」的评算测试确保了即使不是正整数也不会发生无穷回圈,因为进行的不是「不等于0」的测试。
同样地,在相互呼叫的递回函式(函式互相呼叫彼此)也没有什限制。请看下面的定义以及呼叫的实例:
Function countdown_first($num_arg)
{
If($num_arg >0)
{
Print(“Counting dow(first)from $num_arg< BR >”);
Countdown_second($num_arg – 1);
}
}
Function countdown_second($num_arg)
{
If($num_arg>0)
{
Print(“Counting dow(second)from $num_arg< BR >”);
Countdown_first($num_arg – 1);
}
}
Countdown_first(5);
上面的程序码会产生这样的浏览器输出:
Counting down (first) from 5
Counting down (first) from 4
Counting down (first) from 3
Counting down (first) from 2
Counting down (first) from 1
进价函式技巧
现在我们来看看函式的一些更神奇的属性,其中包括使用可变参数个数的方法、让函式能够修改传入变数的方法,以及让函式成为资料使用的法方。
这一节的内容是本章最具挑战性的一节,它只适合具有冒险精神、求知欲极强或经验丰富的程序设计师。
可变参数个数
在依据情况呼叫传入函式时,知道实际参数数量是很有用的,在PHP中有三种可能的方式处理,其中一个只能在PHP4使用:
1. 定义带有预设参数的函式,当函式在呼叫中遗漏任何参数时,它都会用预设值来代替,不会显示警告资讯。
2. 使用阵列参数存放这些值,由呼叫的程序码中负责包装这个阵列,函式本体必须适当地将其中的资料分离。
3. 使用PHP4中的可变参数函式(func_num_args()、func_get_arg()和 func_get_args())。
预设参数
为了定义带有预设参数的函式,只需把形式参数变成指定运算式即可。如果实际呼叫时的参数比定义时的形式参数少,PHP会拿形式参数和实际参数进行比对匹配,直到用完为止,然后就使用预设的指定来填满其余参数。
例如,下面的函式中的变数都有是用预设值定义的:
function tour_guide($city = “Gotham City”,
$desc = “vast metropolis”,
$how_many = “dozens”,
$of_what = “costumed villains”)
{
print(“$city is a $desc filled with
$how_many of $of_what.< BR >”);
}
tour_guide();
tour_guide(“Chicago”);
tour_guide(“Chicago”,“wonderful city”);
tour_guide(“Chicago”,“wonderful city”,
“teeming millions”);
tour_guide(“Chicago”,“wonderful city”,
“teeming millions”,
“gruff people with hearts of
gold and hard-luck stories to tell”);
浏览器会输出类似下面的结果,句中的换行符号由所用浏览器决定:
Gotham City is a great metropolis filled with dozens of costumed villains.
Chicago is a great metropolis filled with dozens of costumed villains.
Chicago is a wonderful city filled with dozens of costumed villains.
Chicago is a wonderful city filled with teeming millions of costumed villains.
Chicago is a wonderful city filled with teeming millions of gruff people whit hearts of gold and hard-luck stories to tell.
预设参数的主要限制是,实际参数到形式参数的匹配是由两者的依序比对确定的,先到先服务。因而不能乱用预设参数的设定,以致最后出一堆问题而不自知。
用阵列替代多个参数
如果对多个参数的弹性不怎么满意,可以使用阵列来当成沟通手段,这样可绕过整个参数计数问题。
下面的例子就是使用这个策略,另外还用了几个小技巧,如三元运算子(在第七章中介绍过)和关联阵列(在第六章中提不定期,在第十一章中才会全面讲解):
function tour_brochure($info_array)
{
$city =
IsSet ($info_array[ˋcityˊ])?
$info_array[ˋcityˊ]:“Gotham City”;
$desc =
IsSet ($info_array[ˋcityˊ])?
$info_array[ˋdescˊ]:“great metroprlis”;
$how_many =
IsSet ($info_array[ˋhow_manyˊ])?
$info_array[ˋhow_manyˊ]:“dozens”;
$of_what
IsSet ($info_array[ˋof_whatˊ])?
$info_array[ˋof_whatˊ]:“costumed villains”;
print(“$city is a $desc filled with
$how_many of $of_what.< BR >”);
}
这个函式检查将传入的阵列参数与特定字符串相隔的四种不同的值进行比较,使用三元条件运算子「?」,区域变数被指定为传入的值(如果已经被储存在阵列中),不然就是以预设值指定。现在,我们尝试用两个不同的阵列呼叫这个函式:
tur_brochure(array()); //空阵列
$tour_info =
aray(ˋcityˊ=>ˋCozumelˊ,
ˋdescˊ=>ˋdestination getawayˊ,
‘of_what’= >‘sandy beaches’);
tur_brochure($tour_info);
在这个例子中,我们首先用空阵列(对应于无参数)呼叫tour_brochure,然后用一个阵列来呼叫它,阵列中储存了四种可能关联值中的三种。其浏览器输出为:
Gotham City is a great metropolis filled with dozens of costumed villains.
Cozumel is a destination getaway filled with dozens of sandy beaches.
在两种情况下,「dozens」数量是预设的,因为两阵列都没有任何内容储存在「how_many」关联部份中。
在PHP4中使用多重参数
最后,PHP4提供了一些函式,可在函式本体内重新获得参数的个数和值,这些函式是:
fnc_num_args()不带参数,传回呼叫该函式时传入的参数个数。
fnc_get_arg()带一个整数参数n,传回该函式呼叫的第n个参数。参数计数从0开始。
fnc_get_args()不带参数,传回一个阵列,其中包含该函式呼叫的所有参数,阵列索引从0开始。
如果在函式本体外呼叫它们,这三个函式皆会丢出警告讯息,如果呼叫时所用的索引高于传入的最后的参数索引,func_get_arg()也会丢出警告。
如果使用者的函式会用到这些函式功能进行参数的解码,可以充分利用这里提到的关于函式呼叫,PHP不会因为实际参数比定义中的形式参数个数多而有所报怨。使用者可以定义不带参数的函式,然后使用这个函式来对比匹配任何实际传入的函式。
举例来说,请思考下面的两个函式实例,这些函式都传回一个被传入的参数阵列:
fnction args_as_array_1()
{
$arg_count = func_num_args();
$counter = 0;
$local_array = array();
wile($counter < $arg_count)
{
$local_array[$counter] =
fnc_get_arg($ary_counter);
$counter = $counter + 1;
}
rturn($local_array);
}
fnction args_as_array_2()
{
rtun(func_get_args());
}
第一个累赘的函式使用了func_get_arg()来撷取每个单独的参数,并使用func_num_args()的结果来给回圈定出界限,因此检索的参数不会比实际传入的多。每个参都被存放到阵列中,然后把这个阵列传回。把这样的参数包装起来实际上已经由func_get_arps()完成了,因此该函式的第二个版本就很简短了。
这里是另外一个例子,我们重写前面的tour_guide()函式,它使用了多个参数函式来替换预设参数:
fnction tour_guide_2()
{
$num_args=func_num_args();
$city = $num_args > 0 ?
fnc_get_arg(0):“Gotham City”;
$desc = $num_args >1 ?
$desc = $num_args > 1 ?
fnc_get_arg(1):“great metropolis”;
$how_many = $num_args > 2 ?
fnc_get_arg(2):“dozens”;
$of_what = $num_args > 3 ?
fnc_get_arg(3):“costumed villains”;
pint(“$city is a $desc filled with
$how_many of $of_what. < BR >”);
}
tur_guide2();
上面的程序码与预设参数形的程序码作用和效果相同,而且受到同样的限制。参数按照位置传入,因此没有办法用别的内容来替换「costumed villains」,只有用「Gotham City」为预设值。
按值呼叫(call-by-value)vs .按参引呼叫(call-by-reference)
PHP中使用者定义函式的预设动作是「按值呼叫(call-by-value传值呼叫)」,这意味着当把变数传递给函式呼叫时,PHP帮变数值制作一份副本并传给函式。因此,无论函式做什么,它都不能更改出现在函式呼叫中的实际变数。这种行为虽有好处,但也有坏处。当我们只想利用函式的传回值时,这当然是很好的方式,但如果修改传入的变数是实际目标,这样反而会有所妨碍。
下面会示范一个相当没有效率的减法例子的实作来按值呼叫的应用:
fnction my_subtract($numl,$num2)
{
i ($numl < $num2)
de(“Negative numbers are imaginary”);
$return_result = 0;
wile($numl >$num2)
{
$numl = $numl – 1;
$return_result = $return_result + 1;
}
rturn($return_result);
}
$first_op = 493;
$second_op = 355;
$result1 = my_subtract($first_op,$second_op);
pint(“result1 is $result1< BR >”);
$result2 = my_subtract($first_op,$second_op);
Print(“result2 is $result2< BR >”);
真好,我们看到了执行同样减法两次所得的结果会是一样:
rsult1 is 138
rsult2 is 138
即使my_subtract改变了它的形式参数$numl的值,还是会得到这样的结果,$numl变数只存放实际参数$first_op中值的副本而已,因此$first_op不会受到影响。
按照引呼叫(call-by-reference)
PHP提供了两种不同方式让函式在定义中,或者在函式呼叫中,更有能力来修改参数,也称为传址呼叫。
如果要定义一个直接对传入变数进行操作的函式,可以在定义中时在形式参数前面加一个「&」符号,如下所示:
fnction my_subtract_ref(&$numl,&$num2)
{
i($numl-<$num2)
de(“Negative numbers are imaginary”);
$return_result = 0;
wile($num1 >$num2)
{
$numl = $num1 – 1;
$return_result = $return_result + 1;
}
rturn($return_result);
}
$first_op = 493;
$second_op = 355;
$result1 = my _subtract_ref($first_op, $second_op);
pint(“result1 is $result1< BR >”);
$result2 = my_subtract_ref($first_op,$second_op);
pint(“result2 is $result2< BR >”);
现在,如果像前在那样执行同样的减法呼叫,会得到这样的输出:
rsult1 is 138
rsult1 is 0
这是因为形式参数$numl和实际参数$first_op所指的内容相同,修改一个就等于修改了另一个。
还可以透过在实际参数前加上「&」符号,强制一个函式按参此传递参数(这是一个渐遭淘汰的功能,而且可能在未来的PHP版本中被移除)。也就是说,可以使用最初的按值呼叫函式得到按参引的动作,如下所示:
$first_op = 493;
$second_op = 355;
$result1 = my_subtract(&$first_op,&$second_op);
Print(“result1 is $result1< BR >”);
$result2= my_subtract(&$first_op,&$second_op);
Print(“result2 is $result2< BR >”);
这次又得到了下面的结果:
rsult1 is 138
rsult1 is 0
关于PHP4,变数参引也能够用在函式呼叫的外面。一般来说,将一个变数参引(&$varname)指定给变数会使这两种变数互相成为彼此的别名,
(也就是分身),而不是具有同样值的两个不同变数。例如:
$name_1 = “Manfred von Richtofen”;
$name_2 = “Percy Blakeney”;
$alias_1 = $name_1; //变数具有相同值
$alias_2=&$name_2;//变数相同
$alias_1 = “The Red Baron”;//实际名称没有改变
$alias_2 = “The Scarlet Pimpernel”;//无论是什么都已经无关紧要了
Prnt(“$alias_1 is $name_1< BR> ”);
Prnt(“$alias_2 is $name_2< BR >”);
上面的程序码会得到这样的浏览器输出:
The Red Baron is Manfred von Richtofen
The Scarlet Pimpernel is The Scarlet Pimpernel
可变函式名称
PHP中的一个非常灵活的技巧就是用变数来代替使用者定义函式的位置。也就是说,在程序码中不必键入字面上的函式名称,而是可以键入以「&」符号开头的变数,在执行时实际呼叫的函式取决于指定给该变数的字符串。在某种意义上,这就允许把函式当成资料来使用。这种技巧对于进价C语言程序设计师来说可能很熟悉,对于任何种类的Lisp语言(如Scheme或Common Lisp)的初学者来说也是如此。
例如,下面的两个函式呼叫完全是相等的:
function customized_greeting()
{
print(“You are being greeted in a customized way !< BR >”);
}
customized_greeting();
$my_greeting = ‘customized_greeting’;
$my_greeting();
上面的程序码产生同样的输出:
You are being greeted in a customized way !
You are being greeted in a customized way !
因为函式名称就是字符串,所以也可以当成函式的参数,或者当成函式结果来传回。
延伸扩充范例+
让我们来看看使用函式的一个更进阶特性会遇到什么样的麻烦,包括使用函式名称为函式参数。
范例8-1这个例子所显示的是函式的一个延伸的扩充范例,该函式完成实作替换密码的功能,这是一种最原始的密码系统,用字母表中的一人字母替换另一个字母,以弄乱显示的资讯。
下面的程序码算是本书到目前为止所示范的任何程序码中比较长的,也更进阶。对于不想了解它细节的人来说,可以跳过这段程序码。
范例 8-1 密 置换
/*第一部份:密码演算和工具函*/
function add 1($num)
{
return(($num+ 1)%26);
}
function sub_1($num)
{
return(($num+ 25)% 26);
}
function swap 2 ($num)
{
if($num % 2 == 0)
return($num+ 1);
else
return($num - 1);
}
function swap 26($num)
{
return(25 - $num);
}
function lower letter($char string)
{
return(ord($char string) >=ord(‘a’))&&
(ord(&char string)< =ord(‘z’)));
}
function upper letter($char string)
{
return((ord($char string) >=ord(‘A’))&&
(ord($char string)< =ord(‘z’)));
}
/*第二部份:letter cipher函式 */
function letter cipher ($char string, $code func)
{
if!(upper letter($char string)||
lower letter ($char string)))
return($char string);
if(upper leter($char string))
$base num = ord(‘A’);
else
$base num = ord($char string) –
$base num);
return(chr($base num +
($code func ($char num )
% 26)));
}
/*第三部份:主要string cipher 函式 */
function string cipher($message,$cipher func)
{
$coded message = ””;
$message length = strlen($message);
for($index= 0;
$index < $message length;
$index++)
$coded message .=
letter cipher ($message[$index],$cipher func);
return($coded message);
}
范例8-1共有三个部分。在第一部份中,我们定义了几个对数字0—25进行简单数字运算的函式,这些数字代表密码程序中的字母A—Z。函式add_1把给它的数字加1,以「26」为模数(仅意味着26以及超过26的数字会“绕回”到开头从「0」开始。0变成1 、 1变成2 、…、25变成0。Sub_1则是沿另一个方向转换数字,透过加上25(在这种模数算法中就等于减1),25变成24 、 24变成23 、… 、0变成25。Swap_2则是以成对的方式置换数字的位置(0到1 、1到0 、 2到3 、 3到2…等等),swap_26则把较高的数字和较低的数字地行对调置换(25到0 、0到25 、 24到1 、1到24…等等)。
所有这些函式都算是这个简易密码程序的基础。最后还有两个公用程序函式,用以测试字符是大写还是小写字母。
第二部分就是个letter_cipher()函式,并该函式的工作是取得第一部分中的一个算术函式,然后应用它来对简单字母进行编码。该函式会先测试它处理的字符串(其中只有单元一字符)是否为字母;如果不是,则按原样传回。如果字符是个字母,则可使用。Ord()的功用是将其转化为数字,然后减支适当的字母(a或A),以把数字限定到0—25之间。一旦位于这个范围内,就可以应用其名字以字符串方式传入的密码函式中,然后再把数字转换成字母,最后传回它。
最后,第三部分是string_cipher()函式,该函式带有一个字符串讯息,还有一个密码函式,它传回的是一个新字符串值,其值为透过函式进行编码的内容。这里用到一两个以前没见到的功能(其中包括字符串连接运算子「.=」,在第十章中会提到),这个函式从讯息字符串的字母逐一进行处理,建构出新字符串,并用新字母是对对照旧字母的数字表示应用了$cipher_func得到的结果,到这里你大概了解这些足够了。
下面,我们编写一些程序码测试string_cipher();
$originak = “My secret message is ABCDEFG”;
Print(“Original message is :$orginal< BR >”);
$coding_array = array(‘add_1’,
‘sub_1,
‘swap_2’,
‘swap_26’);
For($count = 0;
$count < sizeof($coding_array);
$coded_message =
String_cipher($original,$code);
Print(“$code encoding is :$coded_message< BR >”);
}
这些测试程序码使用了预先定义的四个字母编码函式,把它们隐藏在一个阵列中,然后回圈遍巡该阵列,编码$original讯息,并输出显示编码后的版本。浏览器输出如下所示:
original message is: My secret message is ABCDEFG
add_1 encoding is :Nz tfdsfu nfttbhf jt BCDEFGH
sub_1 encoding is :Lx rdbqfs nfttbhf jt BADCFRH
swap_2 encoding is :Nz tfdqfs nfttbhf jt BADCFEH
swap_26 encoding is : Nb hvxivg nvhhztv rh ZYXWVUT
我们可以把这个函式做为资料的方法再深入一步,编写一个给序列中的讯息应用多个密码的函比。这个函式也使用了PHP4的可变参数型函式:
Function chained_code ($message)
{
/* 先取得反映定讯息,然后替编辑码函式名称找个任意数目。
在前一结果上应用每道编码函式,然后回传其结果。 */
$argc = func_num_args();
$coded = $message;
For ($count = 1 ;$count<$argc;$count++)
{
$function_name = func_get_arg($count);
$coded =
String_cipher($coded,
$function_name);
}
Return($coded);
}
Chained_code()的第一个参数应该是个讯息字符串,后面是对应于密码函式的任意数目的名称。被编码的讯息是让讯息套用第一个编码函式的结果,然后再让结果套用第二个编码函式,以此类推。我们可以透过使用预先定义的字母编码函式的各种组合方式对它进行测试。
$tricky =
Chained_code($original,
‘add_1’‘swap_26’,
‘add_1’‘swap_2’);
Print(“Tricky encoded version is $tricky< BR >”);
$easy =
Chained_code($original,
‘add_1’,‘swap_26’,
‘swap_’,‘sub_1’,
‘add_1’, ‘swap_2,’
‘swap_26,’‘sub_1,’);
Print(“Easy encoded version is $easy< BR >”);
其结果为:
Tricky encoded version is Ma guwjh muggysu qg YXWXUVS
Easy encoded version is My secret message is ABCDEFG
正你所看到期的,「tricky」讯息编者按码前是前程序码的组合形式,但没有确切对应任何一种单独的编码函式。而「easy」编码是这些函式更复杂的组合形式,产生的结果就是最初的讯息……没有任何改变!(这并不是因为密钥程式码无效,我们只是想让读者弄明白为什么这种特定的编辑函式序列能够再次回到原来开始的讯息。)
这个小小的密码script所示范围的目的是想让你了解,虽然密码程式稍微复杂了些,但由于PHP支援使用函式名称当成函式参数来用,使得这件事情就变得相当简单。
摘要
大多数的PHP功能都存在于大量的内建函式中,它们是由PHP的开放原始码开发团队所提供的。每个函式在http://www.php.net/的线上手册中都应该有说明(虽然有些很简短)。
你也可以编写属于自己的函式,然后该函式可以与内建函式以同样的方式取用。函式用简单的C语言风格的语法来编写,如下所示:
Function my_function ($argl,$arg2,…)
{
Statement1;
Statement2;
…
Return($value);
}
使用者定义的函式可以使用任何PHP型别的参数配合,另外也可以传回任何型别的值。且不需要针对参数和回传值的型别进行宣告。
在PHP4中,函式定义和函式呼叫的次数没有区别,只要呼叫的每个函式曾经定义就可以了。对于单独的函式宣告或原型设计是不需要的。函式内指定的变数会限制在该函式区域中,除非用了global宣告。区域变数可以宣告为static,这意谓著它们在函式呼叫之间可保留自己的值。
使用者定义函式的预设动作行为是「按值呼叫(call_by_reference)」,这是指该函式在运作上使用的是参数的副本,因此不能在函式呼叫中修改原始变数。透过在参数前加一个「&」,可以强制进行「按引呼叫(call-by-reference)」,不管是在定义方,或者在呼叫方都可配合使用。PHP提供了多种途径让函式所带的参数数量可变动。最后,要呼叫的函式可以在执行时期确定,用一个字串变数替代使用者定义函式的名字,那就允许把函式当成资料来对待,并让它在其它函式之间来回传递。