上一周,我们完成了个人项目编程。看过我队友的源码之后,我感触颇深,觉得非常有必要来写一篇文章分析一下队友的代码。他的代码是用C++写的,兼顾性能和可读性,下面是我对我队友源码的一些思考。
一、主函数
int main() { string type="",id=""; int number=0; id=login(); cout<<"请输入题目数量(10-30,输入-1则退出当前用户,重新登陆,输入0则表示切换出题类型):"<<endl; while(1) { cin>>number; if(number==-1) { id=login(); cout<<"请输入题目数量(10-30,输入-1则退出当前用户,重新登陆;输入0则表示切换出题类型):"<<endl; } else if(number==0) { cout<<"请输入要切换的类型:小学、初中或高中" <<endl; string s; int count; while(1) { cin>>s; if(s.find("小学")!=s.npos) { type="小学"; cout<<"准备生成小学数学题目,请输入题目数量:"<<endl; cin>>count; CreateProblemforLow(id,type,count); break; } else if(s.find("初中")!=s.npos) { type="初中"; cout<<"准备生成初中数学题目,请输入题目数量:"<<endl; cin>>count; CreateProblemforMid(id,type,count); break; } else if(s.find("高中")!=s.npos) { type="高中"; cout<<"准备生成高中数学题目,请输入题目数量:"<<endl; cin>>count; CreateProblemforHigh(id,type,count); break; } else { cout<<"请输入小学、初中和高中三个选项中的一个"<<endl; } } break; } else { if(id.find("张三")!=type.npos) { type="小学"; CreateProblemforLow(id,type,number); } else if(id.find("李四")!=type.npos) { type="初中"; CreateProblemforMid(id,type,number); } else if(id.find("王五")!=type.npos) { type="高中"; CreateProblemforHigh(id,type,number); } break; } } return 0; }
主函数基本实现了全部需求的逻辑,但逻辑不是非常的清晰,还有循环的嵌套,但函数名、变量名命名规范,从命名即可得知其功能,提升了代码的可读性,在主函数中将出卷类型以及账户名分开,为后续的分情况调用函数以及将题目输出到对应文件夹提供了参数。
二、为小学生成题目:
int CreateProblemforLow(string id,string type,int number) //生成number个小学题目 { string topic[60][30]; for(int i=0;i<60;i++) { for(int j=0;j<30;j++) { topic[i][j]=""; } } srand((unsigned)time(NULL)); int flag=0; //对是否有括号进行标记 int flag2=0; //对括号是否只括了一个操作数进行标记 for(int i=0;i<number*2;i+=2) { int operand=(rand()%(5-1))+2;//操作数数量 int z=2; stringstream temp; temp<<i/2+1; temp>>topic[i][0]; topic[i][1]=". ";//题目序号 for(int j=0;j<operand;j++) { int l_bracket=(rand()%2);//随机选择是否产生括号,0无括号,1有括号 if(flag==0&&l_bracket==1&&j<operand-1&&j>0) //左括号 { topic[i][z]="("; z++; flag=1; } int operand_data=(rand()%100)+1;//操作数 stringstream ss; ss<<operand_data; ss>>topic[i][z];//写入操作数 if(flag==1) { flag2++; } z++; int r_bracket=(rand()%2); if(flag==1&&r_bracket==1&&flag2>1) //右括号 { topic[i][z]=")"; z++; flag=0; flag2=0; } if(j==operand-1&&flag==1) { topic[i][z]=")"; z++; flag=0; flag2=0; } if(j<operand-1) { int sign=(rand()%4)+1; switch(sign) { case 1:topic[i][z]="+";z++;break; case 2:topic[i][z]="-";z++;break; case 3:topic[i][z]="*";z++;break; case 4:topic[i][z]="/";z++;break; } } else { topic[i][z]="="; } } } number*=2; Makefile(number,id,"小学",topic); cout<<"小学试题已生成完毕,请在对应文件夹查看"<<endl; }
出题过程中充分体现了随机的想法,特别是在括号是否出现以及括号的位置考虑了各种可能出现的情况,十分全面,但是,过多的随机数+if语句的组合使得代码显得有些臃肿,而且,似乎没有考虑题目可能重复的情况。关键的、难以理解的地方有简洁明了的注释,恰到好处。看代码的过程中了解了stringstream的用法。
三、为初中生成题目:
int CreateProblemforMid(string id,string type,int number) //生成number个初中题目 { string topic[60][30]; for(int i=0;i<60;i++) { for(int j=0;j<30;j++) { topic[i][j]=""; } } srand((unsigned)time(NULL)); int flag=0; //对是否有括号进行标记 int flag2=0; //对括号是否只括了一个操作数进行标记 for(int i=0;i<number*2;i+=2) { int operand=(rand()%5)+1;//操作数数量 1-5 int p=2; int exp=rand()%2,pos=rand()%operand;//保证至少有一个平方或开根号运算 stringstream temp; temp<<i/2+1; temp>>topic[i][0]; topic[i][1]=". ";//题目序号 for(int j=0;j<operand;j++) { int l_bracket=(rand()%2);//随机选择是否产生括号,0无括号,1有括号 if(flag==0&&l_bracket==1&&j<operand-1&&j>0) //左括号 { topic[i][p]="("; p++; flag=1; } int operand_data=(rand()%100)+1;//操作数 stringstream ss; ss<<operand_data; ss>>topic[i][p];//写入操作数 p++; int ex=rand()%2; if(j==pos) { if(exp==0) topic[i][p++]="^2"; else if(exp==1) topic[i][p++]="^(1/2)"; } else if(ex==1) { if(exp==0) topic[i][p++]="^2"; else if(exp==1) topic[i][p++]="^(1/2)"; } if(flag==1) { flag2++; } int r_bracket=(rand()%2); if(flag==1&&r_bracket==1&&flag2>1) //右括号 { topic[i][p]=")"; p++; flag=0; flag2=0; } if(j==operand-1&&flag==1) { topic[i][p]=")"; p++; flag=0; flag2=0; } if(j<operand-1) { int sign=(rand()%4)+1; switch(sign) { case 1:topic[i][p]="+";p++;break; case 2:topic[i][p]="-";p++;break; case 3:topic[i][p]="*";p++;break; case 4:topic[i][p]="/";p++;break; } } else { topic[i][p]="="; } } } number*=2; Makefile(number,id,type,topic); cout<<"初中试题生成完毕,请在对应文件夹查看"<<endl; }
这个函数的大部分代码是重用了上一个函数的代码,包括为高中生成题目的函数也是一样,只是增加了平方和根号或者三角函数的部分,其实可以想办法将这三个函数合并,可以大大减小代码冗余程度。
四、输出到文件函数:
int Makefile(int number,string account,string type,string topic[60][30]) { time_t t=time(0); char file[30]; strftime(file,sizeof(file),"%Y-%m-%d-%H-%M-%S.txt",localtime(&t)); ofstream openfile((account+'/'+type+'/'+file).c_str()); for(int i=0;i<number;i++) { for(int j=0;j<30;j++) { openfile<<topic[i][j]; } openfile<<endl; } }
这部分基本都是在调用函数,运用了c++的输入输出流,strftime()函数,将不同账户不同类型的题目分到不同的文件夹。在看他代码的过程中我也学到了一些有用的函数。