一、界面布局
下面图片是计算器的界面布局。从界面可以得知计算器的基本功能,有加减乘除基本运算,还有余弦,正弦等函数运算。
二、功能简单介绍及运算展示
在以上运算中涉及了对数、指数、乘法和加法,乘法是在“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_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
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();//操作数1
num.pop();
double temp2=num.top();//操作数2
num.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;
}
else
temp=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();//操作数1
num.pop();
double temp2=num.top();//操作数2
num.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;
}
这是我第一次写博客,有许多不足的地方。有什么问题大家可以提出来,一起进步。
源码及可执行文件下载地址:
链接: 项目源码.
百度网盘链接:https://pan.baidu.com/s/1tiTodh2O4JGfK3aVaPeN9g
提取码:adh3