<>第三章 程序控制流

时间:2022-12-14 20:39:38

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语句的使用