zoukankan      html  css  js  c++  java
  • 6.QT-简易计算器实现(详解)


    界面展示









    1.用户界面类设计

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

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

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

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

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

    数字前面不能为:右括号

    比如:

    10+3)5*2          //出错,数字5前面不能为右括号

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

    比如:

    1.23.45      //出错,一串数字只能有一个小数点

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

    比如: 

    7*-+10           //出错,+号前面出现两次加减乘除
    7. +            //出错,+号前面不能有小数点 
    7-(--5)          //出错, -5数字前面有个减号

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

    比如: 

    *1+(/5+10)                   //出错, *前面不能为空,且除法前面不能为左括号

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

    比如: 

    ( )+10(11+10)               //出错,( 前面不能为数字

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

    比如:  

    ) + (10+ 5.)                   //出错,右括号不能出现在开头,并且右括号前面不能有小数点

    2.逻辑模块类设计

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

     

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

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

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

    +7-5;                    //+出现在开头,说明这个+,表示的是正号,而不是加号

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

    7*-5;                            //-前面还有*,说明这个-,表示的是负号,而不是减号

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

    9+(-3+4)               //-前面还有(,说明这个-,表示负号,而不是减号

     

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

     

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

    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转换为后缀表达式为:

     +9,  -3, -1, -, -5, *, +

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

    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代码如下:

    #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代码如下:

    #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},                                                    //<- 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->resize(240,30);
        mline->move(10,10);
        mline->setAlignment(Qt::AlignRight);
        mline->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]->resize(butnPos[i][2],butnPos[i][3]);
             mbuton[i]->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->setText("");
            line.clear();
            ClearLine=0;
        }if(text>="0"&&text<="9")    //数字
        {
            QString tmp= line.right(1);
            if(tmp.length() && tmp[0]==')')   //数字前面不能为右括号
            {
                return;
            }
            line+=text;
        }
    
        else if(text=="." )    //小数点
        {
            QString tmp= line.right(1);
            if(tmp.length()) //小数点前面只能是数字
           {
                if(MatchingBoth(tmp,"0123456789")== -1)  //没找到数字
                {
                    return;
                }
           }
           else             //小数点前面为空
           {
                    return ;
           }
    
           int pos= LastMatchingBoth(line,"+-*/.()");   //反向查找
            if(pos!= -1 &&line[pos]=='.' )        //一串数字只能有一个小数点
            {
                return ;
            }
             line+=text;
        }
    
        else if(text=="+"||text=="-")       //加减号
        {
            QString tmp= line.right(1);
           if(tmp.length()&& tmp[0]=='.')     //前面不能为:小数点
           {
              return ;
           }
           tmp= line.right(2);
           if(tmp.length()==2)          //前面不能连续有两次加减乘除
           {
               if(tmp[0]=='+'||tmp[0]=='-'||tmp[0]=='*'||tmp[0]=='/'||tmp[0]=='(')
                    if(tmp[1]=='+'||tmp[1]=='-'||tmp[1]=='*'||tmp[1]=='/')
                                return ;
           }
            line+=text;
        }
    
        else if(text=="*"||text=="/")       //乘除号
        {
             QString tmp= line.right(1);
             if(tmp.length())       //前面不能为:左括号,小数点,加减乘除,
             {
                 if(MatchingBoth(tmp,"(.+-*/")!= -1) //查找左括号,小数点,加减乘除
                 {
                     return;
                 }
             }
             else                   //乘除号前面不能为空
                  return;
    
            line+=text;
        }
    
        else if(text=="(")       //左括号
        {
            QString tmp= line.right(1);
            if(tmp.length())             //前面不能为:右括号,数字,小数点
            {
                if(MatchingBoth(tmp,")0123456789.")!= -1) //查找右括号,数字,小数点
                {
                    return;
                }
            }
    
            tmp= line.right(2);
            if(tmp.length()==2)          //前面不能连续有两次加减乘除
            {
                 if(tmp[0]=='+'||tmp[0]=='-'||tmp[0]=='*'||tmp[0]=='/')
                     if(tmp[1]=='+'||tmp[1]=='-'||tmp[1]=='*'||tmp[1]=='/')
                                 return ;
            }
             line+=text;
             bracket_cnt++;
        }
    
        else if(text==")")       //右括号
        {
            QString tmp= line.right(1);
            if(bracket_cnt==0)  //前面没有左括号
               return;
    
            if(tmp.length())             //前面不能为:加减乘除,小数点,左括号
            {
               if(MatchingBoth(tmp,"+-*/.(")!= -1) //查找加减乘除,小数点,左括号
               {
                   return;
               }
            }
            else                    //右括号前面不能为空
               return;
    
            line+=text;
            bracket_cnt--;
        }
    
        else if(text=="<-")       //<-
        {
            if(line.length())
            line.chop(1);
        }
    
        else if(text=="CE")       //清空
        {
            line.clear();
            bracket_cnt=0;
        }
    
        else if(text=="="&& line.length())
        {
            QString ret=mDec.Result(line);
            if(ret==NULL)   //除数为0
            {
                line += " : ";
                line +="除数不能为0";
            }
            else if(ret=="Error")
             {
                line += ":";
                line +="格式出错";
             }
             else
             {
                 line += "=";
                 line += ret;
             }
            ClearLine =1;
        }
        mline->setText(line);
    }
    
    void QCalculatorUI::show()              //显示窗口
    {
        QWidget::show();
        this->setFixedSize(this->width(),this->height());
    }

     

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

    QCalculatorDec.h代码如下:

    #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& l,QString& op,QString& r ); QString ValidNum(QString str); public: QCalculatorDec(); QString Result(const QString& exp); }; #endif // QCALCULATORDEC_H

    QCalculatorDec.cpp代码如下:

    #include "QCalculatorDec.h"
    
    QCalculatorDec::QCalculatorDec()
    {
    }
    
    QQueue<QString> QCalculatorDec::Split(const QString& exp)          //分离前缀
    {
        QQueue<QString> ret;
        QString num="";
    
        for(int i=0;i<exp.length();i++)
        {
            if( (exp[i]=='.') || ( (exp[i]>='0') && (exp[i]<='9') ))    //判断小数点和数字
            {
                num += exp[i];
            }
    
            else if(exp[i]== '(' || exp[i]== ')' || exp[i]== '*' || exp[i]== '/'  )
            {
                if(!num.isEmpty())
                {
                    ret.enqueue(num);        //将数字入队列
                    num.clear();
                }
                ret.enqueue(exp[i]);
            }
    
            else if(exp[i]== '+' || exp[i]== '-')           // + - 需要特殊处理
            {
                if(i==0)       //表达式开头,说明是正负号
                {
                 num+= exp[i];
                }
    
                else if(exp[i-1]=='(' || exp[i-1]=='+' || exp[i-1]=='-' || exp[i-1]=='*' || exp[i-1]=='/')
                {
                 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(&num_ok);
    
          if(num_ok==true)          //数字
          {
               stack.push(symbol);
          }
    
          else if(symbol=="+"||symbol=="-")
          {
              while(!stack.isEmpty() &&(stack.top()!="("))
              {
                  ret.enqueue(stack.pop());     //取出栈顶运算符并入队列
              }
              stack.push(symbol);
          }
    
          else if(symbol=="*"||symbol=="/")
          {
              while(!stack.isEmpty() && (stack.top()!="(") && (stack.top()!="+") && (stack.top()!="-"))
              {
                  ret.enqueue(stack.pop());     //取出栈顶运算符并入队列
              }
              stack.push(symbol);
          }
    
          else if(symbol == "(")
          {
             stack.push(symbol);
          }
    
          else if(symbol ==")")
          {
              while(!stack.isEmpty() && (stack.top()!="("))
              {
                  ret.enqueue(stack.pop());     //取出栈顶运算符并入队列
              }
              if(stack.top()=="(")
                stack.pop();
          }
        }
    
        while(!stack.isEmpty()&& (stack.top()!="("))         //遍历完成,判断栈里是否为空
        {
           ret.enqueue(stack.pop());     //取出栈顶运算符并入队列
        }return ret;
    }
    
    QString QCalculatorDec::ValidNum(QString str)
     {
        QString num;
    if(str.indexOf(".")== -1) //判断是否小数 return str; while(str.length()>1) //避免0被去掉 { num=str.right(1); if(num=="."||num=="0") { str.chop(1); if(num==".") 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 == "-") { res = left - right; } else if(op == "*") { res = left * right; } else if(op == "/") { if( (right>(-0.000000000000001)) && (right<(0.000000000000001)) ) //判断除数为0 return NULL; else res = left/right; } ret.sprintf("%f",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(&num_ok); if(num_ok==true) //数字 { stack.push(symbol); } else //运算符 { if(stack.size()<2) return "Error"; 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 "Error"; } } QString QCalculatorDec::Result(const QString& exp) { QQueue<QString> q=Split(exp); //分离中缀 q=Transfer(q); //转换为后缀 return Calculate(q); //返回结果 }

    3.3 main.cpp代码如下

    #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->show(); return app.exec(); }

     

  • 相关阅读:
    CefSharp 屏蔽右键菜单
    CEfSharp下载文件 弹出保存框,实现 IDownloadHandler 接口
    C#使用Xamarin开发移动应用 ---- 系列文章
    浅析 fstab 与移动硬盘挂载方法
    树莓派设置固定IP地址
    win10 任务栏上的工具栏,重启消失的解决方法
    《丽人行》
    Chrome调试 ---- 控制台获取元素上绑定的事件信息以及监控事件
    Bootstrap基础学习 ---- 系列文章
    C#进阶系列 ---- 《CLR via C#》
  • 原文地址:https://www.cnblogs.com/lifexy/p/8901369.html
Copyright © 2011-2022 走看看