3.1 语句与程序块
在表达式之后加上一个分号(;),它们就变成了语句。用一对花括号“{”与“}”把一组声明和语句括在一起就构成了程序块,在语法上等价于单条语句。
3.2 if-else语句
每个else与最近的前一个没有else配对的if进行匹配。
if (n > 0) if (a > b) z = a;else z = b;
程序的缩进结构明确表明了设计意图,但编译器无法获得这一信息,它会将else部分与内层的if配对。所以最好经常把if else语句块括起来! 二分查找:/* binsearch:在v[0]<=v[1]<=v[2]<=……<=v[n-1]中查找x */
int binsearch ( int x, int v[ ], int n )
{
int low, high, mid;
low = 0;
high = n - 1;
while ( low <= high ) {
mid = ( low + high ) / 2;
if ( x < v[mid] )
high = mid - 1;
else if ( x > v[mid] )
low = mid + 1;
else /* 找到了匹配的值 */
return mid;
}
return -1; /* 没有查到*/
} 练习3-1 上面折半查找的例子中,while循环语句内执行了两次测试。重写该函数,使循环内部只执行一次测试。比较两种版本函数的运行时间。答:while (low <= high) { mid = (low + high) / 2; if (x < v[mid]) high = mid - 1; else low = mid ;}if (x == v[mid]) return mid;else return -1; 3.4 switch语句
s w i t c h语句是一种多路判定语句,它根据表达式是否与若干常量整数值中的某一个匹配来相
应地执行有关的分支动作。
switch ( 表达式) {
case 常量表达式: 语句序列
case 常量表达式: 语句序列
default: 语句序列} 如果没有default情形,且没有任何一个情景匹配,那么该switch语句不执行任何动作。各个case的执行顺序是任意的。 在s w i t c h语句中c a s e情形的作用就像标号一样,在某个c a s e情形之后的代码执行完后,就进入下一个c a s e情形执行,除非显式控制转出。转出
s w i t c h语句最常用的方法是使用b r e a k语句与r e t u r n语句。为了防止直接进入下一个情形,最好在每个情形后以break结尾。 练习3-2 编写一个函数escape(s, t),将字符串t复制到字符串s中,并在复制过程中将换行符、制表符等不可见字符分别转换为\n、\t等相应可见的转义字符。再编写一个相反功能的函数。答:#include <stdio.h>void escape(char s[], char t[])
{
int i, j;
for (i = 0, j = 0; s[i] != '\0'; i++) {
switch (s[i]) {
case '\n':
t[j++] = '\\';
t[j++] = 'n';
break;
case '\t':
t[j++] = '\\';
t[j++] = 't';
break;
default:
t[j++] = s[i];
break;
}
}
t[j] = '\0';
}
void escape2(char s[], char t[])
{
int i, j;
for (i = 0, j = 0; s[i] != '\0'; j++) {
switch (s[i]) {
case '\\':
if (s[i+1] == 'n') {
t[j] = '\n';
i += 2;
}
else if (s[i+1] == 't') {
t[j] = '\t';
i += 2;
}
else {
t[j] = s[i];
i++;
}
default:
t[j] = s[i++];
break;
}
}
}
练习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 i, j, k;
i = j = 0;
while (s1[i] != '\0') {
if (s1[i] == '-' && 0 < i && s1[i+1] != '\0' &&
s1[i-1] != '-' && s1[i+1] != '-') {
j--; // avoid duplicate letter
for (k = s1[i-1]; k <= s1[i+1]; k++, j++)
s2[j] = k;
i += 2;
} else {
s2[j++] = s1[i++];
}
}
s2[j] = '\0';
}
main()
{
char s1[] = "-a-b-hAbC-G0-8---";
char s2[100];
expand(s1, s2);
printf("before expand:%s\nafter expand: %s\n", s1, s2);
}
3.5 while循环与for循环
for (表达式1; 表达式2; 表达式3) 语句
等价于=>
表达式1;while (表达式2) { 语句 表达式3;}
如果表达式1与表达式3 被省略了,那么它退化成了w h i l e循环语句。
如果用于测试的表达式2 不存在,那么就认为表达式2 的值永远是真的,从而,f o r循环语句
for ( ; ; ) {
…
}
就是一个“无限”循环语句,这种语句要用其他手段(如b r e a k语句或r e t u r n语句)才能终止执行。
例子:atoi 函数的实现,将字符转化为数字,除去前面的空白字符,处理+ - 操作,
#include <ctype.h>
/* atoi:将s转换成整数;第2版*/
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;
}
shell排序:
/* shellsort:以递增顺序对v[0]、v[1]、……、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;
}
}
}
将整型转化为字符串itoa的实现:
void itoa ( int n, char s[ ] );
{
int i, sign;
if ( ( sign = n ) < 0 ) /* 记录符号 */
n = -n; /* 使n成为正数 */
i = 0;
do { /* 以反序生成数字 */
s[i++] = n % 10 + '0'; /* 取下一个数字 */
} while ( (n /= 10) > 0); /* 删除该数字 */
if (sign < 0)
s[i++] = '-';
s[i] = '\0';
reverse(s);
}
因为即使n为0也要至少把一个字符放到数组 s中,所以在这里有必要使用 d o - w h i l e语句,至
少使用d o - w h i l e语句要方便一些。
atof 将字符串转化为浮点数 比如123.45 ---与atoi相比,在小数点.之后开始记录小数点位数pow*=10.0
/* 把字符串s转换成相应的双精度浮点数 */
double atof( char s[ ])
{
double val, power;
int i, sign;
for ( i = 0; isspace(s[i]); i++ ) /* 跳过空白 */
;
sign = (s[i] == '-' ) ? -1 : 1;
if ( s[i] == '+' || s[i] == '-' )
i++;
for (val = 0.0; isdigit(s[i]); i++)
val = 10.0 * val +(s[i] -'0' );
if (s [i] ] = = '.')
i++;
for ( power = 1.0; isdigit(s[i]); i++) {
val = 10.0 * val +(s[i] -'0' );
power *= 10.0;
}
return sign * val / power;
}
练习3-4 在数的对二的补码表示中,上面的itoa函数不能处理最大的负数-2的(字长-1)次方 的情况。解释其原因,并修改函数使它在任何机器上运行时都能打印出正确的值。 答: 例如char字长为8位,则对二补码范围为-128~127。 128的二进制源码为10000000,-128通过补码的负数转换规则也得到10000000,即-128二进制码为80(可用prinf("%hhx);验证)。即-128的二进制表示实际上市128,所以值为-128的char,n=-n;后值仍为-128。
修改函数,不将n转为正数,而是将每次取模运算的结果转为正数。从而避开无法将最大负数转为正数的问题。 #include <stdio.h> #define abs(x) ((x) < 0 ? -(x) : (x))
void itoa(int n, char s[])
{
int i, sign;
sign = n;
i = 0;
do {
s[i++] = abs(n % 10) + '0';
} while ((n /= 10) != 0);
if (sign < 0)
s[i++] = '-';
s[i] = '\0';
//reverse(s);
}
main()
{
char s[20];
itoa(10, s);
printf("%s\n", s);
itoa(-128, s);
printf("%s\n", s);
}
练习3-5 编写函数itob(n, s, b),将整数n转换为以b为底的数,并将转换结果以字符的形式 保存到字符串s中。例如,itob(n, s, 16)把整数n格式化为十六进制整数保存在s中。 答: #include <stdio.h>
#include "reverse.c"
#define abs(x) (x) < 0 ? -(x) : (x)
void itob(int n, char s[], int b)
{
int i, x, sign;
sign = n;
i = 0;
do {
x = abs(n % b);
if (x >= 10)
s[i++] = (x - 10) + 'A';
else
s[i++] = x + '0';
} while ((n /= b) != 0);
if (sign < 0)
s[i++] = '-';
s[i] = '\0';
reverse(s);
}
main()
{
char s[20];
itob(29, s, 2);
printf("%s\n", s);
itob(-257, s, 16);
printf("%s\n", s);
}
练习 3-6 修改itoa函数,使得该函数可以接收三个参数。第三个参数为最小字段宽度。 为了保证转换后结果至少具有第三个参数指定的最小宽度,必要时在结果左边填充一定的空格。 答: ... if (sign < 0) s[i++] = '-'; while (i <= w-1) // fill space s[i++] = ' '; ...
3.7 break和continue语句 在f o r循环语句中,c o n t i n u e语句的执行则意味着使控制传递到增量部分。 c o n t i n u e语句只
能用于循环语句,不能用于 s w i t c h语句。如果某个 c o n t i n u e语句位于s w i t c h语句中,而后者又位
于循环语句中,那么该c o n t i n u e语句用于控制下一次循环。 在循环的某些部分比较复杂时常常要使用 c o n t i n u e语句。如果不使用 c o n t i n u e语句,那么就
可能要把测试反过来,或嵌入另一层循环,而这又会使程序的嵌套更深。 3.8 goto语句与标号 最常见的用法是在某些深度嵌套的结构中放弃处理,例如一次中止两层或多层循环。 b r e a k语句不能直接用于这一目的,它只能用于从最内层循环退出。下面是使用g o t o语句的一个例子:
for ( ⋯ )
for ( ⋯ ) {
⋯
if (disaster)
goto error;
}
⋯
error:
清理操作
如果错误处理比较重要并且在好几个地方都会出现错误,那么使用这种组织就比较灵活方便。
标号的形式与变量名字相同,其后要跟一个冒号。标号可以用在任何语句的前面,但要与
相应的g o t o语句位于同一函数中。标号的作用域是整个函数 看一个例子,考虑判定在两个数组 a与b中是否具有相同元素的问题。一种可能的解决方
法是:
for (i = 0; i < n; i++)
for (j = 0; j < m; j++)
if (a[i] == b[j])
第3章 控 制 流计计 53
下载
goto found;
/* 没有找到相同元素 */
⋯
found:
/* 取一个满足a[i] ==b[j]的元素 */
⋯
所有带有g o t o语句的程序代码都可以改写成不包含 g o t o语句的程序,但这可能需要以增加一
些额外的重复测试或变量为代价。例如,可将这个判定数组元素是否相同的程序段改写成如下
形式:
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-1] ==b[j-1]的元素 */
⋯
else
/* 没有找到相同元素 */
⋯
除了以上介绍的几个程序段外,依赖于 g o t o语句的程序段一般都比不使用 g o t o语句的程序段
难以理解与维护。虽然不特别强调这一点,但我们还是建议尽可能减少 g o t o语句的使用。