上一周,我们完成了个人项目编程。看过我队友的源码之后,我感触颇深,觉得非常有必要来写一篇文章分析一下队友的代码。他的代码是用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()函数,将不同账户不同类型的题目分到不同的文件夹。在看他代码的过程中我也学到了一些有用的函数。