C++ QT 课程设计:科学计算器

cug chx

目录

一 课程设计题目与要求

二 需求分析

2.1 用户需求

2.2 功能性需求

2.3 非功能性需求

2.4 运行环境

三 概要设计

3.1 流程设计

3.2 交互设计

3.3 底层设计

四 详细设计

4.1 界面设计

4.2 计算功能

4.3 历史记录

4.4 自动纠正

4.5 安全检查

4.6 错误检查

4.7 键盘输入

五 测试

5.1 基础功能测试

5.2 自动纠正测试

5.3 安全检查测试

4.4 错误检查测试

5.5 键盘输入测试


一 课程设计题目与要求

        仿照Windows系统的计算器软件,如图1.1所示,为教材第12.4节通用计算器设计界面,开发一款实用的计算器软件,并按照实验指导书附录中课程设计报告模板要求撰写结课报告。

0a8d8b27c8e84d8d9314edcb21cf0b0e.png

图1.1 Windows系统的计算器界面

二 需求分析

2.1 用户需求

        (1)设计一套算法,实现常数数学表达式的混合计算,遵循运算优先级顺序,支持小括号。

        (2)提供一套UI界面,使用户能够方便地输入表达式,直观地获得计算结果。

2.2 功能性需求

        (1)数学运算功能。包括但不限于四则运算、三角函数、幂、对数的数学运算;支持整形和浮点型运算;支持输入小括号改变运算顺序。

        (2)输入输出功能。支持屏幕按钮和键盘两种输入方式,支持删除、清除功能;提供计算历史记录,和历史记录清除功能。

        (3)结果调用功能。在进行sin、cos等函数运算时,允许用户直接调用上一次的运算结果,进行函数运算。

2.3 非功能性需求

        (1)自动纠正功能。用户在输入算式时若出现低级错误,如连续输入两个运算符;或出现符合书写习惯但不符合程序计算规则的错误,如数字和函数运算符之间省略乘号,程序应当自动进行纠正,以避免发生潜在的输入错误。

        (2)错误检查功能。对于分母为零、对负数求算数平方根、arcsin范围超出[-1, 1]等一些不符合约定的数学错误,程序应当检测出错误类型,并向用户发出错误警告。

        (3)安全检查功能。用户的输入是多种多样的、难以枚举的,当意料之外的非法输入时发生时,应当避免程序自身崩溃,同时向用户报告错误。

2.4 运行环境

        操作系统Windows 10;Qt Creator版本4.11.0;Qt版本5.14.1

三 概要设计

3.1 流程设计

        Qt基于信号与槽机制,因此程序总体上并非顺序执行的流程。在主函数中,完成应用程序QApplication和主要窗口MainWindow类的对象创建后,程序就进入事件循环,等待用户触发事件。程序框图如图3.1所示。

7ae1f051217b4a3c9a80c9ef053e6330.png

图3.1 主函数程序框图

3.2 交互设计

        用户在操作应用程序时,实际是与程序外层的mainwindow类进行信息交互,如图3.2所示。mainwindow是由QMainWindow、QPushButton、QTextBrowser、QLabel等一系列Qt提供UI设计类构成。借助Qt提供的类,程序员可以通过代码或Qt Designer很方便地创建和布局窗口,并通过信号和槽机制处理事件。

417082c94dcf4c83b87b7799ccd66031.png

  图3.2 程序交互设计示意图

        位于外层的mainwindow对用户输入的信息进行处理后,将其转化为底层operator、stack、factory类能够理解的形式,然后进行计算。因此,可以认为mainwindow起到了“桥梁”的作用,使得用户能够借助mainwindow类,以一种友好、可视化的方式与底层负责运算的类进行交流,用户只能看到图形化的界面,而内核部分对用户是屏蔽的。实际上,用户也无需知道底层的算法,这样面向对象的设计思想对于用户是友好的,对于开发者也是友好的。

3.3 底层设计

        除mainwindow类外,本应用程序底层中主要有operator、stack、factory三个类,其UML图如图3.3所示。

2711d4b226a14eeb976b0ac0a406b1c9.png

 图3.3 程序UML图

        为了提高计算程序的可读性、可维护性和可拓展性,本程序参考教材12.4节通用计算器,设计了Operator类对程序的逻辑关系进行了优化。Operator类存储了每一种运算的标签、目数和优先级,同时提供了计算函数getResult(),该函数是一个纯虚函数,需要在具体运算符的子类中实现。

        在程序中,所有运算符类都继承自Operator,重写getResult()函数以便实现不同的运算功能。当开发者增添、删改某种运算时,只需要对相应的运算符类进行修改,而不需要像实验8.2简易计算器一样修改if-else语句。对象工厂Factory类则是为了更加方便地创建运算符类。

四 详细设计

4.1 界面设计

        Qt允许程序员不通过任何设计工具,以纯粹的C++代码来设计界面,同时也提供了一个可视化的界面设计工具:Qt Designer。通过Qt Designer可以很方便地创建部件,修改部件属性,调整窗口布局。

        窗体的布局是十分重要的,只有灵活运用水平布局、垂直布局、栅格布局等方式将所有部件组合起来,并设置合理的尺寸策略(sizePolicy),才能在用户任意拖动窗口大小时,窗口内的部件随窗口尺寸一起缩放,且布局比例不变,保持美观。本程序的界面布局如图4.1所示。

38f0196a420946dba1cba70eed0bd2c8.png

 图4.1 窗口布局

        程序套用了CSS样式表来对界面外观进行风格化设置,并将图标设置为自己设计的logo,上述操作的代码如下:

a.setWindowIcon(QIcon("./logo.ico"));  
  
QFile file(":/qss/psblack.css");  
file.open(QFile::ReadOnly);  
if(file.isOpen())  
{  
    QString styleSheet = this->styleSheet();  
    styleSheet += QLatin1String(file.readAll());  
    this->setStyleSheet(styleSheet);  
}  

        最终呈现出的界面如图4.2所示。

d59862311b03488aa857e80a4695e168.png

 图4.2 计算器界面效果图

4.2 计算功能

        每一个按键的clicked信号与对应的槽函数绑定,程序在槽函数中执行相关操作,或将运算符、数字追加到计算式qstr中。当用户按下等号键时,表示一串计算式输入完毕,程序将调用doIt( )函数一次性计算完整个算式。

        在doIt( )函数中,通过for循环遍历计算式exp,依次判断并读取计算式中的数字和运算符,分别压入数字栈和符号栈。程序代码如下:

for(auto it=exp.begin(); it!=exp.end();)  
{  
    //如果是数字  
    if (isNum(it))  
        m_num.push(std::move(readNum(it)));  
    //如果不是数字  
    else  
    {  
        //处理含字母的运算符(sin,cos,ect.)  
        if(isSig(it))  
            oo = Factory::create(readSig(it));  
        //处理单字符运算符(+,-,*,ect.)  
        else  
        {  
            string temp;  
            temp += *it++;  
            oo = Factory::create(temp);  
        }  
        //如果当前运算符比栈顶优先级低, 执行计算  
        if(oo->symbol()!="(")  
        {  
            while (oo->precedence() <= m_opr.top()->precedence())  
            {  
                if (m_opr.top()->symbol() == "#")  
                    break;  
                calculate();  
            }  
        }  
        //运算符入栈,等号除外  
        if (oo->symbol() != "=")  
            m_opr.push(std::move(oo));  
    }  
}  

        当遍历到运算符时,比较当前运算符与符号栈栈顶运算符的优先级,若当前运算符优先级更低,则调用calculate( )函数对栈顶运算符执行一步计算,然后将当前运算符入栈。

        在calculate中,若数字栈非空,则根据运算符的目数从数字栈中弹出相应数量的数字,调用该运算符的getResult( )方法进行计算,并将计算结果再次压入数字栈。该运算符的所有计算操作至此结束,将其弹出符号栈丢弃。

        特别地,当符号栈栈顶为运算符"#"时,证明符号栈中已经没有运算符,直接将当前遍历到的运算符入栈即可,不执行计算。上述操作的代码实现如下:

void MainWindow::calculate()
{  
    double a[2] = {0,0};
    for(auto i=0; i<m_opr.top()->numOprand(); ++i)
    {  
        if(!m_num.empty())
        {  
            a[i] = m_num.top();
            m_num.pop();
        }  
    }  
    m_num.push(std::move(m_opr.top()->getResult(a[1], a[0])));
    m_opr.pop();  
}  

        在整个计算式遍历结束后,所有计算也已经结束,结果置于数字栈栈顶,可以将其取出并通过UI展示给用户。

4.3 历史记录

        历史记录栏保存了用户输入的所有有效计算式与其计算结果。每次计算结束后,调用showText的setText( )方法刷新显示计算结果栏的同时,调用historyText的append( )方法,将相同的内容追加到历史记录栏,这样就用简单的方法实现了历史记录功能。

ui->showText->setText(showstr);  
ui->historyText->append(showstr);  

        按下C History按钮,触发槽函数,以清空历史记录栏historyText的内容。

void MainWindow::clickedclcH()  
{  
    ui->historyText->clear();  
} 

4.4 自动纠正

        自动纠正旨在帮助用户在输入时实时纠正一部分简单的公式错误。这里主要考虑了两种情况。

        第一种情况,符合书写习惯但违背计算机规则的错误。在书写公式时,我们约定,常数与sin、log等函数运算符相乘,之间的乘号可以省略,但这会给程序带来错误。自动纠正功能将在用户输入函数运算符时,检测前一个输入的字符是否为数字,若为数字,自动补全乘号。以运算符"sin"为例,实现代码如下:

auto lastInput = showstr.toStdString().end()-1;  
if(isNum(lastInput))  
{  
    qstr += "*sin";  
    showstr += "*sin";  
    lastQstrLength = 4;  
    lastShowstrLength = 4;  
}  
else  
{  
    qstr += "sin";  
    showstr += "sin";  
    lastQstrLength = 3;  
    lastShowstrLength = 3;  
}  

        第二种情况,不符合规则的低级错误。计算式中不应连续出现两个双目运算符,例如"+-"。自动纠正功能将在每次输入双目运算符后,将lastQstrLength、lastShowstrLength置为输入运算符在qstr和showstr中所占的字符长度。

        而在每次输入输入双目运算符之前,判断lastQstrLength、lastShowstrLength是否非零,若非零,则证明上一个输入已经是双目运算符,那么将在qstr、showstr中分别删除lastQstrLength、lastShowstrLength长度的字符,以将上一次输入的双目运算符删除,替换为新输入的运算符。

        以"+"为例,代码实现如下:

void MainWindow::clickedadd()  
{  
    if(lastQstrLength!=0)  
    {  
        qstr.chop(lastQstrLength);  
        showstr.chop(lastShowstrLength);  
    }  
    qstr += "+";  
    showstr += "+";  
    lastQstrLength = 1;  
    lastShowstrLength = 1;  
    ui->showText->setText(showstr);  
}  

4.5 安全检查

        尽管程序已经具有了基本的自动纠正功能,但仍然无法避免一些意料之外的非法输入产生,在遇到非法输入时,安全检查机制可以避免程序崩溃,并及时向用户报告错误。

        本程序中,导致程序崩溃的原因是用户连续输入了两个及以上运算符,形成了不符合数学约定的非法运算符组合,例如sincos,导致在使用Factory创建运算符时出错。

        C++提供了map::find( )方法,若在ms_operator中找不到所给定的key,则返回一个指向ms_operator末尾的迭代器。利用这个特性,若ms_operator.find(opr)返回值不等于ms_operator.end( ),则证明opr是已注册的运算符,返回正确的指针;否则证明用户输入的opr是非法字符,返回空指针nullptr。

static unique_ptr<Operator> create(string opr)  
{  
    auto it = ms_operator.find(opr);  
    if (it != ms_operator.end())   
        return it->second();  
    else  
        return nullptr;  
}  

        当程序检测到create( )函数返回了nullptr,即输入不合法,通过QMessageBox创建警告窗口上报用户,调用一次清除键clc的槽函数,将显示缓存和数据栈复位,然后使用return结束本次计算操作。至此,安全检查成功避免了程序崩溃,同时对数据和程序执行了复位操作。

if(isSig(it))  
{  
    oo = Factory::create(readSig(it));  
    //若创建运算符失败,则返回空指针  
    if(oo==nullptr)  
    {  
        QMessageBox::warning(NULL,"输入错误","输入不合法,请检查算式");
        clickedclc();  
        return 0;  
    }  
}  

4.6 错误检查

        错误是指某些违背数学约定,可能会导致结果未被定义,或出现歧义,但并不会导致程序崩溃的数学性错误。例如分母为0、对负数取平方根、arctan超出范围、数字无穷大等情况。

        C++标准库提供了十分完善的方式处理这些数学错误。若计算返回值为nan,说明出现了未定义的数学运算,或未定义的结果;若返回值为inf,说明计算结果为无穷大。通过isnan( )和isinf( )函数,即可对以上两种情况作出判断,然后通过QMessageBox创建警告窗口上报用户。

if(isnan(result))  
    QMessageBox::warning(NULL, "运算错误","存在数学错误,运算结果未定义");
else if(isinf(result))  
    QMessageBox::warning(NULL, "运算错误","存在数学错误,运算结果无穷大");

4.7 键盘输入

        对于数字、加减乘除、等于、删除、清除等使用频率非常高的按键,使用键盘直接输入可以大大提高效率。

        重写keyPressEvent事件的槽函数,在槽函数内使用switch语句将按键与对应UI界面上按钮的槽函数绑定,等效于每次按下键盘时,触发了一次对应按钮的槽函数,即可实现键盘输入。实现代码如下:

void MainWindow::keyPressEvent(QKeyEvent *ev)  
{  
    switch(ev->key())  
    {  
    case(Qt::Key_Backspace):  
        clickedback();  
        break;  
    case(Qt::Key_Delete):  
        clickedclc();  
        break;  
    case(Qt::Key_Return):  
        clickedEqu();  
        break;  
    case(Qt::Key_0):  
        clicked0();  
        break;  
    case(Qt::Key_1):  
        clicked1();  
        break;  
        ······  
    }  
}  

五 测试

5.1 基础功能测试

        点击屏幕上的按钮输入算式,按"←"键删除一个数字或运算符,按"C"键清除输入,按"="键得出计算结果,本次计算自动添加至历史记录栏,按"C history"键清除历史记录栏。经检验,计算结果正确,如图5.1所示。

d6dc82e9b3994fb29d9b4753f28aeaeb.png

 图5.1 基础功能测试

5.2 自动纠正测试

        依次点击"1"、"-"、"+"、"2"、"√"、"4"、"=",得到如图5.2所示结果。可以看到,在依次按下"-"、"+"时,输入被自动修正为"+";在依次按下"2"、"√"时,输入被自动修正为"2*√"。最终计算结果正确。

a39d26f964f84c179f292e9f64a5af5c.png

 图5.2 自动纠正测试

5.3 安全检查测试

        输入一个不合法的算式,点击"=",程序报告“输入不合法,请检查算式”,如图5.3所示。点击“OK”关闭警告窗口后,输入算式被自动清空。

32b5cb82ef5e4e0ca4137f8feb0ce2e6.png

图5.3 安全检查测试

4.4 错误检查测试

        输入一个数除以零,程序报告“存在数学错误,运算结果无穷大”,如图5.4所示。点击“OK”关闭警告窗口后,输入算式被自动清空。

3b4d4b5ae8ef4f95ade8af7378f5c42f.png

 图5.4 错误检查测试1

        对一个负数求平方根,程序报告“存在数学错误,运算结果未定义”,如图5.5所示。点击“OK”关闭警告窗口后,输入算式被自动清空。

38b1d5b7257b484aa5dbc302806091d4.png

 图5.5 错误检查测试2

5.5 键盘输入测试

        键盘输入支持数字键输入"0"~"9";退格键删除字符;delete或ESC键清空输入;enter键计算结果;"+"、"-"、"*"、"/"、"%"、"^"、"("、")"、"!"运算符的输入。测试结果如图5.6所示。

01c5f60c730749a6bb61a0d0f603c629.png

 图5.6 键盘输入测试

完整工程下载地址:https://download.csdn.net/download/yul13579/53395197

  • 9
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

河上七月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值