首页 > 代码库 > 后缀表达式实战:Qt制作计算器

后缀表达式实战:Qt制作计算器

导言

相信学过数据结构的人都听说过后缀表达式,就是在学习栈的时候。可能也有很多人实现过这一算法,不过基本上也都是在控制台窗口里用用。相信大家也都用过计算器windows里面的calc。但是有没发现它只能单步计算,而不能一次计算一个表达式。后缀表达式就有了用武之地,可以一次性计算一整个个式子。科技要为生产服务,所以我就实际去做了一个依据后缀表达式的带有图形化界面的计算器。

什么是后缀表达式

后缀表达式又称逆波兰式,用于简化计算数学表达式,是计算器类软件开发的重要理论依据。这部分有两个要点:

  • 中缀表达式转后缀表达式
  • 后缀表达式的计算

中缀表达式转后缀表达式

正常的数学表达式就是一个中缀表达式,假设使用字符串存储这个表达式。设置一个运算符栈op,并把字符‘\0‘进栈,再设置一个保存结果的字符串(字符数组)suffix。然后顺序扫描中缀表达式,如果是数字字符则直接加入到suffix,如果是 运算符 + - * /。则和运算符栈op的栈顶元素比较,若是比栈顶运算符的级别高,则进栈。否则退出栈顶元素,把它加入到suffix,然后重复上一操作继续与栈顶比较,直到比栈顶级别高然后进栈为止。

其中符号的优先级:

#* 、/+、-
0012

其中左括号较为特殊,如果是左括号则直接进入op,无需比较。进栈之后左括号用于最低优先级,可以理解为这是一个新的数学表达式的开始。若是右括号,则运算符栈一直退栈,直到退出一个左括号为止。在扫描完中缀表达式后,把op栈中的元素依次出栈加到suffix中(除了#)。注意每个数字之后要加空格符分隔,以免两个相邻的数字产生歧义。

后缀表达式的计算

设置一个新的栈S存储double类型的数据。从左到右扫描suffix字符串,遇到空格符,就把它前面的数字字符都转换成相应的double型数字,然后压入栈S中。遇到操作符就要把栈S连续出栈两次,然后结合该操作符进行相应的计算,再把结果压入栈S中。直到扫描完suffix字符串,那么栈S的栈顶元素即为结果。

理论说了这么多,相信大家一定很难只看文字就能体会的。所以接下来我们进入实战部分

Qt实战

关于图形化界面的绘制这部分不是本文重点,故不做介绍。最简单的方式就是新建Qt的ui文件,然后用拖控件的方式绘制出来(囧)。基本的思路就是:

  1. 按一个按键,比如数字键,运算符(+ - * /)或者左右括号,然后可以在计算器屏幕上显示出来。
  2. 当你按下等于号 = 的时候,开始计算。
  3. 从计算器显示屏(使用QLneEdit实现)获取字符串QString。
  4. 把获取的中缀串转化成后缀串
  5. 从后缀串计算出结果,并让计算器显示屏显示出来。

实现中缀转成后缀

我们的主窗口类是MainWindow,在其中添加一public函数:toPostfix()  在这个函数中我们首先要获得已经输入的表达式字符串。

QString exp = ui->lineEdit->text();

这里的lineEdit就是我们计算器的显示屏对象指针。它的text方法返回它所显示的文字。然后为了比较不同运算符的优先级,我们要再定义一个函数 getLevel

int MainWindow::getLevel(const QChar &oper)
{
    switch(oper.cell())
    {
    case ‘#‘:
    case ‘(‘:return 0;
    case ‘+‘:
    case ‘-‘:return 1;
    case ‘*‘:
    case ‘/‘:return 2;
    }
    return 0;
}

注意QChar类型当然是不能直接放到switch里面的。它的cell方法返回普通char类型。我们还要在MainWindow类里定义两个私有成员:

  • 一个栈来opStack充当运算符栈。QStack qt的栈类型,为了保持一致性以及避免一些问题。我们最好不要采用C++的STL里的Stack。幸运的是他们的方法名很多都是一样的。
  • 一个字符串postfix用来接受转换后的后缀表达式。QString类型。

最后toPostfix函数为:

void MainWindow::toPostfix()
{
    QString exp = ui->lineEdit->text();

    for(int i=0;i<exp.length();i++)
    {
        if(exp[i].isDigit()||exp[i]==‘.‘)
        {
            postfix.push_back(exp[i]);
        }
        else if(exp[i]==‘(‘)
        {
            opStack.push(exp[i]);
        }
        else if(exp[i]==‘)‘)
        {
            postfix.push_back(‘ ‘);//空格用于分隔
            while(opStack.top()!=‘(‘)
            {
                postfix.push_back(opStack.pop());
            }
            opStack.pop();
        }
        else if(getLevel(exp[i])>getLevel(opStack.top()))
        {
            postfix.push_back(‘ ‘);
            opStack.push(exp[i]);
        }
        else
        {
            postfix.push_back(‘ ‘);qDebug()<<postfix;
            while(getLevel(exp[i])<=getLevel(opStack.top()))
                postfix.push_back(opStack.pop());
            opStack.push(exp[i]);
        }
    }
    while(opStack.top()!=‘#‘)
    {
        QChar c = opStack.pop();
        postfix.push_back(‘ ‘);
        postfix.push_back(c);
    }
}

实现后缀表达式计算

定义一个函数evaluation用于计算。我们要新建一个double类型的栈来保存每一次运算的结果。一个临时字符串用于存储后缀表达式中的数字。

QString tem;
QStack<double> ans;
QString类有方便的方法来进行字符串与基本数据类型之间的转换。比如
tem.toDouble()
此外,因为字符串也是一种容器,所以它支持clear方法来清空字符串。就能获得一个double类型的数据啦。是不是很方便。最后evaluation函数为:

void MainWindow::evaluation()
{
    QString tem;
    QStack<double> ans;
    for(int i=0;i<postfix.size();i++)
    {
        if(postfix[i].isDigit()||postfix[i]==‘.‘)
            tem.push_back(postfix[i]);
        else if(postfix[i]==‘ ‘)
        {
            if(!tem.isEmpty())
            {
                ans.push(tem.toDouble());
                tem.clear();//转换完了就清空。
            }
        }
        else
        {
            double a,b;
            a=ans.pop();
            b=ans.pop();
            switch(postfix[i].cell())
            {
            case ‘+‘:ans.push(b+a);break;
            case ‘-‘:ans.push(b-a);break;//注意a,b顺序
            case ‘*‘:ans.push(b*a);break;
            case ‘/‘:ans.push(b/a);break;//注意a,b顺序
            }
        }

    }
    //计算器屏幕显示结果
    ui->lineEdit->setText(QString::number(ans.top()));
}

项目地址


后缀表达式实战:Qt制作计算器