You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于K&R C《程序设计语言》中逆波兰计算器的技术疑问

K&R C逆波兰计算器代码疑问解答

我是C语言新手,具备一定通用编程基础,正在通过K&R C《程序设计语言》自学,在理解书中的逆波兰计算器示例代码时遇到困惑,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#define MAXOP 100
#define NUMBER '0'
#define MAXVAL 100
#define BUFSIZE 100

int getop(char []);
void push(double);
double pop(void);
int getch(void);
void ungetch(int);

int main(void) {
    int type;
    double op2;
    char s[MAXOP];

    while ((type = getop(s)) != EOF) {
        switch (type) {
        case NUMBER:
            push(atof(s));
            break;
        case '+':
            push(pop() + pop());
            break;
        case '*':
            push(pop() * pop());
            break;
        case '-':
            op2 = pop();
            push(pop() - op2);
            break;
        case '/':
            op2 = pop();
            if (op2 != 0.0)
                push(pop() / op2);
            else
                printf("error: zero divisor\n");
            break;
        case '\n':
            printf("\t%.8g\n", pop());
            break;
        default:
            printf("error: unknown command %s\n", s);
            break;
        }
    }

    return 0;
}

int sp = 0;
double val[MAXVAL];

void push(double f) {
    if (sp < MAXVAL)
        val[sp++] = f;
    else
        printf("error: stack full, can't push %g\n", f);
}

double pop(void) {
    if (sp > 0)
        return val[--sp];
    else {
        printf("error: stack empty\n");
        return 0.0;
    }
}

int getop(char s[]) {
    int i, c;

    while ((s[0] = c  = getch()) == ' ' || c == '\t')
        ;
    s[1] = '\0';

    if (!isdigit(c) && c != '.')
        return c;

    i = 0;
    if (isdigit(c))
        while (isdigit(s[++i] = c = getch()))
            ;
    if (c == '.')
        while (isdigit(s[++i] = c = getch()))
            ;
    s[i] = '\0';
    if (c != EOF)
        ungetch(c);
    return NUMBER;
}

char buf[BUFSIZE];
int bufp = 0;

int getch(void) {
    return (bufp > 0) ? buf[--bufp] : getchar();
}

void ungetch(int c) {
    if (bufp >= BUFSIZE)
        printf("ungetch: too many characters\n");
    else
        buf[bufp++] = c;
}

疑问1:getop函数内的s[1] = '\0';语句作用是什么?输入 1 2 - 4 5 + *时是否会导致处理异常?

s[1] = '\0'的核心作用是确保单个非数字/非小数点字符(比如运算符、换行符)能被包装成合法的C字符串。当getop读取到这类字符时(比如'-'),s[0]会被赋值为该字符,s[1]设为字符串结束符,这样后续如果进入错误分支(比如未知命令)打印s时,能正确输出这个单个字符,而不会出现内存越界或乱码。

针对输入 1 2 - 4 5 + *的情况,这个语句不会导致异常:

  • 处理数字'1'时,getop跳过前置空格后读到'1',此时会进入数字处理分支:i初始化为0,循环读取后续字符(直到遇到空格),最后会用s[i] = '\0'覆盖之前的s[1] = '\0',最终s是合法的数字字符串"1"。
  • 处理'-'时,getop跳过空格后读到'-',此时s[0]='-',s[1]='\0',因为'-'不是数字/小数点,直接返回该字符,s是合法的"-"字符串。

所以完全不会出现你担心的 \0 2 - ...的错误情况。

疑问2:每次执行type = getop(s)时,程序如何定位下一个运算符/操作数?是否总是从输入开头读取?

程序不会每次都从输入开头读取,而是通过**getch和ungetch实现的字符缓冲机制**,按顺序逐个处理输入中的"token"(操作数或运算符):

  1. getch优先从缓冲区buf中取字符,只有缓冲区为空时才调用getchar()从标准输入读取新字符。
  2. 每次getop处理完一个token后,会把多余读取的字符(比如数字后面的空格、运算符后的换行符)通过ungetch放回缓冲区,供下一次getop调用时读取。
  3. getop内的i初始化为0是因为每次处理新token时,需要重新使用s数组存储当前token,每个token的构建都是独立的,和之前的token无关。

比如处理输入 1 2 -时:

  • 第一次getop:跳过空格,读取'1',处理为数字token,把后面的空格放回缓冲区。
  • 第二次getop:先从缓冲区取到空格,跳过它,读取'2',处理为数字token,把后面的空格放回缓冲区。
  • 第三次getop:取到缓冲区的空格,跳过,读取'-',返回运算符。

整个过程是连续的,不会重复读取前面的内容。

疑问3:减号-和除号/为何必须先将pop()结果赋值给op2?直接写pop() - pop()不行吗?

这是由栈"后进先出"的特性和加减乘除的运算顺序决定的:

  • 逆波兰表达式中,比如2 1 -对应的是数学运算2 - 1。执行时,先push(2),再push(1)。此时栈顶是1,栈底是2。
  • 如果直接写pop() - pop(),会先弹出1,再弹出2,运算结果是1 - 2 = -1,和预期的2-1完全相反。
  • 先把第一个弹出的值(1)存到op2,再弹出第二个值(2),执行2 - op2,才能得到正确结果。

除法同理,比如4 2 /对应4 / 2,直接pop() / pop()会得到2 / 4 = 0.5,而先存op2=2,再执行pop() / op2(即4/2)才是正确结果。

因为减和除是非交换运算符,操作数的顺序不能颠倒,必须借助临时变量op2保留先弹出的那个操作数,才能保证运算顺序正确。


内容的提问来源于stack exchange,提问作者Larry Ling

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 11:54:56