6 表达式求值

目录

一、概述

二、代码

2.1 访问栈顶函数

2.2 符号优先级判断

2.3 符号优先级判断

2.4 操作符栈与数据栈的计算

2.5 核心计算

2.6 测试样例

 2.7 测试结果

三、总结&反思


一、概述

        比较难的一个点,之前看CSAPP的时候里面有提到中缀表达式和后缀表达式。当时看的时候就没有明白,今天结合老师的代码仔细地顺一下这个思路。

二、代码

一些栈的基本操作就不再赘述,讲几个核心函数

2.1 访问栈顶函数

/*访问栈顶元素*/
int TopElem(CharStackPtr TempStack, char *e)
{
    if (TempStack->top == -1)
    {
        return ERROR;
    }
    *e = TempStack->data[TempStack->top];
    return OK;
}

        弹栈操作会让Top值变化,有时候我们只是需要访问以下栈顶元素,方便后续的判断。

2.2 符号优先级判断

static char priority[128] = {0};
/*判断符号优先级*/
void Init_Priority()
{
    /*
    通过利用各个符号的ASCII码进行一个数组赋值
    数值越低,优先级越高
    */
    priority['+'] = 3;
    priority['-'] = 3;
    priority['*'] = 2;
    priority['/'] = 2;
    priority['('] = 1;
    priority[')'] = 1;
}

        在后续判断是否需要讲符号栈和数字栈的数字取出来进行计算,我们需要判断当前符号和栈顶符号优先级的先后顺序。

        这里巧妙地地方在于,把整个数组设置成128长度,然后使用符号地ASCII码进行储存,浪费了很多地空间,但是一定程度上优化了可读性。

2.3 符号优先级判断

/*比较运算符号的优先级*/
int Compare_Priority(char op1, char op2)
{
    /*如果op1优先于op2,返回正整数*/
    return priority[op2] - priority[op1];
}

        书接上文,对操作符优先级地判断。

2.4 操作符栈与数据栈的计算

/*运算符和操作数进行运算*/
int Op_Num_Col(CharStackPtr Nums, CharStackPtr Ops)
{
    int a, b;
    char op;
    Pop(Ops, &op);

    if (OK != Pop(Nums, &b))
    {
        return ERROR;
    }

    if (OK != Pop(Nums, &a))
    {
        return ERROR;
    }

    switch (op)
    {
    case '+':
    {
        Push(Nums, a + b);
        break;
    }
    case '-':
    {
        Push(Nums, a - b);
        break;
    }
    case '*':
    {
        Push(Nums, a * b);
        break;
    }
    case '/':
    {
        Push(Nums, a / b);
        break;
    }
    }
    return OK;
}

        需要获取操作符栈顶元素,以及操作数栈顶的两个元素,这里非常非常容易的一个错误就是ab获取的先后顺序,在除法和减法中他们的顺序安排。

2.5 核心计算

int Calculate(char *exp, int *result)
{
    CharStackPtr Nums, Ops;
    Nums = (CharStackPtr)malloc(sizeof(CharStack));
    Nums->top = -1;

    Ops = (CharStackPtr)malloc(sizeof(CharStack));
    Ops->top = -1;

    int flag_NumJudge = 0; // 1代表是数字,0代表非数字
    int temp = 0;
    int op;
    while (*exp != 0)
    {
        /*如果是数字,关键判断是不是连续多位数*/
        if (isdigit(*exp))
        {
            if (flag_NumJudge == 1)
            {
                Pop(Nums, &temp);
            }
            else
            {
                temp = 0;
            }
            flag_NumJudge = 1;             //表面现在操作的符号是一个数字
            temp = temp * 10 + *exp - '0'; //注意这个减去'0'
            Push(Nums, temp);
        }
        /*如果是符号,关键看目前遇到的这一层符号优先级是不是低于栈里面的元素*/
        else if ('/' == *exp || '*' == *exp || '+' == *exp || '-' == *exp)
        {
            flag_NumJudge = 0;
            while ((Ops->top > -1) &&           //首先操作符栈要有数
                   (OK == TopElem(Ops, &op)) && //注意这里不是弹栈,是得到栈顶元素
                   '(' != op &&
                   ')' != op &&                       //这个操作符是加减乘除
                   (Compare_Priority(*exp, op) <= 0)) //在里面的操作符是优先于目前遇到的符号的
                                                      //比如 2*6+9 所以这一步可以去算2*9
                                                      //如果是2+6*9 这里的9应该是和下面的去匹配,所以不会去运算
            {
                Op_Num_Col(Nums, Ops);
            }

            Push(Ops, *exp);
        }
        /*左括号直接入栈*/
        else if ('(' == *exp)
        {
            flag_NumJudge = 0;
            Push(Ops, *exp);
        }
        /*右括号处理和左括号之间的所有符号数*/
        else if (')' == *exp)
        {
            flag_NumJudge = 0;
            while (OK == TopElem(Ops, &op) && '(' != op)
            {
                Op_Num_Col(Nums, Ops);
            }
            Pop(Ops, &op);
        }
        else
        {
            flag_NumJudge = 0;
        }
        exp++;
    }
    //结果这一层一定把括号都处理完了

    while (Ops->top > -1 && Nums->data > -1)
    {
        Op_Num_Col(Ops, Nums);
    }
    return 0;
}

        这个是核心的函数,我们一点点看其中关键的核心设计。该函数涉及多个循环,第一个循环,也就是最外层的循环是用于指针的移动,移动表达式数组的。增加了一个标志变量flag,用于配合一个while循环把数字全部统计成int类型,比如“698”这是一个符号串。这一步讲符号串化成198(int)

        如果目前读到的元素是操作符,需要判断这个操作符与栈顶操作符的优先关系,如果是小于,可以进行计算,如果不是则NO。代码中增加的注释能更好地理解这一点。

        如果是左括号,直接入栈。

        如果是右括号,去寻找左括号,并在里面讲可以计算地进行计算

        最后出来整个训话之后,能确保地一点是括号全部被操作,但是依然可能存在一些留存地操作符和操作数。需要最后再将栈去空。但此时其实是已经将表达式进行了后缀表达式地转换。

2.6 测试样例

        

 

 

 2.7 测试结果

        

三、总结&反思

        虽然代码出了正确结果,但有一些地方我觉得可能在边缘测试样例上存在问题。并不是完全自己独立写出来的,尤其是核心函数,那个顺序的安排,以及在第二个关键while中的截至条件,参考了CSDN和B站上的思路。

        我觉得关键的难点是,对于表达式字符串指针的把握,因为不止在一个while循环中这个指针会改变。

        理解这个代码的关键我觉得可以去去康康,中缀表达式和后缀表达式,在写的过程中,尤其是符号优先级的对于是否要计算的影响,不会写,本质是没明白怎么转成后缀表达式,在理解转表达式的基础上,一些地方也就解决了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值