读书笔记:C程序设计语言,第三章:控制流( 课后题全解)

来源:互联网 发布:淘宝自动设计软件 编辑:程序博客网 时间:2024/06/05 11:00

第三章主要讲控制流。

  • 答案摘自:练习题全解:"The C Programming Language", 2nd edition, Kernighan and RitchieAnswers to Exercises

3.1 语句与程序块

  1. 一个分号代表语句结束
  2. 由{ }组成复合语句,也称程序块

3.2 if-else语句

  1. else是可选的,所以多个if语句中的else要注意其匹配,一般是与最近的匹配,用花括号更能看清其匹配的对象
  2. 有多个if语句嵌套时,强烈建议用花括号匹配

3.3 else-if语句

  1. 用else -if 语句来完成折半查找,折半查找要求数组v有序排列,本例中是升序。
    /* binsearch: find x in v[0] <= v[1] <= ... <= v[n-1] * n代数组v的长度 * */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 matchreturn mid;}return -1;// no match}

练习题

Exercise 3-1. Our binary search makes two tests inside the loop(while循环内作了两次测试), when one would suffice (在while内)(at the price of more tests outside.) Write a version with only one test inside the loop and measure the difference in run-time.
答:
看了答案之后觉得没什么有意义呀。
/*  Implementation of binsearch() using    only one test inside the loop        */int binsearch2(int x, int v[], int n) {    int low, high, mid;        low = 0;    high = n - 1;    mid = (low+high) / 2;    while ( low <= high && x != v[mid] ) {        if ( x < v[mid] )            high = mid - 1;        else            low = mid + 1;        mid = (low+high) / 2;    }    if ( x == v[mid] )        return mid;    else        return -1;}

3.4 switch语句

  1. 多路判定的一种方式
  2. 如果没有显示的跳转语句:break和return,那么从一个分支会进入下一个分支执行代码。这是一种非常不好的方式,应该避免。必须的使用的时候,请加上足够多的注释。

练习题

Exercise 3-2. Write a function escape(s,t) that converts characters like newline and tab into visible escape sequences like \n and \t as it copies the string t to s. Use a switch. Write a function for the other direction as well, converting escape sequences into the real characters.
答:
/*    EX3_2.C  =======    Suggested solution to Exercise 3-2*/#include <stdio.h>void escape(char * s, char * t);void unescape(char * s, char * t);int main(void) {    char text1[50] = "\aHello,\n\tWorld! Mistakee\b was \"Extra 'e'\"!\n";    char text2[51];        printf("Original string:\n%s\n", text1);        escape(text2, text1);    printf("Escaped string:\n%s\n", text2);        unescape(text1, text2);    printf("Unescaped string:\n%s\n", text1);        return 0;}/*  Copies string t to string s, converting special    characters into their appropriate escape sequences.    The "complete set of escape sequences" found in    K&R Chapter 2 is used, with the exception of:        \? \' \ooo \xhh        as these can be typed directly into the source code,    (i.e. without using the escape sequences themselves)    and translating them is therefore ambiguous.          */void escape(char * s, char * t) {    int i, j;    i = j = 0;        while ( t[i] ) {                /*  Translate the special character, if we have one  */                switch( t[i] ) {        case '\n':            s[j++] = '\\';            s[j] = 'n';            break;                    case '\t':            s[j++] = '\\';            s[j] = 't';            break;                    case '\a':            s[j++] = '\\';            s[j] = 'a';            break;                    case '\b':            s[j++] = '\\';            s[j] = 'b';            break;                    case '\f':            s[j++] = '\\';            s[j] = 'f';            break;                    case '\r':            s[j++] = '\\';            s[j] = 'r';            break;                    case '\v':            s[j++] = '\\';            s[j] = 'v';            break;                    case '\\':            s[j++] = '\\';            s[j] = '\\';            break;                    case '\"':            s[j++] = '\\';            s[j] = '\"';            break;                    default:                        /*  This is not a special character, so just copy it  */                        s[j] = t[i];            break;        }        ++i;        ++j;    }    s[j] = t[i];    /*  Don't forget the null character  */}/*  Copies string t to string s, converting escape sequences    into their appropriate special characters. See the comment    for escape() for remarks regarding which escape sequences    are translated.                                             */void unescape(char * s, char * t) {    int i, j;    i = j = 0;        while ( t[i] ) {        switch ( t[i] ) {        case '\\':                        /*  We've found an escape sequence, so translate it  */                        switch( t[++i] ) {            case 'n':                s[j] = '\n';                break;                            case 't':                s[j] = '\t';                break;                            case 'a':                s[j] = '\a';                break;                            case 'b':                s[j] = '\b';                break;                            case 'f':                s[j] = '\f';                break;                            case 'r':                s[j] = '\r';                break;                            case 'v':                s[j] = '\v';                break;                            case '\\':                s[j] = '\\';                break;                            case '\"':                s[j] = '\"';                break;                            default:                                /*  We don't translate this escape                    sequence, so just copy it verbatim  */                                s[j++] = '\\';                s[j] = t[i];            }            break;                    default:                        /*  Not an escape sequence, so just copy the character  */                        s[j] = t[i];        }        ++i;        ++j;    }    s[j] = t[i];    /*  Don't forget the null character  */}

3.5 while循环和for循环

  1. 当while和for循环内contine语句时,需要具体分析,不能简单认为两者等价
  2. for循环内各个部分都可以保省略,唯独分号必须保留。省略中间那一部分则认为永远为真
  3. 更通用的atoi的例子,能够处理space和正负:
    #include <ctype.h>/* atoi: convert s to integer; version 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;/* skip white space *//* skip sign */}
  4. shell排序,这里的shell排序和我传统认识的有点不一样:先比较距离远的元素,而不是像简单交换排序算法那样先比较相邻的元素。这样可以快速减少大量无序的情况,从而减轻了后续工作。
    /* shellsort: sort v[0]...v[n-1] into increasing order */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;}}
    最外层的for语句控制两个被比较的元素的之间的距离。gap最后会变成1,变1之后就是直接插入排序。
    中间的for循环语句用于在元素中移动位置
    最内层的for语句用于比较各个相对位置的元素,当这两个元素逆序时把它们互换过来
    上述代码还是比较难的,请看下图,这一个希尔排序的过程:



  5. 逗号,是优先级最低的运算符
  6. 虽然可以,但是请慎用下面的方式,在单个表达式中进行多步计算操作:
    常规:
    for (i = 0, j = strlen(s)-1; i < j; i++, j--) {c = s[i];s[i] = s[j];s[j] = c;}
    非常规:
    for (i = 0, j = strlen(s)-1; i < j; i++, j--)c = s[i], s[i] = s[j], s[j] = c;

练习题

Exercise 3-3. Write a function expand(s1,s2) that expands shorthand notations like a-z in the string s1 into the equivalent complete list abc...xyz in s2. Allow for letters of either case and digits, and be prepared to handle cases like a-b-c and a-z0-9 and -a-z. Arrange that a leading or trailing - is taken literally.(扩展速记录符:a-d,扩展为abcd,0-3扩展为:0123)
答:
/*  EX3_3.C  =======  Suggested solution to Exercise 3-3*/#include <stdio.h>#include <string.h>void expand(char * result, char * from);int main(void) {    char *s[] = { "a-z-", "z-a-", "-1-6-",                  "a-ee-a", "a-R-L", "1-9-1",                  "5-5", NULL };    char result[100];    int i = 0;    while ( s[i] ) {        /*  Expand and print the next string in our array s[]  */        expand(result, s[i]);        printf("Unexpanded: %s\n", s[i]);        printf("Expanded  : %s\n", result);        ++i;    }    return 0;}/*  Copies string s2 to s1, expanding    ranges such as 'a-z' and '8-3'     */void expand(char * result, char * from) {    static char upper_alph[27] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";    static char lower_alph[27] = "abcdefghijklmnopqrstuvwxyz";    static char digits[11]     = "0123456789";    char * start, * end, * p;    int i = 0;    int j = 0;    /*  Loop through characters in s2  */    while ( from[i] ) {        switch( from[i] ) {        case '-':            if ( i == 0 || from[i+1] == '\0' ) {                /*  '-' is leading or trailing, so just copy it  */                result[j++] = '-';                ++i;                break;            }            else {                 /*  We have a "range" to extrapolate. Test whether                     the two operands are part of the same range. If                     so, store pointers to the first and last characters                     in the range in start and end, respectively. If                     not, output and error message and skip this range.                     此外:                     char *strchr(const char *s,char c);查找字符串s中首次出现字符c的位置                    */                if ( (start = strchr(upper_alph, from[i-1])) &&                     (end   = strchr(upper_alph, from[i+1])) )                    ;                else if ( (start = strchr(lower_alph, from[i-1])) &&                          (end   = strchr(lower_alph, from[i+1])) )                    ;                else if ( (start = strchr(digits, from[i-1])) &&                          (end   = strchr(digits, from[i+1])) )                    ;                else {                    /*  We have mismatched operands in the range,                        such as 'a-R', or '3-X', so output an error                        message, and just copy the range expression.  */                    fprintf(stderr, "EX3_3: Mismatched operands '%c-%c'\n",                            from[i-1], from[i+1]);                    result[j++] = from[i-1];//然后把-之前的那个复制进去                    result[j++] = from[i++];//然后把-复制进去                    break;                }                /*  Expand the range  */                p = start;                while ( p != end ) {                    result[j++] = *p;                    if ( end > start )                        ++p;                    else                        --p;                }                result[j++] = *p;//这个是把end所指的那个复制进result                i += 2;//+1指向end那个字母,再加+1指向end下面那个字母,由此开始了新的循环            }            break;        default:            if ( from[i+1] == '-' && from[i+2] != '\0' ) {                /*  This character is the first operand in                    a range, so just skip it - the range will                    be processed in the next iteration of                    the loop.                                   */                ++i;            }            else {                /*  Just a normal character, so copy it  */                result[j++] = from[i++];            }            break;        }    }    result[j] = from[i];    /*  Don't forget the null character  */}

3.6 do-while 循环

  1. 很显然do-while比用while用的少的多。
  2. 证明do-while有用的itoa,也就是int变为char[]
    #include <string.h>/* 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;}}/* itoa: convert n to characters in s */void itoa(int n, char s[]){int i, sign;if ((sign = n) < 0)n = -n;/* record sign *//* make n positive */i = 0;do {/* generate digits in reverse order */s[i++] = n % 10 + ’0’; /* get next digit */} while ((n /= 10) > 0);/* delete it */if (sign < 0)s[i++] = ’-’;s[i] = ’\0’;reverse(s);//很显然最后一个'\0'是不会被转换的}

练习题

Exercise 3-4. In a two’s complement number representation, our version of itoa does not handle the largest negative
number, that is, the value of n equal to . Explain why not. Modify it to print that value correctly, regardless of the machine on which it runs.(在数的对二的补码表示中,我们编写的itoa函数不能处理最大的负数,即n等于)的情况,请解释原因。并且修改整个函数,使它在任何机器上都能够运行。答:解释原因的部分我是被绕晕了,不过我觉得我再看一遍深入理解计算系统之后应该对这一部分清晰点,先研究直接看改正后的代码:change 'n % 10 + '0'' to 'abs(n % 10) + '0''代码:
#include <stdlib.h>#include <stdio.h>#include <limits.h>void itoa(int n, char s[]);void reverse(char s[]);int main(void) {    char buffer[20];        printf("INT_MIN: %d\n", INT_MIN);    itoa(INT_MIN, buffer);    printf("Buffer : %s\n", buffer);        return 0;}void itoa(int n, char s[]) {    int i, sign;    sign = n;        i = 0;    do {        s[i++] = abs(n % 10) + '0';    } while ( n /= 10 );    if (sign < 0)        s[i++] = '-';        s[i] = '\0';    reverse(s);}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;    }}
Exercise 3-5. Write the function itob(n,s,b) that converts the integer n into a base b(进制为b的) character representation in the string s. In particular, itob(n,s,16) formats s as a hexadecimal integer in s.(将n换成16进制的,保存在s之中)
答:我觉得想出这些办法来的人真的很聪明。
/*  EX3_5.C  =======  Suggested solution to Exercise 3-5*/#include <stdlib.h>#include <stdio.h>    void itob(int n, char s[], int b);void reverse(char s[]);int main(void) {    char buffer[10];    int i;    for ( i = 2; i <= 20; ++i ) {        itob(255, buffer, i);        printf("Decimal 255 in base %-2d : %s\n", i, buffer);    }    return 0;}/*  Stores a string representation of integer n    in s[], using a numerical base of b. Will handle    up to base-36 before we run out of digits to use.      暂时只能处理base小于36的情况    */void itob(int n, char s[], int b) {    static char digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";    int i, sign;    if ( b < 2 || b > 36 ) {        fprintf(stderr, "EX3_5: Cannot support base %d\n", b);        exit(EXIT_FAILURE);    }    if ((sign = n) < 0)        n = -n;    i = 0;    do {        s[i++] = digits[n % b];///这是谁发现这么巧的事情的。    } while ((n /= b) > 0);    if (sign < 0)        s[i++] = '-';    s[i] = '\0';    reverse(s);}/*  Reverses 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;    }}
Exercise 3-6. Write a version of itoa that accepts three arguments instead of two. The third argument is a minimum field width; the converted number must be padded with blanks on the left if necessary to make it wide enough.
答:
#include <stdio.h>#include <limits.h>void itoa(int n, char s[], int width);void reverse(char s[]);int main(void) {    char buffer[20];    itoa(2000, buffer, 7);    printf("Buffer:%s\n", buffer);    return 0;}void itoa(int n, char s[], int width) {    int i, sign;    if ((sign = n) < 0)        n = -n;    i = 0;    do {        s[i++] = n % 10 + '0';        printf("%d %% %d + '0' = %d\n", n, 10, s[i-1]);    } while ((n /= 10) > 0);    if (sign < 0)        s[i++] = '-';    while (i < width )    /*  Only addition to original function  */        s[i++] = ' ';    s[i] = '\0';    reverse(s);}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.7 break语句和continue语句


  1. break可以在 for、while和do-while循环中用于跳出循环
    下面的函数用于删除字符串尾部的空格符,当发现最右边的字符为非空格符、非制表符、非换行符时,就使用break语句从循环中推出。
    /* trim: remove trailing blanks, tabs, newlines */int trim(char s[]){int n;for (n = strlen(s)-1; n >= 0; n--)//strlen(s)返回字符串长度if (s[n] != ’ ’ && s[n] != ’\t’ && s[n] != ’\n’)break;s[n+1] = ’\0’;return n;}

  2. continue可以防止程序嵌套的更深。continue开始执行下一个循环for、while、do-while的下一个循环

3.8 goto语句和标号

  1. 虽然建议不要使用,但是我认为还是要知道这个数东西的存在。
    for (i = 0; i < n; i++)for (j = 0; j < m; j++)if (a[i] == b[j])goto found;/* didn’t find any common element */...found:/* got one: a[i] == b[j] */...
    这是使用goto语句的一个方式

其它问题总结

进制之间的转换计算

摘自:百度知道
========================================
十进制287.47转为二进制


287/2=143......1(余数)
143/2=71........1
71/2=35..........1
35/2=17..........1
17/2=8...........1
8/2=4.............0
4/2=2.............0
2/2=1.............0
1/2=0.............1
倒过来写的余数就是100011111


.47   
.47x2=0.94.....0(0是0.94的整数部分)
.94x2=1.88.....1
.88x2=1.76.....1
.76x2=1.52.....1
.52x2=1.04.....1
.04x2=0.08.....0
.08x2=0.16.....0
.16x2=0.32.....0
.32x2=0.64.....0
.64x2=1.28.....1
.28x2=0.56.....0
.56x2=1.12.....1
                      :
       :
       :
小数点顺着写就是 .011110000101
这个看就是无止境的,要看你需要的位数和你的储存空间,题目止到0
所以你的2进制是对的100011111.011110
==========================


287.47 的八进制 整数部分除以8,小数点乘以8


287/8=35......7    
35/8=4..........3     
4/8=0............4     
余数倒过来写就是437


.47
.47x8=3.76....3(整数部分是3)
.76x8=6.08....6
.08x8=0.64....0
.64x8=5.12....5
.12x8=0.96....0
      :
      :
      :
这个也是无止境的,小数点就是 .36050
八进制就是437.36050
==============================


287.47 的16进制  整数部分除以16,小数乘以16


287/16=17.....F(15)(余数)
17/16=1........1
1/16=0..........1
余数倒过来就是11F


.47
.47x16=7.52....7(整数部分是7)
.52x16=8.32....8
.32x16=5.12....5
.12x16=1.92....1
        :
        :
                       :
这也是无止境,小数就是 .7851 
16进制就是11F.7851
=======================


如果你不会直接从二进制转化成8或者16进制,可以先把它们转成10进制的,或者画表格
Decimal ,Hexadecimal, Octal, Binary 
0, 0, 0, 0000 
1, 1, 1, 0001 
2, 2, 2, 0010 
3, 3, 3, 0011 
4 ,4, 4, 0100 
5, 5, 5, 0101 
6, 6,6, 0110 
7, 7, 7, 0111 
8, 8, 10, 1000 
9, 9, 11, 1001 
10, a, 12, 1010 
11, b, 13, 1011 
12, c, 14, 1100 
13, d, 15, 1101 
14, e, 16 ,1110 
15, f, 17, 1111 



0 0