高級軟件工程2017第2次作業
1.github項目地址:
軟件工程第二次作業鏈接
2.解題思路及設計過程
剛開始拿到題目後,看到要求上寫著:
完成一個能自動生成小學四則運算題目的命令行 “軟件”
首先想到的是用rand()函數生成操作數和運算符,然後先從簡單地只生成兩個操作數的四則運算式,並且只考慮整數之間的運算。實現前面一步,再看題目運算符要求三個以上,這就需要用數組來存放操作數和運算符,同樣很容易能隨機生成多個操作數的四則運算式。接著遇到了最困難的一步:
要求能處理用戶的輸入,並判斷對錯,打分統計正確率。
要能判斷用戶輸入的對錯就必須計算四則運算式的結果,一開始想到既然操作數和運算符都存放在數組裏了,那麽直接遍歷數組,根據操作符將操作數取出計算。結果發現由於需要考慮運算的優先級,用數組操作過程極為復雜,很難計算出正確結果。於是從網上查找資料,發現用後綴表達式計算四則運算算法思路簡單清晰。共需要準備三個容器,符號棧operatorStack,數字棧numStack和盛放後綴表達式的隊列expQueue。按照下列的規則進行:
- 用for循環,每次隨機生成一個操作數或運算符並打印
- 若產生操作數num,就expQueue.push(num);
- 若產生運算符,分以下兩步處理:
- 1.如果當前運算符優先級不高於符號棧棧頂運算符,則彈出操作符op,並做expQueue.push(op)操作;
- 2.將當前運算符op放入棧中,operatorStack.push(op);
- 當上述操作完成,表示打印出運算式並生成一個後綴表達式,接下來計算後綴表達式
- 遇到數字num,numStack.push(num);
- 若遇到運算符,從numStack棧頂取出兩個操作數並計算,結果放入棧頂
- 不斷進行上面兩步操作,最後只剩一個數字,就是最終結果
由於對棧和隊列的操作不夠熟練,並且以上只用到了基本操作,所以決定用數組來代替棧和隊列,通過下標變換可以實現同樣的效果。不過又遇到一個問題,那就是盛放後綴表達式的expQueue數組需要同時存放操作數和運算符,兩者類型不同無法用數組存放。經過嘗試,想出一個方法那就是:把操作數單獨存放到數組裏面,而在expQueue中存放該操作數對應的下標。完成以上功能後,還需解決題目的最後一個需求:
參與運算的操作數(operands)除了100以內的整數以外,還要支持真分數的四則運算,例如:1/6 + 1/8 = 7/24。
看到這個要求,一時之間又遇到了難題:一個真分數需要考慮分子和分母兩個操作數,如果支持真分數的四則運算,那麽不如把所有操作都想轉換成分數,最後對結果進行化簡,那麽問題就轉化為如何處理兩個分數的運算?經過反復思考,想到一個令自己比較滿意的解決方法,那就是使用結構體,定義兩個成員變量分別代表分子、分母。完成以上的設計分析再開始編程,發現代碼的編寫十分順利!
3.代碼說明:
基本功能
1.返回操作符的優先級
int mPriority(char op){ if(op==‘mPriority+‘) return 1; else if(op==‘-‘) return 1; else if(op==‘x‘) return 2; else if(op==‘%‘) return 2; }
2.求最大公約數
int gcd(int a,int b) { if(b==0) return a; else return gcd(b,a%b); }gcd
3.根據操作符計算兩個操作數
Digit Calculate(Digit num1,Digit num2,char op){ Digit res; int f; //根據運算符,作兩個分數之間的四則運算 switch(op){ case ‘+‘:{ res.x=num1.x*num2.y+num1.y*num2.x; res.y=num1.y*num2.y; break; } case ‘-‘:{ res.x=num1.x*num2.y-num1.y*num2.x; res.y=num1.y*num2.y; break; } case ‘x‘:{ res.x=num1.x*num2.x; res.y=num1.y*num2.y; break; } case ‘%‘:{ res.x=num1.x*num2.y; res.y=num1.y*num2.x; break; } default:{ res.x=0; res.y=1; } } if(res.x<res.y) f=gcd(res.y,res.x); else f=gcd(res.x,res.y); res.x=res.x/f;res.y=res.y/f; //如果分母為負數,取反 if(res.y<0){ res.x=-res.x; res.y=-res.y; } return res; }Calculate
4.隨機生成1個操作數並打印
Digit getNum() { int i,j,f; Digit res; if(rand()%3==1)//1/3的概率生成一個真分數 { i=rand()%11+1; j=rand()%11+1; if(i>j) {int temp=i;i=j;j=temp;} f=gcd(j,i); i=i/f;j=j/f; printf("%d/%d",i,j); }else{ i=rand()%101+1; j=1; printf("%d",i); } res.x=i; res.y=j; return res; }getNum
5.隨機生成一個操作符並打印
char getOperator(){ char op=ss[rand()%4]; //打印操作符 if(op==‘%‘) printf("÷"); else if(op==‘x‘) printf("×"); else printf("%c",op); return op; }getOperator
主函數
int main() { srand((unsigned)time(NULL)); //每次運行進行初始化 int times; //控制生成題目的個數 float score=100; //題目得分 scanf("-n %d",×); printf("本次共%d題,滿分100分\n",times); //第一個for循環,每次生成一個題目 for(int j=0;j<times;j++){ printf("%d: ",j+1); int t=0,q=0,p=0,top=0; Digit opNum[10],numStack[10]; char op,operatorStack[10],expQueue[20]; opNum[q++]=getNum(); expQueue[p++]=q-1+‘0‘; //得到後綴表達式 for(t=0;t<3;t++) { op=getOperator(); if(t==0){ operatorStack[top++]=op; opNum[q++]=getNum(); expQueue[p++]=q-1+‘0‘; continue; } while(mPriority(op)<=mPriority(operatorStack[top-1])&&top>0){ top--; expQueue[p++]=operatorStack[top]; } opNum[q++]=getNum(); expQueue[p++]=q-1+‘0‘; operatorStack[top++]=op; } while(top>0){ top--; expQueue[p++]=operatorStack[top]; } //根據後綴表達式計算結果 top=0; for(int i=0;i<p;i++){ if(expQueue[i]>=‘0‘&&expQueue[i]<=‘9‘) { int ch=expQueue[i]-‘0‘; numStack[top].x=opNum[ch].x; numStack[top].y=opNum[ch].y; top++; }else{ top--; numStack[top-1]=Calculate(numStack[top-1],numStack[top],expQueue[i]); } } printf("="); //用戶輸入計算結果 char userAns[100],rightAns[100]; // printf("%d/%d\n",numStack[top-1].x,numStack[top-1].y); cin>>userAns; int c=getchar(); //得到的正確結果 if(numStack[top-1].y!=1){ sprintf(rightAns,"%d%s",numStack[top-1].x,"/"); sprintf(rightAns,"%s%d",rightAns,numStack[top-1].y); } else sprintf(rightAns,"%d",numStack[top-1].x); //printf("%s\n",rightAns); //判斷對錯 if(strcmp(userAns,rightAns)==0) printf("正確!\n"); else { printf("不正確!正確答案= %s\n",rightAns); //扣分 score-=100*1.0/times; } } printf("本次得分%.2f",score); return 0; }Main
附加功能(9/25改進)
上面代碼只實現了基本功能,對於附加功能運算符個數隨機生成,只需改變t的值即可。至於帶括號的多元復合運算,整個代碼的思路沒有改變,特別對後綴表達式的計算部分不用作任何變動。在得到後綴表達式的過程,分別在運算符之後隨機生成左括號,在合適的操作數之後生成相應的右括號,另外,需要在符號棧的進出棧作一些改變。對代碼作改進的部分如下:
//加入附加功能,運算符個數隨機生成 int op_Num=rand()%5+1; //附加功能,控制括號的生成 int braket_Max=2,braket=0,flag=0;//分別代表生成括號的個數和當前左括號個數 //得到後綴表達式 for(t=0;t<op_Num;t++) { op=getOperator(); //生成運算符 if(t==0){ operatorStack[top++]=op; //隨機決定是否生成左括號 if(rand()%3==1&&t<op_Num-1){ printf("(");//打印括號 braket_Max--; braket++; operatorStack[top++]=‘(‘; //左括號入棧 } opNum[q++]=getNum(); expQueue[p++]=q-1+‘0‘; continue; } //當符號棧頂不是左括號,根據優先級判斷出棧 if(operatorStack[top-1]!=‘(‘){ while(mPriority(op)<=mPriority(operatorStack[top-1])&&top>0&&operatorStack[top-1]!=‘(‘){ top--; expQueue[p++]=operatorStack[top]; } } operatorStack[top++]=op; //隨機決定是否生成左括號 if(rand()%3==1&&t<op_Num-1){ if(braket_Max<0) break;//如果已經生成三對括號,就不再生成 printf("(");//打印括號 flag=t; braket_Max--; braket++; operatorStack[top++]=‘(‘; //左括號入棧 } opNum[q++]=getNum();//產生一個隨機數 expQueue[p++]=q-1+‘0‘; //隨機決定是否生成右括號 if(flag!=t&&rand()%3==1){ if(braket<=0) break; printf(")");//打印右括號 braket--; //一直出棧直到遇到左括號 while(operatorStack[top-1]!=‘(‘) { top--; expQueue[p++]=operatorStack[top]; } top--; } } //如果還有左括號還未匹配 while(braket>0){ braket--; printf(")"); while(operatorStack[top-1]!=‘(‘) { top--; expQueue[p++]=operatorStack[top]; } top--; } while(top>0){ top--; expQueue[p++]=operatorStack[top]; }View Code
4. 遇到了一些問題:
計算結果為分數可能在分母出現負號。
- 對分子、分母求最大公約數,當最大公約數為負數,化簡後分母為負數,這就需要判斷:如果分母小於零,分子、分母同時取反。
if(res.y<0){ res.x=-res.x; res.y=-res.y; }
還有各種小問題,就不一一敘述了。
5.測試運行
根據需要在-n後面輸入要生成的題目個數,每個運算式後面輸入自己的計算結果,由程序自動判斷對錯。
基本功能:
支持附加功能
6.PSP
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 10 | 5 |
· Estimate | · 估計這個任務需要多少時間 | 10 | 5 |
Development | 開發 | 600 | 695 |
· Analysis | · 需求分析 (包括學習新技術) | 60 | 50 |
· Design Spec | · 生成設計文檔 | 10 | 30 |
· Design Review | · 設計復審 (和同事審核設計文檔) | 10 | 10 |
· Coding Standard | · 代碼規範 (為目前的開發制定合適的規範) | 10 | 5 |
· Design | · 具體設計 | 120 | 300 |
· Coding | · 具體編碼 | 200 | 180 |
· Code Review | · 代碼復審 | 30 | 20 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 170 | 100 |
Reporting | 報告 | 250 | 210 |
· Test Report | · 測試報告 | 120 | 100 |
· Size Measurement | · 計算工作量 | 30 | 20 |
·Postmortem& Process Improvement Plan | · 事後總結, 並提出過程改進計劃 | 100 | 90 |
合計 | 860 | 910 |
7.心得體會
第一次做軟件工程項目,雖然是一個小項目,但是也學到了軟件開發的一部分流程。體會比較深的就是,在開發的過程中把更多的時間投入的開發設計和測試上,而不是把重點放在代碼的編寫上。用更多的時間構思,把程序需要解決的問題考慮清楚,這樣寫起代碼思路十分清晰,不會出現代碼混亂,確實提高了開發效率。另外,通過這次作業學習了GitHub的基本用法,知道了如何使用GitHub管理自己的項目,給自己的開發有很大的幫助。
高級軟件工程2017第2次作業