QT入门项目--简易计算器

QT入门项目–简易计算器

一、前言

C语言快要结课了,自然少不了大作业,在众多选项中看中了简易计算器(其实每一项都研究了,发现自己太菜只能选计算器)。
虽然是C语言大作业,但代码是C++,作业要求推荐图形设计使用MFC,在B站看了几个教程后感觉实在接受不了(本来时间就少,MFC学起来太花时间了,而且不支持跨平台),最后看上了QT(太香了)。QT入门是看的吴健老师的教程。简单了解后就开始编写代码了,教程不知道什么原因,后面都缺失了,不过前面的基础篇也够用了,不会的就查手册,或者上网查询,没必要都记下来,也记不下来,边写边查最好了。
计算器的主要实现就是通过槽函数获取按钮信息,用QString储存表达式,经过检查和运算后将结果显示再labl上,表达式的运算利用逆波兰表达式(后缀表达式)实现,不知道该算法的指路C++栈的应用——后缀表达式求值、中缀表达式到后缀表达式的转换,本人也是在此博客基础上修改实现(其实跟没改一样)。计算器的核心也是这个算法和检查机制(之前并没有加检查机制,后面自己找BUG,出现一个改一个)。

二、运行界面

计算器运行界面

报错界面

报错界面
计算器加了个背景音乐,主要是为了排版好看。

三、代码

首先是dialog,因为计算器只有一个界面,所有没有使用mianwindow

dialog.cpp

主要是链接模型代码

#include "dialog.h"
#include "ui_dialog.h"
#include "doexpr.h"
#include "check.h"
#include <QMessageBox>
#include <QString>

const double PI = 3.14159265358;

Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::Dialog)        //构造函数
{
    ui->setupUi(this);
    this->setWindowFlags(Qt::CustomizeWindowHint|Qt::WindowCloseButtonHint); //去除窗口右上角“?”
    this->player->setMedia(QUrl("music/background.mp3"));
    this->player->setVolume(50);
    this->temp = "";
    this->doExpr = false;
    this->loopExpr = false;
}

Dialog::~Dialog()            //析构函数
{
    delete ui;
}

void Dialog::on_btn_0_clicked()
{
     //按钮0槽函数
    if(this->temp != "" && this->temp != "0")  //防止出现多位0
    {
        this->temp += this->ui->btn_0->text();
        if(!this->loopExpr)
            this->ui->lbl_display->setText(this->temp);  //判断是否已经进行过运算
        else
        {
            this->temp = "";                            //若已进行运算,将文本置空
            this->temp += this->ui->btn_0->text();
            this->ui->lbl_display->setText(this->temp);  //显示文本
            this->loopExpr = false;
        }
    }
    else     //若未输入其他按钮,只显示一个0
    {
        this->temp = "0";
        this->ui->lbl_display->setText(this->temp);
    }

}

void Dialog::on_btn_1_clicked()
{
    //按钮1槽函数
    this->temp += this->ui->btn_1->text();
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "";
        this->temp += this->ui->btn_1->text();
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }
}

void Dialog::on_btn_2_clicked()
{
    //按钮2槽函数
    this->temp += this->ui->btn_2->text();
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "";
        this->temp += this->ui->btn_2->text();
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }
}

void Dialog::on_btn_3_clicked()
{
    //按钮3槽函数
    this->temp += this->ui->btn_3->text();
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "";
        this->temp += this->ui->btn_3->text();
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }
}

void Dialog::on_btn_4_clicked()
{
    //按钮4槽函数
    this->temp += this->ui->btn_4->text();
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "";
        this->temp += this->ui->btn_4->text();
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }
}

void Dialog::on_btn_5_clicked()
{
    //按钮5槽函数
    this->temp += this->ui->btn_5->text();
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "";
        this->temp += this->ui->btn_5->text();
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }
}

void Dialog::on_btn_6_clicked()
{
    //按钮6槽函数
    this->temp += this->ui->btn_6->text();
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "";
        this->temp += this->ui->btn_6->text();
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }
}

void Dialog::on_btn_7_clicked()
{
    //按钮7槽函数
    this->temp += this->ui->btn_7->text();
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "";
        this->temp += this->ui->btn_7->text();
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }
}

void Dialog::on_btn_8_clicked()
{
    //按钮8槽函数
    this->temp += this->ui->btn_8->text();
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "";
        this->temp += this->ui->btn_8->text();
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }
}

void Dialog::on_btn_9_clicked()
{
    //按钮9槽函数
    this->temp += this->ui->btn_9->text();
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "";
        this->temp += this->ui->btn_9->text();
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }
}

void Dialog::on_btn_pi_clicked()
{
    //按钮pi槽函数
    this->temp += QString::number(PI,10,7);
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "";
        this->temp += QString::number(PI,10,7);
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }

}

void Dialog::on_btn_point_clicked()
{
    //按钮 "."槽函数
    if(this->temp == "")   //若直接按point,则加上前导0
        this->temp = "0";
    this->temp += this->ui->btn_point->text();
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "0";
        this->temp += this->ui->btn_point->text();
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }
}

void Dialog::on_btn_left_clicked()
{
    //左括号槽函数
    this->temp += this->ui->btn_left->text();
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "";
        this->temp += this->ui->btn_left->text();
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }
}

void Dialog::on_btn_right_clicked()
{
    //右括号槽函数
    this->temp += this->ui->btn_right->text();
    if(!this->loopExpr)
        this->ui->lbl_display->setText(this->temp);
    else
    {
        this->temp = "";
        this->temp += this->ui->btn_right->text();
        this->ui->lbl_display->setText(this->temp);
        this->loopExpr = false;
    }
}

void Dialog::on_btn_div_clicked()
{
    //除法槽函数
    this->temp += this->ui->btn_div->text();
    this->ui->lbl_display->setText(this->temp);
    if(this->doExpr)
        this->loopExpr = false;
}

void Dialog::on_btn_mul_clicked()
{
    //乘法槽函数
    this->temp += this->ui->btn_mul->text();
    this->ui->lbl_display->setText(this->temp);
    if(this->doExpr)
        this->loopExpr = false;
}

void Dialog::on_btn_sub_clicked()
{
    //减法槽函数
    this->temp += this->ui->btn_sub->text();
    this->ui->lbl_display->setText(this->temp);
    if(this->doExpr)
        this->loopExpr = false;
}

void Dialog::on_btn_add_clicked()
{
    //加法槽函数
    this->temp += this->ui->btn_add->text();
    this->ui->lbl_display->setText(this->temp);
    if(this->doExpr)
        this->loopExpr = false;
}

void Dialog::on_btn_clear_clicked()
{
    //CE槽函数
    this->temp = "";
    this->ui->lbl_display->setText("0");
}

void Dialog::on_btn_backspace_clicked()
{
    //退格键槽函数
    this->temp.chop(1);
    this->ui->lbl_display->setText(this->temp);
}

void Dialog::on_btn_reci_clicked()
{
    //求倒数槽函数,与等号相似
    if(!check(this->temp))     //判断表达式是否有误
    {
        QMessageBox::warning(NULL, "错误", "输入表达式有误!o((>ω< ))o");
    }
    else
    {
        if(!checkSignal(this->temp))   //判断是否存在正负号表达式
        {
            //若存在,则改变表达式使其符合算法
            //如(-5)-> (0-5)
            this->temp = insert_zero(this->temp);
        }
        if(!check(this->temp))   //再次检修改后查表达式是否正确
        {
            QMessageBox::warning(NULL, "错误", "输入表达式有误!o((>ω< ))o");
        }
        else
        {
            string reci = this->temp.toStdString();    //QString转为string
            reci = InfixToPostfi(reci);                //将中缀表达式转为后缀表达式
            double res_reci = postfixExpression(reci); //计算后缀表达式
            res_reci = 1 / res_reci;                   //取倒数
            if(res_reci != (long long)res_reci)        //判断是否为整型
                this->temp = QString::number(res_reci,10,7); //精度为小数点后7位,可调
            else
                this->temp = QString::number(res_reci,10,0);
            if(this->temp == "inf" ||this->temp == "nan")  //判断分母是否为0
            {
                QMessageBox::warning(NULL, "错误", "注意,分母不能为零哦!(ง •_•)ง");
                this->temp = "0";
            }
            this->ui->lbl_display->setText(this->temp);  //将结果显示到display
            this->doExpr = true;                         //记录已经进行运算
            this->loopExpr = true;
        }

    }
}

void Dialog::on_btn_equal_clicked()
{
    //等号槽函数,与求倒数相同,缺少求倒数过程
    if(!check(this->temp))
    {
        QMessageBox::warning(NULL, "错误", "注意,表达式输入有误!(@_@;)");
    }
    else
    {
        if(!checkSignal(this->temp))
        {
            this->temp = insert_zero(this->temp);
        }
        if(!check(this->temp))
        {
            QMessageBox::warning(NULL, "错误", "注意,表达式输入有误!(@_@;)");
        }
        else
        {
            string res = this->temp.toStdString();
            res = InfixToPostfi(res);
            double res_equal = postfixExpression(res);
            if(res_equal != (long long)res_equal)
                this->temp = QString::number(res_equal,10,7);
            else
                this->temp = QString::number(res_equal,10,0);
            if(this->temp == "inf" ||this->temp == "nan")
            {
                QMessageBox::warning(NULL, "错误", "注意,分母不能为零哦!(ง •_•)ง");
                this->temp = "0";
            }
            this->ui->lbl_display->setText(this->temp);
            this->doExpr = true;
            this->loopExpr = true;
        }
    }
}



void Dialog::on_btn_musicOn_clicked()
{
    //开启音乐槽函数
    //音乐结束后再次点击此按钮继续播放
    this->player->play();
}

void Dialog::on_btn_musicOff_clicked()
{
    //暂停音乐槽函数
    this->player->pause();
}

doexpr.cpp

主要是逆波兰算法,先将中缀表达式转换成后缀表达式,然后计算
用到了STL中的栈和向量

#include "doexpr.h"
#include <algorithm>
#include <stack>
#include <vector>

using namespace std;

/*********************后缀表达式求值(直接利用C++STL提供的Stack实现)**************************/
double postfixExpression(const string &str)
{
    stack<double> mystack;    //栈空间

    string s = ".0123456789+-*/";
    string empty = " ";
    string numbers = ".0123456789";
    string c = "+-*/";

    double firstnum;  //左侧操作数
    double secondnum; //右侧操作数
    double result;    //结果

    for(size_t i=0; i<str.size(); )
    {
        size_t start = str.find_first_of(s,i);     //查找第一个数字或算术符号
        size_t end = str.find_first_of(empty,i);   //查找第一个空格
        string tempstr = str.substr(start, end-start);     //取出这一个元素

        //判断元素
        if(tempstr == "+" || tempstr == "-" || tempstr == "*" || tempstr == "/")
        {
            secondnum = mystack.top();    //取当前栈顶元素,由于栈的先进后出特性,当前栈顶元素其实是二元操作符中右侧的操作数,
            mystack.pop();                //如表达式3-2的后缀表达式为“3 2 -”,这里secondnum取得数就是2
            firstnum = mystack.top();
            mystack.pop();
            if(tempstr == "+")   //做运算,并将结果压入栈中
            {
                result = firstnum + secondnum;
                mystack.push(result);
            }
            if(tempstr == "-")
            {
                result = firstnum - secondnum;
                mystack.push(result);
            }
            if(tempstr == "*")
            {
                result = firstnum * secondnum;
                mystack.push(result);
            }
            if(tempstr == "/")
            {
                result = firstnum / secondnum;
                mystack.push(result);
            }
        }
        else   //若不是运算符,将元素转为double后压入栈中
        {
            double num = stod(tempstr);
            mystack.push(num);
        }

        //控制迭代,从空格后开始
        i = end + 1;
    }
    return mystack.top(); //返回最后结果
}

//设置操作符优先级,这里考虑到括号("("、")")匹配,定义设置左括号"("的优先级最高,且只有在遇到右括号时才弹出左括号
int priority(const string str)
{
    const char *op = str.c_str();//生成C字符串数组
    switch(*op) //设置优先级
    {
    case ')':
        return 0;
    case '+':
    case '-':
        return 1;
    case '*':
    case '/':
        return 2;
    case '(':
        return 3;
    default :
        return -1;
    }
}

/*********************中缀表达式转为后缀表达式**************************/
string InfixToPostfi(const string &str)
{
    string operatorstr = "*-/+()";      //用于string搜索
    string numbers = "0123456789.";

    //对输入的中缀表达式中每个元素进行切片,每个元素存储到vector<string>Inputvec
    vector<string> Inputvec;   //用向量存储切片结果
    for(size_t i=0; i<str.size(); )
    {
        size_t operatorindex = str.find_first_of(operatorstr,i);     //搜索str中从i开始的第一个操作符
        if(operatorindex != string::npos)
        {
            //如果从i开始搜索到了操作符
            if(operatorindex == i)
            {
                Inputvec.push_back(str.substr(operatorindex,1)); //substr参数:起始字符序号,剪切个数
                i = i+1;
            }
            else
            {
                //将操作数和操作符都加入向量中
                Inputvec.push_back(str.substr(i,operatorindex-i));
                Inputvec.push_back(str.substr(operatorindex,1));
                i = operatorindex+1;
            }
        }
        else
        {
            //如果从i开始搜索到了操作符,即输入的中缀表达式以操作数结尾,不是以操作符结尾
            //(其实一个表达式以操作符结尾的情况只可能是以右括号")"结尾,这里就是为防止这种特殊情况)
            Inputvec.push_back(str.substr(i,str.size()-i));
            i = str.size();
        }
    }

    //遍历切片结果vector中每个元素
    stack<string> operatorstack;     //创建空栈,用来存储操作符
    vector<string> PostfiOutvec;     //存储中缀输出,这里是存储到vector
    for(size_t i=0; i<Inputvec.size(); i++)
    {
        //如果当前元素是操作符
        if(Inputvec[i].find_first_of(operatorstr) != string::npos)
        {
            if(operatorstack.empty())
            {
                operatorstack.push(Inputvec[i]);      //如果操作符栈空,则直接入栈
            }
            else
            {
                if(Inputvec[i] == ")")     //如果当前操作符是右括号
                {
                    while(operatorstack.top() != "(")
                    {
                        PostfiOutvec.push_back(operatorstack.top());     //将栈顶操作符输出
                        operatorstack.pop();    //删除栈顶元素
                    }
                    operatorstack.pop();    //删除栈顶元素(这里是删除左括号"(")
                }
                else
                {
                    int curpri = priority(Inputvec[i]);     //获取操作符的优先级

                    //比较当前操作符与栈顶元素优先级,如果小于或等于栈顶元素优先级则弹出栈顶元素,否则当前操作符入栈
                    while(!operatorstack.empty())
                    {
                        string top = operatorstack.top();     //返回栈顶元素
                        int toppor = priority(top);           //栈顶元素优先级

                        if((curpri <= toppor) && top!="(")    //左括号优先级最大,但是它只有遇到右括号才输出
                        {
                            PostfiOutvec.push_back(top);
                            operatorstack.pop();    //删除栈顶元素
                        }
                        else
                            break;
                    }
                    operatorstack.push(Inputvec[i]);
                }
            }
        }
        //如果当前元素是操作数,直接输出
        else
        {
            PostfiOutvec.push_back(Inputvec[i]);
        }
    }
    while(!operatorstack.empty())
    {
        PostfiOutvec.push_back(operatorstack.top());  //输出操作符栈中的其他操作符
        operatorstack.pop();
    }

    //在输出中插入空格
    vector<string>::const_iterator itr=PostfiOutvec.begin()+1;
    while(itr!=PostfiOutvec.end())
    {
        itr = PostfiOutvec.insert(itr," ");      //这里一定要返回insert之后的指针,因为改变容器的操作会使迭代器失效
        itr+=2;
    }

    PostfiOutvec.push_back(" ");     //添加最后一个空格

    //vector输出为string,作为后缀表达式结果返回
    string result;
    for(size_t i=0; i<PostfiOutvec.size(); i++)
    {
        result += PostfiOutvec[i];
    }

    return result;
}

check.cpp

看注释应该可以看懂,每一项检查都是由可能遇到的错误表达式得来,可能还有部分检查没有遇到,如有BUG请自行改写,也欢迎告诉我(我也不想自己的程序有BUG)。

#include "check.h"


using namespace std;

bool flag(char temp)    //判断字符是否为运算符
{
    if(temp == '+'||temp == '-'||temp == '*'||temp=='/'||temp == '.')
        return true;
    else
        return false;
}

bool check(QString labl_temp)  //表达式检查函数
{
    string temp = labl_temp.toStdString();
    if(temp == "")
        return false;
/****************括号检查***********************/
    int leftCount = 0;   //记录括号个数
    int rightCount = 0;
    int leftFlag = 0;
    for(size_t i = 0; i < temp.size();i++ )
    {
        if(temp[i]=='(')
        {
            if(temp[i+1] == ')') //检查"()"情况
                return false;
            if(i>0&&!flag(temp[i-1])) //检查除首字符左括号前无运算符
                return false;
            leftCount++;
            leftFlag++;
        }
        if(temp[i] == ')')    //出现右括号,左括号与之匹配,未匹配左括号数减一
            leftFlag--;
        if(leftFlag < 0)   //先出现右括号
            return false;
    }
    for(size_t i = 0; i < temp.size();i++ )
    {
        if(temp[i]==')')
            rightCount++;
    }
    if(leftCount != rightCount) //检查左右括号数是否匹配
        return false;
/*****************运算符及小数点检查**********************/
    for(size_t i = 0;i<temp.size() - 1;i++)
    {
        if(flag(temp[i]) && flag(temp[i+1]))  //检查连续运算符错误表达式
            return false;
        if(temp[i] == '(' && (temp[i+1] == '*' || temp[i+1] == '/')) //检查 “(*” 和“(/”错误表达式
            return false;
        if(flag(temp[i]) && temp[i+1] == ')')  //检查类似 “+)”错误表达式
            return false;
    }
    if(temp[0] == '*' || temp[0] == '/' ||flag(temp[temp.size() - 1]))  //检查首和尾部尾运算符
    {
            return false;
    }
    return true;
}
/**************正负号检查**********/
bool checkSignal(QString labl_temp)
{
    string temp = labl_temp.toStdString();
    for(size_t i = 0; i<temp.size() - 1;i++)
    {
        //检查首字符为正负号或左括号后为正负号
        if((temp[0] == '-'||temp[0] == '+')||(temp[i] == '(' && (temp[i+1] == '-' || temp[i+1] == '+')))
            return false;
    }
    return true;
}

/************插入函数(使算法支持带正负号表达式)*********/
QString insert_zero(QString temp)
{
    QString str = temp;
    if(temp[0] == '-') //首字符为负号,加上前导0
    {
        str = "0";
        str += temp;
    }
    else if(temp[0] == '+') //首字符为正号,去除正号
    {
        str = temp;
        str = str.right(str.size() - 1);
    }
    for(int i = 0;i < str.size() - 1;i++) //遍历文本
    {
        QString str2 = str;
        if(str[i] == '(' && str[i+1] == '-')
        {
            //左括号后为负号,加上前导0
            str2 = str.mid(0,i + 1);
            str2 += "0";
            str2 += str.mid(i+1,str.size()-(i+1));
        }
        else if(str[i] == '(' && str[i+1] == '+')
        {
            //左括号后为正号,去除正号
            str2 = str.mid(0,i + 1);
            str2 += str.mid(i+2,str.size()-(i+2));
        }
        str = str2;
    }
    return str;  //返回修改后表达式
}

至于主函数没啥要讲的,默认就行。

四、总结

首次使用QT开发,很方便,自己也需要学习更多,比较粗糙的一个程序,不是很会美化。C++确实难,不过计算器用到的类还比较简单,大多数代码是C风格。

源码链接:https://download.csdn.net/download/What_ever_Y/12382221

有大佬有吴健老师后面部分的视频资源请一定帮帮孩子,孩子很需要(孩子快哭了)。

原创文章 1 获赞 8 访问量 171

猜你喜欢

转载自blog.csdn.net/What_ever_Y/article/details/105893985