不积跬步,无以至千里,不积小流,无以成江海。我为我的拖延症感到深深的愧疚,还是继续吧……
第三章 控制流 >> 3.5 while循环与for循环
1.语法格式
前面已经看过一些while循环和for循环的例子,这一节再好好总结一下。
while循环语句的格式:
while (表达式) 语句
while循环的计算规则是:先计算表达式的值,如果为真(即值不为0),则执行语句,然后再判断表达式是否为真,以此循环……直到表达式为假(即值为0),则跳出while循环。
对于for循环语句,格式如下:
for (表达式1; 表达式2; 表达式3) 语句
for循环的计算规则,先执行一次表达式1,然后判断表达式2的值,如果为真,则执行语句,接着执行表达式3;再判断表达式2的值,如果为真,执行语句,接着执行表达式3……循环进行“如果表达式2为真”->“执行语句”->“执行表达式3”。直到表达式2为假(即值为0),则跳出for循环。
可见,从计算规则来看,上述的for循环等价于:
表达式1; while (表达式2) { 语句 表达式3; }
通常,在for循环中的3个组成部分中,表达式1和表达式3是赋值表达式或函数调用,表达式2是关系表达式。
要注意,这3个表达式的任何一个都可以省略,但分号必须保留。如果省略测试条件(即表达式2),则认为其值永远为真,所以,
for (; ;) ( ... ... )
这个for循环式一个“无限”循环语句,这种情况下就需要借助其他手段(比如break、return、goto语句)才能跳出循环。
2.while和for的选择
在使用while循环还是使用for循环的选择上,尽管取决于程序设计人员的个人偏好,不过有些基本的规则我们还是可以参考的。
如果语句中需要执行简单的初始化或变量递增,建议使用for语句,反之则使用while语句。比如:
for (i = 0; i < n; i++) ... ...
这是用于处理数组多个元素时的常见用法。
for语句的初始化和变量递增部分不应放置一些和循环控制运算无关的表达式。for语句的一大特点便是循环控制部分的结构清晰明了,如果在这里掺入一些无关的表达式,未免画蛇添足之嫌。
3.实例一:将字符串转换为对应整型值
这次来重写第二章的函数atoi()。
#include <stdio.h> #include <ctype.h> /* atoi: convert s to integer; version 2 */ int atoi(char s[]) { int i, n, sign; for (i = 0; isspace(s[i]); i++) /* skip white space */ ; sign = (s[i]== '-') ? -1 : 1; if (s[i]== '+' || s[i]== '-') /* skip sign */ i++; for (n = 0; isdigit(s[i]); i++) n = 10 * n + (s[i]- '0'); return sign * n; } int main() { char *str1 = "+12345"; char *str2 = "-12345"; char *str3 = " 12345"; printf("%s = %d\n", str1, atoi(str1)); printf("%s = %d\n", str2, atoi(str2)); printf("%s = %d\n", str3, atoi(str3)); return 0; }
程序的结构是:
- 如果有空白符,跳过
- 如果有符号(“+”或者“-”),读取符号
- 去整数部分,执行转换
4.实例二:整型数组的Shell排序算法
Shell排序算法由 D.L. Shell与1959年发明,基本思想是:先比较距离远的元素,而不是像冒泡排序算法那样先比较相邻的元素,这样可以快速减少大量的无序情况,从而减轻后续的工作。被比较元素之间的举例逐步减小,直到减小到1,此时排序就变成了相邻元素的互换。
#include <stdio.h> /* shellsort: sort v[0]...v[n-1] into increasing order */ void shellsort(int v[], int n) { int gap, i, j, temp; for (gap = n/2; gap > 0; gap /= 2) for (i = gap; i < n; i++) for (j=i-gap; j>=0 && v[j]>v[j+gap]; j-=gap) { temp = v[j]; v[j]= v[j+gap]; v[j+gap] = temp; } } int main() { int i; int a[10] = {10, 5, 8, 3, 4, 9, 4, 7, 1, 1}; printf("before sorting:\n"); for (i = 0; i < 10; i++) printf("%d ",a[i]); printf("\n"); shellsort(a, 10); printf("after sorting:\n"); for (i = 0; i < 10; i++) printf("%d ",a[i]); printf("\n"); return 0; }
里面有个三重嵌套的for循环语句:
- 最外层的for语句:控制两个被比较元素之间的距离。从n/2开始依次对折,直到距离为0;
- 中间层的for语句:在元素间移动位置;
- 最内层的for语句:比较每一对距离为gap的元素,如果逆序则把它们的互换过来。
当最终gap的值递减到1时,进行相邻元素的比较/互换之后,所有的元素也就都处于正确的排序位置上了。
5.实例三:字符串倒置
倒置字符串s中的各个字符的位置。
#include <stdio.h> #include <string.h> /* reverse: reverse string a in place */ void reverse(char s[]) { int c, i, j; for (i = 0, j = strlen(s)-1; i < j; i++, j--) { c = s[i]; s[i]= s[j]; s[j]= c; } } int main() { char str[] = "0123456789"; printf("befor: %s\n", str); reverse(str); printf("after: %s\n", str); return 0; }
在上面的例子中,我们看到在for循环语句中使用了逗号运算符,用于同时处理循环控制的两个变量。
逗号运算符“,”是C语言中优先级最低的运算符。被逗号分隔的两个表达式按照从左到右的顺序进行求职,表达式右边的操作数的类型和值即为整个表达式结果的类型和值。
6.不是所有的逗号,都是逗号运算符
比如分隔函数参数的逗号、分隔声明中变量的逗号等,这些并不是逗号运算符,当然也不能保证表达式按照从左至右的顺序求值。
逗号运算符应该慎重使用,比较适用于关系紧密的结构,比如上述实例三中的元素交换过程,可以通过如下的方式,将其看成一个单步操作:
for (i = 0, j = strlen(s)-1; i < j; i++, j--) c = s[i], s[i]= s[j], s[j]= c;