PHP 控制结构

所谓控制结构也叫流程控制,在计算机程序设计中,典型的流程控制模式包含以下几种:

  • 顺序结构
  • 选择结构
  • 循环结构
  • 跳转结构

下面我们来一一介绍 PHP 语言对应的实现。首先在 php_learning/basic 目录下新增 structure.php 存放这篇教程编写的代码。

顺序结构

顺序结构非常简单,就是自上而下的执行程序:

-w186

假设我们有一个成绩查询系统,可以查询指定学号同学某科成绩及对应等级,先通过顺序结构初始化系统数据和信息:

-w817

这种逐行逐行执行的模式就是顺序结构了,我们通过常量初始化等级和科目编码,再通过二维数组 $data 存放学生成绩信息,其中第一维键名对应的是学生 ID,第二维存放的是该学生每个科目的成绩信息。

选择结构

选择结构又可以细分为单分支、双分支、多分支选择,首先来看单分支结构。

单分支结构

所谓单分支就是指存在一个条件判断和选择:

还是以成绩查询系统为例,我们要查询学生 ID 为 1 的同学语文成绩对应的等级,可以这样编码:

$studentId = '1';
$score = $data[$studentId][YUWEN];
if ($score >= 80 && $score < 90) {
    printf("学生 %d 的语文分数: %0.1f, 对应等级是: %s\n", $studentId, $score, A);
}

注:上述代码中,%0.1f 表示输出的格式化浮点型数据只保留小数点后一位。

在 PHP 中,通过 if 语句来实现选择结构,这种只有一个 if 条件判断的代码称作单分支结构,上述代码的打印结果是:

-w713

如果条件不符合,则 if 条件判断失败,什么也不会执行,比如将上述代码中的 $studentId 值设置为 2,就是这样的效果。

双分支结构

为了处理 if 条件未命中的情况,我们可以引入一个 else 语句处理其他业务逻辑,这种满足 if 条件执行对应业务逻辑,不满足 if 条件,执行 else 设定业务逻辑的代码模式,称之为双分支结构:

我们按照这种分支结构就可以处理 $studentId = '2' 的情况下正常打印语文成绩等级:

$studentId = '2';
$score = $data[$studentId][YUWEN];
if ($score >= 80 && $score < 90) {
    printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, A);
} else {
    printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, "其他等级");
}

上述代码的打印结果是:

-w648

多分支结构

这种处理方式虽然可以获取一些明确的信息了,比如成绩数据,但是等级信息不明朗,只是一个粗略的「其他等级」,要在 if 条件不成立的情况下获取明确的等级信息,就需要引入多个 else if 语句增加更多的其他条件判断,最后以一个 else 语句作为兜底(默认分支),这种代码选择结构模式称之为多分支结构:

我们按照多分支的模式重构成绩查询系统代码:

$studentId = '2';
$score = $data[$studentId][YUWEN];
if ($score >= 90) {
    printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, A);
} else if ($score >= 80 && $score < 90) {
    printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, B);
} else if ($score >= 60 && $score < 80) {
    printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, C);
} else if ($score < 60) {
    printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, D);
} else {
    printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, "其他等级");
}

这样一来,就可以正常处理所有学生所有科目成绩的查询和打印了,比如上述代码的打印结果是:

-w623

你可以随意切换学生 ID 和科目信息,然后通过命令行执行代码查看打印结果,从而模拟实现成绩查询与打印功能。

switch 分支语句

通过上面的多分支结构已经可以处理所有的场景了,但是代码可读性和可维护性较差,为此,PHP 专门引入了独立的分支语句 switch 来处理这种多分支选择的情况,下面我们通过 switch 语句来重构上面的代码:

$studentId = '2';
$score = $data[$studentId][YUWEN];
/*if ($score >= 90) {
    printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, A);
} else if ($score >= 80 && $score < 90) {
    printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, B);
} else if ($score >= 60 && $score < 80) {
    printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, C);
} else if ($score < 60) {
    printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, D);
} else {
    printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, "其他等级");
}*/
switch ($score) {
    case $score >= 90:
        $level = A;
        break;
    case $score >= 80 && $score < 90:
        $level = B;
        break;
    case $score >= 60 && $score < 80:
        $level = C;
        break;
    case $score < 60:
        $level = D;
        break;
    default:
        $level = "其他等级";
        break;
}
printf("学生 %d 的语文分数: %0.1f, 对应等级: %s\n", $studentId, $score, $level);

我们可以对比这两种实现来看,switch 语句将之前所有的条件判断转移到 case 条件语句中,并且最后通过 default 来兜底,替代之前 else 所承担的功能:当所有 case 条件判断都没有命中(均为 false),则执行 default 分支中的代码。另外,所有的 case 分支代码最后不要漏掉 break; 语句,这行代码的意思是跳出分支判断,否则,会一直执行从命中分支开始后续所有分支语句中的代码。

上述代码的打印结果和之前完全一致,只是实现方式不同罢了。

循环结构

最后我们来看循环结构,所谓循环结构指的是当符合循环条件(菱形方框)时,则循环执行循环体中的代码(矩形方框):

-w156

循环结构的实现一般有 while、do...while、for 循环三种,最终实现的功能一致,只是三者在循环条件的设置上各自不同罢了。

while

要通过 while 循环打印上述成绩信息,可以这样编写实现代码:

$total = count($data);
$i = 1;
while ($i <= $total) {
    echo "第 $i 个学生的成绩信息:\n";
    print_r($data[$i]);
    $i++;
}

在这段代码中,首先获取数据总量 $total,然后设置一个迭代变量 $i,每次打印一个学生信息后将迭代变量 +1,直到迭代变量的值超过数据总量,则循环结束。上述代码打印结果如下:

-w455

字符串和整型之间可以自动转化,所以我们可以通过整型键名访问字符串类型的学生 ID。

do...while

还可以通过 do...while 循环编写上述代码:

do {
    echo "第 $i 个学生的成绩信息:\n";
    print_r($data[$i]);
    $i++;
} while($i <= $total);

打印结果和 while 循环一致,由于 do...while 循环条件设置在 do 语句块之后,所以存在不管循环条件是否满足,始终执行一次循环体的情况,因此在日常编码中,并不常见。

for

日常编码中,最常见的当属 for 循环了,这种循环语句足够灵活,可读性也更好,我们先通过 for 循环重构上述循环代码:

for ($i = 1; $i <= $total; $i++) {
    echo "第 $i 个学生的成绩信息:\n";
    print_r($data[$i]);
}

循环条件和迭代变量的初始化、自增逻辑都放到 for (condition) {...}condition 部分,这样业务代码就专注于编写业务逻辑,非常简洁。

foreach

在 PHP 中,对于数组循环遍历,尤其是关联数组遍历,还提供了另一个更加强大简单的语言结构 —— foreach,通过 foreach 语句,可以更方便地获取键值信息:

foreach ($data as $key => $val) {
    ...
}

其中 $data 表示待遍历的数组(或者实现 ArrayAccess 接口的类),$key 表示数组的键,$val 表示对应的键值,无需设置迭代变量、循环终止条件,遍历完成后,会自动退出循环,下面我们通过 foreach 来编写上述循环代码:

foreach ($data as $id => $score) {
    echo "第 {$id} 个学生的成绩信息:\n";
    print_r($score);
}

其中 $id 表示对应的学生 ID,$score 表示学生 ID 对应的学生成绩信息。

是不是更方便?可读性也相较于 for 循环更好,不过适用场景有限,仅能用于数组遍历和循环,对于其他数据结构,或者非数组循环遍历,还是要使用通用性更好的 for 循环。

break vs. continue

在上述循环语句的所有不同实现中,如果想要在到达循环条件之前强制退出,和 switch 语句一样,可以使用 break 语句:

foreach ($data as $id => $score) {
    echo "第 {$id} 个学生的成绩信息:\n";
    print_r($score);
    if ($id == 2) {
        break;
    }
}

这样,就只会打印到第 2 个学生的成绩:

-w529

然后运行 break 退出整个循环体,继续后续主体代码执行。

break 类似的还有一个 continue 语句,它的作用和 break 不同,是退出当前循环迭代,然后继续执行当前循环体的下一个循环迭代:

foreach ($data as $id => $score) {
    if ($id == 1) {
        continue;
    }
    echo "第 {$id} 个学生的成绩信息:\n";
    print_r($score);
    if ($id == 2) {
        break;
    }
}

执行上述代码,只会打印第 2 个学生的成绩信息:

-w569

因为当 id == 1 时,会命中第一个 if 条件判断,继而执行 continue 跳出了当前循环,进入了下一个循环周期,打印完学生 2 的信息后,命中第二个 if 条件判断,执行 break 跳出整个循环。

跳转结构

所谓跳转结构其实就是 goto 语句,日常编码中很少使用,就不深入介绍了,感兴趣的同学可以查看官方文档介绍:https://www.php.net/manual/zh/control-structures.goto.php

上一篇: PHP 运算符

下一篇: PHP 函数(上):自定义函数和内置函数