C语言学习第二十三天。
3.6 do-while循环
while与for这两种循环在循环体执行前对终止条件进行测试。与此相反,C语言中的第三种循环——do-while循环则在循环体执行后测试终止条件,这样循环体至少被执行一次。
do-while循环的语法形式如下:
do
语句
while(表达式);
在这以结构中,先执行循环体中的语句部分,然后在求表达式的值。如果表达式的值为真,则再次执行语句,依次类推。当表达式的值变为假,则循环终止。
经验表明,do-whiile循环比while循环和for循环用得少的多。尽管如此,do-while循环语句有时还是很有用的,下面通过函数itoa来说明。itoa函数是atoi函数的逆函数,它把数字转换为字符串。如果按照atoi函数中生成数字的方法将数字转换为字符串,则生成的字符串的次序正好是颠倒的,因此,我们首先要生成反序的字符串,然后再把字符串倒置。
/* itoa函数:将数字n转换为字符串并保存到s中 */
void itoa(int n, char s[]) {
int i, sign;
if ((sign = n) < 0) /* 记录符号 */
n = -n;
i = 0;
do { /* 以反序生成数字,求值从个位开始,得到的字符串是数字的反序,最后需要翻转下 */
s[i++] = n % 10 + '0'; /* 取下一个数字*/
} while ((n /= 10) > 0);
if (sign < 0)
s[i++] = '-';
reverse(s);
}
这里有必要使用do-while语句,至少使用do-while语句会方便一些,因为即是n的值为0,也至少要把一个字符放到数组s中。其中的do-while语句体中只有一条语句,尽管没有必要,但我们仍然用花括号将该语句括起来了,这样做可以避免草率的读值将while部分误认为是另一个while循环的开始。
3.7 break语句与cotinue语句
不通过循环头部或尾部的条件测试二跳出循环,有时是很方便的。break语句可用于从for、while与do-while等循环中提前退出,就如同从switch语句中提前退出一样。break语句能使程序从switch语句或最内层循环中立即跳出。
下面的函数trim用于删除字符串尾部的空格符、制表符与换行符。当发现最右边的字符为非空格符、非制表符、非换行符时,就使用break语句从循环中退出。
/* trim函数:删除字符串尾部的空格符、制表符与换行符 */
int trim(char s[]) {
int n;
for (n = strlen(s)-1; n >= 0; n--)
if (s[n] != ' ' && s[n] != '\t' && s[n] != '\n')
break;
s[n+1] = '\0';
return n+1; // 原书这里是n,实际上应该是n+1才对,n是最后一个字符的下标,n+1才是字符串的长度
}
strlen函数返回字符串的长度。for循环从字符串的末尾开始反方向扫描寻找第一个不是空格符、制表符以及换行符的字符。当找到符合条件的第一个字符,或当循环控制变量n变为负数时(即整个字符串都被扫描完时),循环终止执行。
continue语句与break语句时相关联的,但它没有break语句常用。continue语句用于使for、while或do-while语句开始下一次循环的执行。再while与do-while语句中,continue语句的执行意味着立即执行测试部分;再for循环中,则意味这时控制转移到递增循环变量部分。continue语句值用于循环语句,不用与switch语句。某个循环包含的switch语句中的continue语句,将导致进入下一次循环。
例如,下面这段程序用于处理数组a中的非负元素。如果某个元素的值为负,则跳过部处理。
for (i = 0; i < n; i++) {
if (a[i] < 0) /* 跳过负元素 */
continue;
... /* 处理正元素 */
}
当循环的后面部分比较复杂时,常常会用到continue语句。
3.8 goto语句与标号
C语言提供了可随意滥用的goto语句以及标记跳转位置的标号。从理论上讲,goto语句时没有必要的,实践中不使用goto语句也可以很容易地写出代码。但是,再某些场合下goto语句还是用得着的。最常见的用法时终止程序在某些深度嵌套的结构中的处理过程。例如一次跳出两层或多层循环。这种情况下使用break语句是不能达到目的的,它只能从最内层循环退出到上一级的循环。下面是使用goto语句的一个例子:
for (...)
for (...) {
...
if (disaster)
goto error;
}
...
error:
处理错误情况
在该例子中,如果错误处理代码很重要,并且错误可能出现在多个地方,使用goto语句将会比较方便。
标号的命名同变量命名的形式相同,标号的后面要紧跟一个冒号。标号可以位与对应的goto语句所在函数的任何语句的前面。标号的作用域是整个函数。
另外一个例子。考虑判定两个数组a与b中是否具有相同元素的问题。一种可能的解决方法是:
for (i = 0; i < n; i++)
for (j = 0; j < m; j++)
if (a[i] == b[j])
goto found;
/* 没有找到任何相同元素 */
...
found:
/* 找到一个相同元素: a[i]==b[j]*/
...
所有使用了goto语句的程序代码都能改写成不带goto语句的程序,但可能会增加一些额外的重复测试或变量。例如,可将上面判定是否具有相同数组元素的程序段改写成下列形式:
found = 0;
for (i = 0; i < n && !found; i++)
for (j = 0; j < m && !found; j++)
if (a[i] == b[j])
found = 1;
if (found)
/* 找到一个相同元素: a[i]==b[j] */
...
else
/* 没有找到任何相同元素 */
...
大多数情况下,使用goto语句的程序段比不使用goto语句的程序段要难以理解和维护,少数情况除外,比如我们前面所举的几个例子。尽管该问题并不太严重,但我们还是建议尽可能少地使用goto语句。
练习3-4 在数的对二的补码表示中,我们编写的itoa函数不能处理最大的负数,即n等与-2^(字长-1)的情况。请解释其原因。修改该函数,使它在任何机器上运行时都能打印出正确的值。
练习3-5 编写函数itob(n, s, b),将整数n转换为以b为底的数,并将转换结果以字符的形式保存到字符串s中。例如,itob(n, s, 16)把整数n格式化成十六进制整数保存在s中。
编写3-6 修改itoa函数,使得该函数可以接收三个参数。其中第三个参数为最小字符宽度。为了保证转换后所得的结果至少具有第三个参数指定的最小宽度,在必要时应在所得结果的左边填充一定的空格。