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语句与标号