部分答案参考了官方题解和网上的答案,仅供参考,可能也有部分bug未发现或解决。代码在gitee上面。

exercise4-1

编写函数 strindex(s, t),它返回字符串 t 在 s 中最右边出现的位置。如果 s 中不包含 t,则返回-1。

代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void getLine(char s[]) {
    int c, i = 0;
    while ((c = getchar()) != EOF && c != '\n') 
        s[i++] = c;
}

int strindex(char s[], char t[]) {
    int i, j, k;
    int lens = strlen(s), lent = strlen(t);

    for (i = lens - 1; i >= 0; i--) {
        for (j = lent - 1, k = i; j >= 0 && k >= 0 && s[k] == t[j]; j--, k--);
        
        if (j < 0) 
            return k + 1;
    }
    return -1;
}

int main() {
    char s[MAXLEN] = {0};
    char t[MAXLEN] = {0};
    printf("Please input the string s: \n");
    getLine(s);
    printf("Please input the string t: \n");
    getLine(t);
    printf("The rightmost position of '%s' in '%s' is: %d\n", t, s, strindex(s, t));
    return 0;
}

运行:

1
2
3
4
5
Please input the string s: 
this is an idea, a good idea
Please input the string t: 
idea
The rightmost position of 'idea' in 'this is an idea, a good idea' is: 24

exercise4-2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include <ctype.h>

#define MAXLEN 1024

double atof(const char s[]) {
    double val, power;
    int i, sign, expSign, exp;
    
    for (i = 0; isspace(s[i]); i++) // skip white space
        ;
    
    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;
    }
    
    val = sign * val / power;
    
    if (s[i] == 'e' || s[i] == 'E') {
        i++;
        if (s[i] == '+' || s[i] == '-') {
            expSign = (s[i] == '-') ? -1 : 1;
            i++;
        }
        
        // 指数
        for (exp = 0; isdigit(s[i]); i++)
            exp = 10 * exp + (s[i] - '0');
        
        if (expSign == 1)
            while (exp-- > 0)
                val *= 10;
        else
            while (exp-- > 0)
                val /= 10;
    }
    
    return val;
}

int main() {
    char s[MAXLEN] = {0};
    printf("Please input the string s: \n");
    scanf("%s", s);
    printf("%g\n", atof(s));
    return 0;
}

运行:

1
2
3
Please input the string s: 
123e-10
1.23e-08

exercise4-3

在有了基本框架后,对计算器程序进行扩充就比较简单了。在该程序中加入取模(%)运算符,并注意考虑负数的情况。

若要能够处理负数的情况,则需要在getop()的处理负号时进行进一步判断:需要判断下一个字符是空格还是数字,修改后的getop()如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* getop: get next character or numeric operand */
int getop(char s[])
{
    int i, c, next;
    while ((s[0] = c = getch()) == ' ' || c == '\t')
        ;
    s[1] = '\0';
    if (!isdigit(c) && c != '.' && c != '-') 
        return c; /* not a number */
    i = 0;
    if (c == '-') {
        next = getch();
        if (!isdigit(next) && next != '.') {
            ungetch(next);
            return c; /* not a number, return '-' */
        }
        c = next;
        s[++i] = c;
    }
    if (isdigit(c)) /* collect integer part */
        while (isdigit(s[++i] = c = getch()))
            ;
    if (c == '.') /* collect fraction part */
        while (isdigit(s[++i] = c = getch()))
            ;
    s[i] = '\0';
    if (c != EOF)
        ungetch(c);
    return NUMBER;
}

加入取模运算符,则比较简单,与处理 - 与 / 类似:

1
2
3
4
5
/* +和*无需注意操作数的顺序,即a+b=b+a,但是-/%需要注意 */
case '%':
    op2 = pop();
    push((int)pop() % (int)op2);
    break;

运行:

1
2
3
4
5
Please input expression:
3 2 %
        1
3 0 %
        3

exercise4-4

在栈操作中添加几个命令,分别用于在不弹出元素的情况下打印栈顶元素;复制栈顶元素;交换栈顶两个元素的值。另外增加一个命令用于清空栈。

代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* print and return top value from stack without popping */
double top(void)
{
    if (sp > 0) {
        printf("Top of stack: %g\n", val[sp-1]);
        return val[sp-1];
    }
    else {
        printf("error: stack empty\n");
        return 0.0;
    }
}

/* duplicate top value to stack */
void duptop(void)
{
    if (sp > 0)
        push(val[sp-1]);
    else
        printf("error: stack empty\n");
}

/* swap top two values */
void swaptop(void)
{
    if (sp > 1)
    {
        double temp = val[sp-1];
        val[sp-1] = val[sp-2];
        val[sp-2] = temp;
    }
    else
        printf("error: not enough elements to swap\n");
}

exercise4-5

给计算器程序增加访问 sin、exp 与 pow 等库函数的操作。有关这些库函数的详细信息,参见附录 B.4 节中的头文件<math.h>。

引入<math.h>头文件,然后为了编译通过,参考了Undefined reference to pow( ) in C, despite including math.h [duplicate]的解决办法。在main函数中添加了对这三种运算的支持:

代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
case 's':  // sin
    push(sin(pop()));
    break;
case 'e':  // exp
    push(exp(pop()));
    break;
case 'p':  // pow
    op2 = pop();
    push(pow(pop(), op2));
    break;

运行:

1
2
3
4
5
6
7
Please input expression:
3 e
ans is: 20.085537
2 5 p
ans is: 32
3.1415926 s  
ans is: 5.3589793e-08

exercise4-6

给计算器程序增加处理变量的命令(提供 26 个具有单个英文字母变量名的变量很容易)。增加一个变量存放最近打印的值。

为了增加处理变量,需要在代码中设置一个变量var来存储上一次计算出来的结果,并使用一个flag来验证该变量是否有效,因为第一次计算时,var是不存在的。增加main函数中的处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#define VAR 'v'

int getop(char s[])
{
    ...
    if (c == 'v') {
        return VAR;
    }
    ...
}

/* reverse Polish calculator */
int main()
{
    ...
    switch (type)
    {
    // 变量
    case VAR:
        if (flag == 1)
            push(var);
        else
            printf("error: no variable\n");
        break;
    // 数值
    case NUMBER:
        push(atof(s));
        break;
    // 操作符
    ...
    // 结束输入
    case '\n':
        printf("ans is:\t%.8g\n", (var = pop()));
        flag = 1;
        break;
    ...
    }
}

运行:

1
2
3
4
5
6
7
Please input expression:
1 2 +
ans is: 3
3 v *
ans is: 9
v 3 p
ans is: 729

exercise4-7

编写一个函数 ungets(s),将整个字符串 s 压回到输入中。ungets 函数需要使用 buf 和 bufp 吗?它能否仅使用 ungetch 函数?

不需要直接使用buf和bufp,可以直接调用ungetch函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/* push entire string s back on input */
void ungets(char s[])
{
    int len = strlen(s);
    while (len > 0)
        ungetch(s[--len]);
}

int main()
{
    char s[] = "goood!";
    int c;
    ungets(s);
    while ((c = getch()) != EOF)
        putchar(c);
    return 0;
}

运行:

1
goood!

exercise4-8

假定最多只压回一个字符。请相应地修改 getch 与 ungetch 这两个函数。

代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
int getch(void) 
{
    int temp = buf;
    buf = EOF;  
    if (temp == EOF)
        temp = getchar();
    return temp;
}

/* push character back on input */
void ungetch(int c) 
{
    if (buf != EOF)
        printf("ungetch: too many characters\n");
    else
        buf = c;
}

exercise4-9

以上介绍的 getch 与 ungetch 函数不能正确地处理压回的 EOF。考虑压回EOF 时应该如何处理?请实现你的设计方案。

exercise4-8的程序可以正确处理压回的EOF

exercise4-10

另一种方法是通过 getline 函数读入整个输入行,这种情况下可以不使用 getch 与 ungetch 函数。请运用这一方法修改计算器程序。

代码(因完整代码过长,故只显示相对于exercise4-6的代码的增加及更改部分):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
char line[MAXLINE]; /* input line */

/* getop: get next character or numeric operand */
int getop(char s[])
{
    int i, c;

    while ((s[0] = c = line[idx++]) == ' ' || c == '\t')
        ;
}

int Getline() {
    int c, i;
    for (i = 0; i < MAXLINE - 1 && (c = getchar()) != EOF && c != '\n'; i++) {
        line[i] = c;
    }
    if (c == '\n')
        line[i++] = c;
    line[i] = '\0';
    return i;
}

/* reverse Polish calculator */
int main()
{
    int type;
    double op2;
    char s[MAXOP];
    while (1)
    {
        printf("Please input expression:\n");
        if (Getline() == 0)
            break;
        idx = 0;
        while ((type = getop(s)) != '\0')
        {
            ...
        }
    }
    return 0;
}

运行:

1
2
3
4
5
6
7
Please input expression:
1 2 +
ans is: 3
Please input expression:
1 v /
ans is: 0.33333333
Please input expression:

exercise4-11

修改 getop 函数,使其不必使用 ungetch 函数。提示:可以使用一个 static 类型的内部变量解决该问题。

修改 getop.c,使用一个 static 类型的内部变量来存储多读的字符。如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <ctype.h>
#include "calc.h"


/* getop: get next character or numeric operand */
int getop(char s[])
{
    /* record the character read last time */
    static int lastc = ' ';
    int i, c;

    while ((s[0] = c = lastc) == ' ' || c == '\t')
        lastc = getchar();
    s[1] = '\0';
    if (!isdigit(c) && c != '.' && c != '-') 
        return c; /* not a number */
    i = 0;
    if (c == '-') {
        if (!isdigit(lastc = getchar()) && lastc != '.')
            return c; /* not a number, return '-' */
        s[++i] = c = lastc;
    }
    if (isdigit(c)) /* collect integer part */
        while (isdigit(s[++i] = c = getchar()))
            ;
    if (c == '.') /* collect fraction part */
        while (isdigit(s[++i] = c = getchar()))
            ;
    s[i] = '\0';
    lastc = c;
    return NUMBER;
}

exercise4-12

运用 printd 函数的设计思想编写一个递归版本的 itoa 函数,即通过递归调用把整数转换为字符串。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void itoa(int n, char s[]) {
    static int i = 0; // 静态变量,用于记录字符串的当前位置

    if (n < 0) {
        s[i++] = '-';
        n = -n;
    }

    if (n / 10) 
        itoa(n / 10, s);
    
    s[i++] = n % 10 + '0';
    s[i] = '\0'; // 字符串结束符
}

int main() {
    int n;
    char str[MAXLEN];
    printf("Please input n: ");
    scanf("%d", &n);
    itoa(n, str);
    printf("ans is: %s\n", str);
}

exercise4-13

编写一个递归版本的 reverse(s)函数,以将字符串 s 倒置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void reverse(char s[], int l, int r) {
    if (l >= r) 
        return;
    char temp = s[l];
    s[l] = s[r];
    s[r] = temp;
    
    reverse(s, l + 1, r - 1);
}

int main() {
    char str[MAXLINE];
    printf("Please input str:\n");
    scanf("%s", str);
    reverse(str, 0, strlen(str) - 1);
    printf("Reversed string: %s\n", str);
    return 0;
}

exercise4-14

定义宏 swap(t, x, y)以交换 t 类型的两个参数。(使用程序块结构会对你有所帮助。)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

#include <stddef.h>
#include <stdio.h>

// 一个字节一个字节进行交换
#define swap(t, x, y)                               \
    do                                              \
    {                                               \
        unsigned char *a = (unsigned char *)(&(x)); \
        unsigned char *b = (unsigned char *)(&(y)); \
        size_t ByteCount = sizeof(t);               \
        char temp;                                  \
        while (ByteCount--)                         \
        {                                           \
            temp = *a;                              \
            *a = *b;                                \
            *b = temp;                              \
            a++;                                    \
            b++;                                    \
        }                                           \
    } while (0)

int main()
{
    int ix, iy;
    double dx, dy;
    char *cpx, *cpy;

    ix = 2;
    iy = 4;
    printf("integers before swap: %d and %d\n", ix, iy);
    swap(int, ix, iy);
    printf("integers after swap: %d and %d\n", ix, iy);
    dx = 225.1;
    dy = 417.2;
    printf("doubles before swap: %g and %g\n", dx, dy);
    swap(double, dx, dy);
    printf("doubles after swap: %g and %g\n", dx, dy);
    cpx = "gooood";
    cpy = "idea";
    printf("char pointers before swap: %s and %s\n", cpx, cpy);
    swap(char *, cpx, cpy);
    printf("char pointers after swap: %s and %s\n", cpx, cpy);
    return 0;
}
1
2
3
4
5
6
integers before swap: 2 and 4
integers after swap: 4 and 2
doubles before swap: 225.1 and 417.2
doubles after swap: 417.2 and 225.1
char pointers before swap: gooood and idea
char pointers after swap: idea and gooood