前言
在上一篇博客中 开发Windows计算器程序——(一)前期准备 我们对计算器程序有了一定的分析,这次就对其中的功能做一下具体的 代码实现。
窗体及控件
Qt Creator中自带了Widget模板,可以直接通过模板进行生成窗体,我们在主函数中需要 引用以下头文件。
//Headers for main.cpp
#include "Widget.h"
#include <QApplication>
在main函数中只需简单调用QApplication,创建一个新的Widget窗口即可。
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
//set the widget to be visible and focused
w.show();
//maintain the widget to display constantly
return a.exec();
}
创建窗体的布局和控件则使用Qt Creator自带的 UI编辑器 进行设计。
Widget部分
在Widget类中 暂时 需要引用以下头文件。
//Headers for Widget.cpp
#include "Widget.h"
#include "ui_widget.h"
在Widget的构造函数中,首先需要声明UI文件的父子关系,以便使之前设计的UI在本Widget窗体生效。
ui->setupUi(this);
接下来控制窗体的大小,保险起见,可以设置窗口大小不可变化。
Widget::setFixedSize(400,420);
然后将UI中的控件和具体的 槽函数 或 Lamda表达式 进行绑定。可以与用户交互的按钮控件主要分为三类,一类是触发后在可编辑文本框的末尾 添加 一个字符,例如数字和操作符;还有一类是触发后在可编辑文本框的末尾 减少 一个字符,例如Backspace键;最后还有一类是触发后在 清除 可编辑文本框,例如Clear键。
//e.g. add a character to the last of maintext
connect(ui->Button_0,&QPushButton::clicked,[this](){
this->addToMainText("0");
});
//e.g. delete a character to the last of maintext
connect(ui->Button_backspace,&QPushButton::clicked,[this](){
this->backspaceMainText();
});
//e.g. clear the maintext
connect(ui->Button_clear,&QPushButton::clicked,[this](){
this->clearMainText();
});
//the function works like this below
void Widget::addToMainText(QString addText)
{
QString tempText;
//get the original string and attach the new character to the back
tempText = ui->Text_main->displayText() + addText;
//set the new string as maintext
ui->Text_main->setText(tempText);
}
void Widget::clearMainText()
{
//clear the string of maintext
ui->Text_main->clear();
}
void Widget::backspaceMainText()
{
//remove the last character from maintext
ui->Text_main->backspace();
}
接着我们实现等号按钮的绑定,此处由于等号按下后的检错和处理函数较为复杂,后文中我们将其写为槽函数,此处绑定如下
//connect signals and slots with check and process
connect(ui->Button_euqal, &QPushButton::clicked, this, &Widget::processSlot);
}
最后我们写好默认的 析构函数。
槽函数部分
首先我们声明两个变量,一个用于临时保存用户提交的算式,另一个用于标记当前计算是否出现语法错误。
//tempText is used to storage the formula
QString tempText;
//syntaxErrorFlag is used to mark whether there is a syntax Error
//0 means no problem, otherwise means there is a problem
int syntaxErrorFlag = 0;
接下来我们调用检错函数,进行算式的检测和预处理,方便后续计算。
tempText = this->checkMainText();
当检错函数执行完成后,判断syntaxErrorFlag的值,若为0,则在算式尾部添加等号,调用计算函数进行进一步处理;若不为0,则证明出现 语法错误,直接返回,不再进行进一步计算。此处检测到错误后没有进行弹窗提醒,是因为错误类型很多,需要在检错函数中执行弹窗。
if(syntaxErrorFlag == 0)
{
tempText = tempText + "=";
this->processFormula(tempText);
}
else
{
return;
}
检错函数
检错函数实现了整个项目最核心的两个功能之一。
第一步,进行 符号不匹配修复,将空格删除;中文括号替换为英文括号;中文的句号和顿号替换为小数点;数学符号的+-× ÷ 替换为标准的+ - * / ;把英文字符x替换为*。可能用到的 Unicode码 有 + 对应65291;- 对应65293;× 对应215;÷ 对应247。
//e.g. 1st auto fix syntax error(symbol dismatch)
QString tempText;
tempText = ui->Text_main->displayText();
while(tempText.indexOf(" ") != -1)
{
ui->Label_status->setText("Auto fixing");
tempText.replace(tempText.indexOf(" "), 1, "");
}
第二步,进行 常量替换,将常量字符pi和e分别替换为数值,且需要忽略大小写。
//e.g. 2nd auto convert constant symbols
while(tempText.indexOf("pi", 0, Qt::CaseInsensitive) != -1)
{
tempText.replace(tempText.indexOf("pi", 0, Qt::CaseInsensitive),
2, "3.14159");
}
第三步,限制 有效字符,检测全部字符,若出现不支持的字符,则报错提醒,同时将syntaxErrorFlag置为非0,请注意,此处使用指针data_1指向字符串,每次处理字符串后需要重新初始化指针,后文中将 不再重复提醒。
//e.g. 3rd limit all characters are selected
QChar *data_1 = tempText.data();
while(!data_1->isNull())
{
//use unicode to compare the characters
int checkCode = data_1->unicode();
//subFlag used to judge whether
//the character is unsupported after the loop
//when it's 0 means supported, otherwise means unsupported
int subFlag = -1;
QString allSupportedCharacters = "0123456789.^()+-*/";
QChar *data_2 = allSupportedCharacters.data();
//check the character by two loops nesting
while(!data_2->isNull())
{
int supportedCode = data_2->unicode();
if(checkCode == supportedCode)
{
subFlag = 0;
break;
}
++data_2;
}
if(subFlag == -1)
{
//invoke the error function down below
/*---------------------------------*/
return tempText;
}
++data_1;
}
弹窗报错函数调用时需要 引入以下头文件,调用方法如下。请注意,后文中的弹窗报错函数与此基本类似,将 不再重复给出代码。
//Headers for Widget.cpp
#include <QMessageBox>
//invoke like this
tempText.clear();
//pop up a message box
QMessageBox::critical(this, "ERROR", "Unsupported characters!");
//reset the tempText in case of
//mistakenly invoked by processFormula function
tempText = tempText + "0";
syntaxErrorFlag = -1;
第四步,进行 括号不匹配检测,需要 引用的头文件 及实现代码如下。将左括号存入栈内,当遇到右括号时弹出左括号。若弹出时栈空或结束时栈未空,则报错提示。此处使用Unicode码进行括号的检测,左括号为40,右括号为41。
//Headers for Widget.cpp
#include <QStack>
//e.g. 4th missing bracket detection
//this int stack has no actual meaning
//but counts a left bracket
//when detect a left bracket then push a integer 1
QStack <int> leftBracketStorage;
QChar *data_3 = tempText.data();
while(!data_3->isNull())
{
int checkCode = data_3->unicode();
if(checkCode == 40)
{
leftBracketStorage.push(1);
}
if(checkCode == 41)
{
if(leftBracketStorage.empty())
{
//invoke the error function down below
/*---------------------------------*/
return tempText;
}
leftBracketStorage.pop();
}
++data_3;
if(data_3->isNull())
{
if(!leftBracketStorage.empty())
{
//invoke the error function down below
/*---------------------------------*/
return tempText;
}
}
}
第五步,连续运算符修复。当遇到多个连续运算符在同一个作用域时,删除前面的运算符,保留最后的运算符。此处使用Unicode码进行符号的检测,加号为43,减号为45,乘号为42,除号为47,阶乘为94。
/* warning: this part of codes doesn't reinitialized the pointer
* after modified the string, please reinitialized the pointer
* when you doing this part
*/
//e.g. 5th continus operators delete
//when 0 means no operator, -1 means there was an operator
int catchOperator = 0;
//tempDeletePosition records the former operator's position
int tempDeletePosition = 0;
QChar *data_4 = tempText.data();
while(!data_4->isNull())
{
int checkCode = data_4->unicode();
if(checkCode == 43 || checkCode == 45 || checkCode == 42 || checkCode == 47
|| checkCode == 94)
{
if(catchOperator == 0)
{
//meet the first operator
catchOperator = -1;
}
else if(catchOperator == -1)
{
//meet the second operator constantly
tempText.remove(tempDeletePosition - 1, 1);
continue;
}
}
else if(catchOperator == -1)
{
//reset the catchOperator
catchOperator = 0;
}
++data_4;
++tempDeletePosition;
}
第六步,进行 负数修正。当算式开头直接有负号,或者左括号后直接有负号,则在左侧插入左括号和0,右侧操作数结束处补齐右括号。若算式只有一个负数,则可以简化成无括号的形式。此处使用Unicode码进行检测,数字0到9分别对应48到57,小数点为46。
//e.g. 6th negtive number auto fix
//the first situation
QChar *data_5 = tempText.data();
if(!data_5->isNull())
{
int checkCode = data_5->unicode();
if(checkCode == 45)
{
//subFlag used to judge whether the function insert the right bracket
int subFlag = -1;
tempText = "(0" + tempText;
data_5 = tempText.data();
data_5 +=3;
//tempPosition serves as the same function as before
int tempPosition = 3;
while(!data_5->isNull())
{
checkCode = data_5->unicode();
if(checkCode != 46 && (checkCode < 48 || checkCode > 57))
{
//detect the end of operand
tempText.insert(tempPosition, ")");
subFlag = 0;
break;
}
++data_5;
++tempPosition;
}
if(subFlag == -1)
{
//simplify like "-5" --> "(0-5" --> "0-5"
tempText = tempText.mid(1);
}
}
}
//the second situation
data_5 = tempText.data();
int tempPosition = 0;
while(!data_5->isNull())
{
int checkCode = data_5->unicode();
if(checkCode == 40)
{
++data_5;
++tempPosition;
checkCode = data_5->unicode();
if(checkCode == 45)
{
tempText.insert(tempPosition, "(0");
tempPosition+=3;
data_5 = tempText.data();
for(int i = 0; i < tempPosition; i++)
{
++data_5;
}
while(!data_5->isNull())
{
checkCode = data_5->unicode();
if(!(checkCode == 46 || (checkCode >= 48 && checkCode <= 57)))
{
tempText.insert(tempPosition, ")");
data_5 = tempText.data();
for(int i = 0; i < tempPosition; i++)
{
++data_5;
}
break;
}
++data_5;
++tempPosition;
}
}
}
++data_5;
++tempPosition;
}
第七步,重复小数点修正,基本原理和重复操作符修正是一样的,只是需要修改一下检测的范围,以下 仅给出限制范围的实现。
//e.g. 7th multiple dots auto fix
//0 means out of range | 1 means active in range | -1 means active for delete
int catchDot = 0;
QChar *data_6 = tempText.data();
while(!data_6->isNull())
{
int checkCode = data_6->unicode();
if(checkCode >= 48 && checkCode <= 57)
{
if(catchDot == 0)
{
catchDot = 1;
}
}
else if(checkCode == 46)
{
if(catchDot == 1)
{
catchDot = -1;
}
else if(catchDot == -1)
{
//invoke the delete function down below
/*---------------------------------*/
continue;
}
}
else
{
catchDot = 0;
}
++data_6;
}
第八步,修正小数点缩写,例如将.5修正为0.5,将3.修正为3。原理和负数修正较为相似。
//e.g. 8th dot abbreviate(before and after) auto fix
QChar *data_7 = tempText.data();
int tempDotRemovePosition = 0;
while(!data_7->isNull())
{
//fix the part before the dot
int checkCode = data_7->unicode();
if(checkCode == 46)
{
if(tempDotRemovePosition == 0)
{
tempText = "0" + tempText;
++tempDotRemovePosition;
data_7 = tempText.data();
for(int i = 0; i < tempDotRemovePosition; i++)
{
++data_7;
}
}
else
{
--data_7;
checkCode = data_7->unicode();
if(!(checkCode >= 48 && checkCode <= 57))
{
tempText.insert(tempDotRemovePosition, "0");
data_7 = tempText.data();
for(int i = 0; i < tempDotRemovePosition; i++)
{
++data_7;
}
++tempDotRemovePosition;
}
++data_7;
}
++data_7;
//fix the part after the dot
checkCode = data_7->unicode();
if(!(checkCode >= 48 && checkCode <= 57))
{
tempText.remove(tempDotRemovePosition, 1);
data_7 = tempText.data();
for(int i = 0; i < tempDotRemovePosition; i++)
{
++data_7;
}
--tempDotRemovePosition;
}
--data_7;
}
++tempDotRemovePosition;
++data_7;
}
第九步,删除多余的括号,设置一个Flag记录当前括号是否存在有效内容,遇到左括号就将Flag压栈,同时把左括号位置压栈;遇到右括号就弹出Flag和左括号位置,若Flag表示无内容,则删除左括号和右括号。
//e.g. 9th empty brackets delete
//records the position of left bracket
QStack <int> leftBracketStack;
//records the flag
QStack <int> statusBetweenBracketsStack;
QChar *data_8 = tempText.data();
int tempBracketPosition = 0;
int statusBetweenBrackets = -2;
while(!data_8->isNull())
{
int checkCode = data_8->unicode();
//didn't figure out why, but somehow it works
int subFlag = 0;
if(checkCode == 40)
{
leftBracketStorage.push(tempBracketPosition);
statusBetweenBracketsStack.push(statusBetweenBrackets);
statusBetweenBrackets = -1;
}
else if(checkCode == 41)
{
if(statusBetweenBrackets == -1)
{
tempText.remove(tempBracketPosition, 1);
tempText.remove(leftBracketStorage.pop(), 1);
--tempBracketPosition;
data_8 = tempText.data();
for(int i = 0; i < tempBracketPosition; i++)
{
++data_8;
}
subFlag = -1;
statusBetweenBrackets = statusBetweenBracketsStack.pop();
}
else
{
statusBetweenBrackets = statusBetweenBracketsStack.pop();
leftBracketStorage.pop();
}
}
else
{
statusBetweenBrackets = 0;
}
if(subFlag == 0)
{
++data_8;
++tempBracketPosition;
}
}
第十步,缺少操作数检测,若操作符左侧不是数字或者右括号,则报错,同理,若操作符右侧不是数字或左括号,则报错。由于上文中已经执行过空括号检测了,所以可以确保遇到括号一定有操作数,而小数点修正可以保证操作数首尾不是小数点,类似情况下文不再赘述。
//e.g. 10th missing operand detection
QChar *data_9 = tempText.data();
while(!data_9->isNull())
{
int checkCode = data_9->unicode();
if(checkCode == 43 || checkCode == 45 || checkCode == 42
|| checkCode == 47 || checkCode == 94)
{
--data_9;
checkCode = data_9->unicode();
if(!(checkCode >= 48 && checkCode <= 57) && checkCode != 41)
{
//invoke the error function down below
/*---------------------------------*/
return tempText;
}
++data_9;
++data_9;
checkCode = data_9->unicode();
if(!(checkCode >= 48 && checkCode <= 57) && checkCode != 40)
{
//invoke the error function down below
/*---------------------------------*/
return tempText;
}
--data_9;
}
++data_9;
}
第十一步,进行 缺少乘号检测,若遇到类似"5(10)“的修正为"5*(10)”,即检测到数字的右侧为左括号,或右括号的右侧为左括号,或者数字的左侧为右括号的情况,就需要插入一个乘号。
//e.g. 11th missing operator detection like "5(10)"
QChar *data_10 = tempText.data();
int tempInsertPosition = 0;
while(!data_10->isNull())
{
int checkCode = data_10->unicode();
if(checkCode >= 48 && checkCode <= 57)
{
++data_10;
++tempInsertPosition;
checkCode = data_10->unicode();
if(checkCode == 40)
{
//the first situation
tempText.insert(tempInsertPosition, "*");
++tempInsertPosition;
data_10 = tempText.data();
for(int i = 0; i < tempInsertPosition; i++)
{
++data_10;
}
}
else
{
--data_10;
--tempInsertPosition;
}
}
//other situations
else if(checkCode == 41)
{
++data_10;
++tempInsertPosition;
checkCode = data_10->unicode();
if(checkCode == 40 || (checkCode >= 48 && checkCode <= 57))
{
tempText.insert(tempInsertPosition, "*");
++tempInsertPosition;
data_10 = tempText.data();
for(int i = 0; i < tempInsertPosition; i++)
{
++data_10;
}
}
else
{
--data_10;
--tempInsertPosition;
}
}
++data_10;
++tempInsertPosition;
}
最后一步,检测算式是否已经为空,若为空则赋值为"0",方便后续计算。
//12th change empty status to zero like "" --> "0"
QChar *data_11 = tempText.data();
if(data_11->isNull())
{
tempText = tempText + "0";
}
接下来只需要将结果返回即可。
return tempText;
计算函数
计算函数实现了整个项目最核心的另一个功能。
首先需要将 优先级表 进行创建,此处选用整形数组来记录优先级关系,0代表不计算,1代表计算,-1代表括号结束,-2代表不可能出现的情况,具体含义请参考 上一篇博客 中的优先级表。
int priorityTable[6][8] = {1, 1, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 1, 1,
1, 1, 1, 1, 0, 0, 1, 1,
1, 1, 1, 1, 0, 0, 1, 1,
1, 1, 1, 1, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, -1, -2};
接着再创建一个函数,将 操作符映射到优先级表的行或列。
int Widget::judgePriorityNumber(QString tempOperator)
{
if(tempOperator.compare("+") == 0)
{
return 0;
}
if(tempOperator.compare("-") == 0)
{
return 1;
}
if(tempOperator.compare("*") == 0)
{
return 2;
}
if(tempOperator.compare("/") == 0)
{
return 3;
}
if(tempOperator.compare("^") == 0)
{
return 4;
}
if(tempOperator.compare("(") == 0)
{
return 5;
}
if(tempOperator.compare(")") == 0)
{
return 6;
}
if(tempOperator.compare("=") == 0)
{
return 7;
}
//unexpected operand
return -1;
}
然后创建一个函数,接收两个操作符,返回计算与否,注意此时的操作符是有顺序的,不能颠倒,由于下文的计算函数会处理右括号遇到左括号的情况,且无需经由此函数,所以此处将这类情况进行报错。
bool Widget::judgePriority(QString operatorOld, QString operatorNew)
{
int operatorOldNumber = judgePriorityNumber(operatorOld);
int operatorNewNumber = judgePriorityNumber(operatorNew);
if(operatorOldNumber == -1 || operatorNewNumber == -1
|| operatorOldNumber >=6)
{
QMessageBox::critical(this, "Oops", "Unexpected Error!");
syntaxErrorFlag = -1;
return false;
}
if(priorityTable [operatorOldNumber][operatorNewNumber] == 1)
{
return true;
}
else if(priorityTable [operatorOldNumber][operatorNewNumber] == 0)
{
return false;
}
else
{
QMessageBox::critical(this, "Oops", "Unexpected Error!");
syntaxErrorFlag = -1;
return false;
}
}
接着我们写一个函数,将经过检错的算式 显示 在结果上面的不可编辑文本框中。
void Widget::displayFormula(QString tempText)
{
ui->Label_history->setText(tempText);
}
最后我们写计算函数,主要逻辑如下,当读取算式为操作数时,存入操作数栈内,读取为操作符时,有多种情况。若操作符栈为空,则直接存入;若操作符栈非空,则设当前操作符为新操作符,栈顶的操作符为旧操作符,并交给上文的函数判断是否计算,若不计算则直接存入操作符栈内,若计算则取先弹出的操作数作为右操作数,取后弹出的操作数作为左操作数,弹出栈顶的操作符,进行计算,计算后将新的操作数存入栈内,重新判断操作符的优先级。其中有两个特例,若新的操作符是右括号,则当遇到左括号时直接弹出,不计算;若新的操作符是等号,且操作数栈只有一个数,则将结果显示在可编辑文本框内,同时调用显示函数,将原本的算式显示在上方。
void Widget::processFormula(QString tempText)
{
QStack <double> operandStack;
QStack <QString> operatorStack;
double operandLeft = 0;
double operandRight = 0;
QString operandTemp = "";
QString operatorBetween = "";
QChar *data_1 = tempText.data();
int processStatus = -1;
while(processStatus != 0)
{
int checkCode = data_1->unicode();
if(checkCode == 46 || (checkCode >= 48 && checkCode <= 57))
{
operandTemp.append(*data_1);
++data_1;
checkCode = data_1->unicode();
if(!(checkCode == 46 || (checkCode >= 48 && checkCode <= 57)))
{
operandRight = operandTemp.toDouble();
operandStack.push(operandRight);
operandTemp = "";
}
--data_1;
}
else
{
operatorBetween = *data_1;
if(operatorBetween.compare(")") == 0)
{
if(operatorStack.top().compare("(") == 0)
{
operatorStack.pop();
}
else
{
operandRight = operandStack.pop();
operandLeft = operandStack.pop();
operatorBetween = operatorStack.pop();
int operationType = judgePriorityNumber(operatorBetween);
if(operationType == 0)
{
operandLeft = operandLeft + operandRight;
operandStack.push(operandLeft);
}
else if(operationType == 1)
{
operandLeft = operandLeft - operandRight;
operandStack.push(operandLeft);
}
else if(operationType == 2)
{
operandLeft = operandLeft * operandRight;
operandStack.push(operandLeft);
}
else if(operationType == 3)
{
if(operandRight == 0)
{
QMessageBox::critical(this, "Error",
"Divided By Zero Error!");
syntaxErrorFlag = -1;
processStatus = 0;
return;
}
operandLeft = operandLeft / operandRight;
operandStack.push(operandLeft);
}
else if(operationType == 4)
{
if(operandLeft == 0)
{
operandStack.push(0);
}
else if(operandRight == 0)
{
operandStack.push(1);
}
else if(operandRight >= 0)
{
double tempPow = operandLeft;
for(int i = 1; i < operandRight; i++)
{
operandLeft = operandLeft * tempPow;
}
operandStack.push(operandLeft);
}
else
{
double tempPow = operandLeft;
for(int i = 1; i < operandRight; i++)
{
operandLeft = operandLeft * tempPow;
}
operandLeft = 1 / operandLeft;
operandStack.push(operandLeft);
}
}
continue;
}
}
else if(operatorBetween.compare("=") == 0)
{
if(operatorStack.empty())
{
if(operandStack.size() != 1)
{
QMessageBox::critical(this, "Oops", "Unexpected Error!");
syntaxErrorFlag = -1;
processStatus = 0;
return;
}
ui->Text_main->setText(QString::number(
operandStack.pop(), 'g', 6));
processStatus = 0;
ui->Bar_progress->setValue(96);
displayFormula(tempText);
return;
}
else if(operandStack.size() == 1)
{
QMessageBox::critical(this, "Oops", "Unexpected Error!");
syntaxErrorFlag = -1;
processStatus = 0;
return;
}
operandRight = operandStack.pop();
operandLeft = operandStack.pop();
operatorBetween = operatorStack.pop();
int operationType = judgePriorityNumber(operatorBetween);
if(operationType == 0)
{
operandLeft = operandLeft + operandRight;
operandStack.push(operandLeft);
}
else if(operationType == 1)
{
operandLeft = operandLeft - operandRight;
operandStack.push(operandLeft);
}
else if(operationType == 2)
{
operandLeft = operandLeft * operandRight;
operandStack.push(operandLeft);
}
else if(operationType == 3)
{
if(operandRight == 0)
{
QMessageBox::critical(this, "Error",
"Divided By Zero Error!");
syntaxErrorFlag = -1;
processStatus = 0;
return;
}
operandLeft = operandLeft / operandRight;
operandStack.push(operandLeft);
}
else if(operationType == 4)
{
if(operandLeft == 0)
{
operandStack.push(0);
}
else if(operandRight == 0)
{
operandStack.push(1);
}
else if(operandRight >= 0)
{
double tempPow = operandLeft;
for(int i = 1; i < operandRight; i++)
{
operandLeft = operandLeft * tempPow;
}
operandStack.push(operandLeft);
}
else
{
double tempPow = operandLeft;
for(int i = 1; i < operandRight; i++)
{
operandLeft = operandLeft * tempPow;
}
operandLeft = 1 / operandLeft;
operandStack.push(operandLeft);
}
}
continue;
}
else if(operatorStack.empty())
{
operatorStack.push(operatorBetween);
}
else if(!(judgePriority(operatorStack.top(), operatorBetween)))
{
operatorStack.push(operatorBetween);
}
else if(judgePriority(operatorStack.top(), operatorBetween))
{
operandRight = operandStack.pop();
operandLeft = operandStack.pop();
operatorBetween = operatorStack.pop();
int operationType = judgePriorityNumber(operatorBetween);
if(syntaxErrorFlag != 0)
{
processStatus = 0;
return;
}
if(operationType == 0)
{
operandLeft = operandLeft + operandRight;
operandStack.push(operandLeft);
}
else if(operationType == 1)
{
operandLeft = operandLeft - operandRight;
operandStack.push(operandLeft);
}
else if(operationType == 2)
{
operandLeft = operandLeft * operandRight;
operandStack.push(operandLeft);
}
else if(operationType == 3)
{
if(operandRight == 0)
{
QMessageBox::critical(this, "Error",
"Divided By Zero Error!");
syntaxErrorFlag = -1;
processStatus = 0;
return;
}
operandLeft = operandLeft / operandRight;
operandStack.push(operandLeft);
}
else if(operationType == 4)
{
if(operandLeft == 0)
{
operandStack.push(0);
}
else if(operandRight == 0)
{
operandStack.push(1);
}
else if(operandRight >= 0)
{
double tempPow = operandLeft;
for(int i = 1; i < operandRight; i++)
{
operandLeft = operandLeft * tempPow;
}
operandStack.push(operandLeft);
}
else
{
double tempPow = operandLeft;
for(int i = 1; i < (-operandRight); i++)
{
operandLeft = operandLeft * tempPow;
}
operandLeft = 1 / operandLeft;
operandStack.push(operandLeft);
}
}
continue;
}
}
++data_1;
}
return;
}
历史记录部分
我们需要先引用以下头文件,并声明如下全局变量。
//Headers for Widget.cpp
#include <QKeyEvent>
#include <QVector>
//viriables
QVector<QString> historyArray;
QString currentText;
int historyPointer;
接下来定义当按下上箭头时,文本框的变化,若当前文本框为最新值,则保存至缓存后调用显示上一条历史记录,若不是最新值,则直接调用显示上一条历史记录,若已经是最旧的历史记录,则不变。
void Widget::upList()
{
if(historyArray.isEmpty())
{
return;
}
else if(historyPointer == -1)
{
currentText = ui->Text_main->displayText();
++historyPointer;
ui->Text_main->setText(historyArray[0]);
return;
}
else
{
if(historyPointer == historyArray.size() - 1)
{
return;
}
else
{
++historyPointer;
ui->Text_main->setText(historyArray[historyPointer]);
return;
}
}
}
然后定义当按下下箭头时,文本框的变化,若已经是最新的值,则不变,否则,调用下一条历史记录,若刚好没有历史记录,则显示存入缓存的值。
void Widget::downList()
{
if(historyArray.isEmpty())
{
return;
}
else if(historyPointer == -1)
{
return;
}
else
{
if(historyPointer == 0)
{
ui->Text_main->setText(currentText);
--historyPointer;
return;
}
else
{
--historyPointer;
ui->Text_main->setText(historyArray[historyPointer]);
return;
}
}
}
最后我们添加键盘监听事件,绑定上下箭头按键值上文的对应函数,绑定回车和小键盘的回车调用等号的处理函数,作为快捷键。
void Widget::keyPressEvent(QKeyEvent *event)
{
switch(event->key())
{
case Qt::Key_Enter:
processSlot();
break;
case Qt::Key_Return:
processSlot();
break;
case Qt::Key_Up:
upList();
break;
case Qt::Key_Down:
downList();
break;
}
}
修饰性代码
在运算的过程中,可以设置进度条,修改状态栏,在出错后可以修改进度条的颜色等等。
//set the progress bar to zero persent
ui->Bar_progress->setValue(0);
//set the progress bar to default style
ui->Bar_progress->setStyleSheet("QProgressBar::chunk {}");
//set the progress bar to be red after an error
ui->Bar_progress->setStyleSheet(
"QProgressBar::chunk { background-color: rgb(255, 0, 0) }");
//set the status label to display "Idle"
ui->Label_status->setText("Idle");
可以改进的地方
时间比较短,整个项目没有那么多时间去反复修改更正,见谅。在写很多Flag类型的变量时,这次基本上都是使用int类型进行表示的,其实可以换成enum枚举类型,会更加的符合变量的意义;当前虽然支持阶乘,但是仅可以计算整数次幂;整体逻辑其实可以有一定程度的优化,例如弹窗可以单独写一个函数,每次弹窗报错直接调用即可;每一个检测都应该是单独的一个函数,而不是统一放在一个检错函数中;计算后的显示工作可以交给槽函数来进行,而不是统一放在计算函数中;计算函数可以再简化重复代码;以及应该使用try catch来捕获错误。
详细代码
本项目的全部源码以及打包程序均上传至GitHub,并附有说明文档,具体信息请前往Github查看,测试程序请前往Release下载,项目遵循MIT协议。