摘要
本博客是对结对编程队友丑怡丹个人项目:中小学生数学题自动生成系统的分析
一、 实现语言
C++
二、 整体思路
1.首先在teacher.h文件里定义存储教师信息的结构体,各种全局符号、数组、函数体的声明。其中五个函数实现主要功能。
void Init(); //录入教师信息
void sign_in(); //登录
void prepare_exam(); //试卷生成
void creat_txt(); //创建文件
void trans_(); //试卷类型切换
2.在主函数中,各个函数分别实现功能如下:
void Init():
该函数实现录入教师信息的功能,使用文件流,生成TeacherDate.txt的文件,while循环判断读取信息,然后依次录入信息。
void Init() { int n = 0; ifstream inFile( "TeacherDate.txt" ); while (!inFile.eof() && n < 9) { inFile >> t[n].type >> t[n].name >> t[n].password; n++; } inFile.close(); /*test for (int i=0; i<9; i++) { cout << t[i].type << " " << t[i].name << " " << t[i].password << endl; }*/ }
void sign_in()
根据题目要求,登录函数初始会显示================================================
===========中小学数学卷子自动生成程序===========
================================================
====请输入用户名和密码(两者之间用空格隔开)====
输入用户名和密码之后用while循环判断输入的用户名与密码与教师信息里的是否匹配。判断输入的题目数量是否吻合。调用试卷生成函数,生成试卷后在set容器里判断查重,遍历所有之前文件。
void sign_in() //登录 { cout << "================================================" << endl; cout << "===========中小学数学卷子自动生成程序===========" << endl; cout << "================================================" << endl; cout << "====请输入用户名和密码(两者之间用空格隔开)====" << endl; cin >> g_name >> g_password; int i = 0; while (i<9) { if ((g_name==t[i].name) && (g_password==t[i].password)) { t_flag = 1; i++; break; } else i++; } if (t_flag == 0) { cout << "====请输入用户名和密码(两者之间用空格隔开)====" << endl; } else { g_type = t[i-1].type; } }
void creat_txt():
创建存储TXT文件的文件夹,存储给每个账户生成的试卷。这里给的路径是固定的,在D:\\software_engineer\\personal_program\\exam\\"下,用time()函数获取系统时间。
void creat_txt() { //创建文件夹 string floder_path = "D:\\software_engineer\\personal_program\\exam\\"+g_name; if (_access(floder_path.c_str(), 0) == -1) //如果文件夹不存在 _mkdir(floder_path.c_str()); //则创建 //创建txt文件 FILE *fp = NULL; time_t now; char name[100] = {0}; time(&now); //获取从1970至今过了多少秒,存入time_t类型的timep strftime( name, sizeof(name), "%Y-%m-%d-%H-%M-%S.txt",localtime(&now) ); //最后一个参数是用localtime将秒数转化为struct_tm结构体 time_path = floder_path+"\\"+(string)name; if((fp = fopen(time_path.c_str(), "w")) == NULL) perror(""); fclose(fp); }
void prepare_exam()
创建文件后,根据操作个数,账号类型,判断生成试卷种类,主体结构用if语句实现。其中使用随机数,随机生成操作数。
void prepare_exam() { cout << "==============准备生成"<< g_type <<"数学题目==============" << endl << "=====请输入生成题目数量(有效范围为[10,30])====" << endl << "=========输入-1将退出当前用户,重新登录=========" << endl; while(1) { cin >> ques_num; if (ques_num == -1) { g_exitflag = 1; return; } else if ((ques_num>=10) && (ques_num<=30)) { cout << "==============正在生成题目,请稍后==============" << endl; break; } else { cout << "======!!!不符合题目数量的有效输入范围!!!=====" << endl << "===请重新输入生成题目数量(有效范围为[10,30])==" << endl; } } creat_txt(); //获取历史题目记录 set <string> ques_set; //定义set容器缓存历史题目 int tnum = 0; //题号 ifstream infile; infile.open(history_path.c_str()); //打开历史题目文件 string temp_ques; //保存单条历史题目 while (infile>>temp_ques) { //遍历历史记录文件 ques_set.insert(temp_ques); } infile.close(); //出题前先打开试卷文件和历史记录文件 ofstream ofile; ofstream newpaper(time_path.c_str()); ofstream newhistory(history_path.c_str(),ios::app); //出题 if (newpaper) { //试卷输出文件打开成功开始出题 srand(time(0)); //设置系统时间为随机数种子 while (ques_num) { //完成一个题目quesnum减1,quesnum大于0时继续循环出题 int opera_num = rand()%5+1; //操作数数目,1-5随机数 int brac_num = rand()%5; //最多括号数目 int distance=0, lnum=0, rnum=0; //括号距离,左括号数目,右括号数目 int first_flag = 0; //开头有为1 int cnt = 0; //计数器 string ques_str; //生成的题目 string tmp; //数字转字符串中介 stringstream str; //数字转字符串 if (g_type=="小学") { //前opera_num-1个操作数按左括号、操作数、右括号、运算符顺序生成 while ( cnt < opera_num-1) { //1/2概率生成左括号 if(rand()%2>0 && lnum<brac_num) { lnum++; distance = 1; ques_str+="("; } //随机生成1-100的操作数 str << rand()%100+1; tmp = str.str(); ques_str+=tmp; str.str(""); //清空stringstream缓冲区 //1/2概率生成右括号 if(rand()%2>0 && lnum<rnum && distance>1) //避免括号内只有一个操作数的无效括号 { lnum++; distance=0; //生成右括号distance清零 ques_str+=")"; } distance++; //生成算术符 ques_str+=primarysymbols[rand()%4]; cnt++; } //最后一个操作数按操作数、右括号顺序生成 str << rand()%100+1; //生成操作数 tmp = str.str(); ques_str+=tmp; str.str(""); while (lnum > rnum) { //补全右括号 ques_str+=")"; rnum++; } } else if (g_type=="初中") { bool square_flag = 0; //产生平方标志 int junior_num = rand()%opera_num+1; //根号和平方根的数量,大于1的随机数 //前opera_num-1个操作数按左括号、根号、操作数、平方、右括号、运算符顺序生成 while (cnt < opera_num-1) { //1/2概率生成左括号 if (rand()%2>0 && lnum<brac_num) { lnum++; distance = 1; ques_str+="("; } //1/2概率当前操作数有根号或平方 if (rand()%2>0 && junior_num>0) { junior_num--; if (rand()%2 > 0) { //1/2概率是根号 ques_str+=juniorsymbols[0]; } else { square_flag = 1;//否则将产生平方 } } //生成1-100操作数 str << rand()%100+1; tmp = str.str(); ques_str+=tmp; str.str(""); //squareflag为1产生平方 if (square_flag) { ques_str+=juniorsymbols[1]; square_flag=0; } //1/2概率生成右括号 if (rand()%2>0 && distance>1 && lnum<rnum) { rnum++; distance = 0; ques_str+=")"; } distance++; ques_str+=primarysymbols[rand()%4]; cnt++; } //最后一个操作数按根号、操作数、平方、右括号顺序生成 if (junior_num>0) //前面的操作数没产生完根号/平方 { //最后一个必定产生,保证至少有一个 if (rand()%2>0) { //1/2概率是根号 ques_str+=juniorsymbols[0]; } else { //否则产生平方 square_flag = 1; } } //生成1-100操作数 str << rand()%100+1; tmp = str.str(); ques_str+=tmp; str.str(""); //squareflag为1生成平方 if (square_flag) { ques_str+=juniorsymbols[1]; square_flag=0; } //补全右括号 while (lnum>rnum) { ques_str+=")"; rnum++; } } else if (g_type=="高中") { int cir_num = rand()%opera_num+1; //三角的数量,大于1的随机数 int square_flag = 0; //平方标志 int select = 0; while (cnt<opera_num-1) { if (rand()%2>0 && lnum<brac_num) { //1/2概率生成左括号 lnum++; distance = 1; ques_str+="("; } if (rand()%2>0 && cir_num>0) { //1/2概率当前操作数有三角 cir_num--; select = rand()%3; //sin、cos、tan三选一 switch(select) { case 0:ques_str+=seniorsymbols[0]; break; case 1:ques_str+=seniorsymbols[1]; break; case 2:ques_str+=seniorsymbols[2]; break; } } if (rand()%20==10) { //1/20概率当前操作数有根号或平方根 if (rand()%2>0) { //1/2概率是根号 ques_str+=juniorsymbols[0]; } else { square_flag=1; } } //生成1-100操作数 str << rand()%100+1; tmp = str.str(); ques_str+=tmp; str.str(""); //若squareflag为1生成平方 if (square_flag) { ques_str+=juniorsymbols[1]; square_flag=0; } if (rand()%2>0 && distance>1 && lnum<rnum) { //1/2概率生成右括号 rnum++; distance=0; ques_str+=")"; } distance++; //生成算数符 ques_str+=primarysymbols[rand()%4]; cnt++; } if (cir_num>0) { //保证至少有一个三角int select=rand()%3; switch (select) { case 0:ques_str+=seniorsymbols[0]; break; case 1:ques_str+=seniorsymbols[1]; break; case 2:ques_str+=seniorsymbols[2]; break; } } if (rand()%20==0) { //1/20概率有根号/平方 if(rand()%2==0) //1/2概率为根号 ques_str+=juniorsymbols[0]; else square_flag=1; //否则为平方 } //生成1-100操作数 str << rand()%100+1; tmp = str.str(); ques_str+=tmp; str.str(""); //若squareflag为1生成平方 if (square_flag) { ques_str+=juniorsymbols[1]; square_flag=0; } //末尾补齐右括号 while (lnum>rnum) { ques_str+=")"; rnum++; } } //去除最外层无效括号 int length = ques_str.length(); if (ques_str[0]=='(' && ques_str[length-1]==')') { ques_str = ques_str.substr(1,length-2); } ques_str+="="; if (ques_set.find(ques_str)==ques_set.end()) { //find返回为set的末尾说明没有找到相同元素,实现去重 ques_set.insert(ques_str); //题目缓存在历史题库set tnum++; newpaper << tnum; newpaper << "."; newpaper << ques_str << endl; //题目加入试卷 newhistory << ques_str << endl; //题目加入历史题库 ques_num--; } } newpaper.close(); newhistory.close(); } cout << "===============题目已生成,请查看===============" << endl; trans_type(); }
void trans_type()
if语句判断输入为-1时退出登录。其后切换账户类型,主体结构还是用while循环,内嵌if判断语句实现操作。
void trans_type() { if (g_exitflag==1) { cout << "=============已成功退出,下次再见!=============" << endl; cout << "================================================" << endl; system("cls"); } cout << "================================================" << endl; cout << "===========是否需要切换试卷类型(Y/N)==========" << endl; char choice; string t_type; while(1) { cin >> choice; if (choice=='Y') { cout << "=====请输入小学、初中和高中三个选项中的一个=====" << endl; while(1) { cin >> t_type; if (t_type=="小学" || t_type=="初中" || t_type=="高中") { g_type = t_type; break; } else { cout << "=====请输入正确的切换类型(小学/初中/高中)=====" << endl; } } prepare_exam(); } else if (choice=='N') { prepare_exam(); return; } else { cout << "================================================" << endl; cout << "============请输入正确的选项(Y/N)============" << endl; } } }
int main()
主函数中的流程,首先是教师信息初始化,录入,登录与生产文件。If语句判断输入为-1时退出登录。
int main() { while(1) { Init(); sign_in(); prepare_exam(); if (g_exitflag==1) { cout << "=============已成功退出,下次再见!=============" << endl; cout << "================================================" << endl; } } return 0; }
三、整体代码
1.teacher.h
#include <string> using namespace std; struct Teacher { string name; string type; string password; }; //运算符定义 string primarysymbols[]={"+","-","*","/"}; string juniorsymbols[]={"√","^2"}; string seniorsymbols[]={"sin","cos","tan"}; string g_name; string g_password; string g_type; string history_path; //历史题库文件路径 string time_path; //txt存放路径 bool t_flag = 0; //登陆成功标志位 bool g_exitflag=0; //退出标志 signed int ques_num; //出题数目 void Init(); //录入教师信息 void sign_in(); //登录 void prepare_exam(); //试卷生成 void creat_txt(); //创建文件 void trans_type(); //试卷类型切换
main.cpp
#include <iostream> #include <fstream> #include <string> #include <sstream> #include <set> #include <time.h> #include <stdlib.h> #include <direct.h> #include "Teacher.h" using namespace std; Teacher t[10]; //导入教师信息 void Init() { int n = 0; ifstream inFile( "TeacherDate.txt" ); while (!inFile.eof() && n < 9) { inFile >> t[n].type >> t[n].name >> t[n].password; n++; } inFile.close(); /*test for (int i=0; i<9; i++) { cout << t[i].type << " " << t[i].name << " " << t[i].password << endl; }*/ } void sign_in() //登录 { cout << "================================================" << endl; cout << "===========中小学数学卷子自动生成程序===========" << endl; cout << "================================================" << endl; cout << "====请输入用户名和密码(两者之间用空格隔开)====" << endl; cin >> g_name >> g_password; int i = 0; while (i<9) { if ((g_name==t[i].name) && (g_password==t[i].password)) { t_flag = 1; i++; break; } else i++; } if (t_flag == 0) { cout << "====请输入用户名和密码(两者之间用空格隔开)====" << endl; } else { g_type = t[i-1].type; } } void prepare_exam() { cout << "==============准备生成"<< g_type <<"数学题目==============" << endl << "=====请输入生成题目数量(有效范围为[10,30])====" << endl << "=========输入-1将退出当前用户,重新登录=========" << endl; while(1) { cin >> ques_num; if (ques_num == -1) { g_exitflag = 1; return; } else if ((ques_num>=10) && (ques_num<=30)) { cout << "==============正在生成题目,请稍后==============" << endl; break; } else { cout << "======!!!不符合题目数量的有效输入范围!!!=====" << endl << "===请重新输入生成题目数量(有效范围为[10,30])==" << endl; } } creat_txt(); //获取历史题目记录 set <string> ques_set; //定义set容器缓存历史题目 int tnum = 0; //题号 ifstream infile; infile.open(history_path.c_str()); //打开历史题目文件 string temp_ques; //保存单条历史题目 while (infile>>temp_ques) { //遍历历史记录文件 ques_set.insert(temp_ques); } infile.close(); //出题前先打开试卷文件和历史记录文件 ofstream ofile; ofstream newpaper(time_path.c_str()); ofstream newhistory(history_path.c_str(),ios::app); //出题 if (newpaper) { //试卷输出文件打开成功开始出题 srand(time(0)); //设置系统时间为随机数种子 while (ques_num) { //完成一个题目quesnum减1,quesnum大于0时继续循环出题 int opera_num = rand()%5+1; //操作数数目,1-5随机数 int brac_num = rand()%5; //最多括号数目 int distance=0, lnum=0, rnum=0; //括号距离,左括号数目,右括号数目 int first_flag = 0; //开头有为1 int cnt = 0; //计数器 string ques_str; //生成的题目 string tmp; //数字转字符串中介 stringstream str; //数字转字符串 if (g_type=="小学") { //前opera_num-1个操作数按左括号、操作数、右括号、运算符顺序生成 while ( cnt < opera_num-1) { //1/2概率生成左括号 if(rand()%2>0 && lnum<brac_num) { lnum++; distance = 1; ques_str+="("; } //随机生成1-100的操作数 str << rand()%100+1; tmp = str.str(); ques_str+=tmp; str.str(""); //清空stringstream缓冲区 //1/2概率生成右括号 if(rand()%2>0 && lnum<rnum && distance>1) //避免括号内只有一个操作数的无效括号 { lnum++; distance=0; //生成右括号distance清零 ques_str+=")"; } distance++; //生成算术符 ques_str+=primarysymbols[rand()%4]; cnt++; } //最后一个操作数按操作数、右括号顺序生成 str << rand()%100+1; //生成操作数 tmp = str.str(); ques_str+=tmp; str.str(""); while (lnum > rnum) { //补全右括号 ques_str+=")"; rnum++; } } else if (g_type=="初中") { bool square_flag = 0; //产生平方标志 int junior_num = rand()%opera_num+1; //根号和平方根的数量,大于1的随机数 //前opera_num-1个操作数按左括号、根号、操作数、平方、右括号、运算符顺序生成 while (cnt < opera_num-1) { //1/2概率生成左括号 if (rand()%2>0 && lnum<brac_num) { lnum++; distance = 1; ques_str+="("; } //1/2概率当前操作数有根号或平方 if (rand()%2>0 && junior_num>0) { junior_num--; if (rand()%2 > 0) { //1/2概率是根号 ques_str+=juniorsymbols[0]; } else { square_flag = 1;//否则将产生平方 } } //生成1-100操作数 str << rand()%100+1; tmp = str.str(); ques_str+=tmp; str.str(""); //squareflag为1产生平方 if (square_flag) { ques_str+=juniorsymbols[1]; square_flag=0; } //1/2概率生成右括号 if (rand()%2>0 && distance>1 && lnum<rnum) { rnum++; distance = 0; ques_str+=")"; } distance++; ques_str+=primarysymbols[rand()%4]; cnt++; } //最后一个操作数按根号、操作数、平方、右括号顺序生成 if (junior_num>0) //前面的操作数没产生完根号/平方 { //最后一个必定产生,保证至少有一个 if (rand()%2>0) { //1/2概率是根号 ques_str+=juniorsymbols[0]; } else { //否则产生平方 square_flag = 1; } } //生成1-100操作数 str << rand()%100+1; tmp = str.str(); ques_str+=tmp; str.str(""); //squareflag为1生成平方 if (square_flag) { ques_str+=juniorsymbols[1]; square_flag=0; } //补全右括号 while (lnum>rnum) { ques_str+=")"; rnum++; } } else if (g_type=="高中") { int cir_num = rand()%opera_num+1; //三角的数量,大于1的随机数 int square_flag = 0; //平方标志 int select = 0; while (cnt<opera_num-1) { if (rand()%2>0 && lnum<brac_num) { //1/2概率生成左括号 lnum++; distance = 1; ques_str+="("; } if (rand()%2>0 && cir_num>0) { //1/2概率当前操作数有三角 cir_num--; select = rand()%3; //sin、cos、tan三选一 switch(select) { case 0:ques_str+=seniorsymbols[0]; break; case 1:ques_str+=seniorsymbols[1]; break; case 2:ques_str+=seniorsymbols[2]; break; } } if (rand()%20==10) { //1/20概率当前操作数有根号或平方根 if (rand()%2>0) { //1/2概率是根号 ques_str+=juniorsymbols[0]; } else { square_flag=1; } } //生成1-100操作数 str << rand()%100+1; tmp = str.str(); ques_str+=tmp; str.str(""); //若squareflag为1生成平方 if (square_flag) { ques_str+=juniorsymbols[1]; square_flag=0; } if (rand()%2>0 && distance>1 && lnum<rnum) { //1/2概率生成右括号 rnum++; distance=0; ques_str+=")"; } distance++; //生成算数符 ques_str+=primarysymbols[rand()%4]; cnt++; } if (cir_num>0) { //保证至少有一个三角int select=rand()%3; switch (select) { case 0:ques_str+=seniorsymbols[0]; break; case 1:ques_str+=seniorsymbols[1]; break; case 2:ques_str+=seniorsymbols[2]; break; } } if (rand()%20==0) { //1/20概率有根号/平方 if(rand()%2==0) //1/2概率为根号 ques_str+=juniorsymbols[0]; else square_flag=1; //否则为平方 } //生成1-100操作数 str << rand()%100+1; tmp = str.str(); ques_str+=tmp; str.str(""); //若squareflag为1生成平方 if (square_flag) { ques_str+=juniorsymbols[1]; square_flag=0; } //末尾补齐右括号 while (lnum>rnum) { ques_str+=")"; rnum++; } } //去除最外层无效括号 int length = ques_str.length(); if (ques_str[0]=='(' && ques_str[length-1]==')') { ques_str = ques_str.substr(1,length-2); } ques_str+="="; if (ques_set.find(ques_str)==ques_set.end()) { //find返回为set的末尾说明没有找到相同元素,实现去重 ques_set.insert(ques_str); //题目缓存在历史题库set tnum++; newpaper << tnum; newpaper << "."; newpaper << ques_str << endl; //题目加入试卷 newhistory << ques_str << endl; //题目加入历史题库 ques_num--; } } newpaper.close(); newhistory.close(); } cout << "===============题目已生成,请查看===============" << endl; trans_type(); } void creat_txt() { //创建文件夹 string floder_path = "D:\\software_engineer\\personal_program\\exam\\"+g_name; if (_access(floder_path.c_str(), 0) == -1) //如果文件夹不存在 _mkdir(floder_path.c_str()); //则创建 //创建txt文件 FILE *fp = NULL; time_t now; char name[100] = {0}; time(&now); //获取从1970至今过了多少秒,存入time_t类型的timep strftime( name, sizeof(name), "%Y-%m-%d-%H-%M-%S.txt",localtime(&now) ); //最后一个参数是用localtime将秒数转化为struct_tm结构体 time_path = floder_path+"\\"+(string)name; if((fp = fopen(time_path.c_str(), "w")) == NULL) perror(""); fclose(fp); } void trans_type() { if (g_exitflag==1) { cout << "=============已成功退出,下次再见!=============" << endl; cout << "================================================" << endl; system("cls"); } cout << "================================================" << endl; cout << "===========是否需要切换试卷类型(Y/N)==========" << endl; char choice; string t_type; while(1) { cin >> choice; if (choice=='Y') { cout << "=====请输入小学、初中和高中三个选项中的一个=====" << endl; while(1) { cin >> t_type; if (t_type=="小学" || t_type=="初中" || t_type=="高中") { g_type = t_type; break; } else { cout << "=====请输入正确的切换类型(小学/初中/高中)=====" << endl; } } prepare_exam(); } else if (choice=='N') { prepare_exam(); return; } else { cout << "================================================" << endl; cout << "============请输入正确的选项(Y/N)============" << endl; } } } int main() { while(1) { Init(); sign_in(); prepare_exam(); if (g_exitflag==1) { cout << "=============已成功退出,下次再见!=============" << endl; cout << "================================================" << endl; } } return 0; }
四、优缺点分析
1.优点:
该代码基于C++编程,是我们比较熟悉的,代码逻辑清醒,容易读懂理解。功能全部实现的同时,在用户信息登录的问题上,不仅仅只是利用if循环,利用文件流实现了多组用户信息的录入,不更改代码的基础上,操作更便捷。
2.缺点:并未基于UTF-8编码。同时生成的文件路径是绝对的,不能灵活更改。