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(); }
這里並沒有將最初的設計完全實現,且代碼中將中綴表達式轉化為逆波蘭式、計算逆波蘭式都是參考百度百科關於逆波蘭式的介紹
