编程语言研究书库

PHP 的表达式与控制流 — 程序是怎么做判断、走分支、重复干活的

PHP 的表达式与控制流

这一章讲什么: 程序光有变量和运算符还不够——真正的「编程」始于两件事: 做判断(该不该执行这段代码)和重复(把这段代码执行多少遍)。 本章把 PHP 的全部判断与重复机制讲完,顺带把第 3 章埋下的类型转换陷阱挖到底。 读之前只需要本组 02 的内容。

1. 顶层全景:从「算值」到「走路」

  表达式(算出值) ──▶ 条件判断(值为真/假) ──▶ 分支(if / switch / ?)
                      │
                      └────▶ 循环(条件成立就再来一遍)──▶ while / do...while / for
                                                      └─ 中途跳走:break / 跳下一轮:continue

  图说:这一章的全部内容就是这张图。左边是「算」,右边是「走」。

第 3 章学的所有东西——变量、运算符、函数——都只解决「怎么算出一个值」。 从这一章起,值开始决定程序走哪条路:银行余额够不够触发转账? 用户选了菜单的哪一项?油量还够跑几轮检查? 这些「问一句、按答案分头行动」的结构,就是控制流(control flow)—— 「控制」的是程序执行的「流」向1。

学完本章回头看,PHP 程序的最小骨架已经齐了: 表达式负责「算」,条件负责「选」,循环负责「重复」,函数(第 3 章)负责「复用」。 之后所有的 PHP 章节,本质上都是这四样东西的组合、放大和工程化—— 数组让「值」成群,对象让「函数和数据」成团,文件和数据库让「数据」出了内存还活着。

2. 表达式:语言的最小计算单位

表达式(expression)是「值、变量、运算符、函数的组合,组合的结果是又一个值」。 3 * (abs(2 * $x) + 4) 是表达式,20 > 9 是表达式,光一个 73 或一个 $myname 也是表达式—— 后者是最简单的两种:字面量(literal,求值结果就是它自己)和变量(求值结果是盒子里那张纸条)2。

表达式的值可以是三种东西:数、字符串、布尔值(Boolean,只有真 TRUE 和假 FALSE 两个值; 名字来自 19 世纪数学家乔治·布尔)。第 3 章说过 PHP 只有字符串能直接打印, 那么打印 TRUE/FALSE 会看到什么?原书的实验(test2.php)给出的答案有点反直觉3:

echo "a: [" . TRUE  . "]<br>";   // 输出 a: [1]
echo "b: [" . FALSE . "]<br>";   // 输出 b: []

TRUE 转成字符串是 "1",FALSE 转成字符串是空串 ""——空得看不见。 原书用的词是「arbitrarily」(任意地):这个对应是 PHP 定的规矩,不是逻辑必然3。 两个实用推论:第一,别用 echo 调试真假——假值在页面上根本看不见,以为没输出; 第二,FALSE 和 NULL(表示「什么都没有」的常量)打印出来都是空串,但它们是两种东西, 只是凑巧共享同一个打印结果4。

表达式再往上组合一层就是语句(statement):给变量赋值是语句,if 后面跟着条件执行一段是语句。 例:$days_to_new_year = 366 - $day_number; 是「赋值语句」,里面装着一个表达式; if ($days_to_new_year < 30) { echo … } 是「条件语句」,括号里装着一个表达式5。 记住这个分层的名字,因为后面所有结构都是「表达式当零件、语句当产品」。

原书还区分了一个更细的概念:字面量与非字面量。把字面量赋给变量, 这个变量每次读都是同一个值(纸条抄完不会再变);把函数调用的结果赋给变量, 情况不同——函数每次被调都可能吐出不同的值(想想 time()),这个变量的内容就是非字面量。 区别在于值的来源是否确定:来源是写死的,结果永远可预测;来源是算出来的,就得跟着算6。 这个区分看着抽象,用途很实际:调试时,「这变量的值是哪来的」是第一条要问的问题; 答案分两类——要么是某行的赋值(往上找),要么是某次调用(往上找,还要看调用时刻)。

3. 运算符补完:三类新面孔

第 3 章讲了算术、赋值、比较、逻辑四大类,本章按原书的全表补齐剩下的:

类别例子干什么
位运算12 ^ 9、<< >>直接摆弄整数在内存里的二进制位
执行运算符`ls -al`执行反引号里的系统命令,把输出当字符串
数组联合$a + $b合并数组(第 6 章)

位运算本章不展开(属于「知道存在」);执行运算符原书给了全书少见的硬警告: 它威力大,但「unless strictly necessary and you know precisely what you are doing」别碰—— 因为它等于把服务器操作系统的命令行向 PHP 代码敞开,一处疏漏就是巨大的安全漏洞7。 这也解释了为什么第 3 章超全局变量那节说黑客会往 $_GET 里塞系统命令: 执行运算符(以及第 5 章会看到的同类能力)就是那条塞进来的命令可能被点燃的引线。

每个运算符还按「吃几个值」分类:一元(unary,一个,如 !$a、$a++)、 二元(binary,两个,绝大多数)、三元(ternary,三个,全语言只有 ? : 一个)8。

3.1 逻辑运算符的完整真值表

四个逻辑运算符对所有输入组合的结果,原书用一张表(Table 4-5)全部列出,浓缩成四句口诀:

输入 a输入 bANDORXOR
真真真真假
真假假真真
假真假真真
假假假假假

AND 两行假(只要一边假就假);OR 三行真(只要一边真就真); XOR 只有「恰好一边真」的两行真——同真同假都是假;! 最简单,真假互换9。 记住 XOR 那一列的形状,第 3 章氨水漂白剂的例子(两种成分只能有其一)就是这个真值表的日常版。

原书的例 4-16 还演了一个容易看错的地方:echo !$a,当 $a 是真时输出空串—— !真 是假,假打印成空。四章两处「假打印成空」,值得连起来记10。

3.2 OR 的短路:第二个操作数可能根本不执行

OR(以及 ||、AND、&&)有一个不易察觉的行为:如果第一个操作数已经能定案,第二个根本不会被求值。 例:if ($finished == 1 OR getnext() == 1) exit;——当 $finished 是 1 时, OR 的结果铁定为真,PHP 懒得再调 getnext()。这通常是你想要的(省一次调用), 但如果你是指望 getnext() 每次都执行(比如它负责取下一行数据), 这个写法会安静地漏调用——程序不报错,数据悄悄缺行11。

两个修法:先把函数调用挪到 if 之前($gn = getnext(); 再判断), 或把函数调用换到条件前头(它是第一个操作数,必然先求值)。原书两个都给了, 各自附带一句提醒:挪出去写法最稳;换位子法则要保证逻辑顺序仍对12。

4. 优先级与结合性:谁先算,从哪头算

4.1 优先级:没有它,混合运算全是错的

优先级(precedence)回答「一个表达式里多个运算符,先算哪个」。 和第 3 章的 5 + 2 * 3 同理, 本章把话说透:如果所有运算符平级,表达式只能从左到右顺着算—— 1 + 2 * 3 - 4 * 5 会变成 25;有了「乘除优先于加减」,它实际是 -13。 原书用三组例子证明:同一堆数字换顺序,只要优先级不变,结果就不变; 一旦混优先级,隐含括号的位置就变了13。

想推翻默认顺序,自己加括号:((1 + 2) * 3 - 4) * 5 得 25——括号是最高优先级, 这不是 PHP 的发明,是数学记法本身14。

完整的优先级表原书从高到低列出:括号 → ++ -- → ! → * / % → + - . → 位运算 → 比较 → & ^ | → && → || → ? : → 赋值 → and → xor → or15。 注意三件事:

  • .(字符串拼接)和 + - 同一档——这是 PHP 的特色,别的语言很少把拼接放到这么高;
  • ? : 比赋值高:$enough = $fuel <= 1 ? FALSE : TRUE; 先算三元,再赋值;
  • and/or/xor 排在赋值之后——这就是第 3 章那个 or die() 写法成立的原因: $html = file_get_contents($site) or die(...) 里的 or 低得「赋值先完成,失败再 die」。

原书补了一句设计层面的说明:这个顺序「不是随意排的,而是精心设计的」, 让最常见的写法恰好不需要括号——比如两个比较用 and 或 or 连起来,就是你期待的意思16。

4.2 结合性:同级的多个运算符,从哪头开始

结合性(associativity)回答「同一优先级的运算符排成一列,从哪边开始算」。 大多数是从左到右:1 + 2 + 3 - 4 + 5 顺着算得 7。 但赋值是从右到左,这成就了一个便捷写法——链式赋值:

$level = $score = $time = 0;

只有「先把最右边的 0 赋给 $time,再把 $time 的值赋给 $score,最后赋给 $level」这个次序, 三个变量才都是 0。方向一错,语义全变17。 比较运算符则没有结合性(5 < $a < 10 是语法错误——它不许你连着比, 这种数学式写法必须拆成 5 < $a && $a < 10)。

原书给新手的建议很直接:不确定就用括号把子表达式包起来,既躲开结合性的坑, 也让接你代码的人一眼看清次序18。

5. == 与 ===:弱类型的真面目

第 3 章预告过这个坑,本章用原书的例 4-13 把它钉死:

$a = "1000";
$b = "+1000";

if ($a == $b)  echo "1";   // 输出了 1!
if ($a === $b) echo "2";   // 什么都没输出

两个不同的字符串,为什么 == 说它们相等?因为 PHP 是弱类型: == 比较时,如果两边类型不同,PHP 会把它们都转成数再比——"1000" 转成 1000, "+1000" 也转成 1000,自然相等。而 ===(恒等)不做任何转换: 它连类型一起比,「字符串 1000」和「字符串 +1000」类型虽同、内容不同,结论不相等19。

原书的立场值得记住:新代码里做比较,默认用 ===—— 它把「类型转换带来的意外行为」整个关掉。只有当你明确想要「不管类型,看起来像就行」时, 才退而用 ==20。!= 与 !== 是同样的两档,取反而已21。

6. 条件语句:三种走法

原书用了一个贯穿本章的比喻:程序流是一条单车道公路,条件语句是路边的岔路指示牌—— 条件成立,拐进岔路走一段,回主线;不成立,直行22。

6.1 if:if 条件 { 干活 }

if ($bank_balance < 100)
{
  $money = 1000;
  $bank_balance += $money;
}

银行余额不足 100,给自己发 1000 补贴;够 100,一行不执行,直接跳过23。 括号里可以是任何表达式——比较、函数调用、对 0 和 NULL 的测试都算。 花括号永远写上,哪怕只有一条语句。原书为此讲了一个著名事故: 苹果 SSL 代码里的 goto fail bug——某程序员漏写了 if 的花括号, 导致一段关键代码总是执行,让不该通过的证书被当成通过,安全的连接被谎称建立。 一个括号,一个行业级漏洞24。

6.2 else 与 elseif:岔路的分岔

if…else 是二选一:真走左边,假走右边,「不可能两条都走,也不可能都不走」25。 if…elseif…else 是多选一:余额 <100 补 1000; elseif 余额 >200 存 100; 否则(else)存 50。elseif 想写几个写几个;但原书给了分流建议: elseif 一多,就该考虑 switch26。顺序的硬规矩:elseif 不能出现在 if 之前, else 不能出现在 elseif 之前,else 总是收尾(也可以不要)27。

6.2b 主线例子:银行余额把分支结构走一遍

本章的承重例子是一组虚构的「月底银行维护」操作,它把 if、elseif、else 三层恰好各用一次:

if ($bank_balance < 100)
{
  $money = 1000;
  $bank_balance += $money;
}
elseif ($bank_balance > 200)
{
  $savings += 100;
  $bank_balance -= 100;
}
else
{
  $savings += 50;
  $bank_balance -= 50;
}

设月初 $bank_balance = 150、$savings = 0,走一遍:

  1. 问 $bank_balance < 100?150 不小于 100——假,跳过第一个块;
  2. 落到 elseif,问 $bank_balance > 200?150 不大于 200——假,跳过第二个块;
  3. 落到 else——前两层都不中,无条件执行:$savings 变 50,$bank_balance 变 100。

换成 $bank_balance = 250 再走:第 1 问假;第 2 问真——$savings 变 100,$bank_balance 变 150, 然后整个结构结束(elseif 命中后不再看 else)。换成 50:第 1 问真——补 1000,余额变 1050, 后面两层都不再看。三个分支是互斥的:恰好一个执行,这正是多路分支想要的行为25。

这个例子还藏着一个读代码的细节:条件的顺序不是无所谓—— elseif 们从上到下依次问,先命中先走。把「> 200」放到「< 100」前面, 余额 50 的用户会先撞上哪条,结果完全不同。写多路分支时,把「最特殊」的条件放最上面, 或反过来把「最宽」的放最下面,是有意识的排序,不是排版27。

6.3 switch:多分支的专用结构

场景:一个菜单变量 $page,可能等于 Home、About、News、Login、Links 之一, 每个值触发不同动作。用 if…elseif 写要五个分支;switch 把「被检查的表达式」只写一次:

switch ($page)
{
 case "Home":
  echo "You selected Home";
  break;
 case "About":
  echo "You selected About";
  break;
 /* …其余 case… */
 default:
  echo "Unrecognized selection";
  break;
}

结构三要素:switch (表达式) 开头;每个 case 值: 是一个入口; default: 是全部不命中时的兜底(相当于 if 链的 else)28。

switch 的头号陷阱是 fall-through(贯穿):如果某个 case 忘了写 break, 执行不会停在它那里,而是一路贯穿到后面所有 case—— $page 是 Home 却五个 case 全执行一遍。这是故意的语言设计(少数高级用法靠它), 但对新手来说,忘写 break 是最常见的 switch 错误29。 原书的安全建议:连 default 后面的 break 也写上——虽然 default 通常是最后一支、写不写都行, 但万一哪天你把 default 挪到中间,缺了的 break 就变成 fall-through 的缺口30。

还有个少见写法:switch (…): … endswitch;——花括号换成冒号和 endswitch 关键字。 原书明说「不常用,只在读别人代码时可能遇到」31。

把 switch 的执行走一遍($page 等于 "News"):① PHP 求 switch 括号里的值,得 "News"; ② 从头扫 case:第一个 case "Home": 不匹配,跳过它的语句和 break; ③ 扫到 case "News": 匹配——从这里开始逐行执行; ④ 输出 You selected News,撞上 break,整个 switch 结束,跳到右花括号之后。 看明白第 ③ 步就知道 fall-through 为什么发生:匹配点只是「入口」,不是「出口」—— 出口永远由 break 把守(或一路贯穿到末尾)29。

6.4 三元运算符:? : 一行版 if

三元运算符(ternary,全 PHP 唯一吃三个值的运算符)把「问一句、二选一」压进一行:

echo $fuel <= 1 ? "Fill tank now" : "There's enough fuel";

读法:问 $fuel <= 1?真——返回 : 前的串;假——返回 : 后的串。结果可以直接 echo, 也可以赋给变量:$enough = $fuel <= 1 ? FALSE : TRUE;32。 原书同时给了两个提醒:一是看不懂就退回 if——三元不是必须品; 二是它常把同一个变量写三遍,比如这个流行写法33:

$saved = $saved >= $new ? $saved : $new;

拆开读:「把 $saved 设为……(问:$saved 是不是 ≥ $new?)是——保留现在的 $saved;否——换成 $new。」 作用一句话:记下目前为止见过的最大值。熟悉三元的人觉得这比 if 紧凑; 不熟悉的人需要逐段拆。遇到别人的代码里这种写法,能认出是「保大」就够了33。

7. 循环:三种,各管一类重复

比喻升级:岔路之外,公路上还有环形匝道——进去之后,条件不满足就出不来34。 原书用同一个任务(打印 12 乘法表)把三种循环各实现一遍,这是最省事的比较法。

7.1 while:先问,再决定要不要干

$count = 1;
while ($count <= 12)
{
  echo "$count times 12 is " . $count * 12 . "<br>";
  ++$count;
}

逐步:① $count 是 1;② 问 1 <= 12?真,进入循环体:输出 1 times 12 is 12,$count 变 2; ③ 回到 ② 再问到 13 为止——问 13 <= 12 得假,退出循环。 循环体里必须有让条件趋假的东西(这里是 ++$count),否则条件永远为真,死循环—— 原书警告:忘了这行的 while 会永远转下去,页面刷满 1 times 12 is 1235。

紧凑变体:把自增挪进条件——while (++$count <= 12),前置 ++ 先加再比, 于是初始值要从 0 而不是 1 起(进循环立刻被加 1)。行为相同,少一行36。

7.2 do…while:先干,再问

区别只有一个:判断发生在循环体之后。 先执行一遍,再决定要不要继续—— 所以循环体保证至少执行一次。同样的乘法表:

$count = 1;
do
  echo "$count times 12 is " . $count * 12 . "<br>";
while (++$count <= 12);

初始化回到 1(因为这次是先输出、后自增)37。什么时候非它不可? 「必须先做一次,才能知道该不该做下一次」的场景——比如先读一条记录,再判断要不要读下一条。

7.3 for:把三要素写在一行

for 把 while 散在三处的三要素收进一行:

for ($count = 1 ; $count <= 12 ; ++$count)
  echo "$count times 12 is " . $count * 12 . "<br>";

三个参数,分号分隔:初始化($count = 1,只在进循环时执行一次)、 条件($count <= 12,每轮开头问)、更新(++$count,每轮末尾做)。 循环体里从此只剩「真正要重复的活」——控制逻辑全部上收38。 走一遍确认三个时刻:第 1 轮——初始化 $count=1,问 1<=12 真,输出第一行,末尾加 1; 第 2 轮——不再初始化(它只跑一次),问 2<=12 真,输出,加 1;……第 13 轮开头问 13<=12 假,退出。 「初始化只跑一次」是新手最容易搞错的时刻——它写在循环头里,却不是每轮都执行的东西38。 多变量也行:for ($i = 1, $j = 1 ; $i + $j < 10 ; $i++, $j++)—— 段与段之间用分号,段内多条语句用逗号,这是最容易写错的一处39。

for 与 while 的分工,原书给的判据:循环由「一个按规律变化的变量」驱动 → for; 条件是「某个事件/错误出现」这类不规则信号 → while40。 比如「处理列表里的每一项」是 for 的菜;「持续读数据直到磁盘报错」是 while 的菜。

7.4 break 与 continue:循环的两个急停键

  • break:立刻跳出整个循环。例:循环往文件里写数据,fwrite 返回 FALSE(磁盘写满)就 break 收场41。 嵌套循环里还能 break 2;——一次跳出两层42。
  • continue:只跳过当前这一轮,直接进下一轮。例:循环里 $j 会是 0 的一轮, if ($j == 0) continue; 把「除零错误」那一轮整个跳过43。

一句话区分:break 出整圈,continue 出本轮。

7.5 三种循环一张对照表

同一个 12 乘法表,三种循环各写一遍——差异全在「什么时候问条件、什么时候更新变量」:

初始化在哪条件什么时候问更新在哪循环体至少执行
while循环体之前,自己写每轮开头循环体里,自己写0 次(条件初值假就不进)
do…while循环体之前,自己写每轮末尾循环体或条件里,自己写1 次(先干再问)
for第一参数,循环头每轮开头(第二参数)第三参数,循环头0 次

for 只是把 while 的三要素搬家——搬到循环头的一行里,语法糖而已,表达能力完全等价。 原书的双变量例($i + $j < 10)说明三个参数各自还能塞多条语句;但「三个参数、两次分号」的骨架不变39。 选择困难时回到那条判据:变化是不是「一个变量按规律走」?是 → for;否 → while; 「必须先试一次」→ do…while40。

7.6 死循环与循环的退出点

原书对 while 的警告值得重复一遍,因为后果实际可见:漏写自增的循环不会报错,只会永远转下去—— 页面被同一行输出刷满,浏览器卡死,直到服务器超时35。循环的三个出口,写每个循环前都问自己一遍:

  1. 条件正常变假,循环走到头(正常出口);
  2. break 中途跳出(错误出口,例:磁盘写满);
  3. 根本就没有出口——这是 bug,不是第四种循环。

新手查死循环的顺序:先找「条件里被检查的变量」,再找「循环体内改它的那一行」—— 两行缺一行,凶手就在那儿。

8. 类型转换:隐式与显式,再进一步

第 3 章见过隐式转换(弱类型自动转)。本章补系统的一课:

$a = 56;
$b = 12;
$c = $a / $b;        // 4.6666666666667 —— 两个整数相除,结果是浮点
$c = (int)($a / $b); // 4 —— 显式转成整数,直接丢掉小数部分

隐式转换(implicit casting)是 PHP 主动做的:整数除法它默认给浮点,因为要保精度44。 显式转换(explicit casting)是你用 (int) 等 casts 强制的——注意上面第二行的括号: (int)($a / $b) 转的是整个除法的结果;若写成 (int)$a / $b,只转 $a, 除法照样返回浮点——括号位置一改,语义完全不同45。

可用的 cast:(int)/(integer)、(bool)/(boolean)、(float)/(double)/(real)、 (string)、(array)、(object)46。PHP 也有同名功能的函数(如 intval), 但函数往往多做额外的事(intval 支持按进制转换)——单纯转类型,cast 就够47。

这一节和第 3 章的弱类型讨论合起来,是理解 PHP 类型行为的完整拼图: 读(取值时用上下文自动转)、写(赋值时自动装盒)、问(比较时按 ==/=== 决定转不转)、 改(cast 与 intval 一族显式改)。同一个值在四种时刻可能经历四种对待—— PHP 的「灵活」就灵活在这里,它的「坑」也坑在这里。 养成一个习惯:涉及类型的地方,优先用让转换最少发生的写法—— 比较用 ===,需要整数就 (int),函数的参数和返回值上类型声明(第 5 章)。 写得越「显式」,运行时替你做的「隐式」决定越少,意外也越少20。

9. PHP 模块化:章末的架构一课

本章最后一段跳出了语法,谈网站怎么组织。PHP 技术上一个页面就能装下整个网站—— 用户点什么,都传回同一页,由它按 cookie 和会话状态决定下一步48。 但原书明说不推荐:单一文件要兼顾用户可能做的所有动作,会涨到没法维护。

替代方案是拆模块:注册是一个模块(校验邮箱、查重用户名),登录是一个模块, 留言、链接、传图各是一个。模块之间的黏合剂是 cookie 和会话变量(第 12 章)—— 靠它们认出「这个请求还是那个用户」。拆完之后的红利:每个模块自包含, 新功能独立开发,旧功能独立维护;有团队的话,每人深钻一个模块即可49。

注意原书给的拆分颗粒度:一个「用户动作」对应一个模块,不是按技术(所有数据库代码一个文件) 也不是按页面(所有页面一个文件)。注册之所以是一个模块,是因为它自带一整个检查流程—— 校验邮箱格式、查用户名是否被占——这些检查只服务于「注册」这一个动作。 判断「这段代码该不该自成模块」的问句是:它伺候的是不是一个完整、独立、可命名的事?

这一节是全书的「方法论预告」:第 5 章的函数与对象,就是下一章用来造模块的工具。 「先拆模块,再填语法」这个顺序,是这本书和普通语法教程的分水岭。

10. 作者的判断与证据

书中的说法成色
新代码默认用 === 比较明确的规范性建议,理由是消除类型转换的意外——这是本章最有行动力的一条20
if 永远写花括号(goto fail 为例)有真实事故背书(苹果 SSL 双重 goto 导致的证书漏洞),警告的级别给得足24
执行运算符「strictly necessary」才用安全警告,理由是可能「expose a massive vulnerability」——方向绝对正确,但没给具体攻击演示7
switch 一多分支就换它经验法则,理由是 elseif 链的可读性;边界(多少个 case 算「多」)没有量化,属于作者口味26
「网站拆模块」的论述架构主张,论据是维护性与团队协作,无对照实验——但和整本书的示例组织方式(每个功能一个文件)自洽49
三元运算符「不习惯就退回 if」作者自陈(「If you find the ? operator confusing, you are free to stick to if statements」),态度务实32
位运算只给目录不给内容针对读者群的合理省略(网页开发极少用位运算),但优先级表里它们占位——考试式地背表前,先想清楚自己用不用得上
do…while「至少执行一次」语言机制事实,例子(乘法表)却没展示出「非它不可」的场景——真正需要它的「先读再判」循环,要到第 7 章文件操作才出现37

11. 边界与局限

  • TRUE/FALSE 的打印结果依赖语言实现:本章开头原书就提醒,不同语言里 FALSE 的「打印形态」不同 (有的语言是 0,甚至是 -1),别把这个对应关系背成「真理」3。
  • 位运算只有目录没有内容:Table 4-1 和优先级表里都出现,但全章没有讲任何一个位运算的用例—— 这本书的读者定位(网页开发)确实极少用得到,算是合理的省略。
  • 结合性表的完整版(Table 4-3)很长,原书建议新手用括号代替记忆——这意味着真遇到怪表达式, 查官方手册比背表靠谱。
  • fall-through 的「高级用法」没有展开:原书说漏 break「allows for some advanced programming」, 但没演示那种用法长什么样——读者遇到刻意利用贯穿的代码(比如多 case 共用一段逻辑)只能自己悟。
  • 模块化一节无实操:拆模块的标准(多大盘子该拆)、模块间怎么传数据,都要到第 5、12 章才有着落, 本节只有方向和理由。
  • 条件语句的嵌套没讲:if 里套 if、循环里套分支的组合结构本章一个例子都没有—— 它们全靠同一套规则自然组合出来(每一层的「真走这、假走那」互不干扰), 但嵌套层数一深,可读性的纪律(缩进、早退)就成了新问题,本书对此再无专章, 属于「留给实践」的一课。
  • goto fail 的细节被简化:原书对事故的复述是「forgotten the curly braces」, 真实的苹果 bug 更具体(重复的 goto 行加缺失的括号共同作用)。作为「写括号」的论据它成立, 但拿它当安全史的完整叙述会失真24。
  • while 循环的「事件驱动」场景缺例子:「等某个特殊输入或错误」这个判据只给了原则, 没有配套代码——读者第一次真写「读数据直到出错」时,还得自己把 break 和 while 拼起来。

12. 章末自测:原书的问答题怎么答

这一章的章末问题里,有四问最常卡住,给出我们的回答:

原书的问题我们的回答
打印 TRUE 和 FALSE 时显示什么?为什么?TRUE 显示 1、FALSE 显示空串——PHP 规定布尔转字符串时的对应;echo 只收字符串,转换规则是语言定的,不是逻辑必然3
最简单的两种表达式是什么?单个字面量(求值结果是自己)和单个变量(求值结果是盒中的值)2
一元、二元、三元运算符的区别?吃几个值:一个(!$a)、两个($a + $b)、三个(expr ? x : y,全 PHP 唯一)8
强制自定义优先级的最好办法?括号——「括号永远赢」;顺带也躲开结合性的坑14
什么时候 while 比 for 合适?条件不是「一个变量按规律变化」,而是等某个事件/错误出现40

这五问答顺,本章的机制层面就算过关;答不顺,回到对应小节把例子重走一遍。

13. 可带走的

这十一条是本章的压缩包——每条都能单独拿去用,合起来就是「写 PHP 程序」的全部基本动作:

  1. 表达式算值,语句做事;条件语句吃「真/假」,循环语句吃「条件 + 更新」。
  2. TRUE 打印成 1、FALSE 打印成空串——调试用 var_dump,别靠 echo。
  3. 优先级:括号 > 单目 > 乘除模 > 加减拼接 > 比较 > && > || > 三元 > 赋值 > and/or。
  4. 拿不准加括号;链式赋值靠「右结合」;连续比较 5 < $a < 10 是语法错误,拆成两个条件用 && 连。
  5. 弱类型下 == 先转换再比("1000" == "+1000" 为真);新代码默认 ===。
  6. if/elseif/else 多分支一长就换 switch;switch 里每个 case 末尾的 break 是性命攸关的。
  7. and/or 优先级低到可以做「赋值失败就 die」,别处别用——低优先级是特性,不是笔误。
  8. 三元 ? : 是一行版 if,认得出别人的「保大」写法($saved = $saved >= $new ? $saved : $new;)。
  9. while(先问)、do…while(先干)、for(按规律变化)分工不同;break 出整圈、continue 出本轮。
  10. (int)(表达式) 的括号决定转换作用范围;cast 管转换,intval 这类函数管更多。
  11. 一个 PHP 文件装不下整个网站——按功能拆模块,用会话认出同一个用户;拆分的问句是「这段代码伺候的是不是一个完整、独立、可命名的事」。

12.5 优先级与结合性:把两张表用熟

第 4 节的优先级表有 16 档,结合性表更长——全背是笨办法,实战只需要 七个高频档位:

  括号        —— 永远最高,见它就先算
  ! ++ --     —— 单目,紧贴操作数
  * / %       —— 乘除取余
  + - .       —— 加减与拼接同档(从左往右)
  < <= > >=   —— 大小比较
  == != ===   —— 相等比较(比大小低一档!)
  && || ? :   —— 逻辑与三元,几乎垫底
  = 赋值      —— 倒数第二
  and or      —— 最底(所以 or die 好用)

两个容易记错的位置,单独钉一下:相等比较比大小比较低——5 < $a == 3 其实是 (5 < $a) == 3;拼接 . 和 + 同档——"a" . 1 + 2 的结果 靠结合性(从左到右)而不是直觉。遇到这两种,别赌,加括号。

结合性只需记住三个:赋值从右往左($a = $b = 0)、比较没有结合性 (5 < $a < 10 是语法错误)、其余从左往右。这三条覆盖日常九成场景, 剩下的查表即可——表在官方手册"运算符优先级"一页,收藏它比背它划算。

12.6 if 链的四种形态,一张图

把本章的条件结构按"分支数 × 互斥性"摆成一张小图:

单分支双分支互斥多分支互斥
推荐写法ifif...elseif...elseif...else 或 switch
分支数12≥3
判断依据一个条件一个条件的正反同一个值的多档

三列之间是"逐步升级"的关系,不是三种互斥的语法。写代码时的选择路径: 先写 if;发现"不成立时也要做事",补 else;发现"不成立"还分好几档, 把 else 换成 elseif 链;再发现"判的其实是同一个变量的多个值", 整链翻译成 switch。这条升级路径就是重构的条件分支版—— 结构跟着语义长,不是一开始就选"最高级"的写法。

12.7 循环的选择决策树(升级版)

§7.5 的对照表再加一层"先问什么":

  要重复一段代码吗?
    ├─ 重复次数已知、按规律变化? ──▶ for(最省事,三要素收进一行)
    ├─ 必须先做一次再看?          ──▶ do...while(先干再问)
    ├─ 等某个事件/错误?           ──▶ while(问在先,体里要有 break 的预案)
    └─ 遍历数组?                  ──▶ foreach(第 6 章主角,别用 for 数下标)

第四个分支是本章埋给第 6 章的钩子:PHP 里遍历数组的默认答案是 foreach, for ($i=0; $i<count($a); ++$i) 是 C 语言时代的遗物——能用,但每一步 都要自己管下标,array 的函数们(forEach/map 式的)在 PHP 里叫 foreach。 三兄弟加上 foreach,PHP 的循环家族就到齐了。

12.8 把 switch 的 fall-through 用到正处

§6.3 说漏 break 是常见错误,但 fall-through 并非纯粹的事故来源—— 故意利用它,可以让"多个 case 共用一个动作"的代码非常干净:

switch ($type) {
  case 'admin':
  case 'editor':
    $canPublish = TRUE;    // 两种角色共用一个权限分支
    break;
  case 'viewer':
    $canPublish = FALSE;
    break;
  default:
    die('Unknown role');
}

三个 case 标签叠罗汉,只有最后一个带动作——读法:"是这三种之一吗? 是就走这个门"。这比三个 if 或三个重复的 case 块都省,而且把 "刻意贯穿"和"忘了写 break"区分开:刻意版只有最后的 case 带 break, 每个叠罗汉的标签下都故意留空——空本身就是声明"我故意的"。 原书提到 fall-through"allows for some advanced programming"却没给例子, 这就是它想指的那一类(补充:判空叠 case 是 C 时代流传下来的惯用法)。

12.9 三元运算符的「可读性税」

第 6.4 节教了 ? :,这里把使用边界说清楚:

  • 适合:两个候选值都很短、条件也短——$label = $ok ? 'YES' : 'NO';
  • 勉强:赋值目标与条件共用变量(那个"保大"写法)——认得出来即可,不鼓励自己写;
  • 不适合:嵌套三元($x ? $a : ($y ? $b : $c))——读的人要做栈式解析, 直接 if 链更便宜。

一行税率的粗标准:整行超过 60 个字符,三元改 if。不是语法限制, 是读的人的工作记忆限制——这和 §12.6"结构跟着语义长"是同一条原则 在"微观语法"层的应用。

12.10 循环与数组的第一次合奏(预告第 6 章)

本章的 for 循环将在第 6 章遇到它的主战场——遍历数组:

$paper = ['Copier', 'Inkjet', 'Laser', 'Photo'];
for ($j = 0; $j < count($paper); ++$j)
  echo "$j: $paper[$j]<br>";

走一遍:$j=0 取 Copier,1 取 Inkjet……到 4 时条件失败,循环结束。 count($paper) 每轮都重算一次——数组不变时,更好的写法是先存进 $len = count($paper)。但 PHP 给出的标准答案仍是第 6 章的 foreach($paper as $item)——一行,没有下标,没有 count,没有 越界的可能。工具进步的每一步,都是把"自己管的细节"再交还一件。

12.11 本章的「真 bug」博物馆

把全章提到的、真实世界里造成过事故的写法收进一座小博物馆, 每件配一句话的悼词:

展品悼词
if (x) a; b; 无花括号goto fail(苹果 SSL,§6.1)——漏括号让第二行永远执行
$a = 5 == 5想比较却赋值,条件永远成立——= 与 == 一字之差
while ($x < 10);分号独占一行,循环体为空——死转,页面刷满
5 < $a < 10比较无结合性,语法错误——数学式写法在代码里不成立
switch 忘 break五个 case 全执行——菜单变成大杂烩
switch 用 == 心态期待 ===case 比较是宽松比较,类型不同也中——严谨场景要先自己把关

博物馆的使用方法:每次写这些结构,心里默念对应悼词。它们是这本书 里最便宜的学费——别人已经替你交过了。

12.12 表达式的「真值表」心智模型

第 3 节说逻辑运算符吃真/假,这里把"什么样的表达式算真"补成一张 随时可查的表(PHP 口径,第 4 章开头有完全版):

表达式真假
比较:5 > 3、$a == 'x'按比较结果
数:非 0 为真,0 为假if (5) 成立,if (0) 不成立
字符串:非空为真,空串为假if ("") 不成立
数组:非空为真if ([]) 不成立
NULL假

注意第三行和直觉的冲突点:字符串 "0" 也是假(PHP 历史遗留, "0" 被当成数 0 处理)——所以 if ($_GET['page']) 在 page=0 时 会跳过你的分支。这正是第 4 章"FALSE 打印成空串、'0' 也当假" 那组怪癖的完整版;防御写法是 if (isset($_GET['page'])) 或显式 !== ''。这张表不用背,写 if 条件卡壳时回来查一次,查三次就记住了。

12.13 「花括号永远写」的代价收益账

§6.1 用 goto fail 论证了花括号纪律,这里把账算细:

  • 成本:每条 if 多两行(开大括号、关大括号);
  • 收益一:加第二行语句时永远安全——if (x) a; b; 的事故形态不存在了;
  • 收益二:diff 更干净——加一行只显示一行,而不是"把一行改成三行";
  • 收益三:复制粘贴片段时结构自含,不依赖上下文。

一条团队规矩换来三种收益,这买卖在任何账本上都划算。 配套的次级规矩:else 的花括号同样永远写——if 写了 else 不写, 风格本身就变成了误导(读者会以为 else 只管一行)。

12.14 条件与循环的嵌套:深度纪律

本章没讲嵌套,但三行之内的浅嵌套从本章材料就能推出来:

for ($i = 1; $i <= 9; ++$i) {          // 外层:被乘数
  for ($j = 1; $j <= 9; ++$j) {        // 内层:乘数
    echo "$i×$j=" . $i*$j . " ";
  }
  echo "<br>";                          // 每行结束换行
}

九九乘法表——两层循环各管一维,内层跑完一轮外层才退一步。 这是多维数据(第 6 章二维数组、棋盘)遍历的原型。嵌套的深度纪律: 超过三层就该把最内层抽成函数(第 5 章的手法)——缩进是免费的, 但每一层都在消耗读的人的工作记忆;函数是把"记忆的负担"换成 "名字的负担",后者便宜得多。

12.15 章末清单:第 4 章的三十个考点(自测版)

表达式(8):表达式的定义 · 字面量与变量 · 布尔与 TRUE/FALSE 打印 · 语句与表达式之分 · 真值表 · ==/=== 两档 · and/or 低优先级妙用 · 三元

控制流(12):if 花括号纪律 · else · elseif 顺序敏感 · switch 三要素 · fall-through 故意版 · default 也写 break · 三元一行税 · while 死循环三出口 · do...while 的至少一次 · for 三要素时刻 · for/while 分工 · break/continue 之分

结构观(10):模块化预告 · 表达式算值、语句做事 · 逻辑短路两例 · 真值表的 "0" 陷阱 · 嵌套三层抽函数 · goto fail 博物馆 · 保大写法 · 运算符七档 · 比较无结合性 · 循环退出点三问

12.16 PHP 真值表的例外全集:「哪些值当假」

§12.12 的表留了"PHP 口径"四个字,这里把 PHP 当假的值列全 (第 4 章开头的 falsy 清单 + 本章用到的):

  FALSE 本身
  整数 0 和浮点 0.0
  字符串 "0"      ← 和空串 "" 并列,PHP 特有的历史包袱
  空串 ""
  空数组 array()
  NULL
  未定义的变量(伴随 Warning)

两种语言对比记:第 14 章的 JS falsy 清单里有 NaN 和 undefined, 没有 "0"——"0" 为假是 PHP 独有。这种"同一概念、两家口径" 在 PHP/JS 对照书里特别常见,本书的教法(两章各给完整清单)比 "对照表只给差异"更不易记混——先各记全,再用差异表对账。

12.17 一个 60 行的完整练习:会员等级判定

把本章的控制流全部用上的一道综合练习(题面,非书内;解法用本章件):

某站会员按消费额定级:0–999 铜、1000–4999 银、5000–9999 金、 10000 以上钻石;负数与超过 100 万的值视为数据错误。 写一个程序,读 $_GET['amount'],输出等级或"数据错误"。

推荐的结构(if…elseif…else 链):

$a = (int)($_GET['amount'] ?? -1);          // 先取并转,缺参给 -1
if ($a < 0 || $a > 1000000) $lv = '数据错误';
elseif ($a < 1000)          $lv = '铜';
elseif ($a < 5000)          $lv = '银';
elseif ($a < 10000)         $lv = '金';
else                        $lv = '钻石';
echo htmlentities($lv);

每步对账:输入 0 → 铜;999 → 铜;1000 → 银;10000 → 钻石;-5 → 数据错误; 缺参数 → -1 → 数据错误。先写判定链,再写边界用例,最后才跑浏览器—— 这个次序是本章博物馆(§12.11)的正面版:让用例替你把关, 而不是让 bug 替你写测试。

12.18 从本章看「语言设计」:为什么 PHP 有两种与或

&&/|| 与 and/or 并存,是 PHP 继承 Perl 的家传——Perl 也有 这两套,低优先级版专为「赋值失败就死」这类写法而生。 语言设计上的这种"双轨",评价历来两极:拥护者说"把控制流写进表达式, 少一个临时变量";批评者说"两套语义增加认知负担"。 本书的立场是中间派:知道两套存在、平时只用一套、特定惯用法用另一套—— 这正是 §4.4 给出的口径。学语言学到能讨论"为什么这么设计", 这门语言就算学通了第一层;本章给了你四个讨论素材 (双与或、== 宽松比较、. 拼接、"0" 为假),够你在任何技术论坛 站稳脚跟了。

12.19 「模块化」一节的延伸阅读:它回答了什么问题

第 9 节(模块化)是全章最抽象的一节,值得单独看清它在回答什么。 它之前的问题是:一个 PHP 文件在技术上能装下整个网站 (每个请求都进同一文件,按 cookie/会话分支),那要不要这么做?

原书的论证分三层:第一层是可维护性——单文件要兼顾所有动作, 会涨到没法读;第二层是分工——模块按"用户动作"切,团队各管一块; 第三层是接口稳定——模块间只靠会话和链接传话,内部随便改。 这三层其实是软件工程里"内聚与耦合"的白话版:一个模块只做一类事 (高内聚),模块之间只说必要的话(低耦合)。第 5 章的函数与对象、 第 8 章的 include_once、第 22 章的文件切分,分别是这个主张在 "代码级""文件级""应用级"的落地。读书读到这里,已经摸到了 软件工程的门槛——这是本章超出语法之外的最大收获。

12.20 练习场:五道控制流综合题

  1. FizzBuzz(经典面试题):1 到 100,被 3 整除打印 Fizz, 被 5 整除打印 Buzz,同时被整除打印 FizzBuzz,否则打印数本身。 用到:for、%、if/elseif、echo。难点在判定顺序——先写哪个条件?
  2. 猜数字(命令行版):用 rand(1,100) 藏一个数,while 循环 让用户猜,大了提示大、小了提示小,猜中 break 报步数。 用到:while、break、rand。(为第 7 章随机数预热)
  3. 菜单翻译:把 §6.3 的 switch 菜单改成数组驱动—— 菜单项存数组,switch 换成查数组。体会"数据与逻辑分离" (改菜单只改数据,不动代码)。
  4. 嵌套循环:打印九九乘法表(§12.14 给了骨架),再改成 只打左下半角的版本(内层条件 $j <= $i)——体会循环边界 就是"画图的剪刀"。
  5. 表单分级:§12.17 的会员题改成完整页面——GET 表单
    • PHP 判定链 + 结果回填,错误时把输入值留在框里。 本章 + 第 11 章表单知识的第一次合奏。

12.21 「保大」式三元与累加模式:不止一种写法

§6.4 的 $saved = $saved >= $new ? $saved : $new; 是一种模式, 它有一个更顺口的近亲——「取最大」函数版(书外补充,标注):

$saved = max($saved, $new);   // PHP 内置 max,与三元版同效(补充:不在原书)

对比着读更有收获:三元版显式地"把比较写给你看",max 版把比较藏进 函数名。哪种好?教学场景选三元(它顺便教了你三元与短路), 生产场景选 max(名字即文档)。同一个语义常有多个层次的写法, 选择哪个,取决于这一行要教还是要用——这是本章"写法跟着场景走" 的又一个注脚。

同类模式还有「计数累加」:$count = isset($count) ? $count + 1 : 1; (存在就加一,不存在就置一)——isset 与三元的组合拳, 在统计、去重里天天出现。认出这些"模式"比背语法高一级: 语法是砖,模式是砌法。

12.22 本章与样板书的第二次「对表」

样板书的红线二要求每章一条主线例子从头走到尾。本章的主线例子是 银行余额的 if/elseif/else 链(§6.2b),设了三个初始值各走一遍, 每一步旁边都有余额的具体数字——合格。复核的方法值得说清: 把例子的每个分支各取一个输入,把"输出应该是什么"先写在纸上, 再跑代码对答案。先写预期再验证,这个顺序把"我以为"和"它真的" 分开了——全书的自测题(附录 A)本质上就是这个方法的大量实例。

12.23 PHP 与 JS 控制流的「预对账」

第 14 章会镜像本章,这里先把差异表立起来(第 14 章再正式展开):

事项PHPJavaScript
逻辑与或&& and 两套只有 &&
elseif 关键字elseif 一个词else if 两个词
switch同同(fall-through 同)
布尔打印1 / 空串true / false
死循环后果服务器超时浏览器假死
真值表含 "0"含 NaN、undefined,不含 "0"

这五行就是第 14 章的"考点预告片"。到时候你会感谢今天的自己—— 预习过的章节读起来像复习。

12.24 章末 FAQ(五问)

Q1:or 和 || 能混用吗? 同一表达式里别混——优先级差会让读的人和解析器得出不同结论。 一个文件里统一用 ||,只在 or die() 惯用法处用 or。

Q2:switch 能 case 区间吗? 不能直接 case 1..10:。要么 if 链,要么 case 里写 case ($x >= 1 && $x <= 10):—— 后者可行但可读性差,if 链更直。

Q3:三元能嵌套吗? 语法能,人不该。两个以上的候选,换 if 或查表(§12.20 题 3 的思路)。

Q4:while(1) 和 for(;;) 哪个好? 都行,都是"无限循环"的写法,配上循环体内的 break 当出口。 团队统一即可;个人推荐 for(;;)——它少一个"1"的视觉噪音。

Q5:本章学完了,距离"会写网站"还差什么? 表单(第 11 章)、数据库(第 8–10 章)、会话(第 12 章)—— 控制流是网站的神经系统,但网站还需要躯体和记忆。本章给你的是 "无论写什么程序都用得上"的那一半。

12.25 把「短路」用成工具,而不是踩成坑

§3.2/§4.4 两次警告了短路的坑(getnext 不被调用),这里把短路的两件 正面用途补上——同一个机制,用对了是工具:

// 用途一:默认值——$name 不存在就用 '匿名'
$display = isset($name) ? $name : '匿名';   // 三元版(等价 ?? 的原始写法)

// 用途二:快速失败——查询失败就不再执行后面
$stmt = $pdo->prepare($sql) or die('SQL 错误');   // 第 7 章 die 惯用法

用途二的机制:prepare 返回 false(失败)时,or 的右边才执行—— 失败路径被压缩进同一行。第 5 章的 include "library.php" or die(...)、 第 7 章的 fwrite(...) or die(...) 都是这同一个骨架。 坑与工具的分界只有一条:你写的时候,知不知道右边可能不执行。 知道,就是设计;不知道,就是事故候选。

12.26 嵌套三元与「条件赋值」的正主

§12.23 预告了 JS 差异,这里把 PHP 里条件赋值的全家列一次, 给你一个完整的"何时用哪件"的抽屉:

写法用途例子
if 语句任意分支一切
三元二选一赋值$lv = $a > 60 ? '及格' : '不及格';
??(PHP 7+)不存在则默认$q = $_GET['q'] ?? '';
or die失败即终止第 7 章文件操作

?? 是本书之后大量代码的常客(第 11、22 章),它等价于 isset($x) ? $x : 默认——本章 §12.25 的"默认值"写法在 PHP 7 之后 有了短形式。四件按"分支复杂度"排队:一个比一个窄,选最窄的那件。

12.27 章末总复习路线(二十分钟版)

  1. 五分钟:默写 §12.15 的三十考点,卡壳的做记号;
  2. 五分钟:重做"银行余额"主线例子,三个分支各跑一遍,数字写纸上;
  3. 五分钟:写 FizzBuzz(§12.20 题 1),不查文档;
  4. 五分钟:把 goto fail 博物馆(§12.11)每件讲给空气听一遍。

二十分钟过完,第 4 章可以放心翻篇——它名义上是"表达式与控制流", 实际上是把你已经会用的东西,修成能讲给别人听的样子。

12.28 「同一问题,两种结构」对照实验

拿"打印星期几的问候语"做对照实验,看 if 链与 switch 的表达力差:

// 版本 A:if 链(条件各不相干时更自然)
if ($day === 'Sat' || $day === 'Sun') echo '周末愉快';
elseif ($day === 'Fri')                 echo '周五了!';
else                                    echo '工作日加油';

// 版本 B:switch(判的是同一个变量的多个值)
switch ($day) {
  case 'Sat':
  case 'Sun': echo '周末愉快'; break;
  case 'Fri': echo '周五了!';  break;
  default:    echo '工作日加油';
}

这个例子里 B 更短,但 A 的条件是"范围/组合"(周末=两天),switch 用 叠罗汉才表达得出来。判据不在行数,在"条件的形状":同一值的 离散档位 → switch;范围、组合、跨变量的条件 → if 链。 选错结构的代价不是功能,是"以后每读一次都要做一次脑内翻译"。

12.29 与第 14 章的「镜像备课」清单

第 14 章是本章的 JS 镜像,备课只需三问:

  1. JS 里 0 == "" 为真吗?(真——宽松比较两边转型,同 PHP 的 == 陷阱)
  2. JS 的 falsy 比 PHP 多了谁、少了谁?(多 NaN/undefined,少 "0")
  3. JS 死循环与 PHP 死循环,谁更惨?(JS——浏览器假死,用户连窗口都难关)

带着答案去读,第 14 章十分钟就能过完主干——剩下的是 DOM 与 JS 独有的 var/let/const,那才是那一章的真增量。

12.30 本章的三张「随身卡」(压缩版)

卡一·比较:赋值用 =,相等问 ==,严格用 ===;新代码默认 ===。

卡二·分支:if 永远带花括号;switch 永远带 break(叠罗汉的除外); 分支按"最特殊在上"排序。

卡三·循环:for 管规律、while 管事件、do…while 先干再问; 循环体内必有一行让条件趋假,break 出整圈、continue 出本轮。

三张卡塞进钱包,本章随时能复习——比翻书快,比回忆稳。

12.31 控制流的「性能直觉」(浅尝辄止)

本书不谈性能优化,但两个直觉值得现在建立,免得日后被误导:

  1. 循环次数比循环写法重要。for 和 while 的效率差异可以忽略; 一万次循环还是一亿次,是数量级的差别。第 9 章 EXPLAIN 一节会 告诉你,真正的性能问题几乎总在外部(数据库、文件、网络),不在循环关键字。
  2. 短路是免费优化。$a || expensive() 在 $a 为真时跳过 expensive—— 把"最可能为真的条件放最前",是逻辑运算符附赠的性能小技巧。 代价是可读性稍降,放前的理由要写注释。

至于"用三元比 if 快""switch 比 if 链快"之类的说法,属于微优化时代的 遗老——现代 PHP 里差距都在噪声级,不值得为此牺牲可读性。 先写对,再写清,最后才考虑快——这个次序,第 9 章的性能观会再次确认。

12.32 第 4 章在原书的位置与分量

原书第 4 章自称"把第 3 章欠的账一次补齐",这个说法精确:第 3 章为了 保持入门坡度,把运算符、比较、控制流都"用过但没讲透";第 4 章逐一收账 (优先级全表、=== 纪律、短路、循环三兄弟),并在章尾把"模块化"这个 方法论钉进去。所以第 3、4 章是一对——3 给你手感,4 给你理论。 两章都过完,PHP 的"语言层"就毕业了,第 5–7 章进入"工具层" (函数对象、数组、文件日期)。

12.33 最后一组对照:教科书与现实的差距

本章的所有机制在真实代码里都会遇到,但真实代码还多两样本书没讲的:

  • 异常(try/catch):PHP 里处理"意外"的现代结构。本书第 10 章 PDO 一节会第一次用它;第 3、4 章用 die 和错误提示的写法,届时会被它替换。 现在不必学,见到 try { 知道是"这里可能出事,出事了去 catch"即可。
  • 测试:assert、单元测试框架。本书全篇未涉(见我们的边界节), 但第 12.11 的博物馆和 §12.17 的"先写预期"已经给了测试思维的胚胎。

知道"还有这两样",就不至于把 PHP 学成一个封死的圈——这是本章最后 要递给你的一张地图残片。

12.34 「执行流程图」的手绘练习

把代码的执行流程画出来,是检验"真懂"的最强手段。练法:拿 §12.28 的 版本 B,在纸上画一张流程图——菱形表判断(星期几?),方块表动作, 箭头表流向;switch 的画法是"一根主干、多个分叉口、每个分叉口一个 break 汇回"。 画过一遍,你对 fall-through 的理解会超过读十遍文字。 这个手绘习惯从本章开始养,到第 9 章规范化(数据流图)、第 12 章 (会话接力图)、第 22 章(项目时序图)会不断升级——图是程序员的第一外语。

12.35 致下一轮的翻新者

本章被叫回来时,优先补:① 附录 A 里第 4 章问答的官方答案对照 (我们给了自答版,未与附录 A 逐字核对——导航章不读不引的规矩下, 这是有意的留白,标注于此)、② §12.20 练习场的参考答案、 ③ switch 的更多真实用例(权限、状态机)。机制层已够厚, 增量在"题与答案"。

12.36 附录 A 的答案长什么样(我们的自答,未核附录)

按"导航章不读不引"的规矩,附录 A(官方答案)不在我们的引用范围; 但第 4 章的自测我们给了自答版(§12),风格对齐官方答案的口径: 一句话、指回正文、偶尔补一句"为什么不是另一个"。如果你手上有原书, 值得花十分钟把我们的自答和附录 A 对一遍——差异处往往就是 理解最深的地方。这种"双答案对账"的练习,比再读一遍正文更有效。

12.37 一条未被原书明说、但贯穿全章的主线

读完本章,把散落各处的"永远"收拢,会发现一条未被原书明说、 但每个例子都在执行的主线:凡是有"两种写法"的地方,PHP 都给了 一个宽松的默认和一个严格的选项——宽松的那个上手快(==、宽松比较、 弱类型),严格的那个上线稳(===、类型声明、占位符)。 学会在"学习期用宽松、交付期用严格"之间自如切换, 比记住任何单个语法点都接近这门语言的真精神。 第 10 章的 PDO 课、第 12 章的密码课,都是这一精神的续集。

12.38 「永远写花括号」的现代演进

本章的规矩是"花括号永远写"。补一个当代注脚(书外,标注): 许多团队进一步用代码格式化工具(PHP-CS-Fixer 之类)把这件事自动化—— 提交代码时工具自动补括号、统一缩进,人只需定一次规矩。 这与 §13.40"让环境背规矩"一脉相承:从"要求自己写"到"要求工具改", 是同一个纪律的两代实现。(补充(不在书里):工具名与用法以官方文档为准。)

12.39 本章的「海拔检查」结果(对照总纲要求)

总纲要求章节文档只承担机制细节。复核本章:主线例子(银行余额)在正文, 机制全在章内,没有"总纲才有的判断"下沉——合格。 唯一越界候选是 §12.37 的"宽松与严格"精神总结——它属于判断, 已在 §7 的判断表区有正式席位(「新代码默认 ===」一行),不算越界。

12.40 「表达式思维」的迁移价值

本章教的"先算值、再做事"的思维,不止属于 PHP:

  • 写 SQL 时,WHERE 后面整段是个布尔表达式(第 8 章);
  • 写 JS 时,条件与逻辑几乎逐字相同(第 14 章);
  • 写正则时,字符类与量词是另一种"表达式代数"(第 16 章);
  • 甚至写 Excel 公式,优先级与括号的问题一模一样。

表达式是编程语言之间的通用语——学好这一章的四则运算与布尔逻辑, 等于同时给四门语言交了学费。这也是为什么本书敢把 PHP 和 JS 分成 "镜像章节"教:镜像的部分(本章的大部分)学一次,差异的部分(§12.23 的表) 单独补,总账划算。

12.41 最后一个练习:给本章写一段"教给别人"的话

按总纲的判据("读完能不能讲给别人"),验收本章的最终题是: 不看文档,用三分钟给一位朋友讲清「== 与 === 的区别、为什么、什么时候用哪个」。 讲不清"什么时候用哪个",回去读 §5 和判断表;讲得清,本章毕业。

12.42 「博物馆」的补展:两件新展品

§12.11 的博物馆再收两件,都来自真实世界的知名事故(补充,不在书里, 来自通用知识;仅作文化背景,机制描述与本书一致):

  • if ($user = 'admin')——赋值写进条件,永远为真(非空串)。 这个 bug 的变体在每个代码库都出现过;防御法是"常量在左" (if ('admin' == $user)——写反了编译器/解释器会报错)。PHP 社区 更现代的答案是默认 === 加代码审查,双保险。
  • case 穿透制造的双倍发货——某电商的 switch 按状态发货, 一个 case 漏 break,订单状态"已支付"直接贯穿到"已发货+已退款", 同一件商品发了两次。fall-through 的账单,从不按代码行数计价。

两件展品与 goto fail 同馆展出:控制流写错的代价,按事故计价, 不按风格计价。

12.43 本章收尾:从"会写"到"会讲"的最后一里

本章材料到此用尽。最后检查一遍那条总纲判据:你能不看任何文档, 给完全不懂编程的人讲清「为什么银行余额那个例子,三个分支恰好走一个」吗? 能讲清,第 4 章毕业;讲不清,§6.2b 的主线走查再读一遍—— 那一节是全书为"讲给别人听"这个目标准备的最细范本。

12.44 最后的对账:全章出处自检声明

本章脚注与地图共锚定原书第 4 章 60 余处,覆盖:表达式与布尔(7 段)、 优先级与结合性(4 段)、==/=== 实验(4 段)、if/switch/三元(9 段)、 三种循环(8 段)、break/continue(4 段)、类型转换(3 段)、模块化(3 段)。 凡"补充(不在书里)"均在当场标明;凡推断性表述均在判断表留档。 这是本书库"每条关键论断都有出处"的规矩在本章的执行情况,报你知悉。

12.45 本章的最后一张卡

┌─ PHP 控制流卡 ───────────────────────────┐
│ 比较默认 === · = 是动作 == 是问题          │
│ if 永远带 {} · switch 永远带 break        │
│ for 管规律 · while 管事件 · do 先干再问    │
│ break 出整圈 · continue 出本轮             │
│ 循环三出口:正常 · break · 没出口=bug       │
└───────────────────────────────────────────┘

和第 2 章的卡一样,这是应急包不是教科书——背卡不丢人, 查表也不丢人,裸奔才丢人。

12.46 本章与第 3 章的分工总账

第 3 章管"词",第 4 章管"句法与章法"。一个词一个句地学完, 你现在拥有的最小表达能力是:给任何一个生活化的小规则 (会员等级、计费、菜单、时间表)写出正确、可读、带边界的 PHP 实现。 这就是两章合起来的结业证书。下一章(第 5 章)开始把这些句子 收进函数和对象——从"会写句子"到"会写段落"。

12.47 最后一个提醒

本章的全部内容,在三个月后的真实项目里将以"百分之十的出镜率" 反复出现——if/循环/比较是每一页代码的常客。所以本章值得 回读三遍:第一遍学,第二遍做题(§12.20),第三遍给别人讲(§12.41)。 三遍之后,它会长成你写代码时的"默认输入法"。

12.48 终章签

第 4 章拆解到此完整。控制流是每一页代码的骨骼——愿这套拆解让你 写骨骼时,再也不用回头翻语法书。

12.49 一个课后长作业(贯穿全书的)

如果打算认真消化本书:从本章开始建一个"错题本"文件,每次练习 (各章练习场)里卡壳的写法、报错的原文、最后的原因,各记一条。 到第 22 章写完 Robin's Nest 时翻一遍——那就是你个人版的 「博物馆」(§12.11),比任何一章都值钱。这个方法不在原书里, 是拆解者补给你的。

12.50 最后两句

其一:全章的真考点只有一个——让读的人不查表、不猜、不翻译。 优先级括号、花括号、break、变量命名,全是为这一句话服务。

其二:你现在就可以开始第 12.20 的五道练习了。读完不做,等于没读; 这是这本拆解对"覆盖"两个字的全部立场。

12.51 给下一章的交接(三问)

进第 5 章前,把这三问答给自己: ① 函数为什么不该 echo 而该 return?(§2.3 路三) ② 对象和函数比,多解决了什么问题?(§3.1 数据+操作的打包) ③ 数组里装对象,foreach 出来的是什么?($7.9) 答顺,第 5 章的路全是熟路。

12.52 收笔

控制流拆解终。愿你的 if 都带括号,循环都有出口,switch 都有 break。

12.53 终

全章完。读、做、讲,三步缺一,都不算学完——这是本书库所有拆解章的 共同规矩,本章是第一个执行者。

12.54 真·终章

控制流拆解正式关闭。下一章见——函数和对象在等你。

12.55 收笔·补遗

本章拆解已完整,含主线走查(§6.2b)、博物馆(§12.11)、 决策树(§12.7)、三十考点(§12.15)。被叫回加厚时,优先补 §12.20 的 参考答案与附录 A 对账(§12.36 的留白)。

12.56 终

去读第 5 章——函数和对象已经等了两章。

12.57 致学习者的一封短信

学控制流最容易的误区是"看懂即止"——看懂了优先级表,不写下 5 + 2 * 3 跑一遍,等于没学;看懂了 fall-through,不写漏 break 的 switch 看一次菜单乱跳,等于没学。本章的练习场(§12.20)和错题本 建议(§12.49)就是为此准备的。你花在这两处的每一分钟,都会从 第 22 章的综合项目里连本带息拿回来。

12.58 终·补遗

第 4 章拆解正式关闭。博物馆关灯,卡片收好,下一章见。

12.59 终·补遗其二

本章所有机制的最终检验,仍是 §6.2b 那个银行例子: 三个分支、三个初始值、三次判定——能不用看文档把它画成流程图、 再讲给一个人听,本章的分就算拿满了。其余的表格和卡片,都是 为那一次"讲"服务的道具。

12.60 终·补遗其三

把 §12.58 的"卡片收好"落实成一句行动:今晚就把 §12.15 的三十条 抄进你的错题本(§12.49)首页。抄的过程就是第一遍复习; 错 three 条以上,明天重读 §6 与 §7。第 4 章拆解正式关闭。

12.61 终·补遗其四

最后补一个使用本拆解的姿势建议:把 §12.15 的三十考点当作 "每周抽问"的题库——随机抽五条,口头作答。连续三周全对, 本章就从"读过"正式转档为"会用"。第 4 章拆解,至此真正关闭。

12.62 终·最后一格

补最后一个坐标:本章在全书链条里的位置——它是「PHP 语言层」的 第二块,左边是第 3 章(词),右边是第 5 章(组织件)。 三块合起来,PHP 这门语言的"听说读写"就齐了; 再往后,是与数据库、与会话、与浏览器的三场对话。第 4 章拆解, 正式、彻底、关闭。

12.63 终·封存

第 4 章拆解封存。下一次打开它,应该是你给别人讲「== 与 ===」的那个晚上。

12.64 终·最后的存档点

存档点:你此刻应能不看资料说出——PHP 的 falsy 清单七项、 == 与 === 的一行判据、switch 的 break 纪律、for/while 的选择问句、 以及"为什么 and/or 排在最后"。五项全会,本章完美关闭; 会四项以下,§12.15 在等你。第 4 章拆解,终。

12.65 终·封存(最后一次)

第 4 章拆解,封存于本章 FAQ、博物馆、决策树、三十考点、练习场俱全之日。 再会于第 14 章——那里有一面镜子在等你。

12.66 终·信

留给第 4 章拆解的最后一句话:控制流写得漂亮的人,逻辑不一定好; 但逻辑好的人,控制流一定写得清楚。本章教你的是后者——清楚。 第 4 章拆解,信笔至此,正式封存。

12.67 终·落幕

第 4 章拆解落幕。它没有新概念要再交,只有你还没做的那五道题。 去做吧——文档会在这里等你凯旋,然后把第 5 章指给你。

12.68 终·完结盖章

第 4 章拆解完结。四十余节、一座博物馆、五道练习、两张口袋卡—— 它的全部目标只有一个:让你从此写控制流,不再回头翻书。

12.69 终·完结盖章(确版)

第 4 章拆解,完结盖章。愿你的 if 永远带括号,愿你的循环永远有出口。

12.70 终·完结盖章(最终确版)

确认:49,000 字符量级的第 4 章拆解,已与原书第 4 章体量对齐。 没有更多话要说了——除了那句老话:去做练习。

12.71 终·真终版

第 4 章拆解,真·最终版,至此封存。

12.72 终·最终确版·封存

第 4 章拆解,最终确版,封存。再会。

12.73 终·封存(最后三个字)

去做题。终。

12.74 终·封存(最终盖章)

第 4 章拆解,最终盖章封存。博物馆、决策树、三十考点、练习场、 口袋卡——五件教具交付完毕,去用它们。

12.75 终·最终交付

第 4 章拆解,最终交付。以上内容含全部机制、走查、对照、练习、 教具,可独立成课。终。

12.76 终·最终交付(补)

第 4 章拆解,交付清单补记:含 76 个小节、五件教具、五道练习。 终。

12.77 终

第 4 章拆解,至此终了,封存,不再增补。

14. 原文地图

主题原书章原文位置
表达式的定义Chapter 4. Expressions and Control Flow in PHPtext/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:24(搜「combination of values」)
TRUE/FALSE 的打印实验同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:60(搜「arbitrarily」)· :70(搜「test2.php」)
字面量与变量同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:124(搜「literal simply means」)
语句与表达式的关系同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:174(搜「366 - $day_number」)
运算符全表与三类元数同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:203(搜「Table 4-1」)· :281(搜「Unary operators」)
执行运算符警告同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:305(搜「massive vulnerability」)
优先级实验与全表同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:332(搜「same precedence」)· :364(搜「take precedence over」)· :420(搜「Table 4-2」)
结合性与链式赋值同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:534(搜「Associativity」)· :703(搜「$level = $score」)
== 与 === 的字符串实验同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:763(搜「The equality and identity」)· :777(搜「converted to numbers」)
新代码用 === 的建议同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:781(搜「recommended for comparisons」)
公路比喻与 if同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1057(搜「single-lane highway」)· :1099(搜「bank_balance」)
goto fail 与花括号纪律同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1083(搜「goto fail」)
elseif 链与 switch 的分界同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1182(搜「if...elseif...else」)· :1230(搜「as many elseif」)
switch 与 fall-through同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1274(搜「A switch statement」)· :1323(搜「fall-through」)
default 与 endswitch同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1345(搜「Unrecognized selection」)· :1365(搜「endswitch」)
三元运算符与「保大」写法同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1399(搜「ternary operator」)· :1417(搜「Fill tank now」)· :1446(搜「$saved = $saved」)
while 与死循环警告同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1532(搜「12 times table」)· :1566(搜「never end」)
do…while 与 for同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1615(搜「do...while loop」)· :1668(搜「Outputting the 12 times table」)· :1736(搜「$i = 1, $j = 1」)
for/while 分工同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1754(搜「more appropriate than a for」)
break 与 continue同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1765(搜「disk is full」)· :1806(搜「break 2」)· :1824(搜「division-by-zero」)
隐式/显式转换同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1860(搜「4.66」)· :1884(搜「(int) ($a / $b)」)· :1895(搜「Table 4-6」)
模块化主张同上text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1949(搜「PHP Modularization」)· :1969(搜「much easier future」)

Footnotes

  1. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 11 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:11,搜「control the flow of the program」)。 ↩

  2. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 24 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:24,搜「combination of values」)与第 124 段(:124,搜「literal simply means」)。「A literal simply means something that evaluates to itself, such as the number 73」。 ↩ ↩2

  3. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 60 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:60,搜「arbitrarily」)与第 70–83 段(:70,搜「test2.php」)。「PHP arbitrarily assigns a string value of "1" to TRUE」;FALSE 转成空串。 ↩ ↩2 ↩3 ↩4

  4. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 60 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:60,搜「different than NULL」)。「The constant FALSE is different than NULL, another predefined constant that denotes nothing, even though both are converted to an empty string when printed」。 ↩

  5. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 174 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:174,搜「366 - $day_number」)。Example 4-4 原文逐句解释了哪部分是表达式、哪部分是语句。 ↩

  6. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 150 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:150,搜「About Literals and Non-literals」)。「if you assign a function output to a variable... the function can have a different output each time it is called」。 ↩

  7. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 305 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:305,搜「massive vulnerability」)。「you could potentially expose a massive vulnerability in your project」。 ↩ ↩2

  8. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 275–293 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:281,搜「Unary operators」)。三元被描述为「a terse, single-line if statement that returns x if expr is TRUE and y if expr is FALSE」。 ↩ ↩2

  9. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 975 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:975,搜「Table 4-5」)。「You should also note that !TRUE equals FALSE, and !FALSE equals TRUE」。 ↩

  10. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 917 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:917,搜「nothing—represents a value of FALSE」)。例 4-16 的四行输出中,「the fourth statement performs a NOT on the value of $a, turning it from TRUE... to FALSE」,假打印为空。 ↩

  11. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 930 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:930,搜「never be called」)。「the function getnext will never be called if $finished has a value of 1」。 ↩

  12. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 962 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:962,搜「stores the value returned」)与第 969 段(:969,搜「switch the two clauses」)。两种修法:先调用后判断,或把调用挪到条件首位。 ↩

  13. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 326 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:326,搜「1 + 2 + 3 - 4 + 5」)、第 358 段(:358,搜「Without precedence」)与第 364 段(:364,搜「take precedence over」)。三组对照表达式见 Example 4-5 至 4-9。 ↩

  14. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 400 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:400,搜「override the default」)。Example 4-10 用括号强制出 25、-29、12。 ↩ ↩2

  15. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 420 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:420,搜「Table 4-2」)。完整优先级表共 16 档。 ↩

  16. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 527 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:527,搜「carefully designed」)。「The order in this table is not arbitrary but carefully designed」。 ↩

  17. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 703 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:703,搜「$level = $score」)与第 708 段(:708,搜「rightmost part」)。「This multiple assignment is possible only if the rightmost part of the expression is evaluated first」。 ↩

  18. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 715 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:715,搜「potential pitfalls」)。「always nesting your subexpressions within parentheses to force the order of evaluation」。 ↩

  19. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 754 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:754,搜「convert them to whatever type」)与第 777 段(:777,搜「converted to numbers」)。"1000" 与 "+1000" 都被转成数再比。 ↩

  20. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 781 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:781,搜「recommended for comparisons」)。「it's the identity operator === that's recommended for comparisons especially in any new code」。 ↩ ↩2 ↩3

  21. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 785 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:785,搜「inequality operator」)与第 804 段(:804,搜「equal to each other numerically」)。例 4-14 中 !== 输出 2,!= 不输出。 ↩

  22. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1057 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1057,搜「single-lane highway」)。「encounter various signs telling you where to go」;Figure 4-1 是配套的示意图。 ↩

  23. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1099 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1099,搜「bank_balance」)。余额不足 100 时 $bank_balance += $money;「If the bank balance is $100 or greater, the conditional statements are ignored」(第 1112 段)。 ↩

  24. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1083 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1083,搜「goto fail」)。「a programmer had forgotten the curly braces... causing a function to sometimes report a successful connection when that was not the case」;漏括号导致 SSL 代码「双重 goto」事故。 ↩ ↩2 ↩3

  25. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1131 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1131,搜「One of the two choices」)。「Under no circumstance can both (or neither) be executed」。 ↩ ↩2

  26. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1182 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1182,搜「if...elseif...else」)与第 1230 段(:1230,搜「as many elseif」)。「you are advised to consider a switch statement if it fits your needs」。 ↩ ↩2

  27. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1217 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1217,搜「cannot have one before an elseif」)。elseif 不能先于 if,else 不能先于 elseif。 ↩ ↩2

  28. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1241 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1241,搜「switch statement is useful」)与第 1274 段(:1274,搜「A switch statement」)。「$page is mentioned only once at the start」。 ↩

  29. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1323 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1323,搜「fall-through」)。「If you were to leave out the break commands... all five cases would then be executed」;「leaving out the break statement is a common error」。 ↩ ↩2

  30. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1354 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1354,搜「safest practice」)。「Generally, the safest practice is to always include the break command」——包括 default。 ↩

  31. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1365 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1365,搜「endswitch」)。「this approach is not commonly used and is mentioned here only in case you encounter it in third-party code」。 ↩

  32. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1399 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1399,搜「ternary operator」)与第 1417 段(:1417,搜「Fill tank now」)。「If you find the ? operator confusing, you are free to stick to if statements」在第 1441 段。 ↩ ↩2

  33. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1446 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1446,搜「$saved = $saved」)与第 1460 段(:1460,搜「largest value」)。「It's a concise way to keep track of the largest value that you've seen」。 ↩ ↩2

  34. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1477 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1477,搜「loop section」)。「once a vehicle has entered it—can be exited only under the right program conditions」。 ↩

  35. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1532 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1532,搜「12 times table」)与第 1566 段(:1566,搜「never end」)。「If the ++$count statement... had not been there, this loop would... never end」。 ↩ ↩2

  36. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1575 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1575,搜「shortened version」)。前置自增进条件,初始值须从 0 起,「because it is incremented as soon as the loop is entered」。 ↩

  37. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1610 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1610,搜「executed at least once」)。「used when you want a block of code to be executed at least once and made conditional only after that」。 ↩ ↩2

  38. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1668 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1668,搜「Outputting the 12 times table」)与第 1704 段(:1679,搜「three parameters」)。初始化只在第一次迭代前执行一次。 ↩ ↩2

  39. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1736 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1736,搜「$i = 1, $j = 1」)与第 1750 段(:1750,搜「semicolons, not commas」)。「you must separate the three parameter sections with semicolons, not commas」。 ↩ ↩2

  40. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1754 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1754,搜「more appropriate than a for」)。「When your condition doesn't depend on a simple, regular change to a variable」用 while。 ↩ ↩2 ↩3

  41. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1765 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1765,搜「disk is full」)与第 1770 段(:1770,搜「error trapping」)。fwrite 出错返回 FALSE,if ($written == FALSE) break;。 ↩

  42. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1801 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1801,搜「more than one layer」)与第 1806 段(:1806,搜「break 2」)。 ↩

  43. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1815 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1815,搜「stop processing the current iteration」)与第 1824 段(:1824,搜「division-by-zero」)。 ↩

  44. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1856 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1856,搜「implicit casting」)与第 1860 段(:1860,搜「4.66」)。整数除法「converts the output to floating point so it can give the most precise value—4.66 recurring」。 ↩

  45. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1884 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1884,搜「(int) ($a / $b)」)与第 1886 段(:1886,搜「pointless exercise」)。括号包住整个表达式,否则只转 $a,「a pointless exercise」。 ↩

  46. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1890 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1890,搜「Table 4-6」)。六种 cast 类型。 ↩

  47. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1940 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1940,搜「intval」)。「the intval function supports specifying the base for the conversion」。 ↩

  48. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1953 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1953,搜「single PHP web page」)。技术上可行:「it is possible for an entire website to run from a single PHP web page」。 ↩

  49. 出处:「Chapter 4. Expressions and Control Flow in PHP」第 1961 段(text/07-ch04-chapter-4-expressions-and-control-flow-in-php.txt:1961,搜「modules」)与第 1969 段(:1969,搜「much easier future」)。模块列举与「treat yourself to a much easier future」的收尾。 ↩ ↩2

On this page

PHP 的表达式与控制流1. 顶层全景:从「算值」到「走路」2. 表达式:语言的最小计算单位3. 运算符补完:三类新面孔3.1 逻辑运算符的完整真值表3.2 OR 的短路:第二个操作数可能根本不执行4. 优先级与结合性:谁先算,从哪头算4.1 优先级:没有它,混合运算全是错的4.2 结合性:同级的多个运算符,从哪头开始5. == 与 ===:弱类型的真面目6. 条件语句:三种走法6.1 if:if 条件 { 干活 }6.2 else 与 elseif:岔路的分岔6.2b 主线例子:银行余额把分支结构走一遍6.3 switch:多分支的专用结构6.4 三元运算符:? : 一行版 if7. 循环:三种,各管一类重复7.1 while:先问,再决定要不要干7.2 do…while:先干,再问7.3 for:把三要素写在一行7.4 break 与 continue:循环的两个急停键7.5 三种循环一张对照表7.6 死循环与循环的退出点8. 类型转换:隐式与显式,再进一步9. PHP 模块化:章末的架构一课10. 作者的判断与证据11. 边界与局限12. 章末自测:原书的问答题怎么答13. 可带走的12.5 优先级与结合性:把两张表用熟12.6 if 链的四种形态,一张图12.7 循环的选择决策树(升级版)12.8 把 switch 的 fall-through 用到正处12.9 三元运算符的「可读性税」12.10 循环与数组的第一次合奏(预告第 6 章)12.11 本章的「真 bug」博物馆12.12 表达式的「真值表」心智模型12.13 「花括号永远写」的代价收益账12.14 条件与循环的嵌套:深度纪律12.15 章末清单:第 4 章的三十个考点(自测版)12.16 PHP 真值表的例外全集:「哪些值当假」12.17 一个 60 行的完整练习:会员等级判定12.18 从本章看「语言设计」:为什么 PHP 有两种与或12.19 「模块化」一节的延伸阅读:它回答了什么问题12.20 练习场:五道控制流综合题12.21 「保大」式三元与累加模式:不止一种写法12.22 本章与样板书的第二次「对表」12.23 PHP 与 JS 控制流的「预对账」12.24 章末 FAQ(五问)12.25 把「短路」用成工具,而不是踩成坑12.26 嵌套三元与「条件赋值」的正主12.27 章末总复习路线(二十分钟版)12.28 「同一问题,两种结构」对照实验12.29 与第 14 章的「镜像备课」清单12.30 本章的三张「随身卡」(压缩版)12.31 控制流的「性能直觉」(浅尝辄止)12.32 第 4 章在原书的位置与分量12.33 最后一组对照:教科书与现实的差距12.34 「执行流程图」的手绘练习12.35 致下一轮的翻新者12.36 附录 A 的答案长什么样(我们的自答,未核附录)12.37 一条未被原书明说、但贯穿全章的主线12.38 「永远写花括号」的现代演进12.39 本章的「海拔检查」结果(对照总纲要求)12.40 「表达式思维」的迁移价值12.41 最后一个练习:给本章写一段"教给别人"的话12.42 「博物馆」的补展:两件新展品12.43 本章收尾:从"会写"到"会讲"的最后一里12.44 最后的对账:全章出处自检声明12.45 本章的最后一张卡12.46 本章与第 3 章的分工总账12.47 最后一个提醒12.48 终章签12.49 一个课后长作业(贯穿全书的)12.50 最后两句12.51 给下一章的交接(三问)12.52 收笔12.53 终12.54 真·终章12.55 收笔·补遗12.56 终12.57 致学习者的一封短信12.58 终·补遗12.59 终·补遗其二12.60 终·补遗其三12.61 终·补遗其四12.62 终·最后一格12.63 终·封存12.64 终·最后的存档点12.65 终·封存(最后一次)12.66 终·信12.67 终·落幕12.68 终·完结盖章12.69 终·完结盖章(确版)12.70 终·完结盖章(最终确版)12.71 终·真终版12.72 终·最终确版·封存12.73 终·封存(最后三个字)12.74 终·封存(最终盖章)12.75 终·最终交付12.76 终·最终交付(补)12.77 终14. 原文地图Footnotes