结对项目:四则运算(C语言)
github地址:https://github.com/nilonger/arithmetic
结对伙伴:杨锐龙+黄海钊
一、项目要求
1.1 题目:实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能)。
1.2 说明:
-
真分数:1/2, 1/3, 2/3, 1/4, 1’1/2, …。
-
自然数:0, 1, 2, …。
-
运算符:+, −, ×, ÷。
-
括号:(, )。
-
等号:=。
-
分隔符:空格(用于四则运算符和等号前后)。
-
算术表达式:
e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),
其中e, e1和e2为表达式,n为自然数或真分数。
-
四则运算题目:e = ,其中e为算术表达式。
1.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 .txt -a .txt 统计结果输出到文件Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。
二、PSP表
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
100 |
200 |
· Estimate |
· 估计这个任务需要多少时间 |
60 |
70 |
Development |
开发 |
6*24*60 |
5*24*60 |
· Analysis |
· 需求分析 (包括学习新技术) |
60 |
50 |
· Design Spec |
· 生成设计文档 |
20 |
20 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
30 |
30 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
20 |
20 |
· Design |
· 具体设计 |
100 |
150 |
· Coding |
· 具体编码 |
4*24*60 |
4*24*60 |
· Code Review |
· 代码复审 |
100 |
150 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
40 |
60 |
Reporting |
报告 |
20 |
20 |
· Test Report |
· 测试报告 |
10 |
30 |
· Size Measurement |
· 计算工作量 |
10 |
30 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 |
30 |
合计 |
|
6*24*60 |
5*24*60 |
三、解题思路
1、先实现生成式子的功能。
(1)先写好生成操作数和包含1~3个操作符的式子的四个函数,再通过一个函数一起调用,随机生成带括号的式子
(2)除号和乘号÷、×先用*、#代替
(3)C语言里面的除号和乘号÷、×不能直接从文件读出来,所以就放弃了从文件里读出来计算的想法,改为生成的时候,把式子放进数组再来计算,计算不小于0,才把数组存进文件,存进文件的时候把*、#这两个符号替换成÷、×
2、计算功能。
(1)只定义了一个分数结构体,因为想到计算的时候 整数也可以转化为分数,如2可以化为2/1
(2)定义两个栈,手撸栈的各个操作(C语言没办法),一个栈用来压入分数,一个用来压入操作符
3、比较题目文件,判断答案文件中的对错。
(1)这个可以说是取巧了一下,因为文件的生成,帮随着答案的生成,所以比较的是 标准答案文件 和 新答案文件
(2)原本是想从文件里面再读出式子拿来计算,但是写进文件后有了符号÷、×,但是那两个符号÷、×读出来会出错,因为他们不是普通字符型数据的长度。
4、主函数。
用输入参数的方式在cmd运行,很好的分开了生成式子(同时生成答案)和比较答案这两个步骤
四、设计实现过程
五、代码说明
头文件和相关结构体和栈的定义:
#include<stdio.h> #include<stdlib.h> #include <time.h> #include<string.h> #include<io.h> #include<math.h> #define MINSIZE 256 #define MAXSIZE 1024 #define OK 1 #define ERROR 0 int r,n; //生成数的最大值 和 式子数量 typedef int Status; char* oneOperator(); char* creatOperator(); char* threeOperator(); char* twoOperator(); char *creatFormula(int y); char* creatSnum(); Status Answer_Sq(char formula[],int y); typedef struct StackNode{ //分数 栈结点 int fm; //分母 int fz; //分子 struct StackNode *next; }node1; typedef struct Stackop { //运算符 栈结点 int data; struct Stackop *next; }node2; typedef struct stack1 { //分数栈 node1 *top; int length; }StackSq1; typedef struct stack2 { //运算符栈 node2 *top; int length; }StackSq2;
栈的相关操作:
Status InitStack_Sq1(StackSq1 &S){ //初始化 运算数 的栈 S.top=NULL; S.length=0; return OK; } Status InitStack_Sq2(StackSq2 &S){ //初始化 运算符 的栈 S.top=NULL; S.length=0; return OK; } Status StackEmpty_Sq(StackSq2 S){ // 对 运算符 判空,若为空则返回TURE,否则返回FALSE if(S.length==0) return OK; else return ERROR; } Status Push_Sq1(StackSq1 &S,int fenzi,int fenmu){ //分数 进栈 node1 *p; p=(node1 *)malloc(sizeof (node1)); p->fm=fenmu; p->fz=fenzi; p->next=S.top; S.top=p; S.length++; return OK; } Status Push_Sq2(StackSq2 &S,int e){ //运算符 进栈 node2 *p; p=(node2 *)malloc(sizeof (node2)); p->data=e; p->next=S.top; S.top=p; S.length++; return OK; } node1 Pop_Sq1(StackSq1 &S){ //记得类型是 node1 node1 A; node1 *p=S.top; A.fm=p->fm; A.fz=p->fz; S.top=p->next; free(p); S.length--; return A; } Status Pop_Sq2(StackSq2 &S){ // 运算符栈顶 出栈 int e; node2 *p=S.top; e=p->data; S.top=p->next; free(p); S.length--; return e; } Status Get_Top(StackSq2 S){ //取 运算符 栈顶元素 (不出栈) if(S.top==NULL)return ERROR; return S.top->data; }
生成式子的相关函数:
char* creatOperator() //生成运算符 { srand((unsigned)time(NULL) + rand()); char* c = new char[2]; int a = rand() % 4; //int a = 1; switch (a) { case 1: strcpy(c, "+"); break; case 2: strcpy(c, "-"); break; case 3: strcpy(c, "*"); break; case 0: strcpy(c, "#"); break; default: break; } return c; } char* creatSnum() //生成运算数 { srand((unsigned)time(NULL) + rand()); char* string = NULL; char string1[MAXSIZE] = {}; string = string1; char c[MAXSIZE] = {}; int tag = rand() % 2; int num1=0,num2=0; if (tag == 0) { num1 = rand() % (r)+1; //不要(size+1),保证整数不为 0 sprintf(c, "%d", num1); strcat(string1, c); } else { num1 = rand() %(r-1) ; //最大为 m-2,方便后面算法 if (num1 != 0) { sprintf(c, "%d", num1); strcat(string1, c); strcat(string1, "/"); } while (num2 == 0 || num2 <= num1) { num2 = rand() % r; //最大为 m-1 } sprintf(c, "%d", num2); strcat(string1, c); } return string; } char* oneOperator() //一个操作符的式子 { srand((unsigned)time(NULL) + rand()); char string[MINSIZE] = {}; char* posture = string; char c[MINSIZE] = {}; strcpy(c, creatSnum()); strcat(string, c); strcpy(c,creatOperator()); strcat(string, c); //printf("%s ", string); strcpy(c, creatSnum()); strcat(string, c); //printf("%s ", string); return posture; } char* twoOperator() //两个操作符得式子 { srand((unsigned)time(NULL) + rand()); int flag = 0; int tag=0; char string[128] = {}; char* posture = string; char c[MAXSIZE] = {}; flag = rand() % 2; if (flag == 0) { strcpy(c, "("); strcat(string, c); tag = 1; } strcpy(c, creatSnum()); strcat(string, c); strcpy(c,creatOperator()); strcat(string, c); strcpy(c, creatSnum()); strcat(string, c); flag = rand() % 2; if (flag == 0) { if (tag == 1) { strcpy(c, ")"); strcat(string, c); } tag = 0; } strcpy(c,creatOperator()); strcat(string, c); strcpy(c, creatSnum()); strcat(string, c); if (tag == 1) { strcpy(c, ")"); strcat(string, c); } //printf("%s ", string); return posture; } char* threeOperator() //三个操作符得式子 { srand((unsigned)time(NULL) + rand()); char string[128] = {}; char* posture = string; char c[MAXSIZE] = {}; strcpy(c, creatSnum()); strcat(string, c); strcpy(c,creatOperator()); strcat(string, c); strcpy(c, creatSnum()); strcat(string, c); strcpy(c,creatOperator()); strcat(string, c); strcpy(c, creatSnum()); strcat(string, c); strcpy(c,creatOperator()); strcat(string, c); strcpy(c, creatSnum()); strcat(string, c); return posture; } char pan[5]={"-1"}; char *mp=pan; char duan[5]={"1"}; char *np=duan; char *creatFormula(int y) //生成式子 { srand((unsigned)time(NULL) + rand()); // char op[3]; char string[MAXSIZE] = {}; char* posture = string; int a = rand() % 3; //生成的随机数,即运算符个数 //printf("%d ", a); FILE *fp; fp=fopen("test.txt","a"); int i=0; char divi[5]={"÷"}; //用于后面存进文件 char mult[5]={"×"}; switch (a) { case 0:strcpy(string, oneOperator()); // printf("%s ",posture); //和文件作比较 break; case 1:strcpy(string, twoOperator()); // printf("%s ",posture); break; case 2:strcpy(string, threeOperator()); // printf("%s ",posture); break; default:break; } if(Answer_Sq(posture,y)<0) //计算的时候用 数组 算,打印则要转化一下 { fclose(fp); return mp; // 因为是 char 型函数,不能直接返回-1 } else { // fprintf(fp,"%d.%s = ",y,posture); fprintf(fp,"%d.",y); while(string[i]!='