一、需求分析
- 生成四則運算題目
- 控制生成題目個數
- 控制生成題目中數字的範圍
- 結果為真分數
- 每道題目運算符個數為3
- 每次運作生成的題目不能重複
- 儲存生成的題目
- 在生成題目的同時,計算出所有題目的答案,并儲存
- 支援一萬道題目的生成
- 支援題目判錯,并儲存統計結果
二、功能設計
- 基本功能
- 每個數字定義為一個包含分子、分母、符号的結構體
- 随機生成數字
- 随機生成四則運算符号
- 按照所有可能出現的情況随機生成式子
- 在重新定義的數字上實作通分、約分、以及加減乘除的基本操作
- 将生成的式子轉化成字尾表達式
- 通過字尾表達式計算式子的值
- 再生成式子時進行查重
- 将生成的式子以及計算出的答案儲存到txt檔案
- 對提供的式子和答案進行打分校驗,包括查重,并将統計結果儲存到txt檔案
- 通過指令行傳遞參數
- 擴充功能
- 解決加括号的意義問題(即括号内的符号至少有一個+或-)
- 完善指令行傳遞參數的各種情況處理,并提供幫助選項
- 對結果為負數的情況進行了處理
三、設計實作
- 我的整體思路是将一個數看做一個有分子、分母和符号的結構體,然後在這個基礎上進行生成和各種運算。數字在記憶體中一律是真分數,隻有在需要将其輸出成字元串時,再将其轉換成對應的整數、帶分數、真分數。而加上一個名為符号的成員變量是為了處理運算結果出現負數的情況。
- 關于查重功能的實作,我分為兩種。一種是在生成式子時的查重,此時隻需要将式子對應的字尾表達式進行排序後存到一個map<string, int>中,其key為字尾表達式排序後的字元串,并将其值置為1,之後遇到計算結果相同的式子隻需要判斷其字尾表達式排序後的字元串對應的map中的值是否為1即可知道是否重複。另一種是在對使用者提供的式子進行查重時,判斷的方式與上面相同;差別在于由于需要獲得兩個重複式子的位置和内容,是以需要将前一個式子的位置資訊和自身提前存起來,等後一個式子判斷與其重複時,再将其資訊傳回,這樣就可以将兩個式子同時存到另一個地方,最後一起輸出。
- 項目主要包括Math.h、Calculator.h、PrintAndFile.h三個頭檔案
- Math.h主要封裝了數字結構體的定義和一些相關的計算函數
- Calculator.h主要封裝了生成數字、生成運算符、生成式子、将式子轉換成字尾表達式和計算字尾表達式的值等函數
- PrintAndFile.h主要封裝了列印指令行提示以及一些讀檔案和存檔案的相關函數,但是不知道為什麼一把它分出來就會編譯出錯,是以就隻好把它寫在main函數裡面了
四、代碼說明
- 生成數字的代碼如下:
//生成Number Number GenerateNumber(int numMax){ int isInt, RealNumerator, IntNumber; Number num; num.sign = 1; isInt = rand() % 2; if (isInt){ num.denominator = 1; num.numerator = rand() % numMax + 1; } else{ num.denominator = rand() % numMax + 2; RealNumerator = rand() % (num.denominator - 1) + 1; //約分 int common = CommonDivisor(num.denominator, RealNumerator); num.denominator = num.denominator / common; RealNumerator = RealNumerator / common; IntNumber = rand() % (numMax - 1); num.numerator = IntNumber*num.denominator + RealNumerator; } return num; }
- 将字元串轉換為Number的代碼如下,主要思想是通過定位特殊符号的位置來進行轉換:
//将字元串轉為Number Number Str2Num(string str){ Number num; int optPosi[2] = { 0, 0 }; for (int i = 0; i < str.size(); i++){ if (str[i] == 39){ optPosi[0] = i; } else if (str[i] == 47){ optPosi[1] = i; } } if (optPosi[0] == 0 && optPosi[1] == 0){ int tem = 0; for (int i = str.size(), j = 0; i > 0; i--, j++){ tem = tem + (str[j] - 48) * pow(10, i - 1); } num.numerator = tem; num.denominator = 1; num.sign = 1; return num; } else if (optPosi[0] == 0 && optPosi[1] != 0){ //有 真分數 1/2 int tem = 0; for (int i = optPosi[1], j = 0; i > 0; i--, j++){ tem = tem + (str[j] - 48) * pow(10, optPosi[1] - j - 1); } num.numerator = tem; tem = 0; for (int i = str.size(), j = optPosi[1] + 1; i > j; i--, j++){ tem = tem + (str[j] - 48) * pow(10, str.size() - j - 1); } num.denominator = tem; num.sign = 1; } else{ //有帶分數 1‘2/3 int tem1 = 0, tem2 = 0, temInt = 0; for (int i = optPosi[0], j = 0; i > 0; i--, j++){ temInt = temInt + (str[j] - 48) * pow(10, i - 1); } temInt = temInt; for (int i = optPosi[1], j = optPosi[0] + 1; i >= j; i--, j++){ tem1 = tem1 + (str[j] - 48) * pow(10, optPosi[1] - j - 1); } for (int i = str.size(), j = optPosi[1] + 1; i >= j; i--, j++){ tem2 = tem2 + (str[j] - 48) * pow(10, str.size() - j - 1); } num.denominator = tem2; num.numerator = temInt * tem2 + tem1; num.sign = 1; } return num; }
- 查重的代碼如下:
//校驗時查重 bool Check(string que, string queStr, map<string, int> &queList, map<string, Question> &QueList, int posi, Question &Que){ Question queStruct; sort(que.begin(), que.begin() + que.size()); if (queList.empty() || queList[que] == NULL){ queList[que] = 1; queStruct.no = posi; queStruct.queStr = queStr; QueList[que] = queStruct; return false; } else{ Que = QueList[que]; return true; } } //生成時查重 bool Check(string que, map<string, int> &queList){ sort(que.begin(), que.begin() + que.size()); if (queList.empty() || queList[que] != 1){ queList[que] = 1; return false; } else{ return true; } }
- 将式子轉換成字尾表達式的代碼如下,是通過棧實作的,通過比較不同符号棧内和棧外的優先級來決定是入棧還是出棧:
//生成字尾表達式 void GeneratePostfix(string que, list<string> &quePostfix, stack<char> &optStack){ map<char, int> isp, icp; isp['+'] = 3; isp['-'] = 3; isp['*'] = 5; isp['/'] = 5; isp['('] = 1; isp[')'] = 6; icp['+'] = 2; icp['-'] = 2; icp['*'] = 4; icp['/'] = 4; icp['('] = 6; icp[')'] = 1; isp['#'] = 0; icp['#'] = 0; string temPostfix; char optNow; que = que + '#'; optStack.push('#'); for (int i = 0; i < que.size(); i++){ if (que[i] > 47 && que[i] < 58){ temPostfix = temPostfix + que[i]; if (que[i + 1] == 39){ temPostfix = temPostfix + que[i + 1] + que[i + 2] + que[i + 3] + que[i + 4]; i = i + 4; } else if (que[i + 1] == 47){ temPostfix = temPostfix + que[i + 1] + que[i + 2]; i = i + 2; } if (que[i + 1] < 48){ temPostfix = temPostfix + " "; } continue; } else if (que[i] < 0){ optNow = '/'; i++; } else{ optNow = que[i]; } while (isp[optStack.top()] > icp[optNow]){ if (optStack.top() == 47){ temPostfix = temPostfix + "÷ "; } else{ temPostfix = temPostfix + optStack.top() + " "; } optStack.pop(); } if (isp[optStack.top()] == icp[optNow]){ optStack.pop(); } else if (isp[optStack.top()] < icp[optNow]){ optStack.push(optNow); } } quePostfix.push_back(temPostfix); }
- 計算表達式的代碼如下,也是通過棧來實作的:
//計算表達式 void Calculate(string que, list<string> &ans, stack<Number> &ansStack, bool isError){ Number num; string temStr; char optNow; for (int i = 0; i < que.size(); i++){ while (que[i] != ' '){ temStr = temStr + que[i]; i++; } if (que[i] = ' '){ if (temStr[0] > 47 && temStr[0] < 58){ num = Str2Num(temStr); ansStack.push(num); temStr = ""; continue; } else if (temStr[0] < 0){ Number num1, num2; //除法 num2 = ansStack.top(); ansStack.pop(); num1 = ansStack.top(); ansStack.pop(); num = Division(num1, num2); ansStack.push(num); temStr = ""; } else if (temStr[0] == 42){ //乘法 Number num1, num2; num2 = ansStack.top(); ansStack.pop(); num1 = ansStack.top(); ansStack.pop(); num = Multiplication(num1, num2); ansStack.push(num); temStr = ""; } else if (temStr[0] == 43){ //加法 Number num1, num2; num2 = ansStack.top(); ansStack.pop(); num1 = ansStack.top(); ansStack.pop(); num = Addition(num1, num2); ansStack.push(num); temStr = ""; } else if (temStr[0] == 45){ //減法 Number num1, num2; num2 = ansStack.top(); ansStack.pop(); num1 = ansStack.top(); ansStack.pop(); num = Subtraction(num1, num2); ansStack.push(num); temStr = ""; } } } if (ansStack.top().denominator == 0){ isError = true; } else{ string a = Num2Str(ansStack.top()); ans.push_back(a); ansStack.pop(); } }
五、測試運作
- 指令行參數提示
-
個人作業1——四則運算題目生成程式(基于控制台) - 參數出錯提示
-
個人作業1——四則運算題目生成程式(基于控制台) - 進行出題
-
個人作業1——四則運算題目生成程式(基于控制台) -
個人作業1——四則運算題目生成程式(基于控制台) - 打分校驗結果
-
個人作業1——四則運算題目生成程式(基于控制台) -
個人作業1——四則運算題目生成程式(基于控制台)
六、PSP展示
PSP2.1 | Personal Software Process Stages | Time Senior Student | Time |
Planning | 計劃 | 10 | 7 |
· Estimate | 估計這個任務需要多少時間 | ||
Development | 開發 | 325 | 903 |
· Analysis | 需求分析 (包括學習新技術) | 20 | |
· Design Spec | 生成設計文檔 | 5 | |
· Design Review | 設計複審 | ||
· Coding Standard | 代碼規範 | 3 | |
· Design | 具體設計 | 60 | 30 |
· Coding | 具體編碼 | 300 | 655 |
· Code Review | 代碼複審 | ||
· Test | 測試(自我測試,修改代碼,送出修改) | 190 | |
Reporting | 報告 | ||
· | 測試報告 | 40 | Nah |
七、小結
這次作業第一次開始接觸到PSP來管理項目開發,讓我在開發過程中效率提高了不少,雖然預估和實際差的還是挺大,但是相對自己之前而言,效率已經提高了不少,之後我會盡可能把這種方式運用到其實事情上來提高自己效率。
此外這次作業的内容看起來并不是很難,但是真正做起來的時候還是很花時間的,尤其是在一些細節的地方,其實有一大部分時間都是在調試、debug,還好最終問題都得到了解決,最後的效果也算是比較完整,個人感覺比較有缺陷的還是代碼的品質上,感覺自己寫的代碼品質不高,一些東西沒有設計好,希望以後可以不斷提高自己的代碼品質。
最後附上代碼位址:https://coding.net/u/DaleAG/p/Calculator/git/tree/master/