Qt、C++实现简易计算器:
以下内容是我实现这个简易计算器整个过程,其中包括我对如何实现这个功能的思考、中途遇到的问题、走过的弯路
整个实现从易到难,计算器功能从简单到复杂,最开始设计的整个实现步骤可以分为以下几步:
* 1.仅支持加减法的整数运算
*
* 2.支持加减法的浮点数运算
*
* 3.支持加减乘除四个操作的多项运算,显示器分为两行,第一行显示运算的
* 等式,第二行显示结果
*
* 4.重新设计整个项目,将各个实现功能在各个文件中实现,将具体操作实现为另一个类,数学函数为另
* 一个类。提高整个代码的重用性
*
* 5.支持更多的数学函数,并支持数学功能扩展,可以让用户选择数学运算模块,对于数学函数,在用户完成
* 每个输入时就将函数运算值计算出来。
* 以sin余弦函数为例:
* 如果是直接对一个数求余弦sin4,可直接将余弦的结果计算出,存储到运算式中
* 如果是对一个表达式求余弦,则可以对表达式求值再求余弦(递归调用表达式求值函数)
*
* 6.支持更多使用设计,例如在输入运算式时,允许退位
*
* 每当用户按下“=”号时显示目前运算式的结果。当用户在“=”之后在按下其他运算符号,则新运算式则会
* 将上次的运算结果当做此次运算式的第一项,这一项的名称叫做lastR
*
* 对于整个运算,应该采用一个数据结构记录整个运算过程,并定义如何去解析整个结构以保证运算顺序
* 解析的顺序需要符合运算法则优先级
首先使用绘制计算器界面(可以使用代码,也可以直接画):
对计算器功能的具体实现:
思考过程:
1.简单实现加减运算,对于加减运算符并没有优先级差异,就不需要考虑过多的运算顺序,只需要从左往右
计算即可,且计算并不是在用户按下”=“时完成的,其实在每一步都有运算过程并且将每次的运算结果都
保存了下来,这样每次运算其实只是两个数的相加减。且在第一步实现过程中并没有考虑小数,只是将运算
锁定在整数之:
在下面代码中用到的一些变量说明:
int curNumber; // 储存每次用户输入的数字,当按下表示运算符的键位时,刷新为0 int lastResult; // 储存上一次计算的结果 int cntPress; // 储存用户此时正想要输入的值的已输入数字数目,当按下表示运算符的键位时,刷新为0 int cntPressPlusOrCut; // 第一次实现思考,用于记录按下加减号的次数,通过它来决定lastResult的计算方式 char lastChar; // 记录上一次输入的运算符 QString strProcess; // 保存运算式,用于显示
实现各个功能需要的函数(这些函数实现各个按键的功能,在使用时可以在接收各个按键发出信号的SLOT中调用这些函数):
MainWindow::MainWindow(QWidget *parent) : // 构造函数 QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // 将下列变量初始化 curNumber = 0; lastResult = 0; cntPress = 0; cntPressPlusOrCut = 0; lastChar = '/0'; } // 函数功能:按下各个数字键后的结果 // 改变运算式、此时正在输入的数字的个数、此时正在输入的数字 // 采用累加的方法记录用户的输入值 // 可以使用connect将此函数与数字键按键信号关联,也可以直接在SLOT函数中调用 void MainWindow::pressNum(int n) // n表示按下的数字键代表的数字 { // 改变此时正在输入的数字的个数cntPress与此时正在输入的数字curNumber的值 cntPress++; curNumber = curNumber * 10 + n; // 改变运算式字符串的值 strProcess += ('0' + n); // 改变出现在显示屏上的运算式 ui->lineEdit->setText(strProcess); } // 清空此次运算的全部内容 void MainWindow::on_clear_clicked() { curNumber = 0; lastResult = 0; cntPress = 0; strProcess = ""; ui->lineEdit->clear(); ui->lineEdit_2->clear(); } // 按下加减乘除四个键后的反应 void MainWindow::pressPlus() { // 显示加号 strProcess += '+'; ui->lineEdit->setText(strProcess); // 初始化lastResult,curNumber,cntPress if (cntPressPlusOrCut == 0){ // 判断,如果此次是用户第一次输入运算符,则将curNumber赋给lastResult lastResult = curNumber; curNumber = 0; // 重置curNumber为0,用于记录下一次的输入 cntPress = 0; } else{ if (lastChar == '+'){ lastResult += curNumber; curNumber = 0; cntPress = 0; } else{ lastResult -= curNumber; curNumber = 0; cntPress = 0; } } cntPressPlusOrCut++; lastChar = '+'; ui->lineEdit_2->setText(QString::number(lastResult)); // 将lastResult转化为QString,并设置为lineEdit_2的Text } void MainWindow::pressCut() { // 显示减号 strProcess += '-'; ui->lineEdit->setText(strProcess); // 初始化lastResult,curNumber,cntPress if (cntPressPlusOrCut == 0){ // 判断,如果此次是用户第一次输入运算符,则将curNumber赋给lastResult lastResult = curNumber; curNumber = 0; // 重置curNumber为0,用于记录下一次的输入 cntPress = 0; } else{ if (lastChar == '+'){ lastResult += curNumber; curNumber = 0; cntPress = 0; } else{ lastResult -= curNumber; curNumber = 0; cntPress = 0; } } cntPressPlusOrCut++; lastChar = '-'; ui->lineEdit_2->setText(QString::number(lastResult)); // 将lastResult转化为QString,并设置为lineEdit_2的Text } // 按下=键 void MainWindow::pressEqu() { // 显示等于号 strProcess += '='; ui->lineEdit->setText(strProcess); // 初始化lastResult,curNumber,cntPress if (cntPressPlusOrCut == 0){ // 判断,如果此次是用户第一次输入运算符,则将curNumber赋给lastResult lastResult = curNumber; } else{ if (lastChar == '+'){ lastResult += curNumber; } else{ lastResult -= curNumber; } } ui->lineEdit_2->setText(QString::number(lastResult)); // 将lastResult转化为QString,并设置为lineEdit_2的Text // 初始化 curNumber = 0; cntPress = 0; cntPressPlusOrCut = 0; lastChar = '/0'; }
第二步:
修改记录用户输入的方法,用以支持浮点数运算
方法:
将用户的输入记录进一个QString中,再使用QString的toDouble()函数将其转化为double。
思路:
能否考虑使用一个信号机制,在用户按下与输入数值无关的按键时,发送一个信号,告诉程序将用户已经完成了一个
数值的输入,此时要求程序将刚才用户输入的数值记录字符串转化为浮点数。
思考:
回顾第一步,那时采用的先记录整数值再将整数值转化为QString,其实也可以和这一步一样,直接记录输入
为QString,再将其转化为int,这样就省去了编写记录整数值时采用的累加法的繁琐。但由于当时只是想暂时
先实现整数运算,并且恰好直接想到了累加记录整数值的方法,于是就采用了那样的实现方法。ps思考:在思
考如何实现一个功能时,可能在会灵光一闪,想到某个实现方法,而此时大脑一定异常兴奋,迫不及待的想去
马上将想法变为现实,但此时如果有可能,更应该仔细思索一下,那个突如其来的想法是否可以派生(就如第一
个和第二个实现方法,只是将记录与转变的顺序颠倒一下)出更好的方法。
如果任使用第一步的思路实现记录浮点数(#~#,自找麻烦),可以考虑记录的思路,可以将浮点数的整数部分和小
数部分分开记录,在用户按下表示小数点的键位时,发出信号,告诉程序此前记录的部分为整数部分,而接下来的
用户输入为小数部分。
思路:
在构造函数和许多的按下某个键后的反应中都需要重新将某些变量重置,是否可以考虑使用一个重置函数(#~#顿时
有了我知道使用某种编程思想的感觉,但却叫不出它的名字,这个思想是不是就是使用函数的优点之一)。
头文件内容:
class myNorO{ public: // 此处是不是需要使用union,或者按照《Effective C++》中建议的将类的数据成员作为私有成员,为了简单直接这样用 union{ double number; // 数值 char ch; // 操作符 }part; bool flag; // flag为真时表示数值,为假时表示操作符 }; class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); public: // 按下数字键的结果函数 void pressNum(int n); // 按下加减乘除四个键 void pressPlus(); void pressCut(); void pressMult(); void pressDivi(); // 按下=键 void pressEqu(); // 按下小数点键 void pressPoint(); // 按下clear键 void pressClear(); // 按下左右括号键 void pressLeftBr(); void pressRightBr(); // 在生活中,我们一个运算式时总是以一个数值一个运算符的形式(中序表达式),所以在输入每个运算符前,用户都已经输入好了 // 一个数值(错误输入除外),在此程序中,每次用户输入运算符后都会将上一个已经输入好的数值存储如表达式中,于是采用一个 // 转换并重置的函数 void init(); // 将运算式转化为逆波兰式 void ProcessToRPN(); // 计算逆波兰式 double cntResult(); // 将逆波兰式转化为二叉树 void RPNtoTree(); // 比较两个运算符之间的优先级,我们定义‘#’优先级最低,ch1位rowL内的,ch2时temp内的 bool ComparePriority(char ch1, char ch2); void testRowL(); void testTrL(); private slots: void on_pushButton_clicked(); void on_pushButton_2_clicked(); void on_pushButton_3_clicked(); void on_pushButton_4_clicked(); void on_pushButton_5_clicked(); void on_pushButton_7_clicked(); void on_pushButton_8_clicked(); void on_pushButton_9_clicked(); void on_pushButton_10_clicked(); void on_clear_clicked(); void on_pushButton_6_clicked(); void on_plus_clicked(); void on_cut_clicked(); void on_mult_clicked(); void on_divi_clicked(); void on_equal_clicked(); void on_point_clicked(); void on_LeftBr_clicked(); void on_RightBr_clicked(); private: Ui::MainWindow *ui; //double curNumber; // 储存此时用户刚刚输入完成的数值 QString curNumberString; // 记录用户每一次的数据输入,当按下表示运算符的键位时,将curNumberString转化为curNumber,并重置它 double lastResult; // 储存上一次运算式的结果,并非像前两步一样 // int cntPress; // 储存用户此时正想要输入的值的已输入数字数目,当按下表示运算符的键位时,刷新为0 // 第二步实现,不需要cntPress去记录用户输入的数值个数 //int cntPressPlusOrCut; // 第一次实现思考,用于记录按下加减号的次数,通过它来决定lastResult的计算方式 //char lastChar; // 记录上一次输入的运算符 QString strProcess; // 保存运算式,用于显示 QList< QSharedPointer<myNorO> > rowL; // 保存原始的将运算符和数值分开后存储的运算式 QList< QSharedPointer<myNorO> > trL; // 保存已转化为逆波兰式的将运算符和数值分开后存储的运算式 // 还用QSharedPointer //QStringList myList; // 运算表达式 //QStringList listRevPN; // 运算解析式,逆波兰表达式 };
各个函数实现:
MainWindow::MainWindow(QWidget *parent) : // 构造函数 QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // 将初始值初始化为0 //curNumber = 0; lastResult = 0; //cntPressPlusOrCut = 0; //lastChar = '/0'; curNumberString = ""; } MainWindow::~MainWindow() { delete ui; } // 比较两个运算符之间的优先级,我们定义‘#’优先级最低,ch1位rowL内的,ch2时temp内的 bool MainWindow::ComparePriority(char ch1, char ch2) { if (ch2 == '#'){ // ‘#’优先级最低,此时无需再比较 return true; } if ((ch1 == '*' || ch1 == '/') && (ch2 == '+' || ch2 == '-')){ // 当rowL运算符比temp运算符优先级大 return true; } return false; } void MainWindow::testRowL() // 用于测试记录输入的代码是否正确(在实现过程中使用,当测试无误后,这个函数就没用了) { QString str; for (int cnt = 0; cnt < rowL.size(); cnt++){ if (rowL[cnt]->flag){ str += QString::number(rowL[cnt]->part.number); } else{ str += rowL[cnt]->part.ch; } } ui->lineEdit_2->setText(str); } void MainWindow::testTrL() // 用于测试将中缀表达式转化为逆波兰式是否正确(在实现过程中使用,当测试无误后,这个函数就没用了) { QString str; for (int cnt = 0; cnt < trL.size(); cnt++){ if (trL[cnt]->flag){ str += QString::number(trL[cnt]->part.number); } else{ str += trL[cnt]->part.ch; } } ui->lineEdit_2->setText(str); } // 将运算式转化为逆波兰式 void MainWindow::ProcessToRPN() { // 使用QList,下文所说的栈顶为QList的最后一个元素 QList< QSharedPointer<myNorO> > temp; // 临时存储运算符 // 先向temp中压入‘#’ QSharedPointer<myNorO> tempItem(new myNorO); tempItem->flag = false; tempItem->part.ch = '#'; temp.append(tempItem); for (int cnt = 0; cnt < rowL.size(); cnt++){ // 遍历整个rowL,将其转化为逆波兰式 if (rowL[cnt]->flag){ // 如果是数值,直接放入trL trL.append(rowL[cnt]); } else{ // 若取出的字符是“(”,则直接送入temp栈顶 if (rowL[cnt]->part.ch == '('){ temp.append(rowL[cnt]); continue; } // 若取出的字符是运算符,则将该运算符与temp栈栈顶元素比较,如果该运算符优先级大于temp栈栈顶运算符优先 // 级,则将该运算符进temp栈,否则,将temp栈的栈顶运算符弹出,送入trL栈中,直至temp栈栈顶运算符低于(不包 // 括等于)该运算符优先级,最后将该运算符送入temp栈 // 加减号 if (rowL[cnt]->part.ch == '+' || rowL[cnt]->part.ch == '-'){ if (temp.last()->part.ch == '#' || temp.last()->part.ch == '('){ // 加号减只有在碰到‘(’和‘#’时,temp进行入栈操作 temp.append(rowL[cnt]); } else{ while (temp.last()->part.ch != '#' && temp.last()->part.ch != '('){ trL.append(temp.last()); temp.removeLast(); } temp.append(rowL[cnt]); } continue; } // 乘除号 if (rowL[cnt]->part.ch == '*' || rowL[cnt]->part.ch == '/'){ if (temp.last()->part.ch == '+' || temp.last()->part.ch == '-' || temp.last()->part.ch == '#' || temp.last()->part.ch == '('){ temp.append(rowL[cnt]); } else{ while (temp.last()->part.ch != '+' || temp.last()->part.ch != '-' || temp.last()->part.ch != '#' || temp.last()->part.ch != '('){ trL.append(temp.last()); temp.removeLast(); } temp.append(rowL[cnt]); } continue; } // 若取出的字符是“)”,则将距离temp栈栈顶最近的“(”之间的运算符,逐个出栈,依次送入trL栈,此时抛弃“(” if (rowL[cnt]->part.ch == ')'){ while (temp.last()->part.ch != '('){ trL.append(temp.last()); temp.removeLast(); } temp.removeLast(); } } } // 处理剩下的temp中的运算符 while (temp.last()->part.ch != '#'){ trL.append(temp.last()); temp.removeLast(); } } // 计算逆波兰式 double MainWindow::cntResult() { // 将最终进行的运算符记为根节点,将两边的表达式分别记为左右子树,依次进行直到所 // 有的运算符与数字或字母标在一棵二叉树上。然后对二叉树进行后序遍历 // 具体代码还没写完,这里就不给出了 } // 按下数字键的结果函数 // 改变运算式、此时正在输入的数字的个数、此时正在输入的数字 void MainWindow::pressNum(int n) { // 将输入的数字显示出来 strProcess += ('0' + n); ui->lineEdit->setText(strProcess); // 写入curNumberString的值 curNumberString += ('0' + n); } // 按下小数点键 void MainWindow::pressPoint() { // 将小数点显示出来 strProcess += '.'; ui->lineEdit->setText(strProcess); curNumberString += '.'; // 将小数点加入curNumberString } // 转换并重置的函数 void MainWindow::init() { // 如果curNumberString不为“”,则将其转化为double,并写入rowL if (curNumberString != ""){ QSharedPointer<myNorO> temp(new myNorO); temp->flag = true; temp->part.number = curNumberString.toDouble(); rowL.append(temp); curNumberString = ""; // 重置curNumberString } //QString str = QString::number(rowL.last()->part.number); //ui->lineEdit_2->setText(str); } // 按下加减乘除四个键后的反应 void MainWindow::pressPlus() { init(); // 显示加号 strProcess += '+'; ui->lineEdit->setText(strProcess); QSharedPointer<myNorO> temp(new myNorO); temp->flag = false; temp->part.ch = '+'; rowL.append(temp); } void MainWindow::pressCut() { init(); // 显示减号 strProcess += '-'; ui->lineEdit->setText(strProcess); QSharedPointer<myNorO> temp(new myNorO); temp->flag = false; temp->part.ch = '-'; rowL.append(temp); } void MainWindow::pressMult() { init(); // 显示乘号 strProcess += '*'; ui->lineEdit->setText(strProcess); QSharedPointer<myNorO> temp(new myNorO); temp->flag = false; temp->part.ch = '*'; rowL.append(temp); } void MainWindow::pressDivi() { init(); // 显示除号 strProcess += '*'; ui->lineEdit->setText(strProcess); QSharedPointer<myNorO> temp(new myNorO); temp->flag = false; temp->part.ch = '/'; rowL.append(temp); } // 按下=键 void MainWindow::pressEqu() { init(); // 显示等于号 strProcess += '='; ui->lineEdit->setText(strProcess); // 计算,并显示结果 //cntResult(); } // 按下clear键 void MainWindow::pressClear() { curNumberString = ""; lastResult = 0; strProcess = ""; rowL.clear(); trL.clear(); } // 按下左右括号键 void MainWindow::pressLeftBr() { init(); // 显示左括号号 strProcess += '('; ui->lineEdit->setText(strProcess); QSharedPointer<myNorO> temp(new myNorO); temp->flag = false; temp->part.ch = '('; rowL.append(temp); } void MainWindow::pressRightBr() { init(); // 显示右括号 strProcess += ')'; ui->lineEdit->setText(strProcess); QSharedPointer<myNorO> temp(new myNorO); temp->flag = false; temp->part.ch = ')'; rowL.append(temp); } // 以下10个信号、槽函数,分别表示按下0-9 void MainWindow::on_pushButton_10_clicked() { pressNum(0); } void MainWindow::on_pushButton_clicked() { pressNum(1); } void MainWindow::on_pushButton_2_clicked() { pressNum(2); } void MainWindow::on_pushButton_3_clicked() { pressNum(3); } void MainWindow::on_pushButton_4_clicked() { pressNum(4); } void MainWindow::on_pushButton_5_clicked() { pressNum(5); } void MainWindow::on_pushButton_6_clicked() { pressNum(6); } void MainWindow::on_pushButton_7_clicked() { pressNum(7); } void MainWindow::on_pushButton_8_clicked() { pressNum(8); } void MainWindow::on_pushButton_9_clicked() { pressNum(9); } void MainWindow::on_plus_clicked() { pressPlus(); } void MainWindow::on_cut_clicked() { pressCut(); } void MainWindow::on_mult_clicked() { pressMult(); } void MainWindow::on_divi_clicked() { pressDivi(); } void MainWindow::on_equal_clicked() { pressEqu(); // 测试过程 //ProcessToRPN(); //testRowL(); //testTrL(); //ui->lineEdit_2->setText(QString::number(trL.size())); } void MainWindow::on_point_clicked() { pressPoint(); } void MainWindow::on_LeftBr_clicked() { pressLeftBr(); } void MainWindow::on_RightBr_clicked() { pressRightBr(); } // 清空此次运算的全部内容 void MainWindow::on_clear_clicked() { pressClear(); }
这里并没有将最初的设计完全实现,且代码中将中缀表达式转化为逆波兰式、计算逆波兰式都是参考百度百科关于逆波兰式的介绍