个人项目
第一次接触这种项目,感觉很难却又有点好玩;第二次写博客,感觉这是一个好习惯,希望后面能够继续坚持,加油!
题目要求
用户:小学、初中和高中数学老师。
功能:
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 |
小学 | 初中 | 高中 | |
难度要求 | +,-,*./ | 平方,开根号 | sin,cos,tan |
备注 | 只能有+,-,*./和() | 题目中至少有一个平方或开根号的运算符 | 题目中至少有一个sin,cos或tan的运算符 |
代码思路
画个不是很标准的流程图来表示一些我的代码的思路吧。
代码
废话不多说,直接放代码吧。
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <time.h>
#include <math.h>
#include <stdlib.h>
using namespace std;
class User {
private:
string Passward;
int Number;
string Name;
string Type;
string folderPath;
string Path;
ofstream Paper;
stringstream question;
public:
void SignIn();
void Exam_Paper();
void Symbol(int option,int ope);
void Switch_Type();
void Name_File();
bool Check();
};
void User::SignIn() { //用户登录函数
while(1) {
cout<<"请输入用户名和密码:";
cin>>Name>>Passward;
if(Name=="张三1"||Name=="张三2"||Name=="张三3") {
if(Passward=="123") {
Type="小学";
break;
}
} else if(Name=="李四1"||Name=="李四2"||Name=="李四3") {
if(Passward=="123") {
Type="初中";
break;
}
} else if(Name=="王五1"||Name=="王五2"||Name=="王五3") {
if(Passward=="123") {
Type="高中";
break;
}
}
cout<<"请输入正确的用户名、密码!"<<endl<<endl;
}
cout<<endl<<"*****当前选择为"<<Type<<"出题*****"<<endl<<endl;
//folderPath="E:\\software_engineering\\Personal_job\\"+Name;//确定当前账号对应的文件夹的位置
folderPath=Name;//确定当前账号对应的文件夹的位置
Name_File();//文件命名
Exam_Paper();//生出符合输入的数学试卷
}
void User::Exam_Paper() {
cout<<"准备生成"<<Type<<"数学题目..."<<endl;
cout<<"请输入生成题目数量(输入-1将退出当前用户,重新登录):";
cin>>Number;//输入生成题目数量
while(Number>30||Number<10) {
if(Number==-1) {
cout<<"您已退出,请重新登录!"<<endl<<endl;
SignIn();
break;
}
cout<<"输入的题目数量无效,请重新输入:";
cin>>Number;
}
Paper.open(Path.c_str());
Paper<<"这是一张"<<Type<<"卷子,共有"<<Number<<"道题目。"<<endl<<endl;
int n=Number;
while(n!=0) {//每道题的出题过程,循环Number次即生成Number个题目
n--;
int OperandNumber=rand()%5+1;//随机生成操作数的个数
if(OperandNumber==1&&Type=="小学") {//小学至少是两位操作数
OperandNumber=rand()%4+2;//生成2-5个操作数
}
int oNum=OperandNumber;//保存操作数的个数
int bracket_left=0;//左括号的位置
int bracket_num=0;//括号括住的操作数的个数
if(OperandNumber>2&&rand()%2==1) { //生成的题目有括号
if(OperandNumber==3) {
bracket_left=rand()%2+1;
bracket_num=2;
} else if(OperandNumber==4) {
bracket_left=rand()%3+1;
if(bracket_left==3) bracket_num=2;//位于最后,最多只能括住两个数
else bracket_num=rand()%2+2;//位于前面,可以括住2-3个数
} else {
bracket_left=rand()%4+1;
if(bracket_left==4) bracket_num=2;//只能括住两个的情况
else if(bracket_left==3) bracket_num=rand()%2+2;//可以括住2-3个的情况
else bracket_num=rand()%3+2;//可以括住2-4个的情况
}
}
bool flag=false;//确保初中的每道题目中都至少有平方或者根号,高中的每道题都至少有sin,cos或tan中的一个
while(oNum!=0) {
oNum--;
if(oNum==OperandNumber-bracket_left) question<<"(";
int option;
if(Type=="小学") option=0;
else if(Type=="初中") {
option=rand()%3;
if(option!=0) flag=true;//说明已经生成了平方或者根号中的一个了
if(oNum==0&&flag==false) option=rand()%2+1;
} else if(Type=="高中") {
option=rand()%6;
if(option>2) flag=true;//说明已经生成了平sin,cos或tan中的一个了
if(oNum==0&&flag==false) option=rand()%5+1;//说明一直到最后一个操作数都没有sin,cos或tan,则最后一个必须有
}
int Operand=rand()%100+1;
Symbol(option,Operand);
if(oNum==OperandNumber-bracket_left-bracket_num+1) question<<")";
if(oNum!=0) {
int opt=rand()%4;
switch(opt) {
case 0:
question<<"+";
break;
case 1:
question<<"-";
break;
case 2:
question<<"*";
break;
case 3:
question<<"/";
break;
}
} else question<<"=";
}
question<<endl;
bool check_result=Check();//检查
if(check_result==true) {
int order=Number-n;
Paper<<order<<"、 "<<question.str()<<endl;
question.str(" ");
} else n++;
}
Paper.close();
}
void User::Symbol(int option,int ope) {//对每个操作数进行处理
switch(option) {
case 0://不处理,即小学的题目
question<<ope;
break;
case 1:
question<<ope<<"^2";
break;
case 2://option为0-2时,表示生成初中的题目
question<<"√"<<ope;
break;
case 3:
question<<"sin"<<ope;
break;
case 4:
question<<"cos"<<ope;
break;
case 5://option为0-5时,生成的是高中的题目
question<<"tan"<<ope;
break;
}
}
void User::Switch_Type() { //切换类型选项
string TypeSwitch;
while(1) {
cout<<endl<<"以下为类型切换选项(若需要切换类型,请输入\"切换为XX\",若不需要,输入\"否\",若退出登录请输入\"-1\")"<<endl;
cout<<"请输入:";
cin>>TypeSwitch;
if(TypeSwitch=="否"||TypeSwitch=="") {
Name_File();
Exam_Paper();//生出符合输入的数学试卷
continue;
} else if(TypeSwitch=="-1") break; //退出操作
if(TypeSwitch=="切换为小学"||TypeSwitch=="切换为初中"||TypeSwitch=="切换为高中") {
Type=TypeSwitch.erase(0,6);
Name_File();
Exam_Paper();//生出符合输入的数学试卷
continue;
} else {
while(1) {
cout<<"请输入小学、初中和高中三个选项中的一个:";
cin>>TypeSwitch;
if(TypeSwitch=="小学"||TypeSwitch=="初中"||TypeSwitch=="高中") {
Type=TypeSwitch;
Name_File();
Exam_Paper();//生出符合输入的数学试卷
break;
} else if(TypeSwitch=="-1") break;
}
}
if(TypeSwitch=="-1") break;
}
cout<<"您已退出本次登录!"<<endl;
}
void User::Name_File() {//给文件命名的函数
char FileName[100];
struct tm *ptr;
time_t It=time(NULL);
ptr=localtime(&It);
strftime(FileName,30,"%y-%m-%d-%H-%M-%S.txt",ptr);//以年月日时分秒对输出文件命名
Path=folderPath+"\\"+Type+FileName;
}
bool User::Check() {//查重
string CheckPath=folderPath+"\\Check.txt";//每个账号中都有一个check文档,该文档保存该账号生成的所有题目
fstream CheckFile;
CheckFile.open(CheckPath.c_str(),ios::in|ios::app);
string temp;
while(getline(CheckFile,temp)) {
stringstream stemp(temp);
if(stemp==question) {
return false;
}
}
CheckFile<<question.str();
CheckFile.close();
return true;
}
int main() {
User teacher;//定义用户对象
srand((int)time(0));//生出随机化种子
teacher.SignIn();//用户登录
teacher.Switch_Type();//切换类型
return 0;
}
代码优缺点分析
优点
1、使用类,封装性比较好,所有的操作都在成员函数中进行,而且各个操作的实现互不影响,但是又紧密联系,并且由于在同一个类中,基本上不需要将参数传入传出,因此在修改的时候,尤其在删减变量的时候,不必考虑对其他函数的影响。
2、我最骄傲的是我的生成试卷函数中,对每个操作数使用的Symbol函数,这一个函数将小学、初中、高中的各种情况都包含进去了,因此在后面生成题目时,只需要改变传入的option就可以控制代码选择相应的操作数,避免了各自对小学、初中、高中写代码。
3、生成括号的函数很简洁,是通过选择括号左边的位置和括号括住的操作数的个数来确定的。但是这种方式只适用于这次的,如果操作数多了可能要另外找其中的规律(理论上还是可行的)。这种方式只能生成一个括号,也是一个很大的不足。
缺点
1、check函数形同虚设,并没有实现想要的功能,可是从代码上来看,我的逻辑是可行的,只是不明白为什么不行,如果有看出来的大佬,欢迎留言告知哦,感谢!
2、代码中没有创建文件夹的函数-_-,这样是有问题的。虽然很多人说文件存储或者文件夹的位置应该是放在绝对路径中,但其实我个人觉得,应该放在相对路径中,这样,将代码传给别人(比如说助教)的时候,就会在自己发送的压缩包解压后的文件夹里找到程序生成的文件,而不是让别人到处找。