v0x  

摘要:本次项目,这里有幸能参看评价梁同学的代码,本人与该代码采用c++实现,部分思路相同,但实现方式不同;在思路上,不同最主要体现在对于生成题目功能;接下来具体分析。关于功能要求与需求分析,详见个人项目需求文件。

 

整体分析:该程序是围绕用户(老师)创建一个类,然后围绕这个类对其进行登陆、切换类型、输出题目等功能。代码的注释十分详尽,具体参看下一条目具体分析。

 

具体分析:

用户类:该类中定义了相关变量和声明了实现各个功能的函数,围绕这个类对接下来各个功能的实现。

 1 class Teacher {
 2     /* 该类是一个用户类,用于生成用户所需要的试卷。实现了登录,注销,切换生成试卷种类,生成试卷功能*/ 
 3  private:
 4   string name;// 用户名
 5   string password;// 密码
 6   string path;// 文件保存的路径
 7   int rank; // 所属等级,1为小学,2为初中,3为高中
 8   ofstream outfile;  // 生成试卷时题目写入的文件流
 9   string str; // 用户输入题目的个数或者用户输入的切换为XX。
10   string checkpath; // check文档的路径
11   stringstream str_work; // 储存着生成的一个题目
12  public:
13   bool Login();
14   // 用于登录的函数,登录成功返回true
15   void TypeChange();
16   // 用于当用户想要切换类型的时候,调用此函数来切换想要的类型
17   string CreateFile();
18   // 创造文件的函数,每次用户要生成题目时,该函数都会生成一个以当前时间命名的txt文件。返回一个以用户名+当前时间为名的路径
19   bool Check();
20   // 检查题目是否重复出现过的函数,没有重复出现返回true
21   bool NumOperation(int num);
22   // 用于创造题目的时候,对操作数进行平方,开根号,三角函数等处理的函数。若对操作数进行了当前等级必须的处理返回true(如高中必须要有三角函数)
23   void QuestionProduce();
24   // 生成用户题目的函数 
25 };
Class Teacher

 

建立哈希表用于保存密码等相关内容:

/* 声明两张哈希表,user是名字对应密码,level是名字对应的学校等级*/
map<string, string> g_user;
map<string, int> g_level;
void Initialization(); //初始化两张哈希表 
Hash_map

 

现分析生成题目函数(其余函数内容简单,详见后续贴出的源码)

该函数先通过对操作数个数的判断来设置括号的规范性:

 1 int n = work_num;// 题目个数
 2     path = CreateFile();
 3     outfile.open(path.c_str());
 4     while (n > 0) {
 5       n--;
 6       outfile << work_num - n << ':' << "   "; // 输出题目的序号
 7       int op_size = rand() % 5 + 1; // 操作数个数为1-5
 8       if (rank == 1) op_size = rand() % 4 + 2;  // 若是小学题目,操作数个数应该为2到5
 9       int bracket_left = 0;  // 左括号位置
10       int bracket_right = 0;  // 右括号位置
11       int bracket_exist = rand() % 2; // 为1括号存在,为0括号不存在
12       if (op_size > 2 && bracket_exist == 1) {
13         if (op_size == 3) {
14           bracket_left = rand() % 2 + 1;
15           bracket_right = bracket_left + 1;
16           /* 若表达式有三个操作数,括号最多括住两个操作数,左括号的位置可以是第1,2个操作数前,右括号的位置则是其后一个操作数后*/
17         }
18         if (op_size == 4) {
19           bracket_left = rand() % 3 + 1;
20           if (bracket_left == 3) {
21             bracket_right = bracket_left + 1;   // 同上,当左括号为第三个操作数前时,括号最多括住两个,右括号就在其后一个操作数后的位置。
22           } else bracket_right = bracket_left + rand() % 2 + 1;// 其他情况,可以括住2-3个,右括号可以在左括号后1,2个操作数后。
23         }
24         if (op_size == 5) {
25           bracket_left = rand() % 4 + 1;
26           if (bracket_left == 4) bracket_right = bracket_left + 1; // 同上
27           else if (bracket_left == 3) bracket_right = bracket_left + rand() % 2 + 1; // 同上
28           else bracket_right = bracket_left + rand() % 3 + 1;// 其他情况括号可以括住2-4个,右括号可以在左括号后1,2,3个操作数后
29 
30         }
31       }
bracket

 

再通过一个while循环对操作数逐个进行数学处理:

 1 int op_left = op_size; // op_left表示还剩下多少操作数
 2       while (op_left > 0) {
 3         op_left--;
 4         bool flag = false; // 用来判断初中和高中的题目是否有其要求的数学符号
 5         if (op_left == op_size - bracket_left) str_work << "("; // 到了左括号的位置,将左括号写入到str_work内 ,且在操作数之前。
 6         int op_num = rand() % 100 + 1;
 7         flag = NumOperation(op_num); // 对操作数进行数学处理并写入到str_work内 
 8         if (flag == false && op_left == 0 && rank == 2) {  // 若已经到了最后一个操作数且其前没有要求的数学符号,就给最后一个操作数增添符号,将其写入到str_work内。
 9           switch (rand() % 2) {
10             case 0 :str_work << op_num << "^2";
11               break;
12             case 1 :str_work << "" << op_num;
13               break;
14           }
15         }
16         if (flag == false && op_left == 0 && rank == 3) {
17           switch (rand() % 3) {
18             case 0 :str_work << "sin" << op_num;
19               break;
20             case 1:str_work << "cos" << op_num;
21               break;
22             case 2 :str_work << "tan" << op_num;
23               break;
24           }
25         }
26         if (op_left == op_size - bracket_right) str_work << ")"; // 若已经到了右括号的位置,将右括号写入str_work内,且在操作数后
27         if (op_left >= 1) {
28           switch (rand() % 4) {
29             case 0 :str_work << "+";
30               break;
31             case 1:str_work << "-";
32               break;
33             case 2:str_work << "*";
34               break;
35             case 3:str_work << "/";
36               break;
37           }
38         }
39 
40       }
operand

 

进行查重,重复即不写入,使n++,重新生成一道新题目:

1 if (Check()) {  // 将生成的题目与以前的题目进行比较,若没有重复就将其写入到文件内,重复就n++,重新生成
2         outfile << str_work.str() << endl;
3       } else n++;
4 
5       str_work.str(" "); // 清空str_work
check

 

优缺点分析:

       首先来对代码的不足进行一些分析,改程序可执行的功能中,对于生成文件,若本身没有预先建立相关用户的文件夹,那么试卷文件就不会生成到对应的位置,我认为可以在这里附加一个功能,当这个程序在不同设备上运行时,即使没有建立指定位置的用户文件夹,也可以指定生成相应的文件夹以生成相关用户的试卷,其次关于输错内容的提示,该代码采用的对字符串判断,若长度大于6,即判断为切换模式,但在实际运行中,若输入的是例如15xx,11ss等在范围内的数字加上一段短字符,程序还是提示生成题目成功,这里存在bug,但这里也不吹毛求疵了。

 

       先抑后扬,此时我们再来说说该代码的优点,对于本次个人项目,代码整体只用了300行左右,第一感觉代码模块结构非常清晰,层次明显(较之自己的代码则是想到哪写到哪,跟partner的代码比起来是相形见绌了)。代码中对各个具体的功能都采用了一个函数来实现,围绕teacher类,关于登录,切换模式,生成题目,查重等功能一一被实现,代码清晰易懂,分层清晰,逻辑性清晰,并且程序运行提示也十分清楚,体验良好。同时该项目还采用了哈希表对用户密码等内容进行了存储,进一步提升了代码的可读性,思路清晰。

        总体来说,代码是优秀的,整体来看,代码功能都得到实现并且都清晰易懂,运行提示也十分简明,除去一些小瑕疵,但瑕不掩瑜。并且代码规范良好,命名清晰易懂,缩进规范。对于项目的分析,代码思路的构思都十分优秀,给人良好的逻辑感与层次感。这种层次感与逻辑感整是我的代码非常需要学习的地方。

        现贴出源代码:

  1 #include <iostream>
  2 #include <fstream>
  3 #include <string>
  4 #include <sstream>
  5 #include <time.h>
  6 #include <math.h>
  7 #include <stdlib.h>
  8 #include <map>
  9 using namespace std;
 10 /* 声明两张哈希表,user是名字对应密码,level是名字对应的学校等级*/
 11 map<string, string> g_user;
 12 map<string, int> g_level;
 13 void Initialization(); //初始化两张哈希表 
 14 class Teacher {
 15     /* 该类是一个用户类,用于生成用户所需要的试卷。实现了登录,注销,切换生成试卷种类,生成试卷功能*/ 
 16  private:
 17   string name;// 用户名
 18   string password;// 密码
 19   string path;// 文件保存的路径
 20   int rank; // 所属等级,1为小学,2为初中,3为高中
 21   ofstream outfile;  // 生成试卷时题目写入的文件流
 22   string str; // 用户输入题目的个数或者用户输入的切换为XX。
 23   string checkpath; // check文档的路径
 24   stringstream str_work; // 储存着生成的一个题目
 25  public:
 26   bool Login();
 27   // 用于登录的函数,登录成功返回true
 28   void TypeChange();
 29   // 用于当用户想要切换类型的时候,调用此函数来切换想要的类型
 30   string CreateFile();
 31   // 创造文件的函数,每次用户要生成题目时,该函数都会生成一个以当前时间命名的txt文件。返回一个以用户名+当前时间为名的路径
 32   bool Check();
 33   // 检查题目是否重复出现过的函数,没有重复出现返回true
 34   bool NumOperation(int num);
 35   // 用于创造题目的时候,对操作数进行平方,开根号,三角函数等处理的函数。若对操作数进行了当前等级必须的处理返回true(如高中必须要有三角函数)
 36   void QuestionProduce();
 37   // 生成用户题目的函数 
 38 };
 39 void Initialization() {
 40   string s[9] = {"张三1", "张三2", "张三3", "李四1", "李四2", "李四3", "王五1", "王五2", "王五3"};
 41   for (int i = 0; i < 9; i++) {
 42     g_user[s[i]] = "123";
 43     g_level[s[i]] = i / 3 + 1;
 44   }
 45 }
 46 bool Teacher::Login() {
 47   string pwd = "";  // 密码
 48   string yourname = "";// 用户名
 49   cout << "请输入用户名,密码" << endl;
 50   cin >> yourname >> pwd;
 51   while (g_user[yourname] != pwd) {     // 查询user哈希表,判断用户名和密码是否对应
 52     cout << "你的输入有误,请重新输入" << endl;
 53     cin >> yourname >> pwd;
 54   } 
 55     switch (g_level[yourname]) {  // 查询level哈希表,得到该用户所属的学校等级
 56       case 1:cout << "当前选择为小学出题" << endl;
 57         rank = 1;
 58         cout << "****************************************" << endl;
 59         break;
 60       case 2:cout << "当前选择为初中出题" << endl;
 61         rank = 2;
 62         cout << "****************************************" << endl;
 63         break;
 64       case 3:cout << "当前选择为高中出题" << endl;
 65         rank = 3;
 66         cout << "****************************************" << endl;
 67     }
 68     name = yourname; // 对对象的一些元素进行初始化
 69     checkpath = name + "\\Check.txt";
 70     password = pwd;
 71     QuestionProduce(); // 由于用户可以在生成试卷时重新登录,要求重新登录后仍能生成试卷,故在这里调用了 生成试卷的函数
 72     return true;
 73 }
 74 string Teacher::CreateFile() {
 75   char filename[100]; // 用来保存文件名字
 76   string s = name;  // s为当前路径
 77   struct tm *ptr;    // tm结构体声明的一个指针,该结构体保存着年,月, 日, 时,秒,分。
 78   time_t It = time(NULL);
 79   ptr = localtime(&It);
 80   // 声明一个time_t指针来调用localtime获取当前时间,随后将该时间写到ptr内
 81   strftime(filename, 30, "%y-%m-%d-%H-%M-%S.txt", ptr);//将ptr内的数据写入到filename内
 82   s = s + "\\" + filename; // 当前路径加上filename得到了生成文件的路径
 83   return s;
 84 }
 85 void Teacher::TypeChange() {
 86 
 87   /* 当检测到字符串str为切换到XX,就判断XX是否为小学,初中,高中的一个。若都不是
 88 则进行进行检测,直到输入的XX为小学,初中,高中的一个,随后就将rank切换为用户所需要的那一个*/
 89 
 90   while (1) {
 91     if (str == "切换为小学" || str == "小学") {
 92       rank = 1;
 93       cout << "切换成功" << endl;
 94       break;
 95 
 96     } else if (str == "切换为初中" || str == "初中") {
 97       rank = 2;
 98       cout << "切换成功" << endl;
 99       break;
100     } else if (str == "切换为高中" || str == "高中") {
101       rank = 3;
102       cout << "切换成功" << endl;
103       break;
104     } else {
105       cout << "请输入小学、初中和高中三个选项中的一个" << endl;
106       cin >> str;
107     }
108     cout << "****************************************" << endl;
109 
110   }
111   QuestionProduce();
112 }
113 void Teacher::QuestionProduce() {
114   int work_num = 0; // 题目生成的个数
115   while (1) {
116     string school = "";
117     switch (rank) {
118       case 1: school = "小学";
119         break;
120       case 2: school = "初中";
121         break;
122       case 3: school = "高中";
123     }
124     cout << "准备生成" << school << "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录)" << endl;
125     cout << "****************************************" << endl;
126     cin >> str; // 用户想要生成题目的个数
127     /* str首先会判断其字节数是否大于6,即对应着切换为XX
128      如果是,就会调用切换函数并返回*/
129     if (str.length() >= 6) {
130       TypeChange();
131       return;
132     }
133     work_num = stoi(str); // 输入的不是切换为XX,而是题目个数,采用stoi将其转换为整数并赋值给work_num
134     while (work_num < 10 || work_num > 30) // 对输入题目个数有要求,判断其是否为10-30间
135     {
136       if (str.length() > 6) {
137         TypeChange();
138         return;
139       }
140       work_num = stoi(str);
141       if (work_num == -1) {
142         cout << "退出登录" << endl;    // 用户输入-1,退出登录,重新调用login登录并返回
143         Login();
144         return;
145       } else {
146         cout << "题目生成的数量应该在10-30之间,请重新输入" << endl; // 输入的题目个数不在范围内,重新输入。
147         cout << "****************************************" << endl;
148         cin >> str;
149       }
150     }
151     int n = work_num;// 题目个数
152     path = CreateFile();
153     outfile.open(path.c_str());
154     while (n > 0) {
155       n--;
156       outfile << work_num - n << ':' << "   "; // 输出题目的序号
157       int op_size = rand() % 5 + 1; // 操作数个数为1-5
158       if (rank == 1) op_size = rand() % 4 + 2;  // 若是小学题目,操作数个数应该为2到5
159       int bracket_left = 0;  // 左括号位置
160       int bracket_right = 0;  // 右括号位置
161       int bracket_exist = rand() % 2; // 为1括号存在,为0括号不存在
162       if (op_size > 2 && bracket_exist == 1) {
163         if (op_size == 3) {
164           bracket_left = rand() % 2 + 1;
165           bracket_right = bracket_left + 1;
166           /* 若表达式有三个操作数,括号最多括住两个操作数,左括号的位置可以是第1,2个操作数前,右括号的位置则是其后一个操作数后*/
167         }
168         if (op_size == 4) {
169           bracket_left = rand() % 3 + 1;
170           if (bracket_left == 3) {
171             bracket_right = bracket_left + 1;   // 同上,当左括号为第三个操作数前时,括号最多括住两个,右括号就在其后一个操作数后的位置。
172           } else bracket_right = bracket_left + rand() % 2 + 1;// 其他情况,可以括住2-3个,右括号可以在左括号后1,2个操作数后。
173         }
174         if (op_size == 5) {
175           bracket_left = rand() % 4 + 1;
176           if (bracket_left == 4) bracket_right = bracket_left + 1; // 同上
177           else if (bracket_left == 3) bracket_right = bracket_left + rand() % 2 + 1; // 同上
178           else bracket_right = bracket_left + rand() % 3 + 1;// 其他情况括号可以括住2-4个,右括号可以在左括号后1,2,3个操作数后
179 
180         }
181       }
182       int op_left = op_size; // op_left表示还剩下多少操作数
183       while (op_left > 0) {
184         op_left--;
185         bool flag = false; // 用来判断初中和高中的题目是否有其要求的数学符号
186         if (op_left == op_size - bracket_left) str_work << "("; // 到了左括号的位置,将左括号写入到str_work内 ,且在操作数之前。
187         int op_num = rand() % 100 + 1;
188         flag = NumOperation(op_num); // 对操作数进行数学处理并写入到str_work内 
189         if (flag == false && op_left == 0 && rank == 2) {  // 若已经到了最后一个操作数且其前没有要求的数学符号,就给最后一个操作数增添符号,将其写入到str_work内。
190           switch (rand() % 2) {
191             case 0 :str_work << op_num << "^2";
192               break;
193             case 1 :str_work << "" << op_num;
194               break;
195           }
196         }
197         if (flag == false && op_left == 0 && rank == 3) {
198           switch (rand() % 3) {
199             case 0 :str_work << "sin" << op_num;
200               break;
201             case 1:str_work << "cos" << op_num;
202               break;
203             case 2 :str_work << "tan" << op_num;
204               break;
205           }
206         }
207         if (op_left == op_size - bracket_right) str_work << ")"; // 若已经到了右括号的位置,将右括号写入str_work内,且在操作数后
208         if (op_left >= 1) {
209           switch (rand() % 4) {
210             case 0 :str_work << "+";
211               break;
212             case 1:str_work << "-";
213               break;
214             case 2:str_work << "*";
215               break;
216             case 3:str_work << "/";
217               break;
218           }
219         }
220 
221       }
222       if (Check()) {  // 将生成的题目与以前的题目进行比较,若没有重复就将其写入到文件内,重复就n++,重新生成
223         outfile << str_work.str() << endl;
224       } else n++;
225 
226       str_work.str(" "); // 清空str_work
227     }
228     outfile.close();
229     cout << "题目生成成功" << endl;
230     cout << "***********" << endl;
231 
232   }
233 }
234 bool Teacher::Check() {    // 每个账号中都有一个check文档,该文档保存该账号生成的所有题目
235   fstream checkfile;
236   checkfile.open(checkpath.c_str(), ios::in);
237   string temp;
238   while (getline(checkfile, temp)) {    // check文件的每一行保存一个题目,每次读取check文件中的一个题目与生成的题目进行判断
239     stringstream stemp(temp);        // 若该题目与生成的题目相等,则说明该题重复,将其舍去,并返回。
240     if (stemp == str_work) {
241       return false;
242     }
243   }
244   checkfile.close();  // 若生成的题目并没有重复,就将它增添到check文档
245   ofstream checkfile1;
246   checkfile1.open(checkpath.c_str(), ios::app);
247   checkfile1 << str_work.str() << endl;
248   checkfile1.close();
249   return true;
250 }
251 bool Teacher::NumOperation(int num) {
252   if (rank == 1) { // 若为小学,不对操作数处理,直接写入到str_Work内
253     str_work << num;
254     return false;
255   }
256   if (rank == 2) {     // 若为初中,存在三种情况,根号和平方的处理返回true,不处理返回false
257     switch (rand() % 3) {
258       case 0 :str_work << num << "^2";
259         return true;
260       case 1 :str_work << "" << num;
261         return true;
262       case 2: str_work << num;
263         return false;
264     }
265   }
266   if (rank == 3) {     // 若为高中,有六种情况,三角函数处理返回true,其他返回false
267     switch (rand() % 6) {
268       case 0 :str_work << num << "^2";
269         return false;
270       case 1 :str_work << "" << num;
271         return false;
272       case 2 :str_work << "sin" << num;
273         return true;
274       case 3 :str_work << "cos" << num;
275         return true;
276       case 4 :str_work << "tan" << num;
277         return true;
278       case 5 : str_work << num;
279         return false;
280     }
281   }
282 }
283 
284 int main() {
285   Initialization(); // 初始化map
286   Teacher one_user;
287   one_user.Login(); // 循环运行
288 }
289     
290     
all

 

posted on 2021-09-28 10:34  v0x  阅读(170)  评论(0编辑  收藏  举报