计算机软件实习日志(一)简单计算器的实现


前言

随着IT行业的不断发展,编程这门技术也越来越重要,以下就是我为期三周左右的软件技术实习日志,里面记录了我第这段时间的每一天。

界面展示


在这里插入图片描述


一、实验要求

  1. 学习图形界面的设计,利用 MFC 应用程序(Java swing 或 QT 框架,或 C#)创建基于对话框的应用程序,添加按钮、编辑框等控件;
  2. 能通过设计的按钮控件输入并实现简单算术运算,要求表达式在编辑框中显示,能将运算结果,输出在编辑框内显示;并保存历史的表达式运算记录。
  3. 也能够实现混合运算的算术表达式求解,算术表达式中包括加、减、乘、除、括号等运算符;并且能够识别括号,优先级正确。

二、实验准备

  1. 学习算术表达式的前缀、中缀、后缀表达方式。
  2. 思考实现该计算器所用到的算法。
  3. 学习Qt图形用户界面框架。

三、用户界面类设计

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

3.1 在代码里处理按键消息时,需要处理下用户输入的格式(方便逻辑模块计算)
  1. 匹配括号成对出现,左括号必然先于右括号出现
  • 当有左括号出现时,则status++.
  • 当有右括号出现时,并且status!=0时,则右括号有效,并status- -.
  1. 判断每个按键是否合法
  • 数字前面不能为:右括号.
  • 小数点前面不能为空,只能是数字,并且一串数字只能有一个小数点.
  • 加减号前面不能为:小数点,并且前面不能连续有两次加减乘除.
  • 乘除号前面不能为:左括号,空,小数点,加减乘除.
  • 左括号前面不能为:右括号,数字,小数点,并且前面不能连续有两次加减乘除.
  • 右括号前面不能为:空,加减乘除,小数点,左括号,并且要与左括号成对出现.

四、逻辑模块类设计

如何运用四则运算表达式。

4.1 将中缀表达式进行数字和运算符的分离,并保存到队列里
  1. 需要考虑 + - 是正负号,还是加减运算符
  1. 当 + - 出现在表达式开头时,表示为正负号.
  2. 当出现 + - 时,并且前面还有运算符时,表示为正负号.
  3. 当出现 + - 时,并且前面还有左括号时,表示为正负号.
  1. 以下图的中缀表达式为例
    在这里插入图aaaa片描述
    分离后,队列的每个元素应该为:
4.2 将分解出来的中缀表达式队列,转换为后缀表达式队列
  • 比如+6 + (-2- -3)* -4,转换为后缀表达式为:
    在这里插入图片描述
    后缀表达式队列的每个元素应该为:
    在这里插入图片描述
思路

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

-当队列元素为数字时

  • 直接保存到队列

-当队列元素为加减时

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

-当队列元素为乘除时

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

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

  • 直接入栈

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

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

-当队列元素判断结束后

  • 判断栈是否为空,如果不为空,则将栈存储的运算符出栈并入队列
    2.3 将后缀表达式的值计算出来
4.3 将后缀表达式的值计算出来

通过逆波兰表达式计算,思路如下
遇到数字时

  • 入栈

遇到运算符时

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

当后缀表达式队列对空时

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

示意图如下所示

以[9,6,7,5,*,+,8,-,-]后缀表达式为例:

Step1: Stack[9,6,7,5]          //将9,6,7,5入栈

Step2: Stack[9,6,35]          //遇到* ,将7与5取出计算,得到35,并入栈

Step3: Stack[9,41]              //遇到+,将6和35取出计算,得到41,并入栈

Step4: Stack[9,41,8]         //遇到10,入栈

Step5: Stack[9,33]               //遇到-,将41和8取出计算,得到33,并入栈

Step6: Stack[-24]                  //再遇到-,将9和33取出计算,得到-24,并入栈

Step7: 遍历结束,结果= -24




五、实验代码

5.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]);

         QObject::connect(mbuton[i],SIGNAL(clicked()),this,SLOT(handler_clicked()));

   }
   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());
}


5.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);          //返回结果
}


5.3 main.cpp代码如下:
#include <QtGui>
#include <QtWidgets>
#include "QCalculatorUI.h"
#include "QCalculatorDec.h"
int main(int argc, char* argv[])
{
    /*设置字体为GBK*/
    QTextCodec *codec = QTextCodec::codecForName("GBK");
    QTextCodec::setCodecForLocale(codec);

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

    ui->show();
    return app.exec();
}


谢谢观看!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值