程序控制
本章深入PHP内部,讲述如何使用函数、表达式和语句以实现对程序的控制。
前面的章节初步介绍了怎样操作数据,如果我们将操作数和操作符看作是构筑元件的话,那么它们组合起来即可形成表达式。进一步讲,表达式可以构成语句,语句用于组成函数,而函数则可用来组成程序。
提示:在学习有关编程语言的基本元素时,从全局理解--即理解这些元素是如何组成一个完整程序的--可能非常困难。但也不必着急,乐观一点。接下来的章节将逐步的显示整个程序,并且一点一点的解释它们是如何构造的。
4.1 表达式
当操作数和操作符组合到一起时,它们即组成表达式。本书的例子中已经展示了许多表达式,然而直到现在,我们才开始将注意力集中在它们身上。
表达式是由一个或多个操作符连接起来的操作数,用来计算出一个值--标量或数组。
最基本的表达式就是数字:
12
从这个简陋的开始,将逐步讨论越来越复杂的表达式:
-12
-12 + 14
-12 + 14 * (24 / 12)
(-12 + 14 * (24 / 12))&& calculate_total_cost()
注意每个表达式,在不考虑复杂性的情况下,每一个表达式事实上是由较小的表达式和一个或多个操作数共同组成的。当计算机编程者使用要定义的概念为该概念下定义时,这称为递归。当一个递归完成时,表达式能被分成较更简单的部分,直到计算机能完全的执行每一部分。
4.1.1 简单表达式
简单表达式是由一个单一的赋值符或一个单一函数调用组成的。由于这些表达式很简单,所以也没必要过多讨论。下面是一些例子:
* initialize_pricing_rules() -- 调用函数。
* $str_first_name = 'John' -- 初始化标量。
* $arr_first_names = array( 'John', 'Marie') -- 初始化数组。
4.1.2有副作用的简单表达式
表达式在它的主要任务之外,还有其它的副作用。当一个或几个变量改变了它们的值,并且这些改变并不是赋值操作符的操作结果时,就会出现这种副作用。例如,一个函数调用可以设置全局变量(全局变量是指在函数内部用global关键字来指定的变量),或者加一操作符也可以改变变量的值。副作用会使得程序很难读懂,因此编程的一个目标就是应该尽可能地减少这种副作用。
不使用global关键字是避免副作用的一个好选择。
让我们看看以下有副作用的表达式例子:
* $int_total_glasses = ++$int_number_of_glasses
-- $int_number_glasses变量在加一以后,再把值赋予$int_total_glasses。
* function one() {global $str_direction_name; $str_directory_name = '/dos_data'; }
-- 当one()函数调用后,全局变量的值将被改变。
4.1.3 复杂表达式
复杂表达式可以以任何顺序使用任意数量的数值、变量、操作符和函数。
尽可能使用简短的表达式,这意味着它们更容易维护。
以下是一些例子:
* ((10+2) /count_fishes() * 114)
-- 包含有三个操作符和一个函数调用的复杂表达式。
* initialize_count( 20 -($int_page_number -1) * 2)
-- 有一个复杂表达式参数的简单函数调用。
提示:有时很难分清左括号和右括号的数目是否相同。从左到右,当左括号出现时,就加一,当右括号出现时,就从总数中减一。如果在表达式的结尾时,总数为零时,左圆括号和右圆括号的数目就一定相同了。
4.2 语句
所有的PHP程序都是由语句构成的,无论是简单的语句,还是复杂的语句,这些语句按顺序执行每一时刻执行一句,直到遇到程序结束、跳转语句、分支语句。
最基本的语句是:
;
此语句什么也没有做,但它仍是合法的。分号符是语句结束的标志。如果需要,语句也可以相当复杂。例如:
$str_house_size =(
$int_number_of_rooms > 9 ?
"large" :
"small"
);
这行代码在房子的数目大于9时,给$str_house_size赋予"large",否则给$str_house_size赋予"small"。
就象人类语言中的语句一样,PHP语句可以分为几个组成部分。在PHP中,组成部分可以是数值、变量、函数和关键字。关键字是PHP留作自己用的单词。这些关键字(_FILE_, _LINE_, if, else, elseif, while, do, for, break, continue, switch, case, default, require, include, function),是组成PHP语言必不可少的,使用它们可以控制程序的流程。
本书将不对关键字_FILE_和_LINE_作介绍,相关内容请参考PHP文档。关键字require和include用来读取和执行PHP脚本, 在PHP手册中有详细介绍。以下部分将重点讲述其余的关键字。
4.2.1 语句类型
PHP共有6种类型语句,如表4.1所示。
表4.1 PHP数据类型
语句类型
描述
非执行语句
需要计算但不执行动作。
执行语句
执行某一动作。
赋值语句
给变量赋值。
判断语句
判断条件,并决定执行哪一个动作。
循环语句
重复执行一系列语句直到某条件为真或直到某条件为假为止。
跳转语句
无条件改变程序流程到程序中另一行继续执行。
非执行语句:
所谓非执行语句,就是PHP需要计算,但并不需要执行什么动作的语句。例如:语句10 + 20,其值是30,但由于没有改变哪个变量值,所以也不需要作什么动作。结果值将不保留,当下一条语句出现时,它很快就被丢弃了。
什么动作也不需要做,那么非执行语句又有什么用呢?我不清楚,如果你能发现它们的用途请告诉我。
执行语句:
执行语句是通过表达式来执行某些动作。它们可以增加或减小某一变量值,或者调用一个函数。执行语句是PHP中使用最多的一种语句类型。
赋值语句:
赋值语句并不复杂,它们可以给一个变量赋予一个数值。关于赋值操作符在前一章“PHP中的数据处理”中已经讲过,在此不再过多赘述。
判断语句:
判断语句使用if 和switch关键字,基于一个表达式的计算结果以决定执行某一段语句,或者基于表达式的结果在两段语句行中选择执行其中的一个。例如,如果要处理的支票值大于1000美圆,则执行一段程序;如果支票数小于1000美圆,就执行另一段程序。
if关键字
在if语句中通过计算表达式,得出真或假值,根据所求出的真、假值来决定执行哪一段程序。
最常见的有以下三种类型的if语句:
1.
if ( EXPRESSION ) {
// code block to be executed when
// expression is true.
}
2.
if ( EXPRESSION ) {
// code block to be executed when
// expression is true.
}
else {
// code block to be executed when
// expression is false.
}
3.
if ( EXPRESSION_1 ) {
// code block to be executed when
// expression_1 is true.
}
elseif ( EXPRESSION_2 ) {
// code block to be executed when
// expression_2 is true.
}
else {
// code blcok to be executed when
// all expression are false.
}
表达式可以包含第三章“PHP中的数据处理”中的任一个操作符,甚至可以是赋值操作符,这是因为赋值操作符要赋的值,可能就是需要判断的值。以上最后一个例子可能有点难以理解,现在让我们再看一个例子:
$int_a = 10;
if ($int_a -= 5)
echo "a = $int_a
";
这些代码将显示
a = 5
该if语句对表达式$int_a -= 5的处理是,$int_a的值先减5,结果值再赋给$int_a。如果结果值为真(即不为零),则执行echo语句。
在这个例子中,除了说明了在只有一个需要执行的语句时,语句段外部的大括号是可选的外,还说明了赋值操作符是可以在if语句中使用的。
提示:即使仅有单个语句时可以不使用大括号,通常还是要使用大括号。这样在以后增加语句时就更加方便。同时也减少了在以后增加语句时忘了添加大括号(这样可能引起细微的逻辑错误)的可能性。
下面的代码将说明else关键字是如何使用的:
$int_a = 5;
if ($int_a -= 5) {
echo "a = $int_a
";
}
else {
echo "a is zero.
";
}
这些代码将显示
a is zero.
elseif关键字按照以下的方法使用:
$str_name = 'John';
if ($str_name == 'Joe') {
echo "Your appointment is on Monday.
";
}
elseif ($str_name == 'John') {
echo "Your appointment is on Wednesday.
";
}
else {
echo "Your appointment is on Firday.
";
}
这些代码将显示:
Your appointment is on Wednesday.
当变量$str_name即不是'Joe',又不是'John'时,将执行if语句的else子语句,否则,将执行其它两个子语句中的一个。通过把错误信息放在else语句块中,以便显示未知的值,else关键字对于捕获未知或未预期的值也非常有用。例如:
if ($str_input == 'A') {
// do A statements.
}
elseif ($str_input == '8') {
// do B statements.
}
else {
echo "Unrecognized Input Error: '$str_input' is unknown.
";
}
在迄今为止所举的例子中,所有if语句的子句中使用的都是同一个的变量。不过,正如下例给出的一个虚构的有关房子和智能型计算机的例子那样,可以充分发挥创造力:
if ($int_left_window_open == 1) {
$int_outside_temperature = check_outside_temperature();
if ($int_outside_temperature close_window('left');
}
}
elseif ($int_right_window_open == 1) {
$bln_mail_exists = look_outside_check_mailbox();
if ($bln_mail_exists) {
make_announcement("The mail is here.');
}
}
else {
$str_window_side = select_side_of_house();
open_window($str_window_side);
}
switch关键字
如果需要同时测试、判断多个条件值时,if语句处理起来就显得比较烦琐--因为所有的elseif语句都要执行过一遍。在这种情况下许多人会发现,使用switch语句会更容易、快速。
switch语句句法结构如下
switch ( VARIABLE ) {
case VALUE1:
break;
case VALUE2:
break;
case VALUEn:
break;
default:
break;
}
在switch语句中,每一个需要检查的值都对应一个相应的case 语句。被检查的变量可以是任意标量值,(也就是说,数字和字符串都可以被检查)。
如果case语句中没有break关键字,那么PHP将执行下一个case语句;如果这个case语句中仍没有break语句,那么就接着执行再下一个case语句,如此下去直至找到break关键字为止。
下面给出一个简短但很完整的例子,讲的是用switch语句来处理命令的命令行程序。每当用户输入一条命令,就会调用switch语句,以决定该执行什么任务。当然,PHP常用于创建Web浏览器程序,可能从来不会这样使用PHP。
switch ($str_input) {
// The print and echo case perform the same task,so
// the print case needs no break keyword.
case 'print':
case 'echo':
// do the echo task.
break;
case 'check_balance':
// do the check balance task.
break;
case 1:
case 2:
// add $str_input to something.
break;
default:
echo "You have entered an unrecognized command or are ";
echo "trying to add a number other than 1 or 2.";
break;
}
使用分支语句的限制是,只能对一个变量进行判断。从另一角度看,这种限制刚好就是switch语句比if语句容易让人理解的原因。
循环语句:
循环语句就是根据程序需要,重复执行一段程序直到一个指定的表达式值为真或假为止。
PHP中有三个控制程序循环的关键字,分别是for,while和do,且这三个关键字处理的循环语句又有细微的差别。但是,无论哪种请况,表达式值都是用来决定循环语句何时应该停止的。使用关键字for的循环语句最为复杂,让我们首先来看它。
for关键字:
从句法结构讲,for语句是由三个表达式和一段语句组成的。形式如下:
for (INITIALIZATION; CONDITION; OPERATION) {
// statement block
}
在循环语句开始之前,首先需要对表达式进行初始化。此时的初始化适用于任意变量,但大多数编程者只对循环语句段中要用到的变量进行初始化。初始化工作可以在for语句之前进行,之所以在for语句内部执行对表达式的初始化工作,是为了有助于生成一自我完备的程序。
提示:初始化表达式通常是赋值表达式。注意在初始化赋值时,不要将等于操作符(==)和赋值操作符(=)搞混淆了。否则,这将会在程序中留下隐患。例如:$iindex == 0就是错误的,正确的表达式应该是$iindex = 0。
条件表达式用于控制循环部分是继续执行,还是停止循环。当条件表达式值判断为假时(就是说为零),则循环终止。
循环变量是条件表达式中使用的变量,用于控制循环什么时候结束。
运算表达式用于在每次执行完循环内部的代码段以后,以某种方式修改在条件表达式中使用的变量(即循环变量)值。
for语句的最基本使用方法就是从零开始计算,直到某一值为止。例如:
for ($loop_variable = 0;
$loop_variable $loop_variable++) {
echo "Inside Loop: loop_variable = $loop_variable
";
}
该语句显示了从0到99的数字:
Inside Loop: loop_variable = 0
Inside Loop: loop_variable = 1
...
Inside Loop: loop_variable = 98
Inside Loop: loop_variable = 99
当循环语句结束时,变量$loop_variable值为100,但这个值不再显示出来,这是因为打印完99以后,又对表达式进行加一运算,得到的变量值为100。这时,判断条件表达式值为假,循环结束。
注意:很关键的一点是,运算表达式(或语句段中的代码)必须要改变循环变量的值,或使用本章稍后讲到的break关键字。否则,循环语句将永远不会结束而成为一死循环。
for循环语句也可以通过对表达式进行递减运算来减小循环变量值:
for ($loop_variable = 100;
$loop_variable $loop_variable --) {
echo "Inside Loop: loop_variable = $loop_variable
";
}
该语句执行后会输出从100到1的数字:
Inside Loop: loop_variable = 100
Inside Loop: loop_variable = 99
...
Inside Loop: loop_variable = 2
Inside Loop: loop_variable = 1
在初始化表达式中能通过逗号隔开的方法对多个变量进行初始化赋值,下例给出了如何对多个变量初始化,以及怎么在一个循环语句中再包含另一个循环语句
for ($row = 0;$row for ($col_value = 0, $col = 0; $col $col_value += $row + $col;
echo "[$row,$col] = $col_value
";
}
}
显示输出为:
[0,0] = 0
[0,1] = 1
[0,2] = 3
[1,0] = 0
...
[2,1] = 5
[2,2] = 9
每次启动内部循环时,$col_calue就会重新初始化为零。
迄今为止,我们只讲了循环变量递加一或递减一的情况。然而,运算表达式是十分灵活的,可以按需要以任何方式改变循环变量值,也可以通过用逗号隔开的办法同时进行多个操作。例如:
$int_number_of_items = 10;
// The following for loop places each
// expression in the loop header on a separate
// line to enhance readability.
For ($first_time = 1,$index =1;
$index $index += 2, $first_time = 0
) {
if ($first_time) {
echo "Report Header
";
}
each "Report Line $index
";
}
if (! $first_time) {
echo "
Report Footer
";
}
代码行输出显示如下:
Report Line 1
Report Line 3
Report Line 4
Report Line 5
Report Line 9
Report Footer
注意,每重复一次循环以后,循环变量($index)会递增2。另外,变量$first_time控制报告的页眉和页脚的输出。
如果改变变量$int_number_of_items的初始值为零的话,那么什么也不会显示出来。第一次对条件表达式值进行判断时,变量$index值为1,变量$int_number_of_item值为0,所以不会进入语句段,也不执行运算表达式。这导致变量$first_time值仍是1,在循环结束时,也不会输出报告的页脚。
while关键字:
当条件为真时,while循环重复一段语句块。如果条件在执行while语句时不为真,语句块就不会被执行。
while循环的语法如下
1.
while ( CONDITION ) {
// statement block.
}
2.
while ( CONDITION ) :
// statement block.
Endwhile;
以下例子显示了使用while语句是如何简单:
$iindex = 0;
while ( $iindex echo "inside while statement: $iindex
";
$iindex++;
endwhile;
echo "outside while statement: $iindex
";
此例子结果将显示
inside while statement: 0
inside while statement: 1
inside while statement: 2
inside while statement: 3
inside while statement: 4
outside while statement: 5
注意当while语句完成以后,$iindex的值为5 -- 而不是条件表达式($iindex 如果$iindex有大于5的值时(随便说一个数,8),当运行到while语句时,以上的例子将显示
outside while statement: 8
do关键字:
当表达式为假时,do循环执行语句块。此语句的测试条件正好和while循环相反,while循环测试表达式是否为真,并且条件表达式是在语句块执行后测试,而不是在语句块前测试,这也是do循环和while循环的不同点,这意味着语句块至少执行一次。
do循环的语法如下
do {
// statement block
} ( CONDITION );
几乎所有的循环都可以用while语句或者do语句来表示。喜欢怎样表达程序的逻辑结构,觉得哪种方法使用起来更顺手,都无关紧要。有时,使用do print_the_page while the number of pages is less than 20这样表述比while the number of page is less than 20的表述更易于理解。
以下do语句是本章较早使用while表述例子的另一个版本。
$iindex = 0;
do {
echo "Inside Do Statement: $iindex
";
$iindex++;
} while ($iindex echo "Outside Do Statement: $iindex
";
此例子的结果将显示
Inside Do Statement: 0
Inside Do Statement: 1
Inside Do Statement: 2
Inside Do Statement: 3
Inside Do Statement: 4
Outside Do Statement: 5
象while循环一样,$iindex变量的结束值也为5。然而和while循环不一样,如果$iindex的启始值为8时语句块至少会执行一次。当$iindex的初始值为8时,将显示以下行:
Inside Do Statement: 8
Outside Do Statement: 9
跳转语句:
PHP提供了两个语句以帮助控制循环的行为:break和continue。
break关键字:
break关键字使PHP停止执行当前的语句块,从紧跟着当前语句块的语句开始运行。下面的例子显示了如何在一个简单的for循环中使用break语句。
清单4.1 break.php3--使用break语句从循环中退出
for ($index = 0; $index // A. When $index is three, the loop ends.
if ($index == 3) {
break;
}
echo "$index
";
}
// B. After the break,execution starts here.
echo "After the loop: index=$index
";
?>
此脚本结果将显示:
0
1
2
After the loop: index=3
break语句的另一个方面是令人讨厌的,在循环语句结束以后(在上面代码的B位置),将没有办法分辨循环语句结束是由于break语句结束的,还是以自然方式结束的。至少,在没有测试$index的值前是没有办法的,这个值依赖于引发break语句而使循环结束的条件。对于不同的循环测试$index变量都会有所不同。因此在没有注释的情况下,循环后测试$index变量是非常令人费解的。
为了解决这个问题,可以使用一个变量作为标志。如果变量值为1时(意味着同意),那么执行break语句;否则,循环是正常终止的。下一个例子,清单4.2显示了如何使用标志变量。
清单4.2 flag.php3--在break语句中使用标志变量
$flg_break_happened = 0;
for ($index = 0; $index if ($index == 3) {
$flg_break_happened =1;
break;
}
echo "$index
";
}
if ($flg_break_happened) {
echo "Loop ended because of break.
";
}
else {
echo "Loop ended naturally.
";
}
?>
此脚本结果将显示:
0
1
2
Loop ended naturally.
我们还需要进一步探索break语句的其它方面,就是如何从嵌套的循环中退出。清单4.3显示了如何使用这种技术。
清单4.3 nested_break.php3--从嵌套的循环中退出
for ($row = 0; $row for ($col = 0; $col if ($col == 5) {
break 2;
}
echo "[$row,$col]
";
}
}
?>
此脚本结果将显示:
[0,0]
[0,1]
[0,2]
[0,3]
[0,4]
正如所看到的,给break语句增加的表达式是告诉PHP需要退出几重嵌套。
continue关键字:
continue关键字中止循环的当前重复,并立即开始下一个重复。在for循环的情况下,下一个重复在操作表达式处开始。清单4.4显示了运行中的continue关键字。
清单4.4 continue.php3--使用continue关键字
for ($index = 0;$index if ($index == 3) {
continue;
}
echo "$index
";
}
?>
此脚本结果将显示:
0
1
2
4
请注意3没有显示出来。在语句块内部的if语句使得该语句块停止运行,并且在for循环的操作子句处重新开始。这样,在循环的第三次重复时,将忽略echo语句。
当使用嵌套的循环时,可以在continue语句中使用表达式,如清单4.5所示。
清单4.5 nested_continue.php3-在嵌套循环中使用continue语句
for ($row = 0; $row for ($col = 0; $col if ($col == 2) {
continue 2;
}
echo "[$row,$col]
";
}
echo "
";
}
?>
此脚本结果将显示:
[0,0]
[0,1]
[1,0]
[1,1]
[2,0]
[2,1]
请注意内部循环总是在2结束,echo "
";语句也从没有执行(由于没有输出空行)。因此,conutinue 2语句一定是停止了内部循环,并且从外部for循环的操作子句$row++重新开始。
如果的确需要在内部for循环完成以后显示空行,该怎么办呢?清单4.5显示了仅仅简单的把echo语句放到for内部循环的结尾是不够的,这是因为continue语句会忽略此语句。可以考虑在外部for循环的操作子句的后面使用逗号。例如:
for ($row = 0; $row ") {
for ($col = 0; $col if ($col == 2) {
continue 2;
}
echo "[$row,$col]
";
}
}
以上循环将显示:
Parse Error: parse error in
nested_continue.php3 on line 3
发生什么了?很明显,语法规则禁止在操作子句中使用echo命令。取而代之,可以使用print命令:
for ($row = 0; $row ") {
for ($col = 0; $col if ($col == 2) {
continue 2;
}
echo "[$row,$col]
";
}
}
此脚本结果将显示:
[0,0]
[0,1]
[1,0]
[1,1]
[2,0]
[2,1]
4.3 函数
函数帮助程序员组织自己的代码,使其成为比较容易理解和使用的代码段。函数让程序员一步步地编写出程序,并以这种方式测试代码。
在对所编程序有一个初步构想之后,会需要在脑子里或在纸上列出编程大纲。所列大纲中的每个步骤,可能就是一个函数,这就称之为模块化的程序设计,这种技术将编程的详细过程隐藏起来,以便读源程序的人明白整个的程序的设计目标。
例如,如果在所编程序中包含有一个计算圆面积的函数,可以调用如下一行程序编码:
$flt_area_of_circle = area_of_circle(5);
读者在看到所调用的函数以后,一般都会明白程序在做什么了,而对函数的实际内容并不需要过多了解。
提示:细心设计的函数和变量名将有助于理解程序。如果出现$areaFC = areaCirc($fRad)这样的代码,其含义就不是十分清晰。
注意:调用函数意味着PHP将停止程序当前行的执行,跳转到所调用的函数中去。在函数执行完毕以后,PHP会重新跳回到程序中调用函数的地方,继续往下进行。
让我们更清楚地看一看函数调用,在命令行中首先出现的是一个标量和一个赋值操作符。应该知道其作用是将赋值操作符右边的数值赋予变量$flt_area_of_circle,但是赋值符右边的究竟是些什么呢?
最先看到的是函数名:area_of_circle(),紧跟其后的圆括号表示这是一次函数调用,在圆括号中的是准备传给函数的一些参数或数值。可以将参数设想为一个足球,传球时,接收方(比如函数)会有几种选择:运球(以某种方式进行修改),传球出去(调用其他程序),犯规(调用错误处理程序)。
一个函数的语法结构如下:
function functionName ( parameterList ) {
// lines of code
}
函数取名有几个规定 -- 其中最重要的一条就是,函数名不能以数字开头,且中间不能有空格。参数表可以任选,它提供了函数可以使用的特定数值。
4.3.1 函数返回值
每一个PHP函数都会返回一个数值给调用者。毕竟,在设计一个计算圆面积的函数时,其意义就在于需要将该值赋予某个变量。清单4.6给出了一个程序,它定义并调用area_of_circle()函数。
清单4.6 area_of_circle.php3--计算圆的面积
function area_of_circle( $flt_radius ) {
return(3.1415 * ($flt_radius * $flt_radius));
}
$flt_area_of_circle = area_of_circle(5);
echo "The area is $flt_area_of_circle.
";
?>
该程序输出如下:
The area is 78.5375.
该例显示了是如何将一个参数传给函数的(记得足球的比喻吗?)。参数是在紧跟函数名后面的圆括号中给定的。在表4.6中的函数调用语句是 area_of_circle(5),其中只给出了一个参数,即数字5。一旦传给函数以后,它就认为是变量$flt_radius的值了。
该函数的第一行为:
return(3.1415 * ($flt_radius * $flt_radius));
它计算圆的面积,并将计算得出的新值返回。事实上打印出的内容说明调用area_of_circle()函数以后的程序流程已经返回,并且计算得出的值将赋给变量$flt_area_of_circle。
注意:一些程序设计语言在函数和子程序之间有些差别,区别在于函数会返回数值,而子程序不会。PHP中不存在这种差别,不管是否返回数值都是函数。
调用函数后返回一个数值是一个不错的特点,同时这也是PHP最关心的事情。你也能够返回一个数组,在清单4.7中定义了一个叫做create_list的函数,其返回值赋给变量$temp,结果输出在图4.1中给出。
清单4.7 create_list_php3--如何从函数返回数值列表
require( 'common.inc');
function create_list( ) {
return array( 100, 200, 300, 400 );
}
$temp = create_list();
dump_array( $temp );
?>
Page 91 图4.1
图4.1 显示函数create_list()的返回值
本例中,将整个返回的数组值赋给了一个变量,有时,这样做并不是处理返回数组的最简便方法。如果使用PHP的list功能,就能够将每一个返回单元值赋予相应的变量,例如:
list( $a, $b, $c ) = create_list();
该赋值语句执行以后,$a,$b和 $c的值分别是100,200和300,因为函数中没有指定变量存放第四个数组元素,所以其值丢掉了。
到目前为止,我们只注意了简单数组--即在程序中没有定义下标/数值对的数组。如果返回的数组指定了下标的话,PHP的反应就会有所不同。在清单4.8中对返回的数组作了修改,所以第二个数组元素就有了指定的下标,在图4.2中给出了该程序的结果输出。
清单4.8 create_list2.php3--如何让函数返回数值列表
require( 'common.inc');
function create_list( ) {
return array(100, 'age' => 200, 300, 400 );
}
list ( $a, $b, $c ) = create_list();
dump_array( create_list() );
echo "a=$a
";
echo "b=$b
";
echo "c=$c
";
?>
Page 92, 图4.2
图4.2 具有指定下标的数组返回结果
注意,正如所预期的那样,赋给$b的值是300,而不是200。使用list赋值时,指定下标的元素被忽略了。
4.3.2 向函数传递参数
一般来说,PHP会将参数的值传递给函数,这就意味着函数不能改变参数表中任何变量的值。在看过下例以后,就会比较清楚为什么会这样了:
function one( $parameter ) {
$parameter++;
}
$a = 10;
one($a);
echo "a=$a
";
如果函数one()中的加一操作对变量$a有影响的话,那么可以预期函数echo的输出结果为 a=11,而事实上并非如此。与此相反,本例的输出显示为a=10。
设计要改变其参数的函数并不是一个好主意。如果要弄明白这是为什么,就要研究一下编程原理。当两段代码共享信息时,它们就被称为是紧耦合的,此时当一段程序改变时--比如加入某种功能--很可能,另外一段程序也要跟着改变。因为两段程序需要同时进行改动,所以这时发生差错的几率就比较高。另一方面,如果编制的两段程序的关系是松耦合的话,那样,对程序的修改是相互隔离的,所以发生错误的几率相应就会降低。只有在某些特定的情况下,有些函数需要修改其参数值。
当函数必须要修改其参数时,那些参数需要通过引用的方式传递给函数。在函数中使用变量引用作为参数时,提供的是存放变量的内存地址,下面给出的函数one就是使用引用变量的例子:
function one( &$parameter ) {
$parameter++;
}
$a = 10;
one($a);
echo "a=$a
";
这段小程序的结果输出为a=11,表明函数one()改变了变量$a的值。注意,采用引用变量的唯一不同就是,需在所定义函数的参数名前面加上&号。
如果使用另一个程序员编的程序,就不要奢望去改变函数的定义。在那种情况下,可以在所调用函数的参数前面加上一个&号,如下示:
one(&$a);
在结束参数传递这一个话题之前,有必要大概讲一讲数组作为函数的参数传递的情况,下例显示了如何从函数中传递出数组元素值:
function array_first( $arr_parameter ) {
return($arr_parameter[0]);
}
$a = array_first( array(3,5) );
echo "a=$a
";
在这个例子中定义了一个查找第一个数组元素的函数。请注意,在此用到了标准的数组元素记号。函数使用数组作为参数和使用标量作为参数的情况并无太大的区别。
4.3.3 给函数赋予缺省值
PHP允许为函数的一些或全部参数赋予缺省值,这一特性使得函数使用起来更加容易理解,因为这样一来,函数调用时用到的参数就会很少。在清单4.9中定义了函数font,其中提供了颜色和大小等参数的缺省值。图4.3显示了运行该程序后的输出结果。
清单4.9 default_values.php3--给参数赋缺省值
function font(
$str_text,
$str_color = 'blue',
$int_size = 2
) {
echo "color=\"$str_color\">$str_text";
}
?>
注意:赋予缺省值的变量总是放在所定义函数参数的最后几个位置。
Page 95,Figure 4.3
图4.3 提供缺省值的font()函数
4.3.4 控制变量的作用域
所谓变量的作用域,指的是在程序中哪一部分可以看到并使用这一变量。PHP只认可两种类型的作用域 -- 页面和函数作用域。页面作用域是指变量适用于整个 Web页面,函数作用域是指变量适用于单个函数。正常情况下PHP变量只在它们的定义的作用域范围内才可以使用。
当需要在函数内部定义页面作用域的变量时,可以使用如下所示的global关键字:
function one( ) {
global $a;
$a++;
}
$a = 10;
one($a);
echo "a=$a
";
此函数结果将显示a=11,表明了在one()函数内部可以使用页作用域的$a变量。使用global关键字与传递引用变量相类似,因为在这两种情况下都要改变函数外部的变量。
注意:在程序中要使用global关键字之前,请慎重考虑一下程序的设计思路,或许使用函数参数同样可行。在函数内部使用页面作用域的变量,并不是最好的程序设计方法,因为函数的内部代码应该与程序中的其它部分相隔离。试想去读一段在多个(或者每个)函数中都能改变页面作用域变量的程序 - 它很难读懂的,不是吗?
4.3.5 嵌套函数调用
函数可以嵌套调用,也就是说一个函数可以调用另一个函数,而被调用的函数又可以调用另一个函数,然后还可以再调用另外的函数,如此等等。准确地讲,函数能嵌套层数的多少由很多因素决定。一般来说,不必考虑任何限制因素。清单4.10讲述了一个应用嵌套函数调用的程序。
清单 4.10 nested_function.php3--嵌套函数调用是非常有用的
function increment_by_two( $int_parameter ) {
$int_parameter += 2;
return($int_parameter);
}
function increment_by_three( $int_parameter ) {
$int_parameter++;
// make a nested function call to increment_by_two().
Return ( increment_by_two($int_parameter) );
}
$a = increment_by_three( 34 );
echo "1 = $a
";
?>
该程序输出显示如下:
a = 37
这个例子只是为了讲解嵌套函数的调用而做的,并没有什么实际的用处。函数increment_by_three增加其参数值后,又调用函数increment_by_two,最后导致总的增量为3。
使用嵌套函数调用最大的优点是可以将程序设计分为几个部分。每一个任务转化为一个嵌套的函数调用,例如:
function do_task() {
initialize_data();
read_data_from_database();
process_data_to_database();
perform_cleanup();
}
4.3.6 递归函数
调用自己的函数就叫做递归函数。可以回忆一下数组也是可递归的,(例如,一个数组中也可以包含另一个数组)。
为了演示递归究竟是怎么一回事,让我们先来看看数学上著名的数字序列--Fibonacci 序列。Fibonacci序列是这样的:
0,1,1,2,3,5,8,13
Fibonacci序列中的每一个数字都是由前两个数字相加得出的。例如,第八个数字13就是5和8相加的结果值。清单4.11包括了Fibonacci函数,它是将序列顺序号作为参数,返回与该顺序号相关的Fibonacci数字。
清单4.11 fibonacci.inc--使用递归函数计算Fibonacci数
function Fibonacci( $var ) {
// the fibonacci series is only defined for
// positive values.
if ($var return( 0 );
}
// the first two elements in the series are
// defined as zero and one and don't need
// recursion.
if ($var return ( $var );
}
// use recursion to find the previous two elements
// in the series.
return( Fibonacci($var-1) + Fibonacci($var-2) );
}
?>
可以用如下方式调用Fibonacci函数:
// include the fibonacci function in
// this script.
require('fibonacci.inc');
// display the result of the fibonacci call.
echo "$n
";
?>
让我们来检查一下当参数为4时调用Fibonacci函数的执行结果。Fibonacci函数每调用自己一次,就是又一级递归调用。第零级是对该函数的初始调用:
Level 0:
$n = Fibonacci(4);
因为4大于3,执行最后一个return语句,并产生新的一级递归调用。
Lelel 1:
return( fibonacci(3) + fibonacci(2) );
此时,函数递归了两次,一次使用的参数是3,一次参数为2。这些Fibonacci调用以后,因为3和2都不小于2,所以又产生了新一级的递归调用:
Level 2:
return( fibonacci(2)+fibonacci(1) +
fibonacci(1)+fibonacci(0) );
Level 3:
return( fibonacci(1)+fibonacci(0)+1 + 1+0 );
Level 2:
return( 1+0+1 + 1+0);
Level 1:
return( 2 + 1 );
Level 0:
return( 3 );
如上例所示,递归展开时,每一级对函数递归调用的结果又会用于再高一级的递归调用中。Level 0的递归结果是3。非常重要的一点是每一个递归函数都需要一个办法让递归停止。有意义的是,因为数组可以递归,所以使用数组变量的函数也必须可以递归。清单4.12给出了一个叫做array_dump的函数,结果显示了一个PHP数组的下标/数值对。
清单4.12 array_dump.inc--使用递归函数显示数组的下标/数值对
function array_dump( $var ) {
// The gettype function returns the data type
// of its parameter.
switch (gettype($var)) {
// inter, double, and strings are simply
// displayed.
case 'integer':
case 'double':
case 'string':
echo "$var";
break;
// array datatypes need to specially
// handled.
case 'array':
// if the array has no entries, display
// a message.
if (! Count($var)) {
echo 'Empty Array.
';
}
else {
// the array has entries, so start an
// HTML table to display them.
echo '
Key | Value | |
---|---|---|
'; echo key($var); echo ' |
'; echo gettype(key($var)); echo ' |
'; // perform the magic of recursion using the // VALUE of the current key/value pair. array_dump($var[key($var)]); echo " |