结对编程队友互评
- 代码:软件2105何志成
- 评价:软件2105陈相彤
一、题目介绍
用户:
小学、初中和高中数学老师。
功能:
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 |
小学 | 张三1 | 123 |
小学 | 张三1 | 123 |
初中 | 李四1 | 123 |
初中 | 李四1 | 123 |
初中 | 李四1 | 123 |
高中 | 王五1 | 123 |
高中 | 王五1 | 123 |
高中 | 王五1 | 123 |
附表-2:小学、初中、高中题目难度要求
小学 | 初中 | 高中 | |
---|---|---|---|
难度要求 | +,-,*./ | 平方,开根号 | sin,cos,tan |
备注 | 只能有+,-,*./和() | 题目中至少有一个平方或开根号的运算符 | 题目中至少有一个sin,cos或tan的运算符 |
二、代码分析
1. Users(用户类)
class Users
{
public:
// 用户的属性 从上之下分别为用户名 用户密码 用户类型 用户试卷存放路径
// 用户试卷查重文件存放路径
std::string name;
std::string password;
std::string type;
std::string path;
std::string check_path;
// 用户姓名写入函数
void SetName(std::string name1) { name = name1; }
// 用户密码设置函数
void SetPassword(std::string password1) { password = password1; }
// 用户类型设置函数
void SetType(std::string type1) { type = type1; }
// 用户试卷存放路径设置函数
void SetPath(std::string path1) { path = path1; }
// 用户试卷查重文件存放路径设置函数
void SetCheckPath(std::string check_path1) { check_path = check_path1; }
};
由代码可以看出,该类包含了用户名、密码、用户类型等属性和相关方法。格式简洁易读,有条理。如果能考虑安全性,将属性添加至private/protected中就更好了。
2. Usersgroup(用户组类)
class Usersgroup
{
public:
// 存放用户的用户组
std::vector<Users> users;
// 以下函数用于设置用户信息并添加置用户组中 共添加9名用户
void zhangsan1()
{
Users use;
use.SetName("张三1");
use.SetPassword("123");
use.SetType("小学");
use.SetPath(".\\paper\\zhangsan1\\");
use.SetCheckPath(".\\paper\\zhangsan1\\check.txt");
users.push_back(use);
}
void zhangsan2()
{
Users use;
use.SetName("张三2");
use.SetPassword("123");
use.SetType("小学");
use.SetPath(".\\paper\\zhangsan2\\");
use.SetCheckPath(".\\paper\\zhangsan2\\check.txt");
users.push_back(use);
}
void zhangsan3()
{
Users use;
use.SetName("张三3");
use.SetPassword("123");
use.SetType("小学");
use.SetPath(".\\paper\\zhangsan3\\");
use.SetCheckPath(".\\paper\\zhangsan3\\check.txt");
users.push_back(use);
}
void lisi1()
{
Users use;
use.SetName("李四1");
use.SetPassword("123");
use.SetType("初中");
use.SetPath(".\\paper\\lisi1\\");
use.SetCheckPath(".\\paper\\lisi1\\check.txt");
users.push_back(use);
}
// ......
// 以下方法重复,故省略
};
该类创建了一个存放用户的vector,并使用9个方法将各用户所在文件夹中的check文本读入,便于查重使用。将函数封装在类中,便于以后维护。稍微冗余,如果使用C++多态结构则会更好。
3.Paper(试卷类)
class Paper
{
public:
// 生成题目的个数
int question_number;
// 查重文件的路径
std::string paper_check_path;
// 以下三组分别为小学 初中 高中题目用的符号
std::vector<std::string> primary_sign_group{"+", "-", "*", "/", "(", ")"};
std::vector<std::string> junior_sign_group{"+", "-", "*", "/",
"(", ")", "^2", "√"};
std::vector<std::string> high_sign_group{"+", "-", "*", "/", "(",
")", "sin", "cos", "tan"};
// 该组的内容用于写入试卷中
std::vector<std::string> write_in_txt;
// 该组的内容用于写入查重文件中
std::vector<std::string> write_in_check_txt;
// 该函数为CreatePrimaryTopic()函数的分离函数 用于一般的重复添加操作数和操作符
void CreatePrimaryTopic1(std::string &question, int &flag1)
{
// 相关内容省略
}
// 该函数用于生成小学类型的题目 传递参数i+1后对应题号
void CreatePrimaryTopic(int i)
{
// 相关内容省略
}
// CreateJuniorHighTopic()函数的分离函数1 用于重复添加操作数和符号
void CreateJuniorHighTopic1(std::string &question, int &flag1, int &flag2)
{
// 相关内容省略
}
// CreateJuniorHighTopic()分离函数2和3 用于随机加入最初的根号 括号
// 操作数和操作符
void CreateJuniorHighTopic2(std::string &question, int &flag2)
{
// 相关内容省略
}
void CreateJuniorHighTopic3(std::string &question, int &flag1, int &flag2)
{
// 相关内容省略
}
// 该函数用于生成初中题目
void CreateJuniorHighTopic(int i)
{
// 相关内容省略
}
void CreateHighTopic1(std::string &question, int &flag1, int &flag2)
{
// 相关内容省略
}
void CreateHighTopic2(std::string &question, int &flag1, int &flag2)
{
// 相关内容省略
}
// 该函数用于生成高中题目 方法与小学题目生成类似
// 不同于在左括号前后随机添加"tan sin cos"三个符号
void CreateHighTopic(int i)
{
// 相关内容省略
}
// 查重函数,打开用户对应的查重文件,若有重复,则舍弃该新生成的题目
int CheckRepeat(int i)
{
// 相关内容省略
}
// 生成小学题目试卷 即重复i次生成题目的操作
void MakeGroupPrimary()
{
// 相关内容省略
}
// 生成初中题目试卷
void MakeGroupJunior()
{
// 相关内容省略
}
// 生成高中题目试卷
void MakeGroupHigh()
{
// 相关内容省略
}
};
该类是项目最重要的类,负责生成试卷等主要功能。下面是各函数的详细分析:
(1). CreatePrimaryTopic及CreatePrimaryTopic1
void CreatePrimaryTopic1(std::string &question, int &flag1)
{
// t2随机是否添加左括号 添加后flag1加一
int t2 = rand() % 6;
if (t2 == 4)
{
question = question + primary_sign_group[t2];
flag1++;
}
// n2随机添加操作数
int n2 = rand() % 100 + 1;
question = question + std::to_string(n2);
int t3 = rand() % 6;
// 若flag1大于0 可随机是否添加右括号 添加后flag1减一
if (t3 == 5 && flag1 > 0)
{
question = question + primary_sign_group[t3];
flag1 = flag1 - 1;
}
// 随机添加操作符 范围在"+ - * /"中
int t4 = rand() % 4;
question = question + primary_sign_group[t4];
}
// 该函数用于生成小学类型的题目 传递参数i+1后对应题号
void CreatePrimaryTopic(int i)
{
i = i + 1;
// 生成操作数个数
int question_number = rand() % 4 + 1;
// flag1用于标志左括号个数 添加左括号加1 添加右括号减1
int flag1 = 0;
// question存放生成题目
std::string question;
// t0随机抽取符号 若为左括号则加置题目中
int t0 = rand() % 6;
if (t0 == 4)
{
question = question + primary_sign_group[t0];
flag1++;
}
// n1随机生成第一个操作数添置题目中
int n1 = rand() % 100 + 1;
question_number = question_number - 1;
question = question + std::to_string(n1);
// t1随机生成一个操作符添加至题目中,范围在"+ - * /"中
int t1 = rand() % 4;
question = question + primary_sign_group[t1];
//
for (int i = 0; i < question_number; i++)
{
CreatePrimaryTopic1(question, flag1);
}
// 随机添加最后的操作数置题目中
int n3 = rand() % 100 + 1;
question = question + std::to_string(n3);
// 判断flag1是否为0 若不为0说明左右括号数不匹配 添加相应数量的右括号
while (flag1 > 0)
{
question = question + primary_sign_group[5];
flag1 = flag1 - 1;
}
// 将新生成的题目写入查重题目组
write_in_check_txt.push_back(question);
// 添加题目序号
question = std::to_string(i) + "." + question;
// 将新生成的题目写入试卷题目组
write_in_txt.push_back(question);
std::cout << question << std::endl;
}
这两个函数负责生成小学题目,由于40行代码的限制,故分为两部分。经测试和观察代码,功能实现完全,并无明显逻辑问题。但存在小瑕疵,如CreatePrimaryTopic1函数传入的参数中,根据Google代码规范规定,如果要修改传入变量的值,只能传入指针;若按引用传递,则必须加const,因此应改为:
void CreatePrimaryTopic1(std::string *question, int *flag1)
CreatePrimaryTopic1(&question, &flag1);
(2). CreateJuniorHighTopic及CreateJuniorHighTopic1,CreateJuniorHighTopic2,CreateJuniorHighTopic3
void CreateJuniorHighTopic1(std::string &question, int &flag1, int &flag2)
{
// 随机加入根号
int g2 = rand() % 8;
if (g2 == 7)
{
question = question + junior_sign_group[g2];
flag2++;
}
// 随机加入左括号
int t2 = rand() % 6;
if (t2 == 4)
{
question = question + primary_sign_group[t2];
flag1++;
// 随机加入根号
int g3 = rand() % 8;
if (g3 == 7)
{
question = question + junior_sign_group[g3];
flag2++;
}
}
// 随机添加操作数
int n2 = rand() % 100 + 1;
question = question + std::to_string(n2);
// 随机添加平方
int p1 = rand() % 8;
if (p1 == 6)
{
question = question + junior_sign_group[p1];
flag2++;
}
// 随机添加右括号
int t3 = rand() % 6;
if (t3 == 5 && flag1 > 0)
{
question = question + primary_sign_group[t3];
flag1 = flag1 - 1;
int p2 = rand() % 8;
// 随机添加平方
if (p2 == 6)
{
question = question + junior_sign_group[p2];
flag2++;
}
}
// 随机添加操作符 "+ - * /"
int t4 = rand() % 4;
question = question + primary_sign_group[t4];
}
// CreateJuniorHighTopic()分离函数2和3 用于随机加入最初的根号 括号
// 操作数和操作符
void CreateJuniorHighTopic2(std::string &question, int &flag2)
{
int g0 = rand() % 8;
if (g0 == 7)
{
question = question + junior_sign_group[g0];
flag2++;
}
}
void CreateJuniorHighTopic3(std::string &question, int &flag1, int &flag2)
{
int t0 = rand() % 6;
if (t0 == 4)
{
question = question + junior_sign_group[t0];
flag1++;
int g1 = rand() % 8;
if (g1 == 7)
{
question = question + junior_sign_group[g1];
flag2++;
}
}
int n1 = rand() % 100 + 1;
question = question + std::to_string(n1);
int p0 = rand() % 8;
if (p0 == 6)
{
question = question + junior_sign_group[p0];
flag2++;
}
int t1 = rand() % 4;
question = question + primary_sign_group[t1];
}
// 该函数用于生成初中题目
void CreateJuniorHighTopic(int i)
{
i = i + 1;
int question_number = rand() % 4 + 1;
// flag1标志括号数 加左括号加1 右括号减1
int flag1 = 0;
// flag2标志根号和平方操作的总数
int flag2 = 0;
std::string question;
CreateJuniorHighTopic2(question, flag2);
CreateJuniorHighTopic3(question, flag1, flag2);
question_number = question_number - 1;
for (int i = 0; i < question_number; i++)
{
CreateJuniorHighTopic1(question, flag1, flag2);
}
// 添加最后的操作数
int n3 = rand() % 100 + 1;
question = question + std::to_string(n3);
// 右括号判断
while (flag1 > 0)
{
question = question + primary_sign_group[5];
flag1 = flag1 - 1;
}
// 若之前无平方和开方的操作 在最后的操作数作平方操作
if (flag2 == 0)
question = question + junior_sign_group[6];
write_in_check_txt.push_back(question);
question = std::to_string(i) + "." + question;
std::cout << question << std::endl;
write_in_txt.push_back(question);
}
生成初中题目,具体逻辑与小学部分相似,增加了平方和开方操作(有保底机制),出现的问题也同上,这里不加赘述。
(3). CreateHighTopic及CreateHighTopic1,CreateHighTopic2
void CreateHighTopic1(std::string &question, int &flag1, int &flag2) {
int t2 = rand() % 6;
if (t2 == 4) {
question = question + high_sign_group[t2];
flag1++;
int s2 = rand() % 9;
if (s2 > 5 && s2 < 9) {
question = high_sign_group[s2] + question;
flag2++;
}
}
int s3 = rand() % 9;
if (s3 > 5 && s3 < 9) {
question = question + high_sign_group[s3];
flag2++;
}
int n2 = rand() % 100 + 1;
question = question + std::to_string(n2);
int t3 = rand() % 6;
if (t3 == 5 && flag1 > 0) {
question = question + high_sign_group[t3];
flag1 = flag1 - 1;
}
int t4 = rand() % 4;
question = question + high_sign_group[t4];
}
void CreateHighTopic2(std::string &question, int &flag1, int &flag2) {
int t0 = rand() % 6;
if (t0 == 4) {
question = question + high_sign_group[t0];
flag1++;
int s0 = rand() % 9;
if (s0 > 5 && s0 < 9) {
question = high_sign_group[s0] + question;
flag2++;
}
}
}
// 该函数用于生成高中题目 方法与小学题目生成类似
// 不同于在左括号前后随机添加"tan sin cos"三个符号
void CreateHighTopic(int i) {
i = i + 1;
std::string question;
int question_number = rand() % 4 + 1;
// flag1标志括号数量
int flag1 = 0;
// flag2标志三角函数操作个数
int flag2 = 0;
CreateHighTopic2(question, flag1, flag2);
int s1 = rand() % 9;
if (s1 > 5 && s1 < 9) {
question = question + high_sign_group[s1];
flag2++;
}
int n1 = rand() % 100 + 1;
question_number = question_number - 1;
question = question + std::to_string(n1);
int t1 = rand() % 4;
question = question + high_sign_group[t1];
for (int i = 0; i < question_number; i++) {
CreateHighTopic1(question, flag1, flag2);
}
int n3 = rand() % 100 + 1;
question = question + std::to_string(n3);
while (flag1 > 0) {
question = question + high_sign_group[5];
flag1 = flag1 - 1;
}
// 若之前无三角函数操作,则在式子最前面加上sin符号
if (flag2 == 0) {
question = high_sign_group[6] + question;
}
write_in_check_txt.push_back(question);
question = std::to_string(i) + "." + question;
write_in_txt.push_back(question);
std::cout << question << std::endl;
}
高中部分同小学及初中部分,这里不赘述
(6). CheckRepeat
// 查重函数,打开用户对应的查重文件,若有重复,则舍弃该新生成的题目
int CheckRepeat(int i) {
std::ifstream read_check(paper_check_path);
std::string line;
while (getline(read_check, line)) {
if (line == write_in_check_txt[i]) {
write_in_check_txt.pop_back();
write_in_txt.pop_back();
return 1;
}
}
return 0;
}
查重函数,通过读取相应文件夹中的check文件查询是否有题目重复,IO读写直观。
(5). MakeGroupPrimary,MakeGroupJunior,MakeGroupHigh
// 生成小学题目试卷 即重复i次生成题目的操作
void MakeGroupPrimary() {
int check_flag = 0;
for (int i = 0; i < question_number; i++) {
CreatePrimaryTopic(i);
check_flag = CheckRepeat(i);
if (check_flag == 1) i--;
}
}
// 生成初中题目试卷
void MakeGroupJunior() {
int check_flag = 0;
for (int i = 0; i < question_number; i++) {
CreateJuniorHighTopic(i);
check_flag = CheckRepeat(i);
if (check_flag == 1) i--;
}
}
// 生成高中题目试卷
void MakeGroupHigh() {
int check_flag = 0;
for (int i = 0; i < question_number; i++) {
CreateHighTopic(i);
check_flag = CheckRepeat(i);
if (check_flag == 1) i--;
}
}
这三个函数可以生成三种类型的试卷,被CreatePrimaryTopic等函数调用,用于批量生成相关的试卷。
4. Login(主界面类)
class Login {
public:
// 初始化用户 用户组 试卷
Users user;
Paper paper;
Usersgroup user_group;
// 获取当前时间函数
std::string GetSystemTime() {
time_t tt;
struct tm *t;
tt = time(0);
t = localtime(&tt);
char char_time[50] = {};
sprintf(char_time, "%04d-%02d-%02d-%02d-%02d-%02d", t->tm_year + 1900,
t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
std::string str_system_time = static_cast<std::string>(char_time);
return str_system_time;
}
// 登录函数 判断用户和密码是否匹配 若匹配flag为1 登陆成功
void SignIn() {
std::string name;
std::string password;
while (1) {
std::cout << "请输入用户名和密码:" << std::endl;
std::cin >> name >> password;
user.SetName(name);
user.SetPassword(password);
user.SetType(" ");
int flag = 0;
for (int i = 0; i < user_group.users.size(); i++) {
if (user.name == user_group.users[i].name &&
user.password == user_group.users[i].password) {
user.SetType(user_group.users[i].type);
user.SetPath(user_group.users[i].path);
user.SetCheckPath(user_group.users[i].check_path);
flag = 1;
break;
}
}
if (flag == 1) break;
std::cout << "请输入正确的用户名、密码" << std::endl;
}
}
// 文件写入函数 将生成的题目组写入并生成新文档
void WriteInTxt() {
std::string sys_time = GetSystemTime();
std::ofstream file;
std::string write_path = user.path + sys_time + ".txt";
file.open(write_path.c_str(), std::ios::out);
for (int i = 0; i < paper.write_in_txt.size(); i++) {
file << paper.write_in_txt[i] << std::endl;
file << std::endl;
}
file.close();
}
// 查重文件写入函数 将生成的题目追加到对应的查重文件
void WriteInCheckTxt() {
std::ofstream file;
std::string write_path = user.check_path;
file.open(write_path.c_str(), std::ios::app);
if (!file) std::cout << 111 << std::endl;
for (int i = 0; i < paper.write_in_check_txt.size(); i++) {
file << paper.write_in_check_txt[i] << std::endl;
}
file.close();
}
// 获取指令并实现对应具体功能
void GetquestionOrChangeType() {
std::string input;
while (1) {
std::cout
<< "准备生成" + user.type +
"数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录"
"):";
std::cin >> input;
//-1指令推出登录 重新进入登录界面 10-30生成具体题目
if (input == "-1") {
SignIn();
continue;
} else if (input >= "10" && input <= "30") {
paper.question_number = atoi(input.c_str());
paper.paper_check_path = user.check_path;
if (user.type == "小学") paper.MakeGroupPrimary();
if (user.type == "初中") paper.MakeGroupJunior();
if (user.type == "高中") paper.MakeGroupHigh();
WriteInTxt();
WriteInCheckTxt();
paper.write_in_txt.clear();
paper.write_in_check_txt.clear();
continue;
}
// 切换指令
std::string changein(input, 0, 6);
std::string changetype(input, 6, 4);
if (changein == "切换为") {
if (changetype == "小学") {
user.SetType("小学");
} else if (changetype == "初中") {
user.SetType("初中");
} else if (changetype == "高中") {
user.SetType("高中");
} else
std::cout << "请输入小学、初中和高中三个选项中的一个" << std::endl;
}
}
}
};
登录的处理比较恰当,并没有粗略使用if语句来判断用户是否输入正确。然而如果用户类使用多态,则会更直观完善。
获取指令出可能会出现问题:
paper.question_number = atoi(input.c_str());
此处调用atoi()函数,如果参数并不是数字字符串,则会出现报错,应该添加相应的处理方法。
并且此处发现其他bug:
else if (input >= "10" && input <= "30")
此处运算符重载会出现问题,已知10以内可以输出2,30以外可以输出100以外的数,希望改进。
5. main(主函数)
int main()
{
srand(time(0));
Login login;
login.user_group.zhangsan1();
login.user_group.zhangsan2();
login.user_group.zhangsan3();
login.user_group.lisi1();
login.user_group.lisi2();
login.user_group.lisi3();
login.user_group.wangwu1();
login.user_group.wangwu2();
login.user_group.wangwu3();
login.SignIn();
login.GetquestionOrChangeType();
}
主函数安排合理,不复杂,如果能更简洁写会更好。
三、功能测试
登录:
切换用户:
生成题目:
文件保存:
更改用户生成题目:
四、总结
总的来说,该项目虽然并没有采用多态、继承等面向对象的特性,但也并没有将所有内容塞在全局函数内,而是创建多个类完成。逻辑结构清楚,虽然有些bug和未发现的错误,但整体看还是非常可观的代码。
优点:
- 注释较为全面,详细阅读注释便可了解代码内容
- 结构整洁规范,虽没有使用逻辑化的继承关系,仍易读
- 数据结构选取恰当,方便使用
- 可迭代性良好,更新迭代并不用大费周章
已发现的bug&问题:
- Google规范相关
{ should almost always be at the end of the previous line
Never use sprintf. Use snprintf instead. [runtime/printf]
public: should be indented +1 space inside class Login
Is this a non-const reference? If so, make const or use a pointer:
已忽略由于文件编码导致的非UTF-8问题
已忽略为了保护线程安全出现的问题
- 字符串处理相关
if (input == "-1") {
SignIn();
continue;
} else if (input >= "10" && input <= "30") {
paper.question_number = atoi(input.c_str());
paper.paper_check_path = user.check_path;
if (user.type == "小学") paper.MakeGroupPrimary();
if (user.type == "初中") paper.MakeGroupJunior();
if (user.type == "高中") paper.MakeGroupHigh();
WriteInTxt();
WriteInCheckTxt();
paper.write_in_txt.clear();
paper.write_in_check_txt.clear();
continue;
}
此处如果输入小于10或大于30的数,有几率报错(可能是和string类型字符串的运算符重载有关)
而且此处如果输入非数字字符串,同样报错
terminate called after throwing an instance of 'std::out_of_range'
what(): basic_string::basic_string: __pos (which is 6) > this->size() (which is 4)
标签:std,结对,group,string,int,question,互评,flag1,卷子
From: https://www.cnblogs.com/arinera/p/17715677.html