- C语言学习指南:从规范编程到专业级开发
- (美)杰夫·苏哈伊
- 1711字
- 2025-02-23 15:03:30
7.5 for()...循环语句
for()...语句的语法是:

for()...语句由控制表达式跟语句体组成,其中,控制表达式分成counter_initialization(计数器初始化)、continuation_expression(延续表达式,也叫条件表达式)与counter_increment(计数器递增[1])这三部分,相邻两部分之间用分号隔开。这三部分都有各自的用途,顺序不能互换。
程序刚遇到for()...语句时,会求出counter_initialization表达式的值,这个表达式只在程序第一次遇到for()...语句的时候求值一次。然后,程序开始求continuation_expression的值,如果它是true,那么就执行statement_body(语句体)。执行完这次的statement_body之后,程序会求counter_increment表达式的值。接下来,程序会再度求continuation_expression的值,并重复上述过程,直到continuation_expression是false为止。如果第一轮求值就发现continuation_expression是false,那么statement_body一次也不会执行。
其中,statement_body这一部分可以是一条简单的语句,甚至可以是空语句(也就是不带任何表达式,只以;结尾的语句),然而在大多数情况下,它都是一条复合语句。请注意,for()...语句本身是不以分号结尾的,如果它的语句体是一条简单语句,那么这条简单语句要用分号结尾,如果它的语句体是一条复合语句,那么这条复合语句应该用一对花括号括起来,这对花括号的末尾不加分号。
在for()...语句里面,所有的控制元件(control element,也就是控制循环是否应该继续执行的那些元件)都会出现在for右侧的那对括号里面。C语言是故意这么设计的,因为这样可以让开发者把这些元件全都写在一起。如果语句体(也就是statement_body这一部分)比较复杂或者比较长,那么这样写的好处尤其明显,它会让阅读代码的人注意到这个循环所使用的各种控制变量。
counter_increment表达式可以是任何一条用来递增、递减或修改计数器的表达式。另外要注意,在for循环里面所声明并初始化的变量不能放在for的语句体(也就是statement_body)之外使用。这就好比函数的参数只能在函数体里面使用一样。这个问题我们会在第25章详细讲解。
现在回到高斯求和问题,我们这次用for()...循环来实现这个函数,让它接受名为N的参数,用以表示有待求和的这个整数序列里面最大的那个数。这个函数返回1~N的整数之和。我们需要用一个变量(也就是sum变量)来存放求和结果,这个变量的初始值应该是0,另外还需要在for()...语句的第一部分(也就是counter_initialization这一部分)里面声明并初始化一个num变量,以充当计数器。这个变量会从0取到(N-1),当它变为N的时候,for循环的循环条件就不再成立(这个条件是num<N,如果num本身也是N,那么N并不小于N,因此条件不再成立),这时程序会结束循环。在循环体里面,我们只需要把当前这个整数(也就是由num+1所表示的这个整数)累计到sum里面即可。循环结束之后,把sum变量的值返回给调用方。
这个名叫sumNviaFor()的函数写在gauss_loop.c程序里面,它的代码是这样的:

与之前讲解while()...循环时类似,这次我们也要注意off-by-one问题。另外,具体到这个例子来说,用for()...循环求解跟用while()...循环一样,也有两种办法。我们除了从小往大算,还可以从大往小算。这种办法也使用函数的参数N作为计数器,因此不需要另建一个计数器变量。前面讲过,调用函数时程序会把调用方传入的参数值复制到相应的参数里面,因此这种参数可以像局部变量一样在函数内部使用。于是,我们就用参数N充当计数器。这次不让它从0开始递增,而是让它从N开始递减,等它降到0时,循环条件就会变成false,这会让程序结束循环。
与之前用while()...循环求解时类似,这次用for()...循环求解时的第二种写法也要比第一种写法稍微好一些,因为它避免了off-by-one问题,而且它所采用的计数器表示的正是我们做求和运算时所要计入的这个整数。
用这种写法写成的sumNviaFor2()函数会放在gauss_loop2.c程序文件里面,它的代码是这样的:

最后要注意,这次的sumNviaFor2()函数把for右侧那一对括号里面的代码写成了三行,让每一部分都占据一行。如果某一部分的写法比较复杂,那么这种格式就很便于阅读,而且便于单独针对这一部分撰写注释。
比方说,如果我们的计数器有两个,其中一个要递增,另一个要递减,那么就可以把这两个计数器都写在counter_initialization这一部分里面,并用序列运算符(也就是逗号运算符)相连接。这两个计数器变量的递增与递减操作可以放在counter_increment这一部分里面执行,这两项操作也用逗号(,)连接。下面以一个这样的for()...循环为例:

为了把控制表达式与循环体清楚地区分开,我们应该适当地使用缩进。刚才这个例子实在太简单了,因此不一定非得调整代码格式,但如果控制表达式比较复杂,那就必须注意代码的格式了。
[1] 也可以叫作计数器调整,因为for循环所用的计数器不一定总是递增的。——译者注