利用Qt实现可视化科学计算器

news2024/12/24 2:24:38

📞个人信息  学号:102101433   姓名:林堂钦 

💡 作业基本信息  

【课程】福州大学2021级软件工程Ahttps://bbs.csdn.net/forums/ssynkqtd-05
作业要求链接https://bbs.csdn.net/topics/617294583
作业目标

实现一个简易计算器,要求具有图形化界面。

功能:具有基本功能的计算器

实现加、减、乘、除、归零基本操作。

附加功能:具有科学计算的计算器

实现次方、幂、三角函数等操作。

参考文献

1. https://www.cnblogs.com/xinz/archive/2011/11/20/2255830.html

2. https://www.cnblogs.com/xinz/archive/2011/10/22/2220872.html


目录

💡 作业基本信息  

📢 Gitcode项目地址

🔨 PSP表格

 💬 解题思路描述

📌 接口设计和实现过程

🔑 主要的类和函数接口展示

🔑 MainWindow类

🔑 CalObj类

🔑 CalTest类

🔑 bepexpression.h

🔑 function.h

✅ 关键功能展示

👓 四则运算基础功能实现

👓 科学运算功能展现

👓 复合运算功能展现

⏰性能改进与优化

🏳‍🌈 时间显示功能

🏳‍🌈 历史记录及清空操作

🏳‍🌈 帮助文档以及ui界面美化

💊 单元测试

🚩 异常处理措施与展示

🎵 心得体会


📢 Gitcode项目地址

玄澈_ / 阿钦的科学计算器 · GitCode


🔨 PSP表格

PSP是卡耐基梅隆大学(CMU)的专家们针对软件工程师所提出的一套模型:Personal Software Process (PSP, 个人开发流程,或称个体软件过程)。

PSP的目的是:记录工程师如何实现需求的效率,和我们使用项目管理工具(例如微软的Project Professional,或者禅道等)进行项目进度规划类似。

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划1515
· Estimate· 估计这个任务需要多少时间1515
Development开发13701570
· Analysis· 需求分析 (包括学习新技术)540540
· Design Spec· 生成设计文档7070
· Design Review· 设计复审6040
· Coding Standard· 代码规范 (为目前的开发制定合适的规范)2030
· Design· 具体设计80180
· Coding· 具体编码300360
· Code Review· 代码复审6050
· Test· 测试(自我测试,修改代码,提交修改)240300
Reporting报告110115
· Test Repor· 测试报告6070
· Size Measurement· 计算工作量2015
· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划3030
· 合计14951700

 💬 解题思路描述

题目的要求是实现一个具有图形化界面的计数器,在实现普通的加减乘除四则运算的基础上,进一步实现科学计算器的相关内容,包括次方、幂、三角函数等相关操作。

出于作者的技术栈,本文选择了使用Qt跨平台C++开发工具在进行编写程序。

基于对题目的理解,以及查阅的相关知识,以下是本人实现该程序的一个解题思路

  1. 首先设计GUI界面。使用Qt Designer等来创建GUI界面,包括数字按钮、运算符按钮、科学函数按钮等,然后将其与代码和函数进行绑定。
  2. 实现数学运算等相关功能。使用Qt的信号和槽机制来实现这些功能。例如,当用户点击加号按钮时,我们需要通过实现相关的响应函数将两个数字相加将其回显到屏幕。

  3. 除了数学运算功能外,还需要实现科学计算功能。可以使用Qt Math库中的函数来实现这些功能。例如,当用户选择平方根函数时,我们需要计算输入数字的平方根并显示结果至屏幕。

  4. 在实现科学计算功能时,我们还需要考虑一些特殊情况。例如,当用户输入的数字为负数时,平方根函数将返回一个复数。我们需要处理这种情况并给出相应的提示信息

  5. 需要编写单元测试用例,确保每个功能都正常工作。我们可以使用Qt自带的测试框架QTest来编写单元测试用例。例如,我们可以测试加法、减法、乘法、除法以及相关的科学运算等功能的正确性。

  6. 通过编写的相关的函数来实现历史记录的功能,并在图形界面上附加当前时间的显示功能以及开发日志及提示等功能,使得该计算器的整体功能更加完善。


📌 接口设计和实现过程

🔑 主要的类和函数接口展示

在实现科学计算器的编程过程中,主要由以下几个类和函数接口类实现的。

  • MainWindow类 :MainWindow类是科学计算器程序中的一个主窗口类,它提供了一个应用程序的主窗口,其中包含了计算器的主要界面和相关按钮。MainWindow类同时会处理具体的用户操作并通过对应的反应函数做出相应的响应。
  • Dailog类:实现了开发日志和帮助文档的ui界面等。
  • CalObj类:实现了计算器的实例类,通过结合MainWindow类中的相关内容,实例化出具体的计算器类,实现了具体的计算函数和实现方法,同时也方便后续进一步的单元测试相关的内容。
  • CalTest类:通过编写该类,通过实例化CalObj类,能够调用QT自带的测试框架QTest来对该科学计算器进行单元测试,从而进一步提高程序的可靠性。
  • bepexpression.h:该头文件主要包含了检验表达式正确性,格式化表达式、通过逆波兰表达式等原理来进行计算器的计算等函数实现。
  • function.h:该头文件主要通过将内置的数学科学计算函数进行整合以及优化,进一步方便其他模块函数的调用和处理。

🔑 MainWindow类

上图是对MainWindow类的一个总体框架的分析,通过对整体的分析,可以将MainWindow类中的函数分为三类

  • 处理相关数字按钮信号(例如:输入1~9,π,e等)
  • 处理相关操作按钮信号(例如:输入+、-、*、÷等操作按钮)
  • 其他操作函数:例如更新时间、清除历史记录等

① 以on_button0_clicked()为例,展示处理相关数字按钮信号的关键代码:

void MainWindow::on_button0_clicked()
{
    ui->textDisplay->insert("0");
}

② 以on_buttonAdd_clicked()为例,展示简单四则运算操作的关键代码,该模块直接将相关的操作符载入缓存区中,为后续的计算做好准备:

void MainWindow::on_buttonAdd_clicked()
{
    ui->textDisplay->insert("+");
}

③ 以on_buttonLn_clicked()为例,展示科学运算过程中所需要的关键代码,参考Windows自带的科学计算器以及本项目的具体编写,本文进行这样的设定:计算器分为两种模式,分别为普通模式函数模式。且两种模式不能混用,即普通算式中不能出现函数,函数式中不能出现运算符这样的设定也符合系统自带计算器的相关操作。

在下面的函数模块中,我们通过一个flag标志来识别此时计算器处于哪一种计算模式,如果出现了计算模式的混用,则会对系统进行报错,并对用户进行提醒"此函数暂不支持混合运算!"

void MainWindow::on_buttonLn_clicked()
{
    if (flag != EMPTY)
    {
        ui->statusbar->showMessage("此函数暂不支持混合运算!", 3000);
        return;
    }
    ui->textDisplay->insert("ln()");
    flag = LN;
    moveCursor();
    ui->textDisplay->setFocusPolicy(Qt::NoFocus);
}

④ on_buttonEqual_clicked(),这个类的重点函数实现,在这个函数中实现了对于表达式的计算操作,首先先判断计算模式是函数模式还是普通模式:如果是函数模式的话通过对于不同的科学操作函数进行不同的操作来得到最后返回的结果;如果是普通模式的话,通过bepexpression.h中的相关函数来对算术表达式进行检验,判断其是否合法,然后通过slove函数对其进行计算,最后得到返回结果并清空相关内容,以便下一次进行计算。

特点:在这个函数中,我们通过在类中定义一个 enum functionSituation 来对不同的计算操作进行标识,从而方便了后续代码的编写。同时,通过捕捉 errorCode 来针对不同的错误情况对用户进行提示,提高了软件的功能性

void MainWindow::on_buttonEqual_clicked()
{
    double answerNumber  = 0;
    QString qExpression  = ui->textDisplay->text();
    string stdExpression = qExpression.toStdString(); //转化为c++的string

    if (flag != EMPTY) //函数模式
    {
        // enum functionSituation{EMPTY = 0,SQUARE,POWER,SQRT,LG,LN,FAC,SIN,COS,TAN,NUM};
        switch (flag)
        {
        case SQUARE:
        {
            //将形似于x^2的表达式中的x提取出来
            QString qNumber = qExpression;
            qDebug() << qNumber << endl;
            qNumber.chop(2); // 去除尾部两个数字,留下所需数字
            double number = qNumber.toDouble();

            answerNumber = getPow(number, 2);
            break;
        }
        case POWER:
        {
            //将形似于x^(y)的表达式中的x提取出来
            int Length   = qExpression.length();
            int position = qExpression.indexOf("^");
            //分离出底数和指数
            QString qBaseNumber = qExpression;
            qBaseNumber.chop(Length - position);
            double baseNumber = qBaseNumber.toDouble();

            QString qPowNumber = qExpression.mid(position + 2);
            qPowNumber.chop(1);
            double powNumber = qPowNumber.toDouble();

            answerNumber = getPow(baseNumber, powNumber);
            break;
        }
        case SQRT:
        {
            //将形似于√(x)的表达式中的x提取出来
            QString qNumber = qExpression.mid(2);
            qNumber.chop(1);
            double number = solve(qNumber.toStdString());

            answerNumber = getSqrt(number);
            break;
        }
        case LG:
        {
            //将形似于lg(x)的表达式中的x提取出来
            QString qNumber = qExpression.mid(3);
            qNumber.chop(1);
            double number = solve(qNumber.toStdString());

            answerNumber = getLg(number);
            break;
        }
        case LN:
        {
            //将形似于ln(x)的表达式中的x提取出来
            QString qNumber = qExpression.mid(3);
            qNumber.chop(1);
            double number = solve(qNumber.toStdString());

            answerNumber = getLn(number);
            break;
        }
        case FAC:
        {
            //将形似于x!的表达式中的x提取出来
            QString qNumber = qExpression;
            qNumber.chop(1);
            double number = solve(qNumber.toStdString());

            answerNumber = getFac(number);
            break;
        }
        case SIN:
        {
            //将形似于sin(x)的表达式中的x提取出来
            QString qNumber = qExpression.mid(4);
            qNumber.chop(1);
            double number = solve(qNumber.toStdString());

            answerNumber = getSin(number);
            break;
        }
        case COS:
        {
            //将形似于cos(x)的表达式中的x提取出来
            QString qNumber = qExpression.mid(4);
            qNumber.chop(1);
            double number = solve(qNumber.toStdString());
            answerNumber  = getCos(number);
            break;
        }
        case TAN:
        {
            //将形似于tan(x)的表达式中的x提取出来
            QString qNumber = qExpression.mid(4);
            qNumber.chop(1);
            double number = solve(qNumber.toStdString());
            answerNumber  = getTan(number);
            break;
        }
        }
        QString answerString = QString::number(answerNumber, 'g', 6);
        //重置光标位置和标志,以进行下一次运算
        ui->textDisplay->setCursorPosition(ui->textDisplay->text().size());
        ui->textDisplay->insert("=" + answerString);
        flag = EMPTY;
        //记录历史信息
        ui->historyDisplay->append(ui->textDisplay->text());
        ui->statusbar->showMessage("记得Clear再进行下一次运算哦~", 3000);
    }
    else //普通算式模式
    {
        try
        {
            if (!analysis(stdExpression.c_str())) //尝试判断表达式是否合法
            {
                answerNumber = solve(stdExpression); // 用这个参数接返回的计算值
                QString answerString = QString::number(answerNumber, 'g', 6);
                //重置光标位置和标志,以进行下一次运算
                ui->textDisplay->setCursorPosition(ui->textDisplay->text().size());
                ui->textDisplay->insert("=" + answerString);
                flag = EMPTY;
                //记录历史信息
                ui->historyDisplay->append(ui->textDisplay->text());
                ui->statusbar->showMessage("记得Clear再进行下一次运算哦~", 3000);
            }
        }
        catch (int errorCode)
        {
            switch (errorCode)
            {
            case 1:
                ui->statusbar->showMessage("运算符位置不正确,请检查后重试",3000);
                break;
            case 2:
                ui->statusbar->showMessage("左右括号不匹配,请检查后重试", 3000);
                break;
            case 3:
                ui->statusbar->showMessage("存在不合法字符,请检查后重试", 3000);
                break;
            }
        }
    }
}

⑤ 相关的QT连接函数以及时间功能的实现

void MainWindow::timerUpdate()
{
    QDateTime time = QDateTime::currentDateTime();
    showtime->setText(time.toString("yyyy-MM-dd hh:mm:ss dddd"));
}

void MainWindow::clearHistory()
{
    ui->historyDisplay->clear();
}

void MainWindow::dealMenuBar()
{
    //连接按键退出槽
    connect(ui->actionExit, &QAction::triggered, this, &MainWindow::close);
    //连接历史记录清空槽
    connect(ui->actionClearHistory, &QAction::triggered, this,
            &MainWindow::clearHistory);
    //连接对话框打开槽
    connect(ui->actionAbout, &QAction::triggered, &myDialog, &QDialog::open);
}

🔑 CalObj类

    enum functionSituation
    {
        EMPTY = 0, //空
        SQUARE = 1,    //平方
        POWER = 2,     //幂
        SQRT = 3,      //平方根
        LG = 4,        // 10为底的对数
        LN,        // e为底的对数
        FAC,       //阶乘
        SIN,       //正弦
        COS,       //余弦
        TAN,       //正切
        NUM = 10       //普通数字计算
    };

CalObj类主要是用来对一个计算器进行实例化操作,方便后面进行单元测试。

其中的Func()函数也是通过实话化不同的计算器对象来得到最后的返回值,以便和测试用例的最终结果进行比较。


🔑 CalTest类

该类是在实例化CalObj类对象的基础上,利用Qt自带的QTest测试框架来对该项目进行单元测试。共编写了11个测试样例,分别针对平方、幂方、log、ln、阶乘等操作进行测试,具体的测试结果见下文所示。

部分测试函数的关键代码如下:

void CalTest::case1_squere()
{
    CalObj c("2^2", 1);
    //std::cout << c.Func() << std::endl;
    QVERIFY(c.Func() == 4);
}

🔑 bepexpression.h

这个模块主要实现了后台处理表达式的相关操作,包括以下几个函数:

  • int analysis(const char *expression) :检验算术表达式的合法性
  • string format(string str):处理负号开头的表达式,处理方法是在前面加个0
  • int comparePriorities(char c):比较运算符优先级
  • vector<string> Infix2Suffix(string str):中缀表达式转后缀
  • double result(vector<string> suffixExpression):计算后缀表达式结果
  • double solve(string str):调用前几个函数,相当于main函数,一步到位求解表达式

接下来介绍一下几个重要的函数,首先是int analysis(const char *expression),这个函数是从来检查算术表达式的合法性的,如果所检验的算术表达式是不合法的话,会抛出对应的异常,有如下几种异常异常代码: 1,运算符位置不正确;2,左右括号不匹配 ;3,存在不合法字符。

如果最后验证是一个正确的表达式的话,则会返回整数0

具体的关键函数实现如下:

int analysis(const char *expression)
{
    int opt = 0;
    int length = strlen(expression);
    bool isNumberEnd = false;
    int numberBracket = 0; //括号总数,检测到左括号就+1,检测到右括号就-1

    for (int i = 0; i < length;)
    {
        if (expression[i] >= '0' && expression[i] <= '9')
        {
            if (isNumberEnd)
                throw 1;
            isNumberEnd = true; //此时检测到数字,所以置为真,因为下面要把连续的数字吃掉
            while ((expression[i] >= '0' && expression[i] <= '9') || expression[i] == '.')
                ++i;
        }
        //数字吃掉了,轮到运算符
        char const *sop = "(+-*/)";
        opt = strchr(sop, expression[i++]) - sop; //计算运算符优先级,从左到右优先级依次增高
        if (opt < 0 || opt > 6)
            throw 3;
        if (opt == 0) //匹配到左括号
        {
            if (isNumberEnd) //此时括号数应为0,否则为错
                throw 1;
            numberBracket++;
        }
        if (opt == 5) //匹配到右括号
        {
            if (numberBracket <= 0) //如果是右括号,那么nbracket必大于0
                throw 2;
            numberBracket--;
        }
        if (opt > 0 && opt < 6 && !isNumberEnd) //运算符正确但后面没有数字
            throw 1;
        if (opt > 0 && opt < 5) //匹配到+-*和/,数字末端置false
            isNumberEnd = false;
    }
    if (numberBracket) //括号不匹配
        throw 2;
    if (!isNumberEnd) //运算符缺少运算数
        throw 1;

    return 0;
}

接下来是vector<string> Infix2Suffix(string str),这个函数主要实现了中缀表达式转后缀的相关操作,大致流程如下:

  1. 初始化一个空栈(用于存储操作符)
  2. 左到右遍历中缀表达式的每个字符。
  3. 如果遇到操作数,直接输出
  4. 如果遇到操作符(如+、-、*、/),则将其与栈顶的操作符进行比较。如果当前操作符的优先级高于栈顶操作符,将当前操作符压入栈;否则,将栈顶操作符弹出并输出,直到遇到优先级低于或等于当前操作符的操作符或栈为空,然后将当前操作符压入栈。
  5. 如果遇到左括号(,将其压入栈。
  6. 如果遇到右括号(,则将栈顶的操作符弹出并输出,直到遇到左括号,然后将左括号弹出
  7. 遍历完中缀表达式后,将栈中剩余的操作符依次弹出并输出

最后实现的关键代码如下:

vector<string> Infix2Suffix(string str)
{
    vector<string> suffixExpression; //存储后缀表达式
    stack<char> operatorStack;
    for (int i = 0; i < str.length(); i++)
    {
        string temp = "";
        switch (str[i])
        {
        //
        case '+':
        case '-':
        case '*':
        case '/':
        {
            if (operatorStack.empty() || operatorStack.top() == '(')
            {
                operatorStack.push(str[i]);
            }
            else
            {
                //栈不为空且栈顶元素优先级大于这个位置的运算符,优先级大的先入栈
                while (!operatorStack.empty() && comparePriorities(operatorStack.top()) >= comparePriorities(str[i]))
                {
                    temp += operatorStack.top();
                    suffixExpression.push_back(temp);
                    operatorStack.pop();
                    temp = "";
                }
                operatorStack.push(str[i]);
            }
            break;
        }
        case '(':
        {
            operatorStack.push(str[i]);
            break;
        }
        case ')':
        {
            while (operatorStack.top() != '(')
            {
                temp += operatorStack.top();
                suffixExpression.push_back(temp);
                operatorStack.pop();
                temp = "";
            }
            operatorStack.pop();
            break;
        }
        //除去+-*、/和括号,剩下的就是数字
        default:
        {
            if ((str[i] >= '0' && str[i] <= '9'))
            {
                temp += str[i];
                //若是连续数字,若不是则跳过而直接放入逆波兰表达式
                while (i + 1 < str.size() && str[i + 1] >= '0' && str[i + 1] <= '9' || str[i + 1] == '.')
                {
                    temp += str[i + 1];
                    ++i;
                }
                suffixExpression.push_back(temp);
            }
        }
        }
    }
    while (!operatorStack.empty())
    {
        string temp = "";
        temp += operatorStack.top();
        suffixExpression.push_back(temp);
        operatorStack.pop();
    }
    return suffixExpression;
}

最后是double result(vector<string> suffixExpression),这个函数将通过上述方法得到的后缀表达式进行计算从而得到最后的结果,大致流程如下:

  1. 从左到右遍历后缀表达式的每个字符。
  2. 如果遇到操作数,将其压入栈中。
  3. 如果遇到操作符,从栈中弹出两个操作数,进行相应的运算,然后将结果压回栈中。
  4. 遍历完后缀表达式后,栈中剩余的元素即为计算结果

具体实现的关键代码如下:

double result(vector<string> suffixExpression)
{
    stack<double> suffixStack; //后缀表达式计算栈
    double num, op1, op2;      //定义数字、操作数1和2
    for (int i = 0; i < suffixExpression.size(); i++)
    {
        string temp = suffixExpression[i];
        if (temp[0] >= '0' && temp[0] <= '9')
        {
            // c_str()函数是将string转化为标准char*,atof()是将字符串转化为浮点数
            num = atof(temp.c_str());
            suffixStack.push(num); //数字压入栈
        }
        else if (suffixExpression[i] == "+")
        {
            //因为是后缀表达式,所以先出栈的是操作数2,后出来的是操作数1
            op2 = suffixStack.top(); //取栈顶
            suffixStack.pop();       //栈顶出栈
            op1 = suffixStack.top();
            suffixStack.pop();
            suffixStack.push(op1 + op2);
        }
        else if (suffixExpression[i] == "-")
        {
            op2 = suffixStack.top();
            suffixStack.pop();
            op1 = suffixStack.top();
            suffixStack.pop();
            suffixStack.push(op1 - op2);
        }
        else if (suffixExpression[i] == "*")
        {
            op2 = suffixStack.top();
            suffixStack.pop();
            op1 = suffixStack.top();
            suffixStack.pop();
            suffixStack.push(op1 * op2);
        }
        else if (suffixExpression[i] == "/")
        {
            op2 = suffixStack.top();
            suffixStack.pop();
            op1 = suffixStack.top();
            suffixStack.pop();
            suffixStack.push(op1 / op2);
        }
    }
    return suffixStack.top();
}

最后通过double solve(string str)函数来将上述的操作进行一个集成,提高整体代码的封装性。

double solve(string str)
{
    str = format(str);
    vector<string> suffixExpression = Infix2Suffix(str);
    double k = result(suffixExpression);
    return k;
}

🔑 function.h

这个模块主要是将一些常用的科学计算函数进行封装,从而提高代码的简洁程度以及减少不同模块代码之间的耦合性。 

以其中一个函数为例来展示关键代码:

double getSin( double number )
{
    double answer = 0;
    // 计算弧度
    double radian = ( number * 3.1415926 ) / 180.0;
    answer = sin( radian );

    return answer;
}

✅ 关键功能展示

👓 四则运算基础功能实现

👓 科学运算功能展现

👓 复合运算功能展现

性能改进与优

🏳‍🌈 时间显示功能

为了能够更好的提高软件的界面美观程度,从实用性的角度出发,我考虑在程序的右下角添加了一个时间显示的框框,在一定程度上能够是程序变得更加具有完备性

以下关键代码用以获取实时时间来展示:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    flag     = EMPTY;
    showtime = new QLabel(this);

    //更新时间,在statusbar右边显示,1000ms刷新一次
    QTimer *timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &MainWindow::timerUpdate);
    timer->start(1000);
    ui->statusbar->addPermanentWidget(showtime);

    dealMenuBar();
}

void MainWindow::timerUpdate()
{
    QDateTime time = QDateTime::currentDateTime();
    showtime->setText(time.toString("yyyy-MM-dd hh:mm:ss dddd"));
}

实现效果如下:

🏳‍🌈 历史记录及清空操作

在一个功能较为完善的计算器中,需要有历史记录功能和清空操作,通过以下关键代码来实现这些功能:

//记录历史信息
ui->historyDisplay->append(ui->textDisplay->text());
ui->statusbar->showMessage("记得Clear再进行下一次运算哦~", 3000);


void MainWindow::dealMenuBar()
{
    //连接按键退出槽
    connect(ui->actionExit, &QAction::triggered, this, &MainWindow::close);
    //连接历史记录清空槽
    connect(ui->actionClearHistory, &QAction::triggered, this,
            &MainWindow::clearHistory);
    //连接对话框打开槽
    connect(ui->actionAbout, &QAction::triggered, &myDialog, &QDialog::open);
}

具体的实现可参考上述关键内容展示中的GIF。

🏳‍🌈 帮助文档以及ui界面美化

在实现了基础的功能的基础上,作者希望能够通过进一步美好用户的操作界面已经相关的帮助操作来带给用户更加优越的使用体验,在这种想法的基础上,作者增加了帮助文档这一操作选项以及对原有的ui界面进行进一步的美化。

设计帮助文档
通过设置相关参数等操作来进一步美化界面

💊 单元测试

上述的内容中,本文已经提到了通过构建CalTest类和CalObj类来实例化计算器对象,从而能够正确调用QT自带的QTest库中的测试相关函数,作者共设置了11个测试样例,涵盖了所有可能发生的情况和操作,具体的结果展示如下:

可以看到,通过了所有的测试,证明该程序在功能上具有一定的科学性和完备性,能够满足正常的使用需求。

🚩 异常处理措施与展示

对于用户在实际应用的过程中出现的相关非法操作,程序会通过相关提示来告知用户进行清空或者其他操作来重新进行运算:

出现左右括号不匹配的情况:

出现错误的使用相关运算符的情况:

出现÷0的情况,并不会直接报错,而是通过输出inf来提示用户:

出现次方或幂方的运算无底数的情况:

出现不完整的算式:

还有其他的异常情况,本文列举了上述几种典型的情况来作为说明,同时,也还有作者所遗漏的一些情况需要改进,将在未来的后续版本中进行进一步的更新。


🎵 心得体会

        在个人成长方面:这次的编程任务对我来说是一次非凡的体验。从最开的选择语言,对qt相关语言的学习,到后面自己慢慢去配置环境,去查阅相关的资料,看别人的博客和代码,学习优秀的经验,接着是对项目的不断打磨和优化,出现了bug的时候需要针对不同的问题一步步的去解决相关的问题,到最后的单元测试和性能测试,让我对于一个软件整体的生命流程有了进一步的认识,知道了一款好的产品是应该不断出来的,不断的测试,在测试中去优化相关的功能和用户体验;最后是对于程序的打包,通过windeployqt.exe,一款Qt自带的工具,用于创建应用程序发布包,将某程序依赖的库、资源拷贝到其所在目录,防止程序在其他电脑上运行报找不到库的错误,在这个过程中我对于一个项目的发布又有了进一步的体验和收获。

        在技术方面:通过使用Qt编程实现了一个科学计算器,在这个过程中,我学到了很多关于GUI设计和事件处理的知识,同时也深刻理解了面向对象编程的思想。例如:我学会了如何使用Qt Designer来设计GUI界面。通过拖拽和调整控件的位置和大小,我可以轻松地创建出符合需求的界面。同时,我也学会了如何使用信号和槽机制来实现各个控件之间的交互,这大大提高了我的编程效率。同时,在实现科学计算器的过程中,我也深刻理解了面向对象编程的思想。我将整个计算器看作是一个对象,将各种功能封装成不同的方法,然后通过调用这些方法来实现计算器的完整功能。这种思想不仅可以应用到科学计算器的开发中,也可以应用到其他程序的开发中。

        总而已知,实践出真知,通过这次的实践任务,让我完整的体验了一次项目的开发流程,这也符合软件工程这门课的目标所在,希望在接下来的课程中能够不断提高自己的能力,开发出更好的项目产品。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1050275.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu系统Linux内核安装和使用

安装&#xff1a; 检查树莓派Linux版本&#xff0c;我的是6.1 uname -r 内核下载链接&#xff1a; Raspberry Pi GitHub 找对应版本下载 导入之后&#xff0c;解压安装即可 unzip linux-rpi-6.1.y.zip 其他内容 treee 指令安装 sudo apt-get install tree 使用这…

一探Redis究竟:超火爆入门指南,你竟然还没看?

Redis入门教程目录&#xff1a;【Redis入门教程目录】 简介 Redis是由C语言编写的开源、基于内存、支持多种数据结构、高性能的Key-Value数据库。 特性 速度快 首先Redis是将数据储存在内存中的&#xff0c;通常情况下每秒读写次数达到千万级别。其次Redis使用ANSI C编写&…

No145.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

LeetCode 377.组合总和IV 可解决一步爬m个台阶到n阶楼顶问题( 完全背包 + 排列数)

给你一个由 不同 整数组成的数组 nums &#xff0c;和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。 题目数据保证答案符合 32 位整数范围 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3], target 4 输出&#xff1a;7 解释&#x…

Spring源码篇(十)@Bean怎么注册一个bean

文章目录 前言配置类里的Bean解析sourceClass是什么解析Bean方法添加Bean注解的方法信息注册总结Bean注册的过程注意点 前言 配置类的解析之前有聊过&#xff0c;这篇也会涉及到一部分&#xff0c;因为Bean本身也是配置类里的一个东西&#xff0c;本篇会着重解析Bean注册bean的…

区块链(6):p2p去中心化介绍

1 互联网中中心化的服务和去中心化服务的概念介绍 目前的互联网公司大都是中心化的 区块链网络大多是去中心化的 去中心化 2 p2p的简单介绍 java 网络编程:socket编程,netty编程,websoket简单介绍 2.1 节点是如何提供服务的(web编程实现)

qml保姆级教程一:布局组件

&#x1f482; 个人主页:pp不会算法v &#x1f91f; 版权: 本文由【pp不会算法v】原创、在CSDN首发、需要转载请联系博主 &#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 QML系列教程 QML教程一&#xff1a;布局组件 文章目录 锚布局anchors属…

前端开发 vs. 后端开发:编程之路的选择

文章目录 前端开发&#xff1a;用户界面的创造者1. HTML/CSS/JavaScript&#xff1a;2. 用户体验设计&#xff1a;3. 响应式设计&#xff1a;4. 前端框架&#xff1a; 后端开发&#xff1a;数据和逻辑的构建者1. 服务器端编程&#xff1a;2. 数据库&#xff1a;3. 安全性&#…

CISSP学习笔记:人员安全和风险管理概念

第二章 人员安全和风险管理概念 2.1 促进人员安全策略 职责分离: 把关键的、重要的和敏感工作任务分配给若干不同的管理员或高级执行者&#xff0c;防止共谋工作职责:最小特权原则岗位轮换:提供知识冗余&#xff0c;减少伪造、数据更改、偷窃、阴谋破坏和信息滥用的风险&…

CSS详细基础(四)显示模式

本帖开始介绍CSS中更复杂的内容 目录 一.显示模式 1.行内元素 2.块级元素 3.行内块元素 二.背景样式 一.显示模式 顾名思义&#xff0c;在CSS中&#xff0c;元素主要有3种显示模式&#xff1a;行内元素、块级元素、行内块元素~ 所谓块级元素&#xff0c;指的是该元素在…

Springboot+vue的企业人事管理系统(有报告),Javaee项目,springboot vue前后端分离项目。

演示视频: Springbootvue的企业人事管理系统&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的企业人事管理系统&#xff0c;采用M&#xff08;model&am…

【kylin】【ubuntu】搭建本地源

文章目录 一、制作一个本地源仓库制作ubuntu本地仓库制作kylin本地源 二、制作内网源服务器ubuntu系统kylin系统 三、使用内网源ubuntukylin 一、制作一个本地源仓库 制作ubuntu本地仓库 首先需要构建一个本地仓库&#xff0c;用来存放软件包 mkdir -p /path/to/localname/pac…

基于微信小程序的手机在线商城小程序设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

摩根大通限制英国客户购买加密货币,市场掀起涟漪!

摩根大通旗下英国数字银行部门宣布&#xff0c;从下个月开始&#xff0c;将禁止客户进行加密货币交易。这一决定归因于人们越来越担心与加密货币相关的诈骗和欺诈行为的增加。 正如该银行周二表示的那样&#xff0c;从10月16日起&#xff0c;该银行的客户将不再可以选择通过借…

Bee2.1.8支持Spring Boot 3.0.11,active命令行选择多环境,多表查改增删(bee-spring-boot发布,更新maven)

天下大势&#xff0c;分久必合&#xff01; Hibernate/MyBatis plus Sharding JDBC Jpa Spring data GraphQL App ORM (Android, 鸿蒙) Bee Spring Cloud 微服务使用数据库更方便&#xff1a;Bee Spring Boot; 轻松支持多数据源&#xff0c;Sharding, Mongodb. 要整合一堆的…

数据大帝国:大数据与人工智能的巅峰融合

文章目录 大数据与人工智能&#xff1a;概念解析大数据与人工智能的融合1. 数据驱动的决策2. 自然语言处理&#xff08;NLP&#xff09;3. 图像识别与计算机视觉4. 智能推荐系统5. 医疗诊断和生命科学 数据大帝国的未来展望1. 智能城市2. 区块链和数据安全3. 自动化和机器人4. …

分布式搜索引擎es-3

文章目录 数据聚合聚合的种类RestAPI实现聚合 数据聚合 什么是聚合&#xff1f; 聚合可以让我们极其方便的实现对数据的统计、分析、运算。例如&#xff1a; 什么品牌的手机最受欢迎&#xff1f;这些手机的平均价格、最高价格、最低价格&#xff1f;这些手机每月的销售情况如…

Linux CentOS7 vim临时文件

在vim中&#xff0c;由于断网、停电、故意退出、不小心关闭终端等多种原因&#xff0c;正在编辑的文件没有保存&#xff0c;系统将会为文件保存一个交换文件&#xff0c;或称临时文件&#xff0c;或备份文件。 如果因某种原因产生了交换文件&#xff0c;每次打开文件时&#x…

多线程总结(线程池 线程安全 常见锁)

本篇文章主要是对线程池进行详解。同时引出了单例模式的线程池&#xff0c;也对线程安全问题进行了解释。其中包含了智能指针、STL容器、饿汉模式的线程安全。也对常见的锁&#xff1a;悲观锁&#xff08;Pessimistic Locking&#xff09;、乐观锁&#xff08;Optimistic Locki…

使用GDIView排查GDI对象泄漏导致的程序UI界面绘制异常问题

目录 1、问题说明 2、初步分析 3、查看任务管理器&#xff0c;并使用GDIView工具分析 4、GDIView可能对Win10兼容性不好&#xff0c;显示的GDI对象个数不太准确 5、采用历史版本比对法&#xff0c;确定初次出现问题的时间点&#xff0c;并查看前一天的代码修改记录 6、将…