#include "implement.h"
int main(){
string str1;
while(1){
system("cls");
My_Login();
here:
Create_test();
cout<<"======================================================================================"<<endl;
cout<<"是否继续出题(Y/N)"<<endl;
cin>>str1;
if(str1=="Y")
{ system("cls");
goto here;
}
system("pause");
}
}
可以看到.cpp中使用了两个主要的函数,一个是My_Login()函数,另一个是Create_test()函数,顾名思义,前者是登录函数,后者是生成卷子的函数。简略浏览了函数之后我们再看到主函数的基本逻辑结构,首先定义了一个string类型的str1,从后文可以看出来该str1的内容选项只有Y/N,分别表示继续出题和结束出题,当str1为“Y”的时候结合运用goto、here来实现重复出题的功能。至此,主函数的基本逻辑已经理清,接下来让我们深入主函数中调用的两个函数。
下面进入My_Login()函数:
1 void My_Login(){
2 int flag=0; //检验标准
3 string checkName;
4 string checkPassword;
5 string checkType;
6 cout<<"=========================欢迎来到中小学数学卷子自动生成程序!=========================="<<endl;
7 cout<<"======================================================================================"<<endl;
8 cout<<"输入用户名和密码,两者之间用空格隔开:"<<endl;
9 while(1)
10 {
11 ifstream inFile("teacher_check.txt");
12 cin>>tea.name>>tea.password;
13 // cout<<tea.name<<"-"<<tea.password<<endl;
14 if(!inFile)
15 {
16 cerr<<"open wrong!"<<endl;
17 exit(1);
18 }
19 while(inFile>>checkName)
20 {
21 //cout<<checkName<<" ";
22 if(checkName==tea.name)
23 {
24 inFile>>checkPassword;
25 if(checkPassword==tea.password)
26 {
27 //cout<<"kkk"<<" ";
28 flag=1;
29 inFile>>checkType;
30 tea.type=checkType;
31 cout<<"======================================================================================"<<endl;
32 cout<<"登录成功!当前选择为"<<tea.type<<"出题: "<<endl;
33 break;
34 }
35 }
36 }
37 if(flag==0)
38 {
39 cout<<"======================================================================================"<<endl;
40 cout<<"请输入正确的用户名、密码!"<<endl;
41
42 }
43 else break;
44 }
45 inFile.close();
46
47 }
该登录函数首先定义了一个flag变量来作为是否登录成功的检验标准,再通过第11行的代码我们可以看出队员将老师的账号和密码信息先储存在一个文件中,当用户输入了账号密码后,程序首先会打开文件流找到储存账号密码的文件,然后将所有账号密码读出并遍历一次,如果存在对应的账号密码则通过,无则返回错误信息。
值得注意的一个小细节是队员在检查账号密码时是将账号和密码分先后顺序检查的,先遍历一遍账号,如果存在对应的账号则继续遍历密码,不存在则结束遍历返回错误信息,这种遍历方法有效的减少了遍历的次数,提高了程序运行效率。
登录程序浏览完成,下面进入Create_test()函数:
1 void Create_test()
2 {
3 string k,s;
4 now_type=tea.type;
5 cout<<"是否需要更换题目类型?(Y/N) 当前出题类型为:"<<now_type<<endl;
6 cin>>k;
7 if(k=="Y")
8 {
9 cout<<"======================================================================================"<<endl;
10 cout<<"命令行输入“切换为XX”可切换出题类型,XX为小学、初中和高中三个选项中的一个"<<endl;
11 while(1)
12 {
13 cin>>s;
14 if(s=="切换为小学") { now_type="小学"; break;}
15 else if(s=="切换为初中") { now_type="初中"; break; }
16 else if(s=="切换为高中") { now_type="高中"; break; }
17 else
18 {
19 now_type=tea.type;
20 //cout<<tea.type<<endl;
21 cout<<"请输入正确的出题类型!(输入应为“切换为XX”,XX为小学、初中和高中)"<<endl;
22 }
23 }
24
25 cout<<"======================================================================================"<<endl;
26 cout<<"准备生成"<<now_type<<"数学题目,请输入生成题目数量(有效值为【10,30】,输入-1将退出当前用户,重新登录):"<<endl;
27 }
28 else
29 {
30 now_type=tea.type;
31 cout<<"======================================================================================"<<endl;
32 cout<<"准备生成"<<now_type<<"数学题目,请输入生成题目数量(有效值为【10,30】,输入-1将退出当前用户,重新登录):"<<endl;
33 }
34
35 while(1)
36 {
37 cin>>question_num;
38 if(question_num==-1)
39 {
40 exit_flag=1;
41 break;
42 }
43 else if(question_num<q_MIN||question_num>q_MAX)
44 {
45 cout<<"======================================================================================"<<endl;
46 cout<<" 请输入有效的题目数量,题目数量应在【10,30】的区间中: "<<endl;
47 question_num=0;
48 }
49 else
50 {
51 Create_Doc();
52 break;
53 }
54 }
55 if(exit_flag==1)
56 {
57 system("cls");
58 cout<<" 感谢使用本程序,欢迎下次使用! "<<endl;
59 cout<<"======================================================================================"<<endl<<endl;
60 My_Login();
61 }
62 }
可以看到进入出卷程序的初始环节便是询问是否更换出题类型,在确定了最后的出题类型后才进入正式的出题环节。
在代码中我们看到出现了一个exit_flag变量来主导整个程序的走向,根据exit_flag的取值将程序分为几个分支,这样的处理使得程序比较简洁易懂,同时实行效率也高。
当用户输入了正确的出题数量后我们看到程序会运行一个Create_Doc()函数,话不多说直接进入该函数:
1 void Create_Doc()
2 {
3 int N=question_num;
4 fileName=get_now()+".txt";
5 filePath="..\\..\\testCreater\\"+tea.name+"\\";
6 fileName=filePath+fileName;
7 sumFile="..\\..\\testCreater\\"+tea.name+".txt";
8 ofstream ofs;
9 ofs.open(fileName,ios::out);
10 sum_ofs.open(sumFile,ios::app);
11 if(ofs.is_open())
12 {
13
14 while(N>0)
15 {
16 left_bracket=0;
17 right_bracket=0;
18 N--;
19 quess="";
20 quess+="第"+to_string(question_num-N)+"题: ";
21 Create_Arithmetic();
22 quess+="= ";
23 cout<<quess<<endl;
25
26 if(ques.find(quess)==ques.end()&&check_plagiarism(quess.substr(7)))
27 {
28 ques.insert(quess);
29 ofs<<quess<<endl<<endl;
30 sum_ofs<<quess<<endl<<endl;
31 }
32 else N++;
33
34 }
35 ques.clear();
36 ofs.close();
37 sum_ofs.close();
38 cout<<"写入文件成功!"<<endl;
39 }
40 else
41 cout<<"写入文件失败!"<<endl;
42 }
可以看到该函数主要是对文件流进行操作,经过分析可知这里是最后将题目插入.txt即生成卷子的环节,所以该函数中肯定还存在一个生成题目的函数,Create_Arithmetic()函数映入眼帘,在该函数之前string类型的quess首先给自己添加了题号(第20行),在该函数之后quess又给自己添加了“=”(第22行),由此可知,该函数是一个只生成题干的函数。暂且跳过该函数接着往下看,我们看到第26行是一个对生成的题目进行查重的步骤,查重函数为check_plagiarism()函数,在对该函数进行分析之前,我们先回到第9行和第10行,这里打开了两个文件,一个是ofs,另一个是sum_ofs,经过分析可知ofs是储存当前出题试卷的文件,sum_ofs是储存该用户出过的所有题的文件,我们很容易联想到该操作与查重有关,因为我们查重的要求是一个用户不允许出现与以前出过的试卷中一样的题目。
查重函数分析过后我们进入最后的出题函数Create_Arithmetic()函数:
void Create_Arithmetic()
{
int oper_num=rand() % 4+ 2;
int o_num=oper_num;
int type_flag=0;//初高中必需题
cout<<oper_num<<" ";
if(oper_num>2&&rand()%2==1) //生成括号否
{
left_bracket=rand()%(oper_num-1)+1;
right_bracket=rand()%(oper_num-left_bracket)+(left_bracket+3);
if(left_bracket==1&&right_bracket==oper_num+2)
{
right_bracket=oper_num+1;
}
o_num+=2;
}
int i=1;
while(i!=(o_num+1))
{
if(i==left_bracket)
{
quess+="(";
i++;
continue;
}
else if(i==right_bracket)
{
quess+=")";
i++;
continue;
}
int op=0;
if(now_type=="小学") op=0;
else if(now_type=="初中")
{
op=rand()%3;
if(op) type_flag=1;
if(i==o_num&&type_flag==0) op=rand()%2+1;
}
else
{
op=rand()%4;
if(op>2) type_flag=1;
if(i==o_num&&type_flag==0) op=3;
}
append_sym(op,i,o_num);
i++;
}
}
整体来看,队员采取的出题方式是全随机的,包括括号存在与否,操作数的范围以及操作符的种类,唯一确定的是在某个出题范畴内随机。值得注意的是队员出题是采取递进式的出题方式,首先确定的是该题是否需要括号,在确定下来后再确定该题的类型,不同的类型将出题方式分为几个分支,这样的处理方式有条有理,且只需要一个函数便可解决多种题目的出题,值得学习。