一、github项目地址:https://github.com/wc-TST-2020/Myapp
项目参与者:李东阳 (3118005055) 李泽辉(3118005058)
二、PSP2.1表格
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
80 |
60 |
· Estimate |
· 估计这个任务需要多少时间 |
80 |
60 |
Development |
开发 |
1510 |
1755 |
· Analysis |
· 需求分析 (包括学习新技术) |
100 |
105 |
· Design Spec |
· 生成设计文档 |
30 |
25 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
40 |
50 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
40 |
50 |
· Design |
· 具体设计 |
50 |
45 |
· Coding |
· 具体编码 |
1000 |
1200 |
· Code Review |
· 代码复审 |
50 |
60 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
200 |
220 |
Reporting |
报告 |
240 |
200 |
· Test Report |
· 测试报告 |
150 |
120 |
· Size Measurement |
· 计算工作量 |
30 |
40 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
60 |
40 |
合计 |
|
1830 |
2015 |
三、效能分析
改进程序性能花费了两三天的时间,主要集中在对答案生成模块进行优化上,程序中消耗最大的函数也为答案生成函数。
由于采用了时间函数,导致生成运算式的速度最快为一秒一条,加上答案生成模块运用了链表数据结构,故内存的分配等又造成了一定的时间花销,总体
上运算式和答案的生成速度还比较慢。
四、设计实现过程
1.运算式生成模块利用随机数生成函数随机生成数字,然后随机生成加减乘除符号和自然数、真分数、带分数等运算数,最后把各项拼接成一条运算式
2.答案生成模块利用链表,对运算式中的各部分进行拆分和连接等操作,分步运算,设计过程如下图:
五、代码说明
1.生成运算式模块
char *creat_term(int max_num,int term_length){/* 生成项的函数 */ int i, j, type, num1, num2;//num1、num2是随机生成的整数,type用来指明参与运算的数的类型 char *term;//term用来存放最终拼接成的项 char num_term[20];//num_term用来存放整数转换成的字符串 char part[5][20]; //二维数组part用来存放这次生成的项的各个组成部分 //0、2、4列存放项,1、3列存放运算符 term = (char *)malloc(20 * sizeof(char));//为term申请空间 memset(term, 0x00, sizeof (char) * 20);//初始化term if (term_length) strcpy(term,"(");//如果这个项被分配有运算符则在项的开头加上'(' srand((unsigned)time(NULL));//生成随机数的种子 for(i = 0;i < term_length; i++){/* 生成项的运算符 */ j = rand() % 4; //生成并项的运算符 switch(j){ case 0: strcpy(part[2 * i + 1],"+"); break; case 1: strcpy(part[2 * i + 1],"-"); break; case 2: strcpy(part[2 * i + 1],"x"); break; case 3: strcpy(part[2 * i + 1],"/"); break; } } for(i = 0;i < term_length + 1; i++){/* 生成参与运算的数 */ //Sleep(1000); //srand((unsigned)time(NULL)); type = rand() % 10;//用随机来判定生成参与运算的数的类型 switch(type){ case 8://生成小于一的分数 do{ strcpy(part[2 * i],"(");//在分数前加上'('来区分分数和除运算 //srand((unsigned)time(NULL)); do{ num1 = rand() % max_num + 1;//生成分母 num2 = rand() % num1 + 1;//生成分子 num1 = num1 / common_divisor(num1,num2);//化简 num2 = num2 / common_divisor(num1,num2); }while(num1 <= num2);/*避免生成非真分数*/ itoa(num2,num_term,10);//将分子转换成字符串 strcat(part[2 * i],num_term);//将二维数组的相应列与生成的字符串拼接起来 strcat(part[2 * i],"/");//将二维数组的相应列与'/'拼接起来 itoa(num1,num_term,10);//将分母转换成字符串 strcat(part[2 * i],num_term);//将二维数组的相应列与生成的字符串拼接起来 strcat(part[2 * i],")");//在分数前加上')'来区分分数和除运算 }while(num1 == num2);/*避免生成 m/m 类型的分数*/ break; case 9://生成大于一的分数 do{ strcpy(part[2 * i],"(");//在分数前加上'('来区分分数和除运算 //srand((unsigned)time(NULL)); do{ num1 = rand() % max_num + 1;//随机生成分数的小数部分 }while(num1 >= max_num);/*保证生成的分数在指定范围内*/ itoa(num1,num_term,10);//将生成的整数转换成字符串 strcat(part[2 * i],num_term);//将二维数组的相应列与生成的字符串拼接起来 strcat(part[2 * i],"'");//将二维数组的相应列与'''拼接起来 //srand((unsigned)time(NULL));/*以下与生成小于一的分数类似*/ do{ num1 = rand() % max_num + 1; num2 = rand() % num1 + 1; num1 = num1 / common_divisor(num1,num2);//化简 num2 = num2 / common_divisor(num1,num2); }while(num1 <= num2);/*避免生成非真分数*/ itoa(num2,num_term,10); strcat(part[2 * i],num_term); strcat(part[2 * i],"/"); itoa(num1,num_term,10); strcat(part[2 * i],num_term); strcat(part[2 * i],")");//在分数前加上')'来区分分数和除运算 }while(num1 == num2);/*避免生成 m/m 类型的分数*/ break; default://随机数为0~7则生成整数 //srand((unsigned)time(NULL)); num1 = rand() % max_num + 1;//随机生成指定范围内的整数 itoa(num1,part[2 * i],10);//将生成的整数转换成字符串并将其存放在二维数组part的相应位置上 break; } } for(i = 0; i < 2 * term_length + 1; i++){/* 将二维数组中的各列拼接成项 */ if(i) strcat(term," ");//在运算符前面加一个空格 strcat(term,part[i]); } if (term_length) strcat(term,")"); return term; } //我将不是项里面的运算符成为主干运算符 char *creat_operation(int max_num){//系统会把空格读成第一个元素//int argc,char *argv[] int formula_length, i, j;//formula_length指的是生成的运算式的长度,即运算的运算符数目 int term_length[5]; //term指的是运算式各部分被分配的运算符数目, //term_length[0]是运算式主干被分配的运算符数目, //term_length[1~4]是运算式第1到第4项被分配的运算符数目 char *term;//term用来存放最终生成的运算式 char part[7][100]; //二维数组part用来存放生成的运算式的各个部分, //0、2、4、6列存放项,1、3、5列存放运算符 term = (char *)malloc(100 * sizeof(char));//为term申请空间 memset(term, 0x00, sizeof (char) * 100);//初始化term //库函数memset(<字符指针>,<命令(0x00是将字符数组置空)>,<长度>)是用来初始化字符数组的 strcpy(term," ");//本来是用来初始化 srand((unsigned)time(NULL));//生成随机数的种子 formula_length = rand() % 3 + 1; for(i = 0; i < 7; i++){//初始二维数组part strcpy(part[i]," ");//起初写这个语句是因为输出的字符串出现了乱码 } //不过后来才清楚出现乱码是因为term没有初始化 for(i = 0; i < 5; i++){//初始term_length,这一步是必要的 term_length[i] = 0; } ++term_length[0];//主干运算符必须要有一个 //srand((unsigned)time(NULL)); for(i = formula_length - 1; i ; i--){//随机分配运算符 j = rand() % 5; ++term_length[j]; } //srand((unsigned)time(NULL)); for(i = 0;i < term_length[0]; i++){ /* 为运算式生成运算符(主干运算符) */ j = rand() % 4; switch(j){ case 0: strcpy(part[2 * i + 1],"+"); break; case 1: strcpy(part[2 * i + 1],"-"); break; case 2: strcpy(part[2 * i + 1],"x"); break; case 3: strcpy(part[2 * i + 1],"/"); break; } } for(i = 0; i < term_length[0] + 1; i++){ /* 生成项 */ Sleep(1000);//延迟一秒,不然输出的项是相同的 strcpy(part[2 * i],creat_term(max_num,term_length[i + 1])); } for(i = 0; i < 2 * term_length[0] + 1; i++){ /* 拼接成运算式 */ if(i) strcat(term," ");//运算符前后加上空格 strcat(term,part[i]); } strcat(term," = ");//在运算式的最后加上'=' return term;
2.答案生成模块
float fraction(float num1,float num2,float num3){//计算非真分数的函数 num2 = num2 / num3; return num1 + num2; } float creat_part_answer(LinkedList &List){//关键函数,已经验证过,可以使用。但是没有释放掉无用的动态空间,有可能会发生内存泄漏,未来需要改进这一点 LinkedList temp1, temp2, temp3, PartList; creat_LinkedList(PartList); temp1 = List->next;//temp1用来检查节点 temp1 = temp1->next;//初始化指向 while(temp1 != List->next){ if(temp1->sign == 6){//若找到( temp2 = temp1->next;//temp2寻找相应的temp1指向的(对应的) while(1){ if(temp2->sign == 7 && temp1->tag - temp2->tag == 1) break; insert_LinkedList(PartList,temp2->num,temp2->sign,temp2->tag);//将成对的括号里的内容截取到另一个独立的单循环链表里 temp2 = temp2->next; } temp1->num = creat_part_answer(PartList);//将括号里的计算结果放入(所在的节点 temp1->sign = 0;//将temp1转化成存放数字的节点 temp1->tag = 0;// if(List == temp2) List = temp1;//如果参与运算的内容有运算式末端的数字或运算符,则将List指向存放运算结果的节点 temp1->next = temp2->next;//删除已经参与运算的内容 } temp1 = temp1->next; } temp1 = List->next; temp2 = temp1; temp1 = temp1->next; while(temp1 != List->next){//寻找非真分数的标志' if(temp1->sign == 5){//以2'3/4为例,temp1指向的是’,temp2指向的是2 temp1 = temp1->next;//将temp1的指向改为3 temp3 = temp1->next->next;//temp3指向4 temp2->num = fraction(temp2->num,temp1->num,temp3->num);//将运算结果存放在temp2指向的节点 if(temp3 == List) List = temp2; temp2->next = temp3->next;//去除运算式中已经参与运算的部分 temp1 = temp2->next;//调整temp1,为下一次循环做准备 continue; } temp2 = temp1; temp1 = temp1->next; } temp1 = List->next; temp2 = temp1; temp1 = temp1->next; while(temp1 != List->next){ if(temp1->sign == 3 || temp1->sign == 4){//检测到有运算符乘或除,temp1指向运算符,temp2指向被乘数或被除数 temp3 = temp1->next;//temp3指向乘数或除数 temp2->num = calculate(temp2->num,temp3->num,temp1->sign);//将运算结果存放在temp2指向的节点 if(List == temp3) List = temp2; temp2->next = temp3->next;//去除运算式中已经参与运算的部分 temp1 = temp2->next;//调整temp1,为下一次循环做准备 continue; } temp2 = temp1; temp1 = temp1->next; } temp1 = List->next; temp2 = temp1; temp1 = temp1->next; while(temp1 != List->next){ if(temp1->sign == 1 || temp1->sign == 2){//检测到有运算符加或减,temp1指向运算符,temp2指向被加数或被减数 temp3 = temp1->next;//temp3指向加数或减数 temp2->num = calculate(temp2->num,temp3->num,temp1->sign); if(List == temp3) List = temp2; temp2->next = temp3->next;//去除运算式中已经参与运算的部分 temp1 = temp2->next;//调整temp1,为下一次循环做准备 continue; } temp2 = temp1; temp1 = temp1->next; } return List->num; } float create_answer(char *operation){ LinkedList List;//存放运算式的链表 char *TempOperation;//存放运算式的字符串 char TempPart[20];//存放被截取的一部分的运算式的字符串 int flag;//控制是否将数字字符串转换成整型的开关 int sign;//运算符的抽象代表 int tag = 1;//括号的编号 int IntNum;//转换成整型的字符串 float FloatNum;//转换成浮点型的字符串 int i, j;//你懂的 TempOperation = (char *)malloc(100 * sizeof(char)); memset(TempOperation, 0x00, sizeof (char) * 100);//初始化 creat_LinkedList(List);//初始化链表 strcpy(TempOperation,operation);//如果输入的字符串就是运算式 for(i = 0; i < 20; i++){TempPart[i] = '