第3章控制流
语言的控制流语句指定了计算执行的顺序。在前面的例子中,我们已经见过了大部分常见的控制流语句;这里我们会将它补全,并给出比之前更精确的说明。
3.1 语句和块
如 x = 0 或 i++ 或 printf(...) 这样的表达式,在末尾添加分号之后,就成为了语句,如
x = 0;
i++;
printf(...);
在C中,分号是语句的结束符,而不是分隔符(如Paslcal这样的语言中的分号是分隔符)。
大括号 { 和 } 用来将一些声明和语句组合成复合语句 ,或者称为块,这样它们在语法上和单条语句是等价的。一个显著的例子是用来包围函数内所有语句的大括号;而另一个例子是 if,else,while 和 for 后面包围多条语句的大括号。(变量可以在任意块中声明,这个将在第四章讨论。)块末尾的右大括号后面,没有分号。
3.2 if-else
if-else 语句用来表示决策。正式的语法为
if (表达式)
语句1
else
语句2
其中 else 部分是可选的。对表达式求值;如果它为真(即表达式有非0值),则执行语句1。如果它为假(表达式为0)且存在 else 部分,则执行语句2。
既然 if 只会简单地检查表达式的数值,则某些代码就能有简化写法。最明显的是写
if (表达式)
而不写
if (表达式 != 0)
有时这个写法是自然而且清晰的,而有时会令人费解。
因为 if-else 的 else 部分是可选的,在嵌套的 if 序列内去掉 一个else 就会造成歧义。通过将 else 关联到最近一个没有 else 的 if,可以消解这个歧义。例如
if (n > 0)
if (z > b)
z = a;
else
z = b;
其中的 else 关联里面的 if,与我们的缩进一致。如果这不是你要的代码逻辑,就需要使用大括号来强制得到合适的关联:
if (n > 0) {
if (z > b)
z = a;
} else
z = b;
这种歧义在一些情况下特别恶劣,比如:
if (n >= 0)
for (i = 0; i < n; i++)
if (s[i] > 0) {
printf("...");
return i;
}
else /* 错了!!! */
printf("error -- n is negative\n");
缩进明确表示了你想要什么,但编译器无法获取这个信息,并将 else 关联到内层的 if 。这种类型的 bug 很难找;而只要遇到嵌套的 if,就加上大括号,是个不错的主意。
顺带一提【应该是给Pascal程序员看的】,下面的 z = a 后面有分号。
if (n > 0)
if (z > b)
z = a;
else
z = b;
这是因为在语法上,if 后面跟的是个语句,而表达式语句如 " z = a; " 总是以分号结尾。
3.3 else-if
if (expression)
statement
else if (expression)
statement
else if (expression)
statement
else if (expression)
statement
else
statement
上面这个结构在C程序中出现得如此频繁,因此值得将它单独拿出来简要地讨论一下。它是写多路选择的最常用的方式。其中的表达式(expression)被依次求值;如果任一表达式为真,则其关联的语句(statement)被执行,且整个链条结束。当然,其中的每个"语句"可以是单条语句,或者是大括号中的一组语句。
当其他条件都不满足时,最后一个 last 部分用来处理"以上皆不是"或者说默认情况。有时对默认情况不需要做显式的动作,此时末尾的
else
statement
就可以去掉,或者用来进行错误校验,捕获"不可能发生"的情况。
下面这个二分查找函数用来确定一个特定的值 x 是否存在于一个已排序的数组 v 中,它演示了三路决策。v 的元素必须是升序的。如果 x 存在于 v 中,函数返回其所在的位置(0 到 n - 1之间的值),否则返回 -1。
二分查找首先将输入值 x 与 数组 v 中间的元素进行比较。如果 x 小于中间的值,则搜索聚焦于数组的下半部分,否则聚焦于上半部分。不管是哪种情况,下一步是将 x 与 所选一半的中间值进行比较。这个过程持续地将范围一分为二,直到找到值,或者范围为空。
/* 二分查找:在 v[0] <= v[1] <= ... <= v[n-1] 中找到x */
int binsearch(int x, int v[], int n)
{
int low, high, mid;
low = 0;
high = n - 1;
while (low <= high) {
mid = (low + high) / 2;
if (x < v[mid])
high = mid -1;
else if (x > v[mid])
low = mid + 1;
else /* 找到匹配值 */
return mid;
}
return -1; /* 没找到 */
}
基本的决策是在每步要判断 x 是小于、大于或等于中间元素 v[mid] 的值;用 else-if 是很自然的。
练习 3-1、我们的二分查找在循环中做了两次判断,而一个判断就足够了(代价是在外面做更多判断)。写个函数只在循环内做一次判断,并测量运行时间的差异。