个人项目要求
用户:小学、初中和高中数学老师。
功能:
1、命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表),如果用户名和密码都正确,将根据账户类型显示“当前选择为XX出题”,XX为小学、初中和高中三个选项中的一个。否则提示“请输入正确的用户名、密码”,重新输入用户名、密码;
2、登录后,系统提示“准备生成XX数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):”,XX为小学、初中和高中三个选项中的一个,用户输入所需出的卷子的题目数量,系统默认将根据账号类型进行出题。每道题目的操作数在1-5个之间,操作数取值范围为1-100;
3、题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录),程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子(具体要求见附表)。同一个老师的卷子中的题目不能与以前的已生成的卷子中的题目重复(以指定文件夹下存在的文件为准,见5);
4、在登录状态下,如果用户需要切换类型选项,命令行输入“切换为XX”,XX为小学、初中和高中三个选项中的一个,输入项不符合要求时,程序控制台提示“请输入小学、初中和高中三个选项中的一个”;输入正确后,显示“”系统提示“准备生成XX数学题目,请输入生成题目数量”,用户输入所需出的卷子的题目数量,系统新设置的类型进行出题;
5、生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行;
附表-1:账户、密码
账户类型 |
账户 |
密码 |
备注 |
小学 |
张三1 |
123 |
|
张三2 |
123 |
|
|
张三3 |
123 |
|
|
初中 |
李四1 |
123 |
|
李四2 |
123 |
|
|
李四3 |
123 |
|
|
高中 |
王五1 |
123 |
|
王五2 |
123 |
|
|
王五3 |
123 |
|
附表-2:小学、初中、高中题目难度要求
|
小学 |
初中 |
高中 |
|
难度要求 |
+,-,*./ |
平方,开根号 |
sin,cos,tan |
|
备注 |
只能有+,-,*./和() |
题目中至少有一个平方或开根号的运算符 |
题目中至少有一个sin,cos或tan的运算符 |
|
测试
首先进行了黑盒测试,运行队友的程序。
输入:张三1 123 20 Yes 切换为高中 20 No -1 李四1 123
输出:
分析:主要功能都可以实现,出题成功后屏幕上会显示生成文件的路径,非常清晰,用户可以轻松找到想要的试卷。个人项目要求上写着每个但是在试卷中每个题目之间没有空一行,
代码
1. Main.h
//定义老师结构体
struct Teacher {
string name; //账户
string password; //密码
string type; //类型
}teacher;
//符号定义
string primarysymbols[]={"+","-","*","/"},
juniorsymbols[]={"√","^2"},
seniorsymbols[]={"sin","cos","tan"};
//全局变量定义
char directorybuffer[100]; //保存当前目录绝对路径
int exitflag=0; //-1退出登录
int gradeflag=0; //1小学,2初中,3高中
string timepath; //时间文件名
string historypath; //历史题库文件路径
//void Login():账户登录模块
//输入:用户名和密码
//功能:登录成功返回可执行出题操作,否则提示输入正确用户名和密码
void Login();
//void Question():出题模块
//输入:题目数量和切换类型等命令
//功能:将题目输出到相应账户文件下的文件
void Question();
在这个文件做了全局变量和函数的定义。首先定义了老师结构体,方便存储用户信息。然后定义了所有的全局变量。最后是对函数的定义。
由于对每个变量和函数都有一个注释,标出了每个函数的输入与功能,方便其他人对代码的理解。
2.Function.h
1.Login()函数
输入用户名与密码,使用文件流将输入的密码与保存在文件里的用户信息比较,
如果找到了,就跳出while循环,且loginflag为1.如果loginflag为0,则登录失败。
//账户登录
void Login() {
int loginflag=0; //登录成功为1
string strN; //读取文件中的用户名
string strP; //读取文件中的密码
string strT; //读取文件中的类型
cout<<"*********************欢迎使用中小学数学卷子自动生成程序*********************"<<endl;
cout<<"登录请输入用户名和密码(空格隔开):"<<endl;
while (1) {
ifstream inFile("UserData.txt"); //打开账户信息文件
cin>>teacher.name>>teacher.password; //输入用户名和密码
//文件打开失败处理
if (!inFile) {
cerr<<"文件打开失败!"<<endl;
exit(1);
}
//遍历账户信息文件
while (inFile>>strN) {
if (strN==teacher.name) {
inFile>>strP;
if (strP==teacher.password) { //用户名密码匹配成功
inFile>>strT;
teacher.type=strT;
loginflag=1; //用户名密码匹配成功loginflag为1
cout<<"用户名密码正确,登录成功!"<<endl<<endl;
break;
}
}
}
if(loginflag==0) { //用户名密码不匹配
cout<<"*** 请输入正确的用户名和密码:\n";
} else {
break; //匹配成功退出文件读取
}
inFile.close();
}
2.CreatePaper ()函数
//题目生成
void CreatePaper() {
//输入出题数目
int quesnum; //题数
cout<<"准备生成"<<teacher.type
<<"数学题目,请输入生成题目数量"<<endl
<<"(有效范围为【10,30】,输入-1将退出当前用户,重新登录)"<<endl
<<"题目数量:";
while (1) {
cin>>quesnum;
if (quesnum==-1) { //题数为-1退出登录
exitflag=1;
return;
} else if (quesnum>=10&&quesnum<=30) { //有效题数范围退出循环开始出题
break;
} else { //超出有效范围继续输入
cout<<"!!!输入超出有效范围!!!"<<endl
<<"有效范围为【10,30】,输入-1将退出当前用户,重新登录"<<endl
<<"请重新输入题目数量:";
}
}
//创建试卷前先创建账户文件夹
CreatFolder();
//获取历史题目记录
set <string> quesSet; //定义set容器缓存历史题目
int tinumber=0; //题号
ifstream infile;
infile.open(historypath.c_str()); //打开历史题目文件
string tempstr; //保存单条历史题目
while (infile>>tempstr) { //遍历历史记录文件
quesSet.insert(tempstr);
}
infile.close();
//出题前先打开试卷文件和历史记录文件
ofstream newpaper(timepath.c_str());
ofstream newhistory(historypath.c_str(),ios::app);
//出题
if (newpaper) { //试卷输出文件打开成功开始出题
srand(time(NULL)); //系统时间为随机数种子
while (quesnum) { //完成一个题目quesnum减1,quesnum大于0时继续循环出题
int shunum=rand()%5+1, //操作数数目,1-5随机数
kuonum=rand()%5; //最多括号数目
int distance=0,zuo=0,you=0;//括号距离,左括号数目,右括号数目
int firstkuo=0; //开头有括号为1
int cnt=0;//计数器
string timu;//生成的题目
string tmp;//数字转字符串中介
stringstream str;//数字转字符串
//小学题目
if (teacher.type=="小学") {
//前shunum-1个操作数按左括号、操作数、右括号、运算符顺序生成
while (cnt<shunum-1) {
//1/2概率生成左括号
if(rand()%2>0&&zuo<kuonum) {
zuo++;
distance=1;
timu+="(";
}
//随机生成1-100的操作数
str<<rand()%100+1;
tmp=str.str();//int转string
timu+=tmp;
str.str("");//注意清空stringstream缓冲区
//1/2概率生成右括号
if(rand()%2>0&&you<zuo&&
distance>1) { //避免括号内只有一个操作数的无效括号
you++;
distance=0;//生成右括号distance清零
timu+=")";
}
distance++;
//生成算术符
timu+=primarysymbols[rand()%4];
cnt++;
}
//最后一个操作数按操作数、右括号顺序生成
str<<rand()%100+1; //生成操作数
tmp=str.str();
timu+=tmp;
str.str("");
while (you<zuo) { //补全右括号
timu+=")";
you++;
}
//初中题目
} else if(teacher.type=="初中") {
int squareflag=0;//为1时产生平方
int juniornum=rand()%shunum+1;//根号和平方根的数量,大于1的随机数
//前shunum-1个操作数按左括号、根号、操作数、平方、右括号、运算符顺序生成
while (cnt<shunum-1) {
//1/2概率生成左括号
if (rand()%2>0&&zuo<kuonum) {
zuo++;
distance=1;
timu+="(";
}
//1/2概率当前操作数有根号或平方
if (rand()%2>0&&juniornum>0) {
juniornum--;
if (rand()%2>0) {//1/2概率是根号
timu+=juniorsymbols[0];
} else {
squareflag=1;//否则将产生平方
}
}
//生成1-100操作数
str<<rand()%100+1;
tmp=str.str();
timu+=tmp;
str.str("");
//squareflag为1产生平方
if (squareflag) {
timu+=juniorsymbols[1];
squareflag=0;
}
//1/2概率生成右括号
if (rand()%2>0&&distance>1&&you<zuo) {
you++;
distance=0;
timu+=")";
}
distance++;
timu+=primarysymbols[rand()%4];
cnt++;
}
//最后一个操作数按根号、操作数、平方、右括号顺序生成
if (juniornum>0) { //前面的操作数没产生完根号/平方
//最后一个必定产生,保证至少有一个
if (rand()%2>0) { //1/2概率是根号
timu+=juniorsymbols[0];
} else { //否则产生平方
squareflag=1;
}
}
//生成1-100操作数
str<<rand()%100+1;
tmp=str.str();
timu+=tmp;
str.str("");
//squareflag为1生成平方
if (squareflag) {
timu+=juniorsymbols[1];
squareflag=0;
}
//补全右括号
while (you<zuo) {
timu+=")";
you++;
}
//高中题目
} else if(teacher.type=="高中") {
int circularnum=rand()%shunum+1;//三角的数量,大于1的随机数
int squareflag=0;//为1将产生平方
while (cnt<shunum-1) {
if (rand()%2>0&&zuo<kuonum) { //1/2概率生成左括号
zuo++;
distance=1;
timu+="(";
}
if(rand()%2>0&&circularnum>0) {//1/2概率当前操作数有三角
circularnum--;
int select=rand()%3;//sin、cos、tan三选一
switch(select)
{
case 0:timu+=seniorsymbols[0];break;
case 1:timu+=seniorsymbols[1];break;
case 2:timu+=seniorsymbols[2];break;
}
}
if (rand()%20==10) {//1/20概率当前操作数有根号或平方根
if (rand()%2>0) {//1/2概率是根号
timu+=juniorsymbols[0];
} else {
squareflag=1;
}
}
//生成1-100操作数
str<<rand()%100+1;
tmp=str.str();
timu+=tmp;
str.str("");
//若squareflag为1生成平方
if (squareflag) {
timu+=juniorsymbols[1];
squareflag=0;
}
if (rand()%2>0&&distance>1&&you<zuo) {//1/2概率生成右括号
you++;
distance=0;
timu+=")";
}
distance++;
//生成算数符
timu+=primarysymbols[rand()%4];
cnt++;
}
if (circularnum>0) {//保证至少有一个三角
int select=rand()%3;
switch (select) {
case 0:timu+=seniorsymbols[0];break;
case 1:timu+=seniorsymbols[1];break;
case 2:timu+=seniorsymbols[2];break;
}
}
if (rand()%20==0) {//1/20概率有根号/平方
if(rand()%2==0) //1/2概率为根号
timu+=juniorsymbols[0];
else squareflag=1; //否则为平方
}
//生成1-100操作数
str<<rand()%100+1;
tmp=str.str();
timu+=tmp;
str.str("");
//若squareflag为1生成平方
if (squareflag) {
timu+=juniorsymbols[1];
squareflag=0;
}
//末尾补齐右括号
while (you<zuo) {
timu+=")";
you++;
}
}
//去除最外层无效括号
int length=timu.length();
if (timu[0]=='('&&timu[length-1]==')') {
timu=timu.substr(1,length-2);
}
timu+="=";
if (quesSet.find(timu)==quesSet.end()) {//find返回为set的末尾说明没有找到相同元素,实现去重
quesSet.insert(timu); //题目缓存在历史题库set
tinumber++;
newpaper<<tinumber;
newpaper<<".";
newpaper<<timu<<endl; //题目加入试卷
newhistory<<timu<<endl; //题目加入历史题库
quesnum--;
}
}
newpaper.close();
newhistory.close();
} else {
cout<<"文件创建错误"<<endl;
}
cout<<"**********************************出题成功**********************************"<<endl;
cout<<"题目地址为:"<<timepath<<endl;
cout<<"****************************************************************************"<<endl;
}
这个函数实现了随机出题与查重功能。首先接收题目量,如果输入-1则exitflag为1,表示退出登录,如果超出了范围则继续输入数量。
然后创建用户文件夹并获取历史题目记录,在这个部分使用了set容器存储历史题目,将存在文件里的所有历史条目存到queSet。
出题前先打开试卷文件与历史记录文件。进入while循环,每生成一个题目,quesnum减一。
生成题目时,随机生成操作数,最多括号数,符号,生成一个题目。在选择操作符时对每个操作符定了概率,保证了初中和高中难度的试卷要求。
最后判断最外面有没有无意义的括号,并使用find函数判断有没有重复的题目,如果没有就将题目加入到试卷和历史题库。出题成功后把文件路径输出到屏幕。
3. Main.c
int main() {
while (1) {
Login();//登录模块调用
Question();//出题模块调用
if (exitflag==1) {
cout<<"*********************************退出成功!*********************************"<<endl<<endl;
exitflag=0;
continue;
}
}
return 0;
}
在main函数里使用while循环,一开始调用Login函数登录,登录成功后调用出题模块,
如果exitflag为1,则重新回到登录模块,并把exitflag置0.
4. 心得体会
通过分析队友的代码,我了解到了解决问题的另一种思路与方法,且在一起写代码的过程中,我感受到了两个人一起讨论的效率是远远高于一个人琢磨的效率,更容易找到问题的突破点。