赞
踩
仿照Windows系统的计算器软件,如图1.1所示,为教材第12.4节通用计算器设计界面,开发一款实用的计算器软件,并按照实验指导书附录中课程设计报告模板要求撰写结课报告。
图1.1 Windows系统的计算器界面
(1)设计一套算法,实现常数数学表达式的混合计算,遵循运算优先级顺序,支持小括号。
(2)提供一套UI界面,使用户能够方便地输入表达式,直观地获得计算结果。
(1)数学运算功能。包括但不限于四则运算、三角函数、幂、对数的数学运算;支持整形和浮点型运算;支持输入小括号改变运算顺序。
(2)输入输出功能。支持屏幕按钮和键盘两种输入方式,支持删除、清除功能;提供计算历史记录,和历史记录清除功能。
(3)结果调用功能。在进行sin、cos等函数运算时,允许用户直接调用上一次的运算结果,进行函数运算。
(1)自动纠正功能。用户在输入算式时若出现低级错误,如连续输入两个运算符;或出现符合书写习惯但不符合程序计算规则的错误,如数字和函数运算符之间省略乘号,程序应当自动进行纠正,以避免发生潜在的输入错误。
(2)错误检查功能。对于分母为零、对负数求算数平方根、arcsin范围超出[-1, 1]等一些不符合约定的数学错误,程序应当检测出错误类型,并向用户发出错误警告。
(3)安全检查功能。用户的输入是多种多样的、难以枚举的,当意料之外的非法输入时发生时,应当避免程序自身崩溃,同时向用户报告错误。
操作系统Windows 10;Qt Creator版本4.11.0;Qt版本5.14.1
Qt基于信号与槽机制,因此程序总体上并非顺序执行的流程。在主函数中,完成应用程序QApplication和主要窗口MainWindow类的对象创建后,程序就进入事件循环,等待用户触发事件。程序框图如图3.1所示。
图3.1 主函数程序框图
用户在操作应用程序时,实际是与程序外层的mainwindow类进行信息交互,如图3.2所示。mainwindow是由QMainWindow、QPushButton、QTextBrowser、QLabel等一系列Qt提供UI设计类构成。借助Qt提供的类,程序员可以通过代码或Qt Designer很方便地创建和布局窗口,并通过信号和槽机制处理事件。
图3.2 程序交互设计示意图
位于外层的mainwindow对用户输入的信息进行处理后,将其转化为底层operator、stack、factory类能够理解的形式,然后进行计算。因此,可以认为mainwindow起到了“桥梁”的作用,使得用户能够借助mainwindow类,以一种友好、可视化的方式与底层负责运算的类进行交流,用户只能看到图形化的界面,而内核部分对用户是屏蔽的。实际上,用户也无需知道底层的算法,这样面向对象的设计思想对于用户是友好的,对于开发者也是友好的。
除mainwindow类外,本应用程序底层中主要有operator、stack、factory三个类,其UML图如图3.3所示。
图3.3 程序UML图
为了提高计算程序的可读性、可维护性和可拓展性,本程序参考教材12.4节通用计算器,设计了Operator类对程序的逻辑关系进行了优化。Operator类存储了每一种运算的标签、目数和优先级,同时提供了计算函数getResult(),该函数是一个纯虚函数,需要在具体运算符的子类中实现。
在程序中,所有运算符类都继承自Operator,重写getResult()函数以便实现不同的运算功能。当开发者增添、删改某种运算时,只需要对相应的运算符类进行修改,而不需要像实验8.2简易计算器一样修改if-else语句。对象工厂Factory类则是为了更加方便地创建运算符类。
Qt允许程序员不通过任何设计工具,以纯粹的C++代码来设计界面,同时也提供了一个可视化的界面设计工具:Qt Designer。通过Qt Designer可以很方便地创建部件,修改部件属性,调整窗口布局。
窗体的布局是十分重要的,只有灵活运用水平布局、垂直布局、栅格布局等方式将所有部件组合起来,并设置合理的尺寸策略(sizePolicy),才能在用户任意拖动窗口大小时,窗口内的部件随窗口尺寸一起缩放,且布局比例不变,保持美观。本程序的界面布局如图4.1所示。
图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所示。
图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展示给用户。
历史记录栏保存了用户输入的所有有效计算式与其计算结果。每次计算结束后,调用showText的setText( )方法刷新显示计算结果栏的同时,调用historyText的append( )方法,将相同的内容追加到历史记录栏,这样就用简单的方法实现了历史记录功能。
- ui->showText->setText(showstr);
- ui->historyText->append(showstr);
按下C History按钮,触发槽函数,以清空历史记录栏historyText的内容。
- void MainWindow::clickedclcH()
- {
- ui->historyText->clear();
- }
自动纠正旨在帮助用户在输入时实时纠正一部分简单的公式错误。这里主要考虑了两种情况。
第一种情况,符合书写习惯但违背计算机规则的错误。在书写公式时,我们约定,常数与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);
- }
尽管程序已经具有了基本的自动纠正功能,但仍然无法避免一些意料之外的非法输入产生,在遇到非法输入时,安全检查机制可以避免程序崩溃,并及时向用户报告错误。
本程序中,导致程序崩溃的原因是用户连续输入了两个及以上运算符,形成了不符合数学约定的非法运算符组合,例如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;
- }
- }
错误是指某些违背数学约定,可能会导致结果未被定义,或出现歧义,但并不会导致程序崩溃的数学性错误。例如分母为0、对负数取平方根、arctan超出范围、数字无穷大等情况。
C++标准库提供了十分完善的方式处理这些数学错误。若计算返回值为nan,说明出现了未定义的数学运算,或未定义的结果;若返回值为inf,说明计算结果为无穷大。通过isnan( )和isinf( )函数,即可对以上两种情况作出判断,然后通过QMessageBox创建警告窗口上报用户。
- if(isnan(result))
- QMessageBox::warning(NULL, "运算错误","存在数学错误,运算结果未定义");
- else if(isinf(result))
- QMessageBox::warning(NULL, "运算错误","存在数学错误,运算结果无穷大");
对于数字、加减乘除、等于、删除、清除等使用频率非常高的按键,使用键盘直接输入可以大大提高效率。
重写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;
- ······
- }
- }

点击屏幕上的按钮输入算式,按"←"键删除一个数字或运算符,按"C"键清除输入,按"="键得出计算结果,本次计算自动添加至历史记录栏,按"C history"键清除历史记录栏。经检验,计算结果正确,如图5.1所示。
图5.1 基础功能测试
依次点击"1"、"-"、"+"、"2"、"√"、"4"、"=",得到如图5.2所示结果。可以看到,在依次按下"-"、"+"时,输入被自动修正为"+";在依次按下"2"、"√"时,输入被自动修正为"2*√"。最终计算结果正确。
图5.2 自动纠正测试
输入一个不合法的算式,点击"=",程序报告“输入不合法,请检查算式”,如图5.3所示。点击“OK”关闭警告窗口后,输入算式被自动清空。
图5.3 安全检查测试
输入一个数除以零,程序报告“存在数学错误,运算结果无穷大”,如图5.4所示。点击“OK”关闭警告窗口后,输入算式被自动清空。
图5.4 错误检查测试1
对一个负数求平方根,程序报告“存在数学错误,运算结果未定义”,如图5.5所示。点击“OK”关闭警告窗口后,输入算式被自动清空。
图5.5 错误检查测试2
键盘输入支持数字键输入"0"~"9";退格键删除字符;delete或ESC键清空输入;enter键计算结果;"+"、"-"、"*"、"/"、"%"、"^"、"("、")"、"!"运算符的输入。测试结果如图5.6所示。
图5.6 键盘输入测试
完整工程下载地址:https://download.csdn.net/download/yul13579/53395197
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。