进价函式技巧
现在我们来看看函式的一些更神奇的属性,其中包括使用可变参数个数的方法、让函式能够修改传入变数的方法,以及让函式成为资料使用的法方。
这一节的内容是本章最具挑战性的一节,它只适合具有冒险精神、求知欲极强或经验丰富的程序设计师。
可变参数个数
在依据情况呼叫传入函式时,知道实际参数数量是很有用的,在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的开放原始码开发团队所提供的。每个函式在的线上手册中都应该有说明(虽然有些很简短)。
你也可以编写属于自己的函式,然后该函式可以与内建函式以同样的方式取用。函式用简单的C语言风格的语法来编写,如下所示:
Function my_function ($argl,$arg2,…)
{
Statement1;
Statement2;
…
Return($value);
}
使用者定义的函式可以使用任何PHP型别的参数配合,另外也可以传回任何型别的值。且不需要针对参数和回传值的型别进行宣告。
在PHP4中,函式定义和函式呼叫的次数没有区别,只要呼叫的每个函式曾经定义就可以了。对于单独的函式宣告或原型设计是不需要的。函式内指定的变数会限制在该函式区域中,除非用了global宣告。区域变数可以宣告为static,这意谓著它们在函式呼叫之间可保留自己的值。
使用者定义函式的预设动作行为是「按值呼叫(call_by_reference)」,这是指该函式在运作上使用的是参数的副本,因此不能在函式呼叫中修改原始变数。透过在参数前加一个「&」,可以强制进行「按引呼叫(call-by-reference)」,不管是在定义方,或者在呼叫方都可配合使用。PHP提供了多种途径让函式所带的参数数量可变动。最后,要呼叫的函式可以在执行时期确定,用一个字串变数替代使用者定义函式的名字,那就允许把函式当成资料来对待,并让它在其它函式之间来回传递。