一、界面布局
下面图片是计算器的界面布局。从界面可以得知计算器的基本功能,有加减乘除基本运算,还有余弦,正弦等函数运算。
二、功能简单介绍及运算展示
在以上运算中涉及了对数、指数、乘法和加法,乘法是在“25”与ln之间默认有一个乘号(在代码中有具体体现)。
功能介绍:
该计算器可以实现简单的加减乘除的运算以及带括号、带基本的三角函数、对数、指数的表达式的运算。
容错处理:
1.在文本框中输入表达式时,会自动检测表达是否正确,若即将输入的字符会导致表达式错误,则该字符不会被输入到文本框中,QVector expr(该变量代码中有介绍)也不会记录下该字符。
2.在表达式不完整的情况下按下“=”键,不会做任何处理。
三、逆波兰式(中缀转后缀)
算法思想:
1:从左到右遍历中缀表达式;
2:遇到数字,直接输出;
3:遇到运算符:
①.若为"(“,直接入符号栈;
②.若为”)“,将符号栈中元素依次输出,直到遇到”(“,”(“出栈,不输出;
③.若为其他运算符,将符号栈中元素依次输出,直至遇到比当前运算符优先级更低的运算符或”(",再将该符号入栈。
④.遍历完后,若符号栈不为空,则将符号栈中的元素依次输出,直至为空,输出得到的新的表达式就是所求表达式的后缀表达式。
4:后缀表达式计算
①.从左到右遍历表达式;
②.若为数字,直接入数字栈;
③.若为双目运算符c,则从数字栈中出栈两个元素,分别记为a1(先出栈),a2(后出栈);令数字a3=a2 c a1(a2在前,a1在后),再将a3压入数字栈中;
④.若为单目运算符c,则从数字栈中出一个元素,分别记为a1,a2(后出栈);令数字a= c a1,再将a压入数字栈中;
⑤遍历完后,数字栈中还剩下一个元素,即为所求表达式的结果。
四、代码分析
// 1.expression.h
设计了expression类来存储按键信息,按键符号由QString data来表示,按键类型由BtnType _type表示。
#include <QObject>enum BtnType{Num,//数字Point,//点BinocularOp,//双目操作符MonocularOp,//单目运算符Special,//特殊符号LeftParentheses,//左括号RightParentheses,//右括号PercentSign,//百分号Index,//指数Back,//退格Equl,//等于Clear,//清除};class expression{public:expression();expression(QString mData,BtnType mType):data(mData),_type(mType){};void setData();void setType();QString getData();BtnType getType();private:QString data;BtnType _type; };
// 2.expression.cpp
按键信息的获取直接由expression的构造函数(即expression(QString mData,BtnType mType):data(mData),_type(mType){};)来实现,信息的提取由getData和getType实现。
#include "expression.h"expression::expression(){//default构造函数}QString expression::getData(){return data;}BtnType expression::getType(){return _type;}
// 3.widget.h
#ifndef WIDGET_H#define WIDGET_H#include<QStack>#include "expression.h"#include <QWidget>QT_BEGIN_NAMESPACEnamespace Ui { class Widget; }QT_END_NAMESPACEclass Widget : public QWidget{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private:void btnConnect();void onclicked(BtnType _type,QString _btn);void InToPo();//中缀表达式转后缀表达式bool checkParentheses();//检测中缀表达式中括号是否正确int checkPoint(const QString& str);//检测数据中点的个数,并返回。bool Operation();//运算(根据后缀表达式进行计算得出结果void TextOpention(const QString& newExprssion);//按键后对文本框的操作bool checkLastChar();//检测表达式的最后一个字符private slots:void on_BtnINV_clicked();void on_BtnRAD_clicked();void on_BtnMc_clicked();void on_BtnM_clicked();void on_Btnm_clicked();void on_BtnMr_clicked();private:Ui::Widget *ui;QVector<expression> expr;//中缀表达式QVector<expression> poexp;//后缀表达式QStack<double> num;//数字栈QStack<expression> symbols;//符号栈};#endif // WIDGET_H
// 4.btnConnect函数
void Widget::btnConnect(){//数字键绑定connect(ui->BtnZero,&QPushButton::clicked,[this](){onclicked(Num,"0");});connect(ui->BtnOne,&QPushButton::clicked,[this](){onclicked(Num,"1");});connect(ui->BtnTwo,&QPushButton::clicked,[this](){onclicked(Num,"2");});connect(ui->BtnThree,&QPushButton::clicked,[this](){onclicked(Num,"3");});connect(ui->BtnFour,&QPushButton::clicked,[this](){onclicked(Num,"4");});connect(ui->BtnFive,&QPushButton::clicked,[this](){onclicked(Num,"5");});connect(ui->BtnSix,&QPushButton::clicked,[this](){onclicked(Num,"6");});connect(ui->BtnSeven,&QPushButton::clicked,[this](){onclicked(Num,"7");});connect(ui->BtnEight,&QPushButton::clicked,[this](){onclicked(Num,"8");});connect(ui->BtnNine,&QPushButton::clicked,[this](){onclicked(Num,"9");});//双目运算符绑定connect(ui->BtnMultiply,&QPushButton::clicked,[this](){onclicked(BinocularOp,"*");});connect(ui->BtnDividing,&QPushButton::clicked,[this](){onclicked(BinocularOp,"÷");});connect(ui->BtnPlus,&QPushButton::clicked,[this](){onclicked(BinocularOp,"+");});connect(ui->BtnSubtracts,&QPushButton::clicked,[this](){onclicked(BinocularOp,"-");});//指数connect(ui->BtnSemicolon,&QPushButton::clicked,[this](){onclicked(Index,"^(-1)");});connect(ui->BtnSquare,&QPushButton::clicked,[this](){onclicked(Index,"^(2)");});connect(ui->BtnIndex,&QPushButton::clicked,[this](){onclicked(Index,"^");});//点connect(ui->BtnPoint,&QPushButton::clicked,[this](){onclicked(Point,".");});//单目运算符绑定connect(ui->BtnRoot,&QPushButton::clicked,[this](){onclicked(MonocularOp,"√");});connect(ui->BtnLn,&QPushButton::clicked,[this](){onclicked(MonocularOp,"ln");});connect(ui->BtnLog,&QPushButton::clicked,[this](){onclicked(MonocularOp,"log");});connect(ui->BtnSin,&QPushButton::clicked,[this](){onclicked(MonocularOp,"sin");});connect(ui->BtnCos,&QPushButton::clicked,[this](){onclicked(MonocularOp,"cos");});connect(ui->BtnTan,&QPushButton::clicked,[this](){onclicked(MonocularOp,"tan");});//百分号connect(ui->BtnPercentSign,&QPushButton::clicked,[this](){onclicked(PercentSign,"%");});//括号绑定connect(ui->BtnLeftParenthesis,&QPushButton::clicked,[this](){onclicked(LeftParentheses,"(");});connect(ui->BtnRightParenthesis,&QPushButton::clicked,[this](){onclicked(RightParentheses,")");});//特殊符号绑定connect(ui->BtnE,&QPushButton::clicked,[this](){onclicked(Special,"e");});connect(ui->BtnPai,&QPushButton::clicked,[this](){onclicked(Special,"π");});//清空connect(ui->BtnClear,&QPushButton::clicked,[this](){onclicked(Clear,"");});//退格connect(ui->BtnDelete,&QPushButton::clicked,[this](){onclicked(Back,"");});//等于connect(ui->BtnEqual,&QPushButton::clicked,[this](){onclicked(Equl,"");});}
其中百分号为单目运算符,指数为双目运算符,因与其他运算符有所差异,故单独设置一个符号类型,在最后计算表达式时任然归为单目、双目运算符。上面将个类型的按键信号与槽函数onclicked绑定。
// 5.onclicked函数
因代码长度问题此处只列出当按下数字键时的代码。
void Widget::onclicked(BtnType _type, QString _btn){int rowCount=ui->textEdit->document()->lineCount();switch (_type){case Num:{//数字if(rowCount==0||rowCount==1){if(!expr.isEmpty()){switch (expr.back().getType()){case Num:{QString preNum=expr.back().getData();if(checkPoint(preNum)==0)//前一数字中无小数点{double tempNum;tempNum=expr.back().getData().toDouble();tempNum*=10;tempNum+=_btn.toDouble();expr.pop_back();//删掉前一数据expression curData(QString::number(tempNum),_type);expr.push_back(curData);//将当前数据压入数组中QString curExpression=ui->textEdit->toPlainText();curExpression+=_btn;TextOpention(curExpression);break;}else if(checkPoint(preNum)==1)//前一数字中有小数点{QString tempNum=expr.back().getData();tempNum+=_btn;expr.pop_back();expression curData(tempNum,_type);expr.push_back(curData);QString curExpression=ui->textEdit->toPlainText();curExpression+=_btn;TextOpention(curExpression);break;}break;}case Special: case RightParentheses: case PercentSign:{expression multiply("*",BinocularOp);expr.push_back(multiply);QString curExpression=ui->textEdit->toPlainText();curExpression+=_btn;TextOpention(curExpression);expression curData(_btn,_type);expr.push_back(curData);break;}case LeftParentheses: case BinocularOp:{QString curExpression=ui->textEdit->toPlainText();curExpression+=_btn;TextOpention(curExpression);expression curData(_btn,_type);expr.push_back(curData);break;}default:break;}}else{QString curExpression=ui->textEdit->toPlainText();curExpression+=_btn;TextOpention(curExpression);expression curData(_btn,_type);expr.push_back(curData);break;}break;}break;}}}
// 6.InToPo函数(中·缀转后缀)
void Widget::InToPo(){while(!expr.isEmpty()){switch(expr.first().getType()){case Num: case Special:{//数字直接输入后缀表达式expression curData(expr.first().getData(),expr.first().getType());poexp.push_back(curData);break;}case LeftParentheses:{//左括号优先级最高直接压入符号栈expression curChar(expr.first().getData(),expr.first().getType());symbols.push(curChar);break;}case RightParentheses:{//符号为右括号则将符号栈中符号一一出栈并输入后缀表达式,直到碰到左括号while(symbols.top().getType()!=LeftParentheses){expression curChar(symbols.top().getData(),symbols.top().getType());poexp.push_back(curChar);symbols.pop();}symbols.pop();//左括号出栈break;}case BinocularOp:{//双目运算符if(expr.first().getData()=="+"||expr.first().getData()=="-"){//+、-的优先级最低while(!symbols.isEmpty()&&symbols.top().getType()!=LeftParentheses){//将符号栈中元素出栈直到符号栈为空或遇到左括号expression curChar(symbols.top().getData(),symbols.top().getType());poexp.push_back(curChar);symbols.pop();}//当前符号入栈expression curChar(expr.first().getData(),expr.first().getType());symbols.push(curChar);break;}if(expr.first().getData()=="*"||expr.first().getData()=="÷"){//*、÷的优先级只高于+、-while(!symbols.isEmpty()&&(symbols.top().getType()!=LeftParentheses&&symbols.top().getData()!="+"&&symbols.top().getData()!="-")){//将符号栈中元素出栈直到符号栈为空或遇到左括号或+、-expression curChar(symbols.top().getData(),symbols.top().getType());poexp.push_back(curChar);symbols.pop();}//当前符号入栈expression curChar(expr.first().getData(),expr.first().getType());symbols.push(curChar);break;}break;}case MonocularOp: case Index: case PercentSign:{//优先级仅次于左括号while(!symbols.isEmpty()&&(symbols.top().getType()==MonocularOp||symbols.top().getType()==Index||symbols.top().getType()==PercentSign)){//符号栈不为空,且栈顶元素为单目运算符或指数符号、百分号时,将栈顶元素输入到后缀表达式中并删除栈顶元素expression curChar(symbols.top().getData(),symbols.top().getType());poexp.push_back(curChar);symbols.pop();}//当前符号入栈expression curChar(expr.first().getData(),expr.first().getType());symbols.push(curChar);break;}default:break;}expr.pop_front();}if(expr.isEmpty()&&!symbols.isEmpty()){while(!symbols.isEmpty()){//将符号栈中剩余符号依次出栈输入到后缀表达式中expression curChar(symbols.top().getData(),symbols.top().getType());poexp.push_back(curChar);symbols.pop();}}}
// 7.Operation(后缀表达式的计算)
bool Widget::Operation(){//根据后缀表达式计算出结果bool chushu=true;while(!poexp.isEmpty()){switch(poexp.first().getType()){case Num:{double temp=poexp.first().getData().toDouble();num.push(temp);poexp.pop_front();break;}case BinocularOp:{double temp1=num.top();//操作数1num.pop();double temp2=num.top();//操作数2num.pop();double temp;if(poexp.first().getData()=="+"){//加法temp=temp2+temp1;}else if(poexp.first().getData()=="-"){//减法temp=temp2-temp1;}else if(poexp.first().getData()=="*"){//乘法temp=temp2*temp1;}else if(poexp.first().getData()=="÷"){//除法if(temp1==0){//除数不可为零ui->textEdit->append("除数为零,表达式有误!");chushu=false;}elsetemp=temp2/temp1;}num.push(temp);poexp.pop_front();break;}case PercentSign:{//百分比double temp1=num.top();//操作数num.pop();double temp=temp1/100;num.push(temp);poexp.pop_front();break;}case Special:{double temp;if(poexp.first().getData()=="e")temp=exp(1);else if(poexp.first().getData()=="π")temp=acos(-1);num.push(temp);poexp.pop_front();break;}case Index:{double temp1=num.top();//操作数1num.pop();double temp2=num.top();//操作数2num.pop();double temp;temp=pow(temp2,temp1);num.push(temp);poexp.pop_front();break;}case MonocularOp:{QString op=poexp.first().getData();double temp=num.top();//操作数if(op=="√")temp=sqrt(temp);else if(op=="sin")temp=sin(temp);else if(op=="cos")temp=cos(temp);else if(op=="tan")temp=tan(temp);else if(op=="ln")temp=log(temp);else if(op=="log")temp=log10(temp);num.pop();num.push(temp);poexp.pop_front();break;}default:break;}}return chushu;}
这是我第一次写博客,有许多不足的地方。有什么问题大家可以提出来,一起进步。
源码及可执行文件下载地址:
链接: 项目源码.
百度网盘链接:/s/1tiTodh2O4JGfK3aVaPeN9g
提取码:adh3