当前位置:嗨网首页>书籍在线阅读

10-求算术表达式的值

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

2.2.3 求算术表达式的值

问题描述

19.png 通过键盘输入一个算术表达式,如21−(15+7×9)/3,要求将其转换为后缀表达式,并计算该算术表达式的值。

【分析】

表达式求值是高级程序设计语言中编译器设计的一个基本问题。它的实现基于栈的后进先出特性。

一个算术表达式是由操作数(运算对象)、运算符和分界符(括号)组成的有意义的式子。运算符从操作数的个数上可分为单目运算符和双目运算符,从运算类型上可分为算术运算符、关系运算符、逻辑运算符等。在此为了简化问题,我们假设运算符只包含加、减、乘、除双目运算符,分界符只包含左、右两种圆括号。

例如,一个算术表达式为a−(b+c*d)/e,这种算术表达式中的运算符总是出现在两个操作数之间,这种算术表达式称为中缀表达式。计算机编译系统在计算一个算术表达式之前,要将中缀表达式转换为后缀表达式,然后对后缀表达式进行计算。后缀表达式中,算术运算符出现在操作数之后,并且不含括号。

计算机在求解算术表达式的值时分为以下两个步骤。

(1)将中缀表达式转换为后缀表达式。

(2)依据后缀表达式计算算术表达式的值。

1.将中缀表达式转换为后缀表达式

要将一个算术表达式的中缀形式转化为相应的后缀形式,首先要了解算术四则运算的规则。算术四则运算的规则如下。

(1)算术运算的优先级——先乘除,后加减。

(2)有括号先算括号内的,后算括号外的,多层括号由内到外进行计算。

(3)同级别的运算从左到右进行计算。

上面的算术表达式转换为后缀表达式如下。

a b c d *  +  e / −

不难看出,转换后的后缀表达式具有以下3个特点。

(1)后缀表达式与中缀表达式的操作数出现的顺序相同,只是运算符的先后顺序改变了。

(2)后缀表达式不出现括号。

(3)后缀表达式的操作符出现在操作数之后。

后缀表达式也叫逆波兰表达式,是波兰逻辑学家简·卢卡西维茨(Jan Lukasiewicz)于1929年提出的表示方法。每个运算符都位于操作数之后,因此称为后缀表达式。

后缀表达式既无括号也无优先级的约束,因此只需要从左到右依次扫描后缀表达式的各个字符,遇到运算符时,直接对运算符前面的两个操作数进行运算即可。

如何将中缀表达式转换为后缀表达式呢?可以设置一个栈,用于存放运算符。依次读入中缀表达式中的每个字符,如果是操作数,则直接输出。如果是运算符,则比较栈顶元素与当前运算符的优先级,然后进行处理,直到整个中缀表达式处理完毕。我们约定“#”作为后缀表达式的结束标志,假设θ1为栈顶运算符,θ2为当前扫描的运算符。则中缀表达式转换为后缀表达式的算法描述如下。

(1)初始化栈,并将“#”入栈。

(2)若当前读入的字符是操作数,则将该操作数输出,并读入下一字符。

(3)若当前读入的字符是运算符,则记作θ2,并将θ2与栈顶的运算符θ1比较。若θ1优先级低于θ2,则将θ2入栈;若θ1优先级高于θ2,则将θ1出栈并将其作为后缀表达式输出。然后继续比较新的栈顶运算符θ1与当前运算符θ2的优先级。若θ1的优先级与θ2相等,且θ1为“(”,θ2为“)”,则将θ1出栈,继续读入下一个字符。

(4)如果θ2的优先级与θ1相等,且θ1和θ2都为“#”,则将θ1出栈,栈为空。完成中缀表达式转换为后缀表达式,算法结束。

运算符的优先关系如表2.1所示。其中,“>”“<”“=”分别表示θ1的优先级大于、小于、等于θ2的优先级。

表2.1 运算符的优先关系

88.png 利用上述算法,将中缀表达式a−(b+c*d)/e转换为后缀表达式的输出过程如表2.2所示(为了便于描述,在表达式的末尾加一个结束标记“#”)。

表2.2 中缀表达式a−(b+c*d)/e转换为后缀表达式的输出过程

| 步骤 | 中缀表达式 | 栈 | 输出后缀表达式 | | 步骤 | 中缀表达式 | 栈 | 输出后缀表达式 | | :----- | :----- | :----- | :----- | :----- | :----- | :----- | :----- | :----- | :----- | :----- | | 1 | a−(b+cd)/e# | # | | | 9 | )/e# | #−(+ | abcd | | 2 | − (b+cd)/e# | # | a | | 10 | /e# | #−(+ | abcd | | 3 | (b+cd)/e# | #− | a | | 11 | /e# | #−( | abcd+ | | 4 | b+cd)/e# | #−( | a | | 12 | /e# | #− | abcd+ | | 5 | +cd)/e# | #−( | ab | | 13 | e# | #−/ | abcd+ | | 6 | cd)/e# | #−(+ | ab | | 14 | # | #−/ | abcd+e | | 7 | d)/e# | #−(+ | abc | | 15 | # | #− | abcd+e/ | | 8 | d)/e# | #−(+ | abc | | 16 | # | # | abcd+e/− |

2.后缀表达式的计算

在计算后缀表达式时,需要设置两个栈——operator栈和operand栈。其中,operator栈用于存放运算符,operand栈用于存放操作数和中间运算结果。具体算法思路如下。

依次读入后缀表达式中的每个字符,如果是操作数,则将操作数入operand栈;如果是运算符,则将操作数出栈两次,然后对操作数进行当前操作符的运算,直到整个后缀表达式处理完毕。

第2章\实例2-07.cpp
/********************************************
*实例说明:求算术表达式的值
*********************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream.h>
typedef char DataType;
#include"LinkStack.h"
#define MAXSIZE 50
/*operand栈定义
typedef struct
{
    float data[MAXSIZE];
    int top;
}OpStack;
/*函数声明*/
void TranslateExpress(char s1[],char s2[]);
float ComputeExpress(char s[]);
void main()
{
    char a[MAXSIZE],b[MAXSIZE];
    float f;
    cout<<"请输入一个算术表达式:"<<endl;
    gets(a);
    cout<<"中缀表达式为:"<<a;
    TranslateExpress(a,b);
    cout<<endl<<"后缀表达式为:"<<b<<endl;
    f=ComputeExpress(b);
    cout<<"计算结果:"<<f<<endl;
}
float ComputeExpress(char a[])
/*计算后缀表达式的值*/
{
    OpStack S;                  
    int i=0,value;
    float x1,x2;
    float result;
    S.top=-1;                   
    while(a[i]!='\0')         
    {
        if(a[i]!=' '&&a[i]>='0'&&a[i]<='9')
        /*如果当前字符是数字字符*/
        {
            value=0;
            while(a[i]!=' ')  
            {
                value=10*value+a[i]-'0';
                i++;
            }
            S.top++;
            S.data[S.top]=value;    /*处理之后将数字入栈*/
        }        
        else                        /*如果当前字符是运算符*/
        {
            switch(a[i])     /*将栈中的数字出栈两次,然后用当前的运算符进行运算,再将结果入栈*/
            {        
                case '+':
                    x1=S.data[S.top];
                    S.top--;
                    x2=S.data[S.top];
                    S.top--;
                    result=x1+x2;
                    S.top++;
                    S.data[S.top]=result;
                    break;
                case '-':
                    x1=S.data[S.top];
                    S.top--;
                    x2=S.data[S.top];
                    S.top--;
                    result=x2-x1;
                    S.top++;
                    S.data[S.top]=result;
                    break;
                case '*':
                    x1=S.data[S.top];
                    S.top--;
                    x2=S.data[S.top];
                    S.top--;
                    result=x1*x2;
                    S.top++;
                    S.data[S.top]=result;
                    break;
                case '/':
                    x1=S.data[S.top];
                    S.top--;
                    x2=S.data[S.top];
                    S.top--;
                    result=x2/x1;
                    S.top++;
                    S.data[S.top]=result;
                    break;
            }
            i++;
        }
    }
    if(!S.top!=-1)                /*如果栈不空,则将结果出栈,并返回*/
    {
        result=S.data[S.top];
        S.top--;
        if(S.top==-1)
            return result;
        else
        {
            printf("表达式错误");
            exit(-1);
        }
    }
}
void TranslateExpress(char str[],char exp[])
/*把中缀表达式转换为后缀表达式*/
{
    LinkStack S;                 /*定义一个栈,用于存放运算符*/
    char ch;
    DataType e;
    int i=0,j=0;
    InitStack(&S);
    ch=str[i];
    i++;
    while(ch!='\0')               /*依次扫描中缀表达式中的每个字符*/
    {
        switch(ch)
        {
            case'(':              /*如果当前字符是左括号,则将其入栈*/
                PushStack(S,ch);
                break;
            case')':              /*如果是右括号,则将栈中的运算符出栈,并将其存入数组exp中*/
                while(GetTop(S,&e)&&e!='(')
                {
                    PopStack(S,&e);
                    exp[j]=e;
                    j++;
                    exp[j]=' ';    /*加上空格*/
                    j++;
                }
                PopStack(S,&e);    /*将左括号出栈*/
                break;
            case'+':
            case'-':               /*如果遇到的是“+”和“-”,因为其优先级低于栈顶运算符的优先级,
                                   所以先将栈顶运算符出栈,并将其存入数组exp中,然后将当前运算符入栈*/
                while(!StackEmpty(S)&&GetTop(S,&e)&&e!='(')
                {
                    PopStack(S,&e);
                    exp[j]=e;
                    j++;
                    exp[j]=' ';    /*加上空格*/
                    j++;
                }
                PushStack(S,ch);   /*当前运算符入栈*/
                break;
            case'*':               /*如果遇到的是“*”和“/”,则先将同级运算符出栈,并存入数组
                                   exp中,然后将当前的运算符入栈*/
            case'/':
                while(!StackEmpty(S)&&GetTop(S,&e)&&e=='/'||e=='*')
                {
                    PopStack(S,&e);
                    exp[j]=e;
                    j++;
                    exp[j]=' ';      /*加上空格*/
                    j++;
                }
                PushStack(S,ch);     /*当前运算符入栈*/
                break;
            case' ':                 /*如果遇到空格,忽略*/
                break;
            default:                 /*如果遇到的是操作数,则将操作数直接送入数组exp中,并在其  
                                     后添加一个空格,用来分隔数字字符*/
                while(ch>='0'&&ch<='9')
                {
                    exp[j]=ch;
                    j++;
                    ch=str[i];
                    i++;
                }
                i--;
                exp[j]=' ';
                j++;
        }
        ch=str[i];              /*读入下一个字符,准备处理*/
        i++;
    }
    while(!StackEmpty(S))       /*将栈中所有剩余的运算符出栈,送入数组exp中*/
    {
        PopStack(S,&e);
        exp[j]=e;
        j++;
        exp[j]=' ';             /*加上空格*/
        j++;
    }
    exp[j]='\0';
}

运行结果如图2.14所示。

89.png

图2.14 运行结果

33.png 注意: 在中缀表达式转换为后缀表达式的过程中,每输出一个数字字符时,都需要在其后补一个空格,与其他相邻数字字符隔开。若把一串数字字符放在一起,会无法区分它是一个数字还是两个数字。

这个算法利用了栈的链式存储结构和基本运算来实现。在调试程序时,由于误将以下代码中的语句PopStack(S,&e)错写成PopStack(&S,&e),因此造成了运行时错误,程序直接崩溃。

while(!StackEmpty(S)&&GetTop(S,&e)&&e!='(')
{
    PopStack(S,&e);
    exp[j]=e;
    j++;
    exp[j]=' ';        /*加上空格*/
    j++;
}

这是许多初学C/C++或数据结构的朋友经常遇到的问题。遇到这样的问题时,要找出哪一行出现了错误,就需要用到Visual C++的调试工具。Visual C++的调试工具可以单步跟踪调试,先设置好断点,然后按F10或F11键单步跟踪每一条语句。发现了错误程序就直接停止,这样就找到了错误行,然后仔细检查就可以很容易地纠正错误。

以上几个实例是通过调用链栈的基本运算实现的。当然,也可以通过调用顺序栈的基本运算实现,它们的使用方法和算法思想是完全一样的。如果仅仅是作为这些基本运算的使用者,那么我们只需要考虑如何利用栈的基本运算,而不需要关心这些运算具体是如何实现的。