一、编程语言:C++
二、代码解读与分析:
1、 项目逻辑
通过输入信息与数组中存储账户信息比对完成用户登录。根据不同的类型要求生成相应类型的试卷并查重。将生成题目以指定的形式存放在指定文件夹下。过程中可切换试卷类型。
2、函数功能说明
int main():主函数负责调用各个功能函数,将各个功能模块按照项目实际的使用情况与应用逻辑结合适当的提示性信息进行组织和套用。
void SetPrimary(int n):生成小学类型的试卷。
void SetMiddle(int n):生成初中类型的试卷。
void SetHigh(int n):生成高中类型的试卷。
string GetTime():获取并以字符串形式返回当前计算机系统时间。
void CreateFile(int n,int m):在当前文件夹下,按照.\账户名\试卷类型的路径判断文件夹是否存在,若不存在则生成新的文件夹。
void StoreFile(string na,int n):将生成的题目以“年-月-日-时-分-秒.txt”的形式保存在账户文件夹下。
int GetFile(string na):获取当前账户以前已生成的所有卷子中的题目。
void CheckFile(string na,int sub,int pronum):对当前新生成的试卷内容与同一老师文件夹下之前生成的题目查重,若存在重复题目就重新生成新的题目替换。
int ChangeModel():切换用户生成试卷的类型或者返回退出系统。
3、函数代码分析与优缺点解读
(1)void SetPrimary(int n)
①代码分析:
spenum代表的是括号的情况,在小学题目里只在含有3/4/5个数字的时候才会产生括号。比如随机得到的数字式子3 23 12 4,然后随机得到的是spenum[1],那么最后的式子就是(3+23)-12-4。除了括号外的加减乘除的计算是在另一个数组里进行随机掉落。即整体是数字先与括号结合,然后数字括号作为整体再和运算符号结合。
但经考虑校验不难发现,括号的枚举情况是不全面的,而且由于int类型起始数字0会被自动省略,无法确保spenum[]内部数字的位数相同,这些原因都致使SetPrimary没有真正实现涵盖全部情况的随机生成。
以表达式中含3个操作数为例,利用了整数的取余取模运算实现到字符的逻辑映射关系。操作数为4和5个时的原理相同。
if(numb==3) { int spenum[]={0,120,12}; cc=rand()%3; for(int i=0;i<3;i++) { spe[i]=spenum[cc]%10; spenum[cc]=spenum[cc]/10; } }
括号外的加减乘除的运算符随机生成与数字括号结合。
for(int i=0;i<numb;i++) { bb=rand()%100+1; itoa(bb,c,10); if(spe[down]==1) { g_a[n]+=spesign[1]; } g_a[n]+=c; if(spe[down]==2) { g_a[n]+=spesign[2]; } if(numb-1==i) { break; } g_a[n]+=signal[(rand()%4)] ; down--; } g_a[n]+=‘=‘;
②优点:解题思路新颖。
虽然没有完整实现一个表达式插入括号的随机化,但是在队友自己设定的几种插入括号的可能情况中,队友巧妙的利用了整数的取余取模运算实现到字符的逻辑映射关系,这个解题思路很新颖。
③缺点:未完全实现括号插入的随机化。
队友默认一个数字的至多添加一对括号,忽略了诸如(((2+3)+4)+5)+6=,(2+3)+(4+5)=等的合理性情况。对括号的可能性采取枚举的方法,因而该功能代码的拓展性不强。
(2)void SetMiddle(int n)
①代码分析:
通过随机数的生成、int类型与string类型的转换、字符串的操作,结合数组,实现数字和运算符号的组合表达式。
char c[5]; int numb=rand()%4+1,pnum,csign,sum=0; for(int i=0;i<numb;i++) { pnum=rand()%100+1; csign=rand()%3; sum+=csign; itoa(pnum,c,10); if(csign==0) { g_a[n]+=spesign[0]; } g_a[n]+=c; if(csign==1) { g_a[n]+=spesign[1]; } if(numb-1==i) { break; } g_a[n]+=signal[(rand()%4)] ; }
②优点:常见语句书写规范。
③缺点:功能考虑情况不全面,没有括号的随机插入。
char signal[]={‘+‘,‘-‘,‘*‘,‘/‘}; string spesign[]={"√","^2"};
(3)void SetHigh(int n)
方法与void SetMiddle(int n)基本一致,此处不再赘述。
功能考虑情况不全面,没有括号的随机插入。
(4)string GetTime()
①代码分析:
获得系统时间。
string GetTime() { time_t t = time(0); char clock[30],ymd[30]; strftime(ymd,sizeof(ymd),"%Y-%m-%d",localtime(&t) ); string stime(ymd); stime+=‘-‘; strftime(clock,sizeof(clock),"%X ",localtime(&t) ); string ss(clock); for(int i=0;i<ss.length();i++) { if(ss[i]==‘:‘) { stime+=‘-‘; } else stime+=ss[i]; } return stime; }
②优点:灵活应用函数解决问题。
(5)void CreateFile(int n,int m)
void CreateFile(int n,int m) { string folderPath = ".\\"+g_name[n]; if (access(folderPath.c_str(), 0)!=0) { mkdir(folderPath.c_str()); } if(m==1) { folderPath+="\\小学"; } else if(m==2) { folderPath+="\\初中"; } else { folderPath+="\\高中"; } if (access(folderPath.c_str(), 0)!=0) { mkdir(folderPath.c_str()); } }
优点:灵活应用函数解决问题。
(6)void StoreFile(string na,int n)
void StoreFile(string na,int n) { string name=na+".txt"; ofstream csout(name.c_str(),ios::out); for(int i=0;i<n;i++){ csout<<i+1<<‘:‘<<g_b[i]<<endl; csout<<endl; } csout.close(); cout<<"*****试卷已保存至 "<<name<<" 中*****"<<endl; }
(7)int GetFile(string na)
①代码分析
利用文件流,将同账户下所有的文件中的题目都整合保存在一个string数组中,便于之后的使用。
②优点:灵活应用函数解决问题。
(8)void CheckFile(string na,int sub,int pronum)
①代码分析
整体我觉得这个函数逻辑可以分成三个阶段:一个是对当前账户已经存在的文件的题目预处理,进行一个计数排序。然后是有选择性的对长度相同的题目进行查重。最后是如果有重复就重新生成题目然后替换。
把该账户以前的题目按题目长短排序,然后新产生的题目直接和他字数相同的题目进行比较,如果没有字数相同的那就可以直接过掉,用ifstream读取输出的时候没有发现有空行。
首先应用了一个计数排序。第一出现时count是存对应长度的string的个数,count[2]=3意味着长度为3的字符串有3个。sum1存对应长度的string的个数。之后count存的是包括比他长度小以及等于的字符串个数。Str[]存储的是按长度排序后的字符串。Replace[]是记录重复题目编号的数组。while循环开始正式查重。pronum是产生的题目数量。查重时根据字数查重,只查过去题目中题目长度和当前题目长度相同的题目,flag记录重复题目数量。switch(sub)这里sub是题目的难度,然后进行题目替换,且只对重复的题目替换。g_b就是最后得到的题目。
这个子函数的代码还是有细细阅读的必要的。
void CheckFile(string na,int sub,int pronum) { int count[30]={0},sum1[30]={0},judge[30]={0},flag=0,replace[30]={0},an=0; int k=GetFile(na); string str[k]; for(int i=0;i<k;i++) { int number=g_s[i].length()-1; count[number]++; } sum1[0]=count[0]; for(int i=1;i<40;i++) { sum1[i]=count[i]; count[i]+=count[i-1]; } memcpy(judge,count,sizeof(count)); for(int i=k-1;i>=0;i--) { int number=g_s[i].length()-1; str[count[number]-1]=g_s[i]; count[number]--; } for(int i=0;i<30;i++) { replace[i]=-1; } for(int i=0;i<pronum;i++) { g_b[i]=g_a[i]; } while(1) { flag=0; for(int i=0;i<pronum;i++) { int number=g_b[i].length()-1,m; if(sum1[number]==0){continue;} else{ for(int j=judge[number-1];j<judge[number];j++) { if(g_b[i]==str[j]){ replace[flag]=i; flag++; break; } } } } if(flag==0) { break; } switch(sub) { case 1: SetPrimary(flag); break; case 2: SetMiddle(flag); break; case 3: SetHigh(flag); break; default: break; } for(int i=0;i<30;i++) { if(replace[i]!=-1) { g_b[replace[i]]=g_a[an]; an++; replace[i]=-1; } else break; } } cout<<"***** 得到的卷子中的题目是: *****"<<endl; for(int i=0;i<pronum;i++) { cout<<i+1<<"、 "<<g_b[i]<<endl; } cout<<endl; }
②优点
我个人觉得队友这个函数写的很棒。其实文件的查重我个人觉得在整体工程性能是个有很大优化空间的模块,可实现的方式有很多种,但不同种的写法根据对文件流的应用在性能上的差别还是很大的。队友相当于对所有的已有题目进行了一个预处理放在一个string数组中,看似可有可无的预处理却实际是在后面的操作进行操作对象的排除,这一点是在实际项目的制作过程中需要注意的。
(9)int ChangeModel()
①代码说明:
简单的字符串与int的逻辑映射。
(10)int main()
①代码说明:
中规中矩的根据程序使用逻辑实现了各个子模块的函数调用。
4、项目代码整体优点缺点点评总结
(1)优点
①函数命名规范
②常见语句书写书写规范
③灵活应用类似mkdir(),access(),strftime(),itoa(),memcpy()等函数解决实际问题。void CheckFile(string na,int sub,int pronum) 模块设计思路较为新颖,应用到了计数排序。多处利用了类似int和string不同类型数据之间逻辑映射以更方便的编写代码。
④代码中将各个功能模块尽可能细分然后编写子函数,这样代码的模块化很清晰,方便代码的正确定检验和逻辑的梳理。
(2)缺点
①功能实现不完整。小学类型出题没有考虑括号插入的全部情况。初中高中类型的试卷忽略了括号运算符。
②代码对初读者的可读性还有待提高,建议在关键思路的地方给予简明的注释。
③部分变量命名不规范
5、优化意见
(1)完善void SetPrimary(int n),void SetMiddle(int n),void SetHigh(int n)功能。
(2)在代码中添加适当的注释增加代码的可读性。
6、个人感悟
同样的一个项目,同样的一份项目需求,但是不同的人用自己不同的思路就可以有很多细节处理不用的方式。通过仔细阅读队友的代码,认真的分析队友的设计思路,再结合自己实现时出现的难点和坑点进行实现的比较的过程,自己学习到了很多,比如复习了C++的一些相关函数,同时自己的思路也得到了开拓,对同一个问题的实现多样性有了比之前更浓厚的兴趣。很享受这种学习模式带来的成就感,感觉很smart。
原文地址:https://www.cnblogs.com/LiuZengyu/p/11552965.html