您的位置:首页 > 其它

<<c程序设计语言>>第三章 程序控制流

2014-07-24 23:31 393 查看
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语句的使用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: