6.QT-简易计算器实现(详解)

界面展示

1.用户界面类设计

需要使用QWidget组件作为顶层窗口,QLineEdit组件作为输入框,QPsuhButton作为按钮

1.1 在代码里处理按键消息时,需要处理下用户输入的格式(方便逻辑模块计算)

1)匹配括号成对出现,左括号必然先于右括号出现

  • 当有左括号出现时,则status++
  • 当有右括号出现时,并且status!=0时,则右括号有效,并status--  

2)判断每个按键是否合法

数字前面不能为:右括号

比如:

代码语言:javascript
复制
10+3)5*2          //出错,数字5前面不能为右括号

小数点前面不能为空,只能是数字,并且一串数字只能有一个小数点

比如:

代码语言:javascript
复制
1.23.45      //出错,一串数字只能有一个小数点

加减号前面不能为:小数点,并且前面不能连续有两次加减乘除,或者是(和运算符

比如: 

代码语言:javascript
复制
7*-+10           //出错,+号前面出现两次加减乘除
7. +            //出错,+号前面不能有小数点 
7-(--5)          //出错, -5数字前面有个减号

乘除号前面不能为:左括号,空,小数点,加减乘除,

比如: 

代码语言:javascript
复制
*1+(/5+10)                   //出错, *前面不能为空,且除法前面不能为左括号

左括号前面不能为:右括号,数字,小数点,并且前面不能连续有两次加减乘除

比如: 

代码语言:javascript
复制
( )+10(11+10)               //出错,( 前面不能为数字

右括号前面不能为:空,加减乘除,小数点,左括号,并且与左括号成对出现

比如:  

代码语言:javascript
复制
) + (10+ 5.)                   //出错,右括号不能出现在开头,并且右括号前面不能有小数点

2.逻辑模块类设计

如何计算四则运算表达式.比如:

2.1 将中缀表达式进行数字和运算符的分离,并保存到队列里

1)需要考虑 + - 是正负号,还是加减运算符

+-出现在表达式开头时,表示为正负号,比如:

代码语言:javascript
复制
+7-5;                    //+出现在开头,说明这个+,表示的是正号,而不是加号

当出现+-时,并且前面还有运算符时,表示为正负号,比如:

代码语言:javascript
复制
7*-5;                            //-前面还有*,说明这个-,表示的是负号,而不是减号

当出现+-时,并且前面还有左括号时,表示为正负号,比如:

代码语言:javascript
复制
9+(-3+4)               //-前面还有(,说明这个-,表示负号,而不是减号

2)以下图的中缀表达式为例

分离后,队列的每个元素应该为:

代码语言:javascript
复制
str[0] = "+9"
str[1] = "+"
str[2] = "("
str[3] = "-3"
str[4] = "-"
str[5] = "-1"
str[6] = ")"
str[7] = "*"
str[8] = "-5"

2.2 将分解出来的中缀表达式队列 转换为后缀表达式队列

比如+9 + (-3 - -1)* -5,转换为后缀表达式为:

代码语言:javascript
复制
 +9,  -3, -1, -, -5, *, +

后缀表达式队列的每个元素应该为:

代码语言:javascript
复制
str[0] = "+9"
str[1] = "-3"
str[2] = "-1"
str[3] = "-"
str[4] = "-5"
str[5] = "*"
str[6] = "+"  

思路

由于运算符处于后缀,所以需要使用栈,用来存储运算符以及括号

转换过程

-当队列元素为数字时

  • 直接保存到队列

-当队列元素为加减时

  • 判断栈顶的运算优先级,由于+-的优先级小于等于所有运算符
  • 所以循环取出栈顶的运算符并入队列
  • 直到遇到栈为空遇到左括号时才停止,最后再将当前+-入栈

-当队列元素为乘除时

  • 判断栈顶的运算优先级,由于*/的优先级只小于等于*/
  • 所以循环判断栈顶运算符,如果栈顶运算符是*/,则取出并入栈
  • 直到遇到栈为空遇到左括号遇到+-时才停止,最后再将当前*/入栈

-当前队列元素为左括号时

  • 直接入栈

-当前队列元素为右括号时

  • 循环将栈顶运算符出栈并入队列
  • 直到遇到左括号停止,并将左括号出栈弃掉.

-当队列元素判断结束后

  • 判断栈是否为空,如果不为空,则将栈存储的运算符出栈并入队列

示意图如下所示

2.3 将后缀表达式的值计算出来

通过逆波兰表达式计算,思路如下

遇到数字时

  • 入栈

遇到运算符时

  • 依次取出右、左操作数,然后进行计算(有除法时,需要判断除数是否为0)
  • 计算完成后,再将结果入栈

当后缀表达式队列对空时

  •   表示遍历结束,此时栈中若只剩下唯一数字,则算出了结果答案.

示意图如下所示

3.代码实现

3.1 与界面相关的模块,用QCalculatorUI类实现

QCalculatorUI.h代码如下:

代码语言:javascript
复制
#ifndef QCALCULATORUI_H
#define QCALCULATORUI_H

#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QDebug>
#include <QString>
#include "QCalculatorDec.h"

class QCalculatorUI : public QWidget
{
Q_OBJECT

private:
QCalculatorDec mDec;
QLineEdit *mline; //显示行
QPushButton *mbuton[20]; //按钮成员
QCalculatorUI();
bool construct();

private slots:
void handler_clicked(); //处理按键消息

public:
int MatchingBoth(QString &str1,const char *str2); //匹配str1和str2,判断str1是否有str2的字符
int LastMatchingBoth(QString &str1,const char str2); //反向匹配str1和str2
static QCalculatorUI
NewIntance(); //成员需要资源申请,所以使用二阶构造
void show();
};
#endif // QCALCULATORUI_H

QCalculatorUI.cpp代码如下:

代码语言:javascript
复制
#include "QCalculatorUI.h"

QCalculatorUI::QCalculatorUI() : QWidget(NULL,Qt::WindowCloseButtonHint)
{
}

bool QCalculatorUI::construct()
{
int ret;
const char* butnText[20]=
{
"<-","CE",
"7","8","9","+","(",
"4","5","6","-",")",
"1","2","3","*","=",
"0", ".","/",
};

const int butnPos[20][4]=       //存放 x y w h
{
  {10,50,90,40},{110,50,140,40},                                                    //&lt;- CE
  {10,100,40,40},{60,100,40,40},{110,100,40,40},{160,100,40,40},{210,100,40,40},    //7 8 9 + (
  {10,150,40,40},{60,150,40,40},{110,150,40,40},{160,150,40,40},{210,150,40,40},    //4 5 6 - )
  {10,200,40,40},{60,200,40,40},{110,200,40,40},{160,200,40,40},{210,200,40,90},    //1 2 3 * =
  {10,250,90,40},               {110,250,40,40},{160,250,40,40},                    //0   . /
};

mline  =new QLineEdit(this);
if(mline==NULL)
    return false;
mline-&gt;resize(240,30);
mline-&gt;move(10,10);
mline-&gt;setAlignment(Qt::AlignRight);
mline-&gt;setReadOnly(1);

// mline->setFont(QFont(0,10)); //设置字体
this->setWindowTitle("计算器");
for(int i=0;i<20;i++)
{
mbuton[i]= new QPushButton(butnText[i],this);
if(mbuton[i]==NULL)
return false;

     mbuton[i]-&gt;resize(butnPos[i][2],butnPos[i][3]);
     mbuton[i]-&gt;move(butnPos[i][0],butnPos[i][1]);

     ret = QObject::connect(mbuton[i],SIGNAL(clicked()),this,SLOT(handler_clicked()));
     if(ret==false)
         return false;

}
return true;
}

QCalculatorUI* QCalculatorUI::NewIntance() //二阶构造
{
QCalculatorUI* ret = new QCalculatorUI();
if(ret==NULL || !ret->construct())
{
delete ret;
return NULL;
}
return ret;
}

int QCalculatorUI::LastMatchingBoth(QString& str1,const char* str2) //反向匹配str1和str2
{
for(int i=str1.length();i>=0;i--)
{
for(unsigned int j=0;j<strlen(str2);j++)
if(str1[i]==str2[j])
return i;
}
return -1;
}

int QCalculatorUI::MatchingBoth(QString& str1,const char* str2) //匹配str1和str2,判断str1是否有str2的字符
{
for(int i=0;i<str1.length();i++)
{
for(unsigned int j=0;j<strlen(str2);j++)
if(str1[i]==str2[j])
return i;
}
return -1;
}

void QCalculatorUI::handler_clicked() //处理按键消息
{
static int ClearLine=0;
static int bracket_cnt=0; //圆括号计数
QPushButton *btn =dynamic_cast<QPushButton* >(sender()); //获取对象
QString line = mline->text();
QString text = btn->text(); //获取消息

if(ClearLine)
{
    mline-&gt;setText(&#34;&#34;);
    line.clear();
    ClearLine=0;
}if(text&gt;=&#34;0&#34;&amp;&amp;text&lt;=&#34;9&#34;)    //数字
{
    QString tmp= line.right(1);
    if(tmp.length() &amp;&amp; tmp[0]==&#39;)&#39;)   //数字前面不能为右括号
    {
        return;
    }
    line+=text;
}

else if(text==&#34;.&#34; )    //小数点
{
    QString tmp= line.right(1);
    if(tmp.length()) //小数点前面只能是数字
   {
        if(MatchingBoth(tmp,&#34;0123456789&#34;)== -1)  //没找到数字
        {
            return;
        }
   }
   else             //小数点前面为空
   {
            return ;
   }

   int pos= LastMatchingBoth(line,&#34;+-*/.()&#34;);   //反向查找
    if(pos!= -1 &amp;&amp;line[pos]==&#39;.&#39; )        //一串数字只能有一个小数点
    {
        return ;
    }
     line+=text;
}

else if(text==&#34;+&#34;||text==&#34;-&#34;)       //加减号
{
    QString tmp= line.right(1);
   if(tmp.length()&amp;&amp; tmp[0]==&#39;.&#39;)     //前面不能为:小数点
   {
      return ;
   }
   tmp= line.right(2);
   if(tmp.length()==2)          //前面不能连续有两次加减乘除
   {
       if(tmp[0]==&#39;+&#39;||tmp[0]==&#39;-&#39;||tmp[0]==&#39;*&#39;||tmp[0]==&#39;/&#39;||tmp[0]==&#39;(&#39;)
            if(tmp[1]==&#39;+&#39;||tmp[1]==&#39;-&#39;||tmp[1]==&#39;*&#39;||tmp[1]==&#39;/&#39;)
                        return ;
   }
    line+=text;
}

else if(text==&#34;*&#34;||text==&#34;/&#34;)       //乘除号
{
     QString tmp= line.right(1);
     if(tmp.length())       //前面不能为:左括号,小数点,加减乘除,
     {
         if(MatchingBoth(tmp,&#34;(.+-*/&#34;)!= -1) //查找左括号,小数点,加减乘除
         {
             return;
         }
     }
     else                   //乘除号前面不能为空
          return;

    line+=text;
}

else if(text==&#34;(&#34;)       //左括号
{
    QString tmp= line.right(1);
    if(tmp.length())             //前面不能为:右括号,数字,小数点
    {
        if(MatchingBoth(tmp,&#34;)0123456789.&#34;)!= -1) //查找右括号,数字,小数点
        {
            return;
        }
    }

    tmp= line.right(2);
    if(tmp.length()==2)          //前面不能连续有两次加减乘除
    {
         if(tmp[0]==&#39;+&#39;||tmp[0]==&#39;-&#39;||tmp[0]==&#39;*&#39;||tmp[0]==&#39;/&#39;)
             if(tmp[1]==&#39;+&#39;||tmp[1]==&#39;-&#39;||tmp[1]==&#39;*&#39;||tmp[1]==&#39;/&#39;)
                         return ;
    }
     line+=text;
     bracket_cnt++;
}

else if(text==&#34;)&#34;)       //右括号
{
    QString tmp= line.right(1);
    if(bracket_cnt==0)  //前面没有左括号
       return;

    if(tmp.length())             //前面不能为:加减乘除,小数点,左括号
    {
       if(MatchingBoth(tmp,&#34;+-*/.(&#34;)!= -1) //查找加减乘除,小数点,左括号
       {
           return;
       }
    }
    else                    //右括号前面不能为空
       return;

    line+=text;
    bracket_cnt--;
}

else if(text==&#34;&lt;-&#34;)       //&lt;-
{
    if(line.length())
    line.chop(1);
}

else if(text==&#34;CE&#34;)       //清空
{
    line.clear();
    bracket_cnt=0;
}

else if(text==&#34;=&#34;&amp;&amp; line.length())
{
    QString ret=mDec.Result(line);
    if(ret==NULL)   //除数为0
    {
        line += &#34; : &#34;;
        line +=&#34;除数不能为0&#34;;
    }
    else if(ret==&#34;Error&#34;)
     {
        line += &#34;:&#34;;
        line +=&#34;格式出错&#34;;
     }
     else
     {
         line += &#34;=&#34;;
         line += ret;
     }
    ClearLine =1;
}
mline-&gt;setText(line);

}

void QCalculatorUI::show() //显示窗口
{
QWidget::show();
this->setFixedSize(this->width(),this->height());
}

3.2 与逻辑相关的用QCalculatorDec类实现

QCalculatorDec.h代码如下:

代码语言:javascript
复制
#ifndef QCALCULATORDEC_H
#define QCALCULATORDEC_H
#include <QString>
#include <QStack>
#include <QQueue>
#include <QDebug>

class QCalculatorDec
{
private:
QQueue<QString> Split(const QString& exp); //分离前缀
QQueue<QString> Transfer(QQueue<QString>& exp); //将中缀队列转换为后缀队列
QString Calculate(QQueue<QString>& exp); //将后缀队列计算出结果

QString Calculate(QString&amp; l,QString&amp; op,QString&amp; r );
QString ValidNum(QString str);

public:
QCalculatorDec();
QString Result(const QString& exp);
};

#endif // QCALCULATORDEC_H

QCalculatorDec.cpp代码如下:

代码语言:javascript
复制
#include "QCalculatorDec.h"

QCalculatorDec::QCalculatorDec()
{
}

QQueue<QString> QCalculatorDec::Split(const QString& exp) //分离前缀
{
QQueue<QString> ret;
QString num="";

for(int i=0;i&lt;exp.length();i++)
{
    if( (exp[i]==&#39;.&#39;) || ( (exp[i]&gt;=&#39;0&#39;) &amp;&amp; (exp[i]&lt;=&#39;9&#39;) ))    //判断小数点和数字
    {
        num += exp[i];
    }

    else if(exp[i]== &#39;(&#39; || exp[i]== &#39;)&#39; || exp[i]== &#39;*&#39; || exp[i]== &#39;/&#39;  )
    {
        if(!num.isEmpty())
        {
            ret.enqueue(num);        //将数字入队列
            num.clear();
        }
        ret.enqueue(exp[i]);
    }

    else if(exp[i]== &#39;+&#39; || exp[i]== &#39;-&#39;)           // + - 需要特殊处理
    {
        if(i==0)       //表达式开头,说明是正负号
        {
         num+= exp[i];
        }

        else if(exp[i-1]==&#39;(&#39; || exp[i-1]==&#39;+&#39; || exp[i-1]==&#39;-&#39; || exp[i-1]==&#39;*&#39; || exp[i-1]==&#39;/&#39;)
        {
         num+= exp[i];
        }
        else        //否则是加减运算符
        {
            if(!num.isEmpty())
            {
                ret.enqueue(num);        //将数字入队列
                num.clear();
            }
         ret.enqueue(exp[i]);
        }
    }
}

if(!num.isEmpty())         //遍历完成,判断是否还有数字
{
    ret.enqueue(num);
    num.clear();
}

return ret;
}

QQueue<QString> QCalculatorDec::Transfer(QQueue<QString>& exp) //将中缀队列转换为后缀队列
{
QStack<QString> stack;
QQueue<QString> ret;
bool num_ok;
QString symbol;

while(!exp.isEmpty())
{
  symbol = exp.dequeue();   //出队列
  symbol.toDouble(&amp;num_ok);

  if(num_ok==true)          //数字
  {
       stack.push(symbol);
  }

  else if(symbol==&#34;+&#34;||symbol==&#34;-&#34;)
  {
      while(!stack.isEmpty() &amp;&amp;(stack.top()!=&#34;(&#34;))
      {
          ret.enqueue(stack.pop());     //取出栈顶运算符并入队列
      }
      stack.push(symbol);
  }

  else if(symbol==&#34;*&#34;||symbol==&#34;/&#34;)
  {
      while(!stack.isEmpty() &amp;&amp; (stack.top()!=&#34;(&#34;) &amp;&amp; (stack.top()!=&#34;+&#34;) &amp;&amp; (stack.top()!=&#34;-&#34;))
      {
          ret.enqueue(stack.pop());     //取出栈顶运算符并入队列
      }
      stack.push(symbol);
  }

  else if(symbol == &#34;(&#34;)
  {
     stack.push(symbol);
  }

  else if(symbol ==&#34;)&#34;)
  {
      while(!stack.isEmpty() &amp;&amp; (stack.top()!=&#34;(&#34;))
      {
          ret.enqueue(stack.pop());     //取出栈顶运算符并入队列
      }
      if(stack.top()==&#34;(&#34;)
        stack.pop();
  }
}

while(!stack.isEmpty()&amp;&amp; (stack.top()!=&#34;(&#34;))         //遍历完成,判断栈里是否为空
{
   ret.enqueue(stack.pop());     //取出栈顶运算符并入队列
}return ret;

}

QString QCalculatorDec::ValidNum(QString str)
{
QString num;

if(str.indexOf(&#34;.&#34;)== -1) //判断是否小数
    return str;

while(str.length()&gt;1)   //避免0被去掉
{
     num=str.right(1);
     if(num==&#34;.&#34;||num==&#34;0&#34;)
     {
         str.chop(1);
         if(num==&#34;.&#34;)
             return  str;
     }
     else
         return str;
}
return str;

}

QString QCalculatorDec::Calculate(QString& l,QString& op,QString& r )
{
double left,right,res;
QString ret="";
left = l.toDouble();
right = r.toDouble();
if(op == "+")
{
res = left + right;
}

else if(op == &#34;-&#34;)
{
    res = left - right;
}

else if(op == &#34;*&#34;)
{
    res = left * right;
}

else if(op == &#34;/&#34;)
{
    if( (right&gt;(-0.000000000000001)) &amp;&amp; (right&lt;(0.000000000000001)) )   //判断除数为0
        return NULL;
    else
        res = left/right;
}

ret.sprintf(&#34;%f&#34;,res);
return ret;

}

QString QCalculatorDec::Calculate(QQueue<QString>& exp) //将后缀队列计算出结果
{
QStack<QString> stack;
QString symbol,L,R,op,ret;
bool num_ok;

while(!exp.isEmpty())
{
  symbol = exp.dequeue();   //出队列
  symbol.toDouble(&amp;num_ok);

  if(num_ok==true)      //数字
  {
    stack.push(symbol);
  }
  else                  //运算符
  {
      if(stack.size()&lt;2)
          return &#34;Error&#34;;

      R= stack.pop();
      L= stack.pop();
      ret = Calculate(L,symbol,R );
      if(ret==NULL)
          return ret;

      stack.push(ret);
  }
}
if(stack.size()==1) //遍历完成,结果只有一个
{
     return ValidNum(stack.pop());
}
else
{return &#34;Error&#34;;
}

}

QString QCalculatorDec::Result(const QString& exp)
{
QQueue<QString> q=Split(exp); //分离中缀
q=Transfer(q); //转换为后缀
return Calculate(q); //返回结果
}

3.3 main.cpp代码如下

代码语言:javascript
复制
#include <QtGui>
#include "QCalculatorUI.h"
#include "QCalculatorDec.h"
int main(int argc, char* argv[])
{
/*设置字体为GBK*/
QTextCodec *codec = QTextCodec::codecForName("GBK");
QTextCodec::setCodecForTr(codec);
QTextCodec::setCodecForLocale(codec);
QTextCodec::setCodecForCStrings(codec);

QApplication app(argc,argv);
QCalculatorUI* ui = QCalculatorUI::NewIntance();
if(ui==NULL)
    return false;

ui-&gt;show();
return app.exec();

}