《C程序设计语言》 第三章 控制流
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);
}
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);
}
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);
}
#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语句与标号