四則運算(c語言實現)
合伙人:魏甫——3118004973 ,溫欽益——3118004975
https://github.com/iamdate/work/tree/master
一.項目及其要求
1.題目:實現一個自動生成小學四則運算題目的命令行程序(也可以用圖像界面,具有相似功能)。
2說明:
自然數:0, 1, 2, …。
-
- 真分數:1/2, 1/3, 2/3, 1/4, 1’1/2, …。
- 運算符:+, −, ×, ÷。
- 括號:(, )。
- 等號:=。
- 分隔符:空格(用於四則運算符和等號前后)。
- 算術表達式:
e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),
其中e, e1和e2為表達式,n為自然數或真分數。
-
- 四則運算題目:e = ,其中e為算術表達式。
3需求:
- 使用 -n 參數控制生成題目的個數,例如Myapp.exe -n 10將生成10個題目。
- 使用 -r 參數控制題目中數值(自然數、真分數和真分數分母)的范圍,例如
Myapp.exe -r 10
將生成10以內(不包括10)的四則運算題目。該參數可以設置為1或其他自然數。該參數必須給定,否則程序報錯並給出幫助信息。
- 生成的題目中計算過程不能產生負數,也就是說算術表達式中如果存在形如e1− e2的子表達式,那么e1≥ e2。
- 生成的題目中如果存在形如e1÷ e2的子表達式,那么其結果應是真分數。
- 每道題目中出現的運算符個數不超過3個。
- 程序一次運行生成的題目不能重復,即任何兩道題目不能通過有限次交換+和×左右的算術表達式變換為同一道題目。例如,23 + 45 = 和45 + 23 = 是重復的題目,6 × 8 = 和8 × 6 = 也是重復的題目。3+(2+1)和1+2+3這兩個題目是重復的,由於+是左結合的,1+2+3等價於(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重復的兩道題,因為1+2+3等價於(1+2)+3,而3+2+1等價於(3+2)+1,它們之間不能通過有限次交換變成同一個題目。
生成的題目存入執行程序的當前目錄下的Exercises.txt文件,格式如下:
- 四則運算題目1
- 四則運算題目2
……
其中真分數在輸入輸出時采用如下格式,真分數五分之三表示為3/5,真分數二又八分之三表示為2’3/8。
- 在生成題目的同時,計算出所有題目的答案,並存入執行程序的當前目錄下的Answers.txt文件,格式如下:
- 答案1
- 答案2
特別的,真分數的運算如下例所示:1/6 + 1/8 = 7/24。
- 程序應能支持一萬道題目的生成。
- 程序支持對給定的題目文件和答案文件,判定答案中的對錯並進行數量統計,輸入參數如下:
Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt
統計結果輸出到文件Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”后面的數字5表示對/錯的題目的數量,括號內的是對/錯題目的編號。為簡單起見,假設輸入的題目都是按照順序編號的符合規范的題目。
二、所遇困難及解決辦法
1.如何實現隨機運算符和數字?
解:1.1:每道題目中出現的運算符個數不超過3個相當於e=a*b/c+d,其中未知數有4個,運算符最大值為三個。問:如何產出最多含4個數字的題目
1.2:經百度得可使用random()函數隨機生成數字及運算符,后使用switch語句判斷生成題目類型。待續~
2.如何進行計算?
解:2.1:題目中所有參數為隨機,只有生成后的題目為已知。問:如何讀取已知的題目,並進行計算測試?
2.2:第一次測試:采用數組儲存題目,利用switch語句判斷運算符。結果:失敗,運算符多,計算式復雜,數組可能太少。
2.3:第二次測試:經詢問,采用鏈表方式,編寫逆波蘭式將中綴表達式轉為后綴表達式進行計算。結果:成功,一般情況下中綴表達式計算比較復雜,但將其轉為后綴表達式簡潔很多,創建兩個棧,一個存放操作數,一個存放運算符,計算時將其拿出。
3.如何將程序寫進文件
解:3.1:由於之前用java寫個人項目的緣故,得知了io數據流,但c和java用法不一,故前去學習。后用fprintf()將生成表達式一並寫入文件,再將答案數組也寫入文件1。
三、關鍵代碼
1.中綴轉后綴並計算
void qiuzhi(char *bds)//中轉后並求值 { FILE *fp; int i = 0; stack *ysf = (stack*)malloc(sizeof(stack));//為表達式開辟一個stack ysf->size = 0; float num[50];//用於求值的數組 int numpos = 0;//用於求值的數組位置,因使用較少為提高效率選擇數組 printf("后綴表達式為:");//附加 while (bds[i] != '=') { if (bds[i] == '\0') { printf("表達式應該有="); return; } if (bds[i] <= '9'&&bds[i] >= '0')//轉化數字 { num[++numpos] = 0; while (bds[i] <= '9'&&bds[i] >= '0') { num[numpos] *= 10; num[numpos] += (bds[i] - '0'); ++i; } if (bds[i] == '.') { double f_car = 0.1;//定義基數 ++i; while (bds[i] <= '9'&&bds[i] >= '0') { num[numpos] += ((bds[i] - '0')*f_car); f_car *= 0.1; ++i; } }//計算小數點 } else { if (empty(ysf)) push(ysf, bds[i]); else { if (bds[i] == '(') push(ysf, bds[i]); else if (bds[i] == ')') { while (top(ysf) != '(') { reckon(&num[numpos - 1], num[numpos], top(ysf)); printf("%c", pop(ysf)); --numpos; } pop(ysf);//彈出右括號 } else { while (compare(bds[i])<=compare(top(ysf))) { reckon(&num[numpos - 1], num[numpos], top(ysf)); printf("%c", pop(ysf)); --numpos; } push(ysf, bds[i]); } } ++i; } } while (!empty(ysf)) { reckon(&num[numpos - 1], num[numpos], top(ysf)); printf("%c", pop(ysf)); --numpos; }
fopen("/練習程序/answer.txt","w+");
printf("\n運算結果為:%.2f\n", num[1]);
fprintf(fp,"%.2f\n",num[1]);
}
void reckon(float *a, float b, char c)//用於將兩數字合並,前數傳地址 { //表達式運算定義 int t; if (c == '-') { if(*a<b)//非負 { t=*a;*a=b;b=t;} (*a)-=b; } else if (c == '+') { (*a) += b; } else if (c == '*') { (*a) *= b; } else if(b!=0) (*a) /= b; }
2.分數運算
void qiuzhi1(int a,int b,int c,int d,char s) { int x,y,t,m; float p,q;//中間數 int re1,re2,u;//分子分母 x=getGcd(a,b);//對a,b約分 a/=x; b/=x; y=getGcd(c,d);//對c,d約分 c/=y; d/=y; FILE *fp=fopen("/練習程序/subject.txt","w+");//讀寫文件位置 switch(s)//選取運算符 { case '+': re1=a*d+c*b; re2=b*d; t=getGcd(re1,re2); re1/=t; re2/=t; printf("%d/%d + %d/%d=%d/%d\n",a,b,c,d,re1,re2); fprintf(fp, "%d/%d + %d/%d=%d/%d\n",a,b,c,d,re1,re2); break; case '-': p=a/b; q=c/d; if(p<q)//判斷結果不為負 { u=a; a=c; c=u; u=b; b=d; d=u; } re1=a*d-c*b;//結果分子的運算 re2=b*d; t=getGcd(re1,re2);//約分 re1/=t; re2/=t; printf("%d/%d - %d/%d=%d/%d\n",a,b,c,d,re1,re2); fprintf(fp, "%d/%d - %d/%d=%d/%d\n",a,b,c,d,re1,re2); break; case '*': re1=a*c; re2=b*d; t=getGcd(re1,re2); re1/=t; re2/=t; if(a==0||c==0)//有分數為0時 { printf("%d/%d * %d/%d=0",a,b,c,d); fprintf(fp, "%d/%d * %d/%d=0",a,b,c,d); } else printf("%d/%d * %d/%d=%d/%d\n",a,b,c,d,re1,re2); fprintf(fp, "%d/%d * %d/%d=%d/%d\n",a,b,c,d,re1,re2); break; case '/': re1=a*d; re2=b*c; t=getGcd(re1,re2); re1/=t; re2/=t; if(a==0) {printf("%d/%d / %d/%d=0",a,b,c,d);//結果為0 fprintf(fp, "%d/%d / %d/%d=0",a,b,c,d); } else{ printf("%d/%d / %d/%d=%d/%d\n",a,b,c,d,re1,re2); fprintf(fp, "%d/%d / %d/%d=%d/%d\n",a,b,c,d,re1,re2); } } }
3.讀取題目信息
void creat1(int num,int r) { char a[]={'+','-','*','/'}; int X,c,t; int i,j,b,count; int x,y,x1,y1,z,e; int n=sizeof(a); srand(time(NULL)); FILE *fp = NULL;//打開文件 fp = fopen("/練習程序/test.txt", "w+"); for(j=0;j<num;j++) { x=rand()%r; y=rand()%r; x1=rand()%r; y1=rand()%r; z=rand()%r; X=rand()%4; i=rand()%n; b=rand()%n; c=rand()%n; x!=y; switch(X)//控制符號數 { case 0: printf("第%d題:%d%c%d= \n",j+1,x,a[i],y); fprintf(fp, "%d%c%d= \n",x,a[i],y); break; case 1: printf("第%d題:%d%c%d%c%d= \n",j+1,x,a[i],y,a[b],z); fprintf(fp, "%d%c%d%c%d= \n",x,a[i],y,a[b],z); break; case 2: fprintf(fp, "%d%c%d%c%d%c%d= \n",x,a[i],y,a[b],x1,a[c],z); printf("第%d題:%d%c%d%c%d%c%d= \n",j+1,x,a[i],y,a[b],x1,a[c],z); break; } } fclose(fp); }
void save() { stack *p; char bds[50]; FILE *fx;//讀取文件 int line,i; if((fx = fopen("/練習程序/test.txt","r")) == NULL) { printf("error\n"); exit (1) ; } char buf[1024]; for (i = 0; i < 1024; i++) { while(fgets(bds,50,fx) != NULL)//輸出文件 { line = strlen(bds); bds[line-1] = '\0'; /*去掉換行符*/ printf("%s \n",bds); qiuzhi(bds); } fclose (fx); } }
4.主函數
int main() { int num1,num2,m,n,r,type; printf("請選擇輸出類型:1or2"); scanf("%d\n",&type); if(type==1) { printf("請輸入生成題目數量: "); scanf("%d \n",&num1); printf("請輸入分子分母取值范圍: "); scanf("%d %d",&m,&n); creat2(num,m,n); } else if(type==2) { printf("請輸入生成題目數量: "); scanf("%d \n",&num2); printf("請輸入取值范圍: "); scanf("%d %d",&r); creat1(num,r); save();//輸出答案 } return 0; }
四、測試
1.分數計算
2.文件生成
五、psp表格
六、總結
魏甫:這次的項目不能說是很完美的運行,由於基礎較差,在經過很多天的學習,實驗下,我們僅僅只是完成了個大概,我在我們隊伍里是負責測試,審核代碼。為了讓代碼更加美觀好看,我將代碼整體結構改正了一下,導致我們總體的程序出現了bug,例如整數有時會運行不出,但調試卻沒有問題。這是我的責任,同時對我來說這個項目並沒有結束,之后我會仔細檢查debug,讓它更完美些。最后一次總結會議上我們在總結自己收獲的同時,也互相反思自己在項目上出現了什么問題,有什么不足之處。我們的問題主要有:溝通不到位,由於第一次進行結對項目,難免會有些個人思想,在反思過程中,明顯發現,函數中參數設置和定義有很大區別,這讓我們不得不去仔細對照查看代碼含義。也告訴我在下次結對時,一定一定要在一開始就定好函數和參數的各類功能和類型,提高效率,節省時間。這次的結對對我們來說是一個很好的經歷,彌足珍貴。
溫欽益:這次的結對項目,一開始看到題目感覺並不是很難,等到和同伴開始對題目的各個要求完成代碼時,才感受到了題目中復雜的地方。我們首先先確定了使用的語言,由於沒有對其他語言的學習,故選擇了C語言。在完成要求的過程中,我們首先開了個會,先各自完成自己認為能完成的部分。然后把對方的代碼發出來一起審核,進行測試。在判斷運算符優先級時,意識到要運用數據結構的內容時,又抽出了時間去復習。除此,我們是第一次做結對項目,,在討論和交流時出現來了偏差。原因是函數定義及參數設置不一,想法未溝通好。在其他的一些功能要求上,由於我們的能力有限,只能完成一部分內容。另外,在交流代碼的過程中,學習到了代碼的規范的重要性,寫的時候要想到如何寫才能讓對方也能理解你的意思。最后,此次結對項目讓我們有了交流的對象,不再是自己一個人的苦思冥想,我們能分享自己遇到的困難,分享自己的經驗,讓我收獲到了許多。