四则运算(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] == ' ') { 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("
运算结果为:%.2f
", num[1]);
fprintf(fp,"%.2f
",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 ",a,b,c,d,re1,re2); fprintf(fp, "%d/%d + %d/%d=%d/%d ",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 ",a,b,c,d,re1,re2); fprintf(fp, "%d/%d - %d/%d=%d/%d ",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 ",a,b,c,d,re1,re2); fprintf(fp, "%d/%d * %d/%d=%d/%d ",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 ",a,b,c,d,re1,re2); fprintf(fp, "%d/%d / %d/%d=%d/%d ",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= ",j+1,x,a[i],y); fprintf(fp, "%d%c%d= ",x,a[i],y); break; case 1: printf("第%d题:%d%c%d%c%d= ",j+1,x,a[i],y,a[b],z); fprintf(fp, "%d%c%d%c%d= ",x,a[i],y,a[b],z); break; case 2: fprintf(fp, "%d%c%d%c%d%c%d= ",x,a[i],y,a[b],x1,a[c],z); printf("第%d题:%d%c%d%c%d%c%d= ",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 "); exit (1) ; } char buf[1024]; for (i = 0; i < 1024; i++) { while(fgets(bds,50,fx) != NULL)//输出文件 { line = strlen(bds); bds[line-1] = ' '; /*去掉换行符*/ printf("%s ",bds); qiuzhi(bds); } fclose (fx); } }
4.主函数
int main() { int num1,num2,m,n,r,type; printf("请选择输出类型:1or2"); scanf("%d ",&type); if(type==1) { printf("请输入生成题目数量: "); scanf("%d ",&num1); printf("请输入分子分母取值范围: "); scanf("%d %d",&m,&n); creat2(num,m,n); } else if(type==2) { printf("请输入生成题目数量: "); scanf("%d ",&num2); printf("请输入取值范围: "); scanf("%d %d",&r); creat1(num,r); save();//输出答案 } return 0; }
四、测试
1.分数计算
2.文件生成
五、psp表格
六、总结
魏甫:这次的项目不能说是很完美的运行,由于基础较差,在经过很多天的学习,实验下,我们仅仅只是完成了个大概,我在我们队伍里是负责测试,审核代码。为了让代码更加美观好看,我将代码整体结构改正了一下,导致我们总体的程序出现了bug,例如整数有时会运行不出,但调试却没有问题。这是我的责任,同时对我来说这个项目并没有结束,之后我会仔细检查debug,让它更完美些。最后一次总结会议上我们在总结自己收获的同时,也互相反思自己在项目上出现了什么问题,有什么不足之处。我们的问题主要有:沟通不到位,由于第一次进行结对项目,难免会有些个人思想,在反思过程中,明显发现,函数中参数设置和定义有很大区别,这让我们不得不去仔细对照查看代码含义。也告诉我在下次结对时,一定一定要在一开始就定好函数和参数的各类功能和类型,提高效率,节省时间。这次的结对对我们来说是一个很好的经历,弥足珍贵。
温钦益:这次的结对项目,一开始看到题目感觉并不是很难,等到和同伴开始对题目的各个要求完成代码时,才感受到了题目中复杂的地方。我们首先先确定了使用的语言,由于没有对其他语言的学习,故选择了C语言。在完成要求的过程中,我们首先开了个会,先各自完成自己认为能完成的部分。然后把对方的代码发出来一起审核,进行测试。在判断运算符优先级时,意识到要运用数据结构的内容时,又抽出了时间去复习。除此,我们是第一次做结对项目,,在讨论和交流时出现来了偏差。原因是函数定义及参数设置不一,想法未沟通好。在其他的一些功能要求上,由于我们的能力有限,只能完成一部分内容。另外,在交流代码的过程中,学习到了代码的规范的重要性,写的时候要想到如何写才能让对方也能理解你的意思。最后,此次结对项目让我们有了交流的对象,不再是自己一个人的苦思冥想,我们能分享自己遇到的困难,分享自己的经验,让我收获到了许多。