C语言实现简单自动生成小学四则运算题目
作者:邱彬
协作者:麦狄龙(麦狄龙指导我指导他为指导我的指导人)
1.GitHub项目网址:
https://github.com/QiuBin666/MyApp.exe
2.个人PSP
PSP(严格来说是基于团队的PSP)
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | |
· Estimate | · 估计这个任务需要多少时间 | 20 | |
Development | 开发 | 1200 | |
· Analysis | · 需求分析 (包括学习新技术) | 180 | |
· Design Spec | · 生成设计文档 | 60 | |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | |
· Design | · 具体设计 | 120 | |
· Coding | · 具体编码 | 360 | |
· Code Review | · 代码复审 | 60 | |
· Test | · 测试(自我测试,修改代码,提交修改) | 180 | |
Reporting | 报告 | 60 | |
· Test Report | · 测试报告 | 60 | |
· Size Measurement | · 计算工作量 | 10 | |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | |
合计 | 2430 |
3.题目介绍与解题思路
3.1题目介绍:
-
使用 -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.答案1
2.答案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表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。
3.2解题思路:
-
生成表达式:表达式由随机数和运算符两部分组成,运算符生成运用rand函数来随机生成一个1到4的变量,分别对应加号,减号,乘号,除号,当运算符为除号的时候我们采用了辗转相除法,写一个gcd函数来求出两个数的最大公约数,这样来实现输出分数的要求;随机数生成也运用rand函数来实现。
- 生成题目文件和答案文件:先定义两个文件指针,指向相对应要写入的文件Answer.txt和Exercise.txt,然后生成文件并打开文件,通过fprinft函数将生成的题目和答案写入到相应的文件。
3. 判定答案中的对错并进行数量统计:只需对前面生成的题目文件和答案文件进行操作,分三步:
(1).计算题目的数量x:遍历Exercise.txt,遇到换行符x加1。
(2).分别读取Answer.txt和Exercise.txt的答案:读取等号右边的数据。
(3).判断第二步读取的两个答案是否相等,记录序号并生成Grade.txt文件。
4.设计实现过程
5.关键代码展示与说明
主函数:
1 int main() 2 { 3 int subnumber,max; 4 int opnum; 5 int caseone(int numb1,int numb2,int max,int i); //函数的声明 6 int casetwo(int numb1,int numb2,int numb3,int max,int i); 7 FILE *fp; 8 fp=fopen("Exercises.txt","w"); 9 fp=fopen("Answers.txt","w"); //建立文件 10 fclose(fp); 11 srand((unsigned)time(0)); //为保证每次运行程序的时候生成的题目都不一样 12 printf("***************随机生成四则运算题目***************************** "); 13 printf("1.使用 -n 参数控制生成题目的个数 "); 14 printf("2.使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围 "); 15 printf("请输入n:"); 16 scanf("%d",&subnumber); 17 printf("请输入r:"); 18 scanf("%d",&max); 19 printf("题目生成中,请稍后... "); 20 for(i=1;i<=subnumber;i++) //for循环生成题目 21 { 22 opnum=rand()%2+1; 23 switch(opnum) 24 { 25 case 1 : //当随机数是生成1个运算符时 26 numb1=rand()%max; 27 numb2=rand()%max; 28 caseone(numb1,numb2,max,i); //执行函数的同时得同时执行把表达式输出到txt文件中 29 break; 30 case 2 : //当随机数是生成2个运算符时 31 numb1=rand()%max; 32 numb2=rand()%max; 33 numb3=rand()%max; 34 casetwo(numb1,numb2,numb3,max,i); 35 break; 36 default:printf("ERROR "); 37 } 38 } 39 char file1[10],file2[10],s[3]; 40 int *exe; 41 int *an; 42 printf("校对文件答案并生成Grade.txt文件 "); 43 printf("请输入练习文本: "); 44 scanf("%s",file1); 45 printf("请输入答案文本: "); 46 scanf("%s",file2); 47 int n; 48 n=Count(file1); 49 exe=(int*)malloc(n*sizeof(int)); 50 an=(int*)malloc(n*sizeof(int)); 51 GetAnswers(file1,exe); 52 GetAnswers(file2,an); 53 Put_Judge(exe,an,n); 54 return 0; 55 56 }
子函数:
1 int numb1,numb2,numb3,i; //随机生成的数值 2 int op1,op2; //运算符 3 FILE *fp; //指向答案文件 4 FILE *fb; //指向题目文件 5 6 int gcd(int numb1,int numb2) //辗转相除法 7 { 8 if(numb2==0) return numb1; 9 return gcd(numb2,numb1%numb2); 10 } 11 12 int caseone(int numb1,int numb2,int max,int i) 13 { 14 int gcd(int numb1,int numb2); 15 int temp; //中间值 16 int result; 17 op1=rand()%4+1; //op1的生成值为1到4,分别对应加法,减法,乘法,除法 18 fb=fopen("Exercises.txt","a"); 19 fp=fopen("Answers.txt","a"); //打开文件 20 switch(op1){ 21 case 1: //op1为1的时候,执行加法操作 22 result=numb1+numb2; 23 fprintf(fp,"%d.答案=%d ",i,result); //写入文件 24 fclose(fp); //关闭文件 25 fprintf(fb,"%d.%d+%d=%d ",i,numb1,numb2,result); 26 fclose(fb); 27 break; 28 case 2 : //op1为2的时候,执行减法操作; 29 if(numb1<numb2) //为保证结果不为零,当出现生成的被减数比减数小,进行两者值的交换 30 { 31 temp=numb1; 32 numb2=numb1; 33 numb1=temp; 34 } 35 result=numb1-numb2; 36 fprintf(fp,"%d.答案=%d ",i,result); 37 fclose(fp); 38 fprintf(fb,"%d.%d-%d =%d ",i,numb1,numb2,result); 39 fclose(fb); 40 break; 41 case 3 : //op1为3的时候,执行乘法操作; 42 result=numb1*numb2; 43 fprintf(fp,"%d.答案=%d ",i,result); 44 fclose(fp); 45 fprintf(fb,"%d.%d*%d =%d ",i,numb1,numb2,result); 46 fclose(fb); 47 break; 48 case 4 : //op1为4的时候,执行除法操作; 49 if(numb2==0) 50 numb2=rand()%max+1; 51 temp=gcd(numb1,numb2); 52 fprintf(fp,"%d.答案=%d/%d ",i,numb1/temp,numb2/temp); 53 fclose(fp); 54 fprintf(fb,"%d.%d/%d =%d/%d ",i,numb1,numb2,numb1/temp,numb2/temp); 55 fclose(fb); 56 break; 57 default:printf("ERROR "); 58 } 59 return 0; 60 } 61 62 int casetwo(int numb1,int numb2,int numb3,int max,int i) 63 { 64 int gcd(int numb1,int numb2); 65 int result, result1,temp,temp1; 66 op1=rand()%4+1; 67 op2=rand()%4+1; //op1,op2的生成值为1到4,分别对应加法,减法,乘法,除法 68 fp=fopen("Answers.txt","a"); 69 fb=fopen("Exercises.txt","a"); 70 switch(op1){ 71 case 1 : 72 switch(op2){ 73 case 1 : //op1,op2均为加法的情况 74 result1=numb1+numb2; 75 result=result1+numb3; 76 fprintf(fp,"%d.答案=%d ",i,result); 77 fclose(fp); 78 fprintf(fb,"%d.%d+%d+%d%=%d ",i,numb1,numb2,numb3,result); 79 fclose(fb); 80 break; 81 case 2 : //op1为加法,op2为减法的情况 82 result1=numb1+numb2; 83 if(result1<numb3) 84 { 85 temp=numb3; 86 numb3=numb2; 87 numb2=temp; 88 } 89 result=numb1+numb2-numb3; 90 fprintf(fp,"%d.答案=%d ",i,result); 91 fclose(fp); 92 fprintf(fb,"%d.%d+%d-%d%=%d ",i,numb1,numb2,numb3,result); 93 fclose(fb); 94 break; 95 case 3 : //op1为加法,op2为乘法的情况 96 result1=numb2*numb3; 97 result=numb1+result1; 98 fprintf(fp,"%d.答案=%d ",i,result); 99 fclose(fp); 100 fprintf(fb,"%d.%d+%d*%d%=%d ",i,numb1,numb2,numb3,result); 101 fclose(fb); 102 break; 103 case 4 : //op1为加法,op2为除法的情况 104 if(numb3==0) 105 { 106 numb3=rand()%max+1; 107 } 108 temp=gcd(numb2,numb3); 109 fprintf(fp,"%d.答案=%d'%d/%d ",i,numb1,numb2/temp,numb3/temp); 110 fclose(fp); 111 fprintf(fb,"%d.%d+%d/%d%=%d'%d/%d ",i,numb1,numb2,numb3,numb1,numb2/temp,numb3/temp); 112 fclose(fb); 113 break; 114 default:printf("ERROR "); 115 } 116 break; 117 case 2 : 118 switch(op2){ 119 case 1 : //op1为减法,op2为加法的情况 120 if(numb1<numb2) //为保证结果不为零,当出现生成的被减数比减数小,进行两者值的交换 121 { 122 temp=numb1; 123 numb2=numb1; 124 numb1=temp; 125 } 126 result=numb1-numb2+numb3; 127 fprintf(fp,"%d.答案=%d ",i,result); 128 fclose(fp); 129 fprintf(fb,"%d.%d-%d+%d%=%d ",i,numb1,numb2,numb3,result); 130 fclose(fb); 131 break; 132 case 2 : //op1为减法,op2为减法的情况 133 do{ 134 numb1=rand()%max; 135 numb2=rand()%max; 136 numb3=rand()%max; 137 }while((numb1-numb2-numb3)<=0); 138 result=numb1-numb2-numb3; 139 fprintf(fp,"%d.答案=%d ",i,result); 140 fclose(fp); 141 fprintf(fb,"%d.%d-%d-%d%=%d ",i,numb1,numb2,numb3,result); 142 fclose(fb); 143 break; 144 case 3 : //op1为减法,op2为乘法的情况 145 do{ 146 numb1=rand()%max; 147 numb2=rand()%max; 148 numb3=rand()%max; 149 }while((numb1-numb2*numb3)<0); 150 result=numb1-numb2*numb3; 151 fprintf(fp,"%d.答案=%d ",i,result); 152 fclose(fp); 153 fprintf(fb,"%d.%d-%d*%d%=%d ",i,numb1,numb2,numb3,result); 154 fclose(fb); 155 break; 156 case 4 : //op1为减法,op2为除法的情况 157 do{ 158 numb1=rand()%max; 159 numb2=rand()%max; 160 numb3=rand()%max; 161 }while((numb1-numb2/numb3)<0||numb3==0); 162 result=numb1-numb2/numb3; 163 fprintf(fp,"%d.答案=%d ",i,result); 164 fclose(fp); 165 fprintf(fb,"%d.%d-%d/%d%=%d ",i,numb1,numb2,numb3,result); 166 fclose(fb); 167 break; 168 default:printf("ERROR "); 169 } 170 break; 171 case 3 : 172 switch(op2){ 173 case 1 : //op1为乘法,op2为加法的情况 174 result=numb1*numb2+numb3; 175 fprintf(fp,"%d.答案=%d ",i,result); 176 fclose(fp); 177 fprintf(fb,"%d.%d*%d+%d%=%d ",i,numb1,numb2,numb3,result); 178 fclose(fb); 179 break; 180 case 2 : //op1为乘法,op2为减法的情况 181 do{ 182 numb1=rand()%max; 183 numb2=rand()%max; 184 numb3=rand()%max; 185 }while((numb1*numb2-numb3)<0); 186 result=numb1*numb2-numb3; 187 fprintf(fp,"%d.答案=%d ",i,result); 188 fclose(fp); 189 fprintf(fb,"%d.%d*%d-%d%=%d ",i,numb1,numb2,numb3,result); 190 fclose(fb); 191 break; 192 case 3 : //op1为乘法,op2为乘法的情况 193 result=numb1*numb2*numb3; 194 fprintf(fp,"%d.答案=%d ",i,result); 195 fclose(fp); 196 fprintf(fb,"%d.%d*%d*%d%=%d ",i,numb1,numb2,numb3,result); 197 fclose(fb); 198 break; 199 case 4 : //op1为乘法,op2为除法的情况 200 do{ 201 numb3=rand()%max; 202 }while(numb3==0); 203 temp1=numb1*numb2; 204 temp=gcd(temp1,numb3); 205 fprintf(fp,"%d.答案=%d/%d ",i,temp1/temp,numb3/temp); 206 fclose(fp); 207 fprintf(fb,"%d.%d*%d/%d%=%d/%d ",i,numb1,numb2,numb3,temp1/temp,numb3/temp); 208 fclose(fb); 209 break; 210 default:printf("ERROR "); 211 } 212 break; 213 case 4 : 214 switch(op2){ 215 case 1 : //op1为除法,op2为加法的情况 216 do{ 217 numb2=rand()%max; 218 }while(numb2==0); 219 temp=gcd(numb1,numb2); 220 fprintf(fp,"%d.答案=%d'%d/%d ",i,numb3,numb1/temp,numb2/temp); 221 fclose(fp); 222 fprintf(fb,"%d.%d/%d+%d%=%d'%d/%d ",i,numb1,numb2,numb3,numb3,numb1/temp,numb2/temp); 223 fclose(fb); 224 break; 225 case 2 : //op1为除法,op2为减法的情况 226 do{ 227 numb3=rand()%max; 228 }while( (numb1/numb2-numb3)<0||numb2==0); 229 result=numb1/numb2-numb3; 230 fprintf(fp,"%d.答案=%d ",i,result); 231 fclose(fp); 232 fprintf(fb,"%d.%d/%d-%d%=%d ",i,numb1,numb2,numb3,result); 233 fclose(fb); 234 break; 235 case 3 : //op1为除法,op2为乘法的情况 236 do{ 237 numb2=rand()%max; 238 }while(numb2==0); 239 temp1=numb1*numb3; 240 temp=gcd(temp1,numb2); 241 result=numb1/numb2*numb3; 242 fprintf(fp,"%d.答案=%d/%d ",i,temp1/temp,numb2/temp); 243 fclose(fp); 244 fprintf(fb,"%d.%d/%d*%d%=%d/%d ",i,numb1,numb2,numb3,temp1/temp,numb2/temp); 245 fclose(fb); 246 break; 247 case 4 : //op1为除法,op2为除法的情况 248 do{ 249 numb2=rand()%max; 250 numb3=rand()%max; 251 }while(numb3==0||numb2==0); 252 result=numb1/numb2/numb3; 253 temp1=numb2*numb3; 254 temp=gcd(temp1,numb1); 255 fprintf(fp,"%d.答案=%d/%d ",i,numb1/temp,temp1/temp); 256 fclose(fp); 257 fprintf(fb,"%d.%d/%d/%d%=%d/%d ",i,numb1,numb2,numb3,numb1/temp,temp1/temp); 258 fclose(fb); 259 break; 260 default:printf("ERROR "); 261 } 262 break; 263 default:printf("ERROR "); 264 } 265 return 0; 266 } 267 268 269 int Count(char file[]) /*计算的题目数量*/ 270 { 271 FILE *fp1; 272 char a; 273 int x=0; 274 if((fp1=fopen(file,"r"))==NULL) 275 { 276 printf("read file failed! "); 277 exit(0); 278 } 279 while(!feof(fp1)) 280 { 281 a=fgetc(fp1); 282 if(a==' ') 283 x++; 284 } 285 fclose(fp1); 286 return x; 287 } 288 289 void Put_Judge(int *str1,int *str2,int n) /* 答案判断对错函数*/ 290 { 291 int i,j; 292 FILE *out; 293 out=fopen("Grade.txt","w"); 294 for(i=0,j=0;i<n;i++) /*对的个数*/ 295 if(*(str1+i)==*(str2+i)) 296 j++; 297 fprintf(out,"Correct:%d (",j); 298 for(i=0;i<n;i++) /*对的序号*/ 299 { 300 if(*(str1+i)==*(str2+i)) 301 fprintf(out," %d ",(i+1)); 302 } 303 fprintf(out,") "); 304 fprintf(out,"Wrong:%d (",(n-j)); 305 for(i=0;i<n;i++) /*错的序号*/ 306 { 307 if(*(str1+i)!=*(str2+i)) 308 fprintf(out," %d ",(i+1)); 309 } 310 fprintf(out,") "); 311 fclose(out); 312 printf("OK"); 313 } 314 315 void GetAnswers(char infile[],int *str) /*读取学生答案的函数*/ 316 { 317 FILE *in; 318 char a; 319 int i=0; 320 if((in=fopen(infile,"r"))==NULL) 321 { 322 printf("read file failed! "); 323 exit(0); 324 } 325 while(!feof(in)) 326 { 327 *(str+i)=0; 328 a=fgetc(in); 329 if(a!='=') 330 continue; 331 while(1) 332 { 333 a=fgetc(in); 334 if(a==' ')break; 335 *(str+i)*=10; 336 *(str+i)+=(a-48); 337 } 338 printf("%d ",*(str+i)); 339 i++; 340 } 341 printf(" "); 342 fclose(in); 343 }
6.测试运行
生成10道题目:
生成100道题目:
生成10000道题目:
故意将前三道题目答案改错:
7.项目完结个人PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 10 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 10 |
Development | 开发 | 1200 | 1200 |
· Analysis | · 需求分析 (包括学习新技术) | 180 | 120 |
· Design Spec | · 生成设计文档 | 60 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | 60 |
· Design | · 具体设计 | 120 | 150 |
· Coding | · 具体编码 | 360 | 390 |
· Code Review | · 代码复审 | 60 | 90 |
· Test | · 测试(自我测试,修改代码,提交修改) | 180 | 210 |
Reporting | 报告 | 60 | 90 |
· Test Report | · 测试报告 | 60 | 30 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 2430 | 2460 |
8.项目小结:
收获:更熟悉psp的操作方法,对c语言文件读写方法有了更深的理解,是一次很好的打码练习,同时作为合作项目,认识到1+1>2的道理,队友的作用不在于分担了多少代码量,关键时刻的提醒和建议往往起到事半功倍的作用。
不足:部分功能未能实现,个人能力需要加强。