- 现代C:概念剖析和编程实践
- (德)延斯·古斯泰特
- 1613字
- 2025-04-08 00:44:16
3.2 循环
在前面,我们遇到了遍历域的for
语句,在我们的介绍性示例中,它声明了一个变量i
,该变量被设置为值0
、1
、2
、3
和4
。这个语句的一般形式是:

这个语句其实很笼统。通常,clause1
是一个赋值表达式或变量定义。它用于声明循环域的初始值。condition2
测试循环是否应该继续。然后,expression3
修改clause1
中使用的循环变量。它在每次循环结束时执行。以下是一些建议:
- 因为我们希望在
for
循环的上下文中严格定义循环变量(参见要点2.11),所以在大多数情况下,clause1
应该是一个变量定义。 - 因为
for
有四个不同的部分,相对比较复杂,而且从直观上不容易理解,所以statement-
or
-block
通常应该是{ ... }
块。
让我们再来看几个例子:

第一个for
中i
从10
减到1
。条件仍然是计算变量i
的值,不需要针对值0
进行多余的测试。当i
变为0
时,它将被计算为false,循环将停止。第二个for
声明了两个变量i
和stop
。和前面一样,i
是循环变量,stop
是我们在条件中比较的对象,当i
大于或等于stop
时,循环终止。
第三个for
看起来会一直走下去,但实际上是从9减到0。事实上,在下一章中,我们将看到C中的“大小”(类型为size_t
的数字)永远不会为负数[练习2]。
注意,这三个for
语句都声明了名为i
的变量。只要它们的作用域不重叠,那么这三个同名的变量就可以很好地共存。
在C语言中还有两个循环语句,while
和do
:

下面的示例展示了第一种方法的典型用法。它实现了所谓的Heron近似值来计算一个数x的倒数1/x。

只要给定条件的计算结果为true,它就会循环。do
循环非常相似,只是它检查依赖块之后的条件:

这意味着如果条件的计算结果为false
,while
循环将根本不运行其依赖块,而do
循环将在终止之前运行一次依赖块。
与for
语句一样,在do
和while
中建议使用{ ... }
块的变体。两者之间还有一个微妙的语法区别:do
始终需要一个;
在while
(条件)之后终止语句。稍后,我们将看到这是一个语法特性,在多个嵌套语句的上下文中非常有用。参见10.2.1节。
通过使用break
和continue
语句,这三个循环语句变得更加灵活。break
语句不需要重新计算终止条件或者执行break
语句后面的依赖块部分就停止循环:

这样我们就可以把a*x
的计算,停止条件的计算,以及x
的更新区分开来,while
条件就变得无关紧要了。也可以用for
来完成同样的事情,在C程序员中有这样一个传统来将其写成:

for
(;;)
在这里相当于while(true)
。事实上,for
的控制表达式(在;;
中间的部分)可以省略,可被解释为“总是true
”,这只是C规则中的一个历史产物,没有其他特殊的用途。
continue
语句的使用频率较低。与break
类似,它跳过依赖块其余部分的执行,因此在continue
之后的块中的所有语句都不会在当前循环中执行。但是,如果条件为真,则重新计算条件并从依赖块的开始处继续执行:

在这些例子中,我们使用了一个标准的宏fabs
,它来自tgmath.h
头文件[1]。它计算一个double
类型的绝对值。清单3.1是一个完整的程序,它实现了同样的算法,其中fabs
被几个与某些固定数字的显式比较所取代:例如,eps1m24
定义为1 - 2-24
,或eps1p24
定义为1 + 2-24
。我们将在后面(5.3节)看到常量0x1P-24
和类似定义的常量是如何工作的。
在第一阶段,将当前观察的数a
与当前估计的数x
的乘积分别与1.5
和0.5
进行比较,然后将x
乘以0.5
或2
,直到乘积接近1
。然后,在第二次循环中看是否接近代码中所示的Heron近似值,并以高精度来计算倒数。
程序的主要任务是计算在命令行上提供的所有数字的倒数。程序执行的样子如下:

为了处理命令行上的数字,该程序使用stdlib.h
中的另一个库函数strtod
[练习3] [练习4] [练习5]。
挑战1 顺序排序算法
你会使用合并排序(利用递归)和快速排序(利用递归)对具有双精度或字符串排序键的数组进行排序吗?
如果不知道程序是否正确,你将一无所获。因此,你能提供一个简单的测试程序来检查结果数组是否真的排序了吗?
这个测试程序应该只扫描一次数组,并且应该比排序算法快得多。
清单3.1 计算数的乘法逆

[1]“tgmath
”代表泛类型数学函数。
[练习2]试着想象一下,当i
的值为0
并且通过运算符--
递减时会发生什么。
[练习3]通过添加对中间值x
的Printf
调用来分析清单3.1。
[练习4]描述清单3.1中参数argc
与argv
的作用。
[练习5]打印出eps1m01
的值,然后观察当你对它们进行微调之后的输出。