C语言学习第二十二天。
3.5 while循环与for循环
在while循环语句
while (表达式)
语句
中,首先求表达式的值。如果其值为真非0,则执行语句,并再次求该表达式的值。这一循环过程一直进行下去,直到该表达式的值为假(0)为止,随后继续执行语句后面的部分。
for循环语句
for (表达式1; 表达式2; 表达式3)
语句
它等价于下列while语句:
表达式1;
while (表达式1){
语句
表达式3;
}
从语法角度看,for循环语句的3各组成部分都是表达式。最常见的情况是,表达式1与表达式3是赋值表达式或函数调用,表达式2是关系表达式。这三个组成部分中的任何部分都可以省略,但分号必须保留。如果在for语句值哦改河南省略表达式1与表达式3,它就退化成了while循环语句。如果省略测试条件,即表达式2,则认为其值永远是真值,因此,下列for循环语句:
for (; ;) {
...
}
是一个“无限”循环语句,这种语句需要借助其他手段(如break语句或return语句)才能终止。
如何选择主要取决于个人偏好。例如,在下列语句中:
while ((c = getchar()) == ' ' || c == '\n' || c == '\t')
; /* 跳过空白符*/
因为其中没有初始化或重新初始化的操作,所以使用while循环语句更自然一些。
如果语句中需要执行简单的初始化和变量递增,使用for语句更合适一些,它将循环控制语句集中放在循环的开头,结构更紧凑、更清晰。 例如:
for (i = 0; i < n; i++)
...
这是C语言处理数组前n各元素的一种习惯性用法。
下面是将字符串转换为对应数值的函数atoi。它可以处理可选的前导空白符以及一个可选的加(+)或减(-)号。
下面是程序结构,从中可以看出输入的格式:
如果有空白符的话,则跳过
如果有符号的话,则读取符号
取整数部分,并执行转换
其中每一步都对输入数据进行相应的处理,并为下一步的执行做好准备。当遇到第一个不能转换为数字的字符是,整个处理过程终止。
#include <stdio.h>
#include <ctype.h>
int main() {
printf("%d", atoi(" -123xf2"));
}
/* atoi函数:将s转换为整数型;*/
int atoi(char s[]) {
int i, n, sign;
for(i = 0; isspace(s[i]); i++) /* 跳过空白符 */
;
sign = (s[i] == '-') ? -1 : 1;
if (s[i] == '+' || s[i] == '-') /* 跳过符号 */
i++;
for (n = 0; isdigit(s[i]); i++)
n = 10 * n + (s[i] - '0');
return sign * n;
}
标准库中提供了一个更完善的函数strol, 它将字符串转换为长整型。
把循环控制部分集中在一起,对与多重嵌套循环,优势更为明显。下面的函数是对整型数组进行排序的shell排序算法。shell排序算法是D.L.Shell于1959年发明的,其基本思想是:先比较距离远的元素,而不是向简单交换排序算法那样先比较相邻的元素。这样可以快速减少大量的无序情况,从而减轻后续的工作。被比较的元素之间的距离逐步减少,直到减少为1,这是排序变换成了相邻元素的互换。
#include <stdio.h>
void shellsort(int v[], int n);
int main() {
int a[] = {3, 5, 62, 4, 6, 32 , 6};
int i = 0;
shellsort(a, 7);
for (; i < 7; i++)
printf("%d ", a[i]);
}
/* shellsort函数:按递增顺序对v[0]...v[n-1]进行排序 */
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;
}
}
该函数中包含一个三重嵌套的for循环语句。最外层的for语句控制两个被比较奥元素之间的距离,从n/2开始,逐步进行对这,直到距离为0.中间层的for循环语句用于在元素间移动位置。最内层for语句用于比较各相对gap个位置的元素,当这两个元素逆序时,把他们互换过来。由于gap的值最终要递减到1,因此所有元素最终都会位于正确的排序位置上。注意,即是最外层for循环的控制变量不是算术级,for语句的书写形式仍然没有变,这就说明for语句具有很强的通用性。
逗号运算符“,”也是C语言优先级最低的运算符,在for语句中经常会用到它。被逗号分隔的一对表达式将按照从左到右的顺序进行求值,各表达式右边的操作数的类型和值即为其结果的类型和值。这样,在for循环语句中,可以将多个表达式放在各个语句成分中,比如同时处理两个循环控制变量。下面以函数reverse(s)来举例。该函数用于倒置字符串s中各个字符的位置。
#include <stdio.h>
#include <string.h>
void reverse(char s[]) ;
int main() {
char a[] = "warning!";
reverse(a);
printf("%s", a);
}
/* reverse函数:倒置字符串s中各个字符的位置 */
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;
}
}
某些情况下的逗号并不是逗号运算符,比如分隔函数参数的逗号,分隔声明中变量的逗号等,这些逗号并不保证各表达式按照从左往右的顺序求值。
应该慎用逗号运算符。逗号运算符最适用于关系紧密的结构中,比如上面的reverse函数的for语句,对于需要在单个表达式中进行多步计算的宏来说也很合适。逗号表达式还适用于reverse函数中的元素的交换,这样,元素的交换过程便可以看成是一个单步操作。
for (i = 0, j = strlen(s)-1; i < j; i++, j--)
c = s[i], s[i] = s[j], s[j] = c;
练习 3-3 编写函数expand(s1, s2) ,将字符串s1中类似于a-z一类的速记符号在字符串s2中扩展为等价的完整列表abc...xyz.该函数可以处理大小写字母和数字,并可以处理a-b-c、a-z0-9于-a-z等类似的情况。作为前导和尾随的-字符原样排印。
#include<stdio.h>
void expand(char s1[], char s2[]);
int main() {
char s1[] = "-a-z0-9a-b-c";
char s2[100];
expand(s1, s2);
printf("%s", s2);
}
void expand(char s1[], char s2[]) {
int i, j, k;
i = j = k = 0;
while(s1[i] != '\0') {
if (i > 0 && s1[i] == '-') {
for (k = 0; k < s1[i+1] - s1[i-1]; k++)
s2[j++] = s1[i-1] + k + 1;
i++;
} else {
s2[j++] = s1[i];
}
i++;
}
s2[j] = '\0';
}