摘要:本次项目,这里有幸能参看评价梁同学的代码,本人与该代码采用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 };
建立哈希表用于保存密码等相关内容:
/* 声明两张哈希表,user是名字对应密码,level是名字对应的学校等级*/ map<string, string> g_user; map<string, int> g_level; void Initialization(); //初始化两张哈希表
现分析生成题目函数(其余函数内容简单,详见后续贴出的源码)
该函数先通过对操作数个数的判断来设置括号的规范性:
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 }
再通过一个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 }
进行查重,重复即不写入,使n++,重新生成一道新题目:
1 if (Check()) { // 将生成的题目与以前的题目进行比较,若没有重复就将其写入到文件内,重复就n++,重新生成 2 outfile << str_work.str() << endl; 3 } else n++; 4 5 str_work.str(" "); // 清空str_work
优缺点分析:
首先来对代码的不足进行一些分析,改程序可执行的功能中,对于生成文件,若本身没有预先建立相关用户的文件夹,那么试卷文件就不会生成到对应的位置,我认为可以在这里附加一个功能,当这个程序在不同设备上运行时,即使没有建立指定位置的用户文件夹,也可以指定生成相应的文件夹以生成相关用户的试卷,其次关于输错内容的提示,该代码采用的对字符串判断,若长度大于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