《C程序设计语言》 第三章 控制流

时间:2022-12-14 20:44:02

3.1 语句与程序块

在表达式之后加上一个分号(;),它们就变成了语句。 用一对花括号“{”与“}”把一组声明和语句括在一起就构成了程序块,在语法上等价于单条语句。

3.2 if-else语句
每个else与最近的前一个没有else配对的if进行匹配。
if (n > 0)      if (a > b)           z = a; else      z = b;
程序的缩进结构明确表明了设计意图,但编译器无法获得这一信息,它会将else部分与内层的if配对。

3.3 else-if语句
/* binsearch: find x in v[0] <= v[1] <= ... <= v[n-1] */ 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     /* found match */                return mid;      }      return -1; }
练习3-1     上面折半查找的例子中,while循环语句内执行了两次测试。重写该函数, 使循环内部只执行一次测试。比较两种版本函数的运行时间。 答: while (low <= high) {      mid = (low + high) / 2;      if (x < v[mid])           high = mid +1;      else           low = mid + 1; } if (x == v[mid])      return mid; else      return -1;

3.4 switch语句
case的作用只是一个标号,从某个分支中的代码执行完后,程序将进入下一分支继续执行。 跳出switch语句最常用的方法是使用break和return语句。
作为一种良好的程序设计风格,在switch语句最后的default分支后面也加上一个break语句。 这样做在逻辑上没有必要,但当我们需要向该switch语句后添加其他分支时,这样会降低犯 错误的可能性。
练习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;
          }
     }
}
main()
{
     char s[] = "this is     cdai";
     char t[20];
     escape(s, t);    
     printf("%s\n", t);

     char t2[20];
     escape2(t, t2);
     printf("%s\n", t2);
}


3.5 while循环与for循环
for (表达式1; 表达式2; 表达式3)      语句
等价于=>
表达式1; while (表达式2) {      语句      表达式3; }
逗号运算符“,”在for语句中经常用到。被逗号分隔的一对表达式将按照从左到右的顺序进行求值, 分隔函数参数的逗号,分隔声明中变量的逗号等不是逗号运算符,不保证从左至右顺序求值。
/* reverse: reverse string s in place */ 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; }
练习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.6 do-while循环
/* itoa: convert n to characters in s */ void itoa(int n, char s[]) {      int i, sign;            if ((sign = n) < 0)               /* record sign and make n positive */           n = -n;
     i = 0;      do {                                   /* generate digits in reverse order */           s[i++] = n % 10 + '0';     /* convert number to char */      } while ((n /= 10) > 0);            if (sign < 0)           s[i++] = '-';      s[i] = '\0';      reverse(s); }
这里使用do-while语句会方便一些,因为即使n为0,也至少要把一个字符放到数组s中。 do-while中只有一条语句,(没有必要)但扔用花括号括起来,因为可以避免将while误认为 是另个while循环的开始。
练习3-4     在数的对二的补码表示中,上面的itoa函数不能处理最大的负数-2的(字长-1)次方 的情况。解释其原因,并修改函数使它在任何机器上运行时都能打印出正确的值。 答: 例如char字长为8位,则对二补码范围为-128~127。值为-128的char,n=-n;后值仍为-128。 128的二进制源码为01111111,通过补码的负数转换规则得到10000000,即-128二进制码为80(可用prinf("%hhx);验证)。
修改函数,不将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语句

3.8 goto语句与标号