开发Windows计算器程序——(二)代码实现

前言

在上一篇博客中 开发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协议。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值