跳至内容
Topos Uranos

摘要

本课结束本课程的第四单元,这一单元专讲流程控制。本课重建从条件通向数值算法的概念链,途经判断与分情形分析、循环及其三种形式、循环模式、不变式与变式;把其中适用于任何语言的内容与 C++ 特有的内容区分开;汇总本单元的未定义行为,它集中在会溢出的计数器、累加器和条件上,并给出一个展示它的程序及其修正;在结业测评中重复入门测评的各项技能;最后给出六道综合整个单元的挑战题,从扩展的辗转相除法一直到一个月的日历。

学习目标

  1. 设计情形互斥且穷尽的判断,并用每个情形、每条边界各一个输入来测试它。
  2. 设计会终止的正确循环,从规格说明出发选择其形式和模式。
  3. 用不变式论证循环的正确性,用变式论证其终止性,包括本单元的数值算法。
  4. 识别本单元的未定义行为(会溢出的计数器、累加器和条件,以及条件本应阻止的除法),并改写循环以消除它。
  5. 在本单元所学的内容中,区分适用于任何语言的内容与 C++ 特有的内容。

本单元的收获

本单元的目标是教会用判断和循环支配执行顺序,并对其正确性进行推理:每个情形都得到处理,每个循环都做它该做的事,并且会终止。这一目标已经达成,所依靠的只是导言声明为已知的内容:第 3 单元的条件、第 2 单元的类型,以及第 1.1 课的跟踪与变式。那个用三个固定步骤计算最大公约数、并且视数据不同会答错或除以零的程序,被一个三行的循环取代,而现在可以证明这个循环对所有非负数据都是正确的。

判断

判断一课讲授了 if 语句,并且始终带花括号。它表明,在一串 else if 中执行的是第一个条件为真的分支,因此每个分支的实际情形是它自己的条件加上前面所有条件的否定;正因如此,顺序很重要,而且某个分支可能在没有任何警告的情况下变得不可达。它确立了一切判断的正确性准则:其情形必须互斥(由判断链的形式保证)且穷尽(若以 else 结尾即是如此),并且必须与规格说明中的情形一致,这要在每个情形的内部以及每条边界的两侧进行测试。它介绍了条件运算符,用来在两个简短的值之间作选择而不必写一个完整的判断。最后讲了 enum class 和 switch,其中遗漏的情形会由编译器发出警告。

循环

循环一课把 while、do 和 for 介绍为同一机制的三种形式:while 在每一轮之前计算条件,可能一轮也不执行;do 至少执行一轮;for 在头部集中了初始化、条件和步进,其循环变量只在循环内存在。它讲授了三者的等价关系、跟踪表(一个因条件而终止的 while 计算条件 k+1k + 1 次,执行 kk 轮;在 do 中则计算 kk 次),以及根据头部计算轮数,这能预防差一错误。它还加入了 break、continue 和嵌套循环,其内层循环体的执行次数是一个和:界限固定时为 m⋅pm \cdot p,三角形时为 n(n+1)/2n(n + 1)/2。

设计正确的循环

设计正确的循环一课把循环变成了设计的对象。它汇集了各种模式(计数器、累加器、最大值与最小值、哨兵和标志),每一种都由其变量在每一轮之后的含义来定义。它定义了不变式,即每次计算条件时都成立的断言,以及它的三项义务:初始化、保持和终止,后者与为假的条件一起给出后置条件;并且把它认作对轮数的归纳。它教会反向设计,从后置条件到不变式,再从不变式到循环体。它用变式论证终止性,并展示了三个被 C++ 的算术破坏了变式的循环:跳过 1.0 的实数循环、发生回绕的无符号计数器,以及溢出的有符号计数器。最后,它带来了与调试器的第一次接触。

数值算法

数值算法一课把上述内容应用于四个经典算法,它们都写在 main 之内并且得到了证明。用试除法进行素性检验,附有只需试到 n\sqrt{n} 的命题,以及条件 d <= n / d,在 d * d <= n 会溢出的地方它不会溢出。辗转相除法,附有不变式 gcd⁡(a,b)=gcd⁡(a0,b0)\operatorname{gcd}(a, b) = \operatorname{gcd}(a_0, b_0) 和变式 bb,以及它在 C++ 中的两个危险:负的结果,以及在循环体之后才计算条件时的除以零。用逐次相除进行进制转换,以及秦九韶算法(霍纳法则),它每个系数只用一次乘法来计算多项式的值。还有二分法,它由介值定理保证,由一个整数计数器控制,其轮数可以事先算出。