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.代码实现 (基于Qt5.x版本)
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() { 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); QFont font; font.setFamily("microsoft yahei"); font.setWeight(51); font.setPixelSize(14); mline->setFont(font); //设置字体 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()); }
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 <QApplication> #include "qcalculatorui.h" #include "qcalculatordec.h" int main(int argc, char* argv[]) { QApplication app(argc,argv); QCalculatorUI* ui = QCalculatorUI::NewIntance(); if(ui==NULL) return false; ui->show(); return app.exec(); }
觉得不错,请帮忙点下赞~
下章学习: 7.QT-Qt对象间的父子关系