学生成绩管理系统
一、设计任务分析
本课题主要的目的是为了管理学生的成绩。可以录入成绩、查询成绩、删除成绩记录、对成绩进行各种排名等操作。方便考完试后对学生的成绩统一处理。
二、学生成绩管理系统总体方案的设计
2.1、系统的数据库
本系统使用的是文本文档 (.txt)来储存数据。数据库的数据项主要数据包括:学号、姓名、年级、专业、成绩1、成绩2、成绩3、平均成绩、总成绩。
每个数据之间以一个制表符来分隔开,便于后面写入数据和读取数据。由于数据项比较多,因此我设计了两个类来存储这些数据项,分别为成绩类和学生类。
2.2、类说明
- 成绩类(Score):包括成绩1、成绩2、成绩3、平均成绩、总成绩五个数据项。
- 学生类(Student):包括学号、姓名、年级、专业、成绩五个数据项。
- 管理员类(Manager):有存储记录的文件的路径和vector<Student*>类型的students这两个属性,还有添加、删除和查询成绩等各种操作。students储存的是数据库文件中的记录封装成的对象的地址,Manager.cpp中包含了main()函数。
2.3、程序流程
程序开始时先把文本文档中的数据加载到可变长数组students中,便于后面进行操作。显示主页面,让使用者输入选择,然后执行相应的功能。
程序流程图如下:
2.4、功能模块
本系统主要有六个功能,分别为添加功能、删除功能、修改功能、查找功能、显示功能、排序功能。
2.4.1、添加功能(add())
- 执行流程:先输入学号,调用read_Number()函数对该输入进行判断。学号合理,则调用checkId()函数对该学号进行检查。若已被占用,且需要修改时,则调用update()函数来修改。若未被占用,则开始输入其余数据项,依然调用read_Number()函数对这些数据项进行检查,检查是否合理。若所有数据项均合理,则进行封装,添加到可变长数组students中,并且将其存入文本文档中;若不合理,则让使用者重新输入。所有输入项均为输入错误三次,直接结束该功能。
程序流程图如下:
2.4.2、删除功能(delete_())
- 执行流程:先调用**isEmpty()函数判断是否有数据。若有数据,则让使用者输入要删除的记录对应的学号,调用read_Number()函数判断输入是否合理。调用findById()执行查找操作,找到待删除项所在的位置。查找成功则在可变长数组students中删除,然后调用saveAll()**保存删除后的students到文件中;查找失败则结束功能。
程序流程图如下:
2.4.3、修改功能(update())
-
执行流程:先调用**isEmpty()函数判断是否有数据。若有数据,则输入要修改的记录对应的学号,调用read_Number()函数判断输入是否合理。调用findById()执行查找操作,找到待修改项所在的位置。查找成功则在可变长数组students中修改,然后调用saveAll()**保存修改后的students到文件中;查找失败则结束功能。
-
修改操作:输入要修改的数据项对应的编号进行修改,修改完成后询问是否要继续修改该项记录。是,则继续修改;反之,则结束功能。
程序流程图如下:
2.4.4、查找功能(query())
-
查找功能可以以名字查询findByName()、以各个成绩查询findByScore()、以学号查询findById()。以名字查找时,是顺序查找,先判断名字的长度是否一致,然后将名字字符串按一个一个字符来比较,比较一个字符就能缩小可能结果的范围,通过层层筛选来找到最终结果集,返回一个数组。以成绩查找时,使用的是折半查找,通过递归找到所有符合条件的结果。以学号查找时,使用的也是折半查找,但是因为学号是唯一的,因此不需要递归。
-
执行流程:先调用**isEmpty()**函数判断是否有数据。若有数据,则显示功能页面,让使用者输入相应查找方式的编号,执行对应的查找操作前,要对数据进行相应的排序。若查询成功,则返回查询结果并显示出来,询问使用者是否要保存到文件当中,根据输入执行相应操作。若查询失败,则结束功能。
程序流程图如下:
2.4.5、显示功能(display())
-
执行流程:先调用**isEmpty()**函数判断是否有数据。若有数据,则遍历程序一开始就加载好的记录数组students,将记录打印出来。由于数据较多,因此采用分页显示的操作。
-
分页操作:通过操作记录在数组中的下标,来控制一页只显示10个记录。
程序流程图如下:
2.4.6、排序功能(order())
-
排序有以学号排序和以成绩排序两种方式,通过输入对应的编号即可选择排序方式,在这里使用的是冒泡排序,因为按成绩排序时,排序一次之后,其他成绩也会基本有序了。
-
执行流程:先调用isEmpty()函数判断是否有数据。若有数据,则对程序一开始就加载好的记录数组students进行排序,排序好之后调用**saveAll()**函数覆盖原来的数据写入到文件中。
程序流程图如下:
2.5、核心算法
2.5.1、getAll()、isEmpty()
- getAll():获取数据,根据定义好的文件,使用fstream文件流读取数据,保存到可变长数组students中。
while (fs >> id >> name >> grade >> pf >> score_1 >> score_2 >> score_3 >> avg >> sum) {
Score score(score_1, score_2, score_3);
students.push_back(new Student(id, name, grade, pf, score));
i++;
}
- isEmpty():判断是否有数据。有,返回1;反之,询问是否要添加记录,返回0。
if (students.empty()) { //没有记录
cout << "暂时还没有记录,是否要添加记录? ";
if (ch == "y" || ch == "Y") {
add(); //执行添加操作
}
}
2.5.2、getFileName()、save()、saveAll()
- getFilename():获取文件名,辅助以名字查找的功能,在查找结果需要保存时,提供一个文件名。文件名需要char*类型,而name类型是string类型,所以使用**getFilename()先把string类型的名字转换为char数组类型,然后使用sprintf()**函数格式化字符串,拼接出一个文件名。
for (int i = 0; i < name.length(); i++)
name_c[i] = name[i]; //string类型转换成char数组类型
name_c[name.length()] = '\0';
sprintf(filename, "name=%s.txt", name_c);
- save():保存单条记录。根据传递进来的stu,用文件流写入到文件中。
fs << stu->getId() << "\t"
<< stu->getName() << "\t"
<< stu->getGrade() << "\t"
<< stu->getPf() << "\t"
<< stu->getScore().getScore_m() << "\t"
<< stu->getScore().getScore_c() << "\t"
<< stu->getScore().getScore_e() << "\t"
<< stu->getScore().getAvg() << "\t"
<< stu->getScore().getSum() << endl;
- saveAll():保存多条数据。根据传递进来的可变长数组stus和文件名,调用save()函数来一一保存,save()函数利用fstream文件流将传递进来的内容写入到对应的文件当中。
for (int i = 0; i < stus.size(); i++) {
save(stus.at(i), filename)
2.5.3、验证输入read_choose()、read_Number()、checkId()
由于程序有多处需要输入数据,因此对数据的合理性检验是必要的。这里主要使用的是正则表达式。在**scanf()**函数读取数据时,对数据进行匹配,判断其是否合理。
-
正则表达式:%[]表示读入一个字符集合, “[”后面紧跟“^”表示取非;*表示不保存该数据,即跳过符合条件的字符串。
-
read_choose():检查选择对应功能时输入的编号是否合理:
int ret1 = scanf("%d", &choose); //获取到数字,返回1;否则,返回0
if (!ret1) //如果读取不到数字,则把输入的内容全部读取,过滤掉
scanf("%*[^'\n']");
scanf("%*[' ']"); //把所有空格读取了
int ret2 = scanf("%[^'\n']", temp); //^表示非。获取所有非换行符的字符
scanf("%*c"); //读取一个字符,*表示不保存到变量中。即获取一个换行符
- read_Number():检查输入的数据是否合理,这里的数据包括学生的成绩和学号。主要用于添加记录和查找记录时对相应输入的检查。
if (!flag) //flag==0时,读取学号;否则,读取分数
ret1 = scanf("%ld", &id);
else
ret1 = scanf("%lf", &score);
if (!ret1) {
scanf("%*[^'\n']");
}
scanf("%*[' ']");
int ret2 = scanf("%[^\n]", temp);
scanf("%*c");
- checkId():检查学号是否已被占用,调用**findById()**函数查找,若查找成功,则说明学号已被占用,询问是否要修改该学号的信息。查找失败则返回-1。
findById(id, index); //以该学号查找
if (index != -1) { //查找成功,则说明学号已被占用
cout << "该学号的学生已有记录,是否要修改?\n输入'y'修改,输入其他返回。";
string choose;
cin >> choose;
if (choose == "y" || choose == "Y") {
system("cls");
update(id); //修改记录
}
}
return index;
2.5.4、分页显示
遍历可变长数组students,控制下标使得每页只显示10个记录。根据每页10个记录和当前记录在数组中的下标,在每页的下方显示出当前页码和总页数。输入对应的指令可以看上一页、下一页以及跳转到指定的页。i为当前显示页的最下面的记录在可变长数组students中的下标。
-
查看上一页时,需要判断是否会有上一页,即是否为第一页(没有上一页)。有上一页,则使i = i - (i % 10) - 11,即找到上一页的第一条记录在可变长数组students中的下标减1,因为后面会加1。
-
查看下一页时,需要判断是否会有下一页,即是否为最后一页(没有下一页)。
-
跳转到对应页码的页面则使用了string库中的**atoi()**函数,它将输入的字符串转换为数字,然后赋值给表示记录的下标的变量,就可以直接跳转到对应的页。
page= atoi(cho.c_str()); //将字符串转换为数字
if (page >= 1 && page <= ((count - 1) / 10 + 1))
i = (page - 1) * 10 - 1; //跳到对应的页码
else if (cho == "n" || cho == "N") { //输入n或N表示下一页
if (i == count - 1) {
cout << "当前已是最后一页,没有下一页了!\n";
//count是从1开始计数的,而i是从0开始计数,因此count先-1
i = (count - 1) - ((count - 1) % 10) - 1;
}
}
else if (cho == "s" || cho == "S") { //输入s或S表示上一页
i = i - (i % 10) - 11; //上一页的第一条记录的下标-1
if (i < -1) {
cout << "当前已是第一页,没有上一页了!\n";
i = -1; //重新赋值为第一页的第一条记录的下标-1
}
}
else //输入其他直接退出
break;
2.5.5、排序
由于成绩排序一次后,其他的成绩就变得基本有序了,所以这里使用的是冒泡排序。排序分为以成绩排序和以学号排序,其实现方式是一致的。
for (int i = 0; i < count - 1; i++) {//按成绩排序(从大到小)
int flag_break = true;
for (int j = 0; j < count - 1 - i; j++) {
if (students.at(j + 1)->getScore(ch) > students.at(j)->getScore(ch)) {
swap(j, j + 1); //交换两个对象
flag_break = false;
}
}
if (flag_break) break;
}
2.5.6、查找
按名字查找时使用的是顺序查找,按分数或学号查找时使用的是折半查找。
- 按名字查找findByName():len为名字字符串的长度,顺序查找时,先比较两个名字的长度是否一致,再比较第一个字符,若相同,则加入到result数组中。然后再对result数组中的可能结果与参数name比较第二个字符,若相同,则加入被清空的result数组中,依次比较名字字符串的所有字符,最后得到的result数组即为最终结果。
while (ct < len) { //ct为名字字符串的下标,len为名字字符串的长度
temp = result;
result = temp_null;
for (int i = 0; i < temp.size(); i++) {
string name_ = temp.at(i)->getName();
if (name_.length() == len) { //长度相同就比较
if (name_[ct] == name[ct])
result.push_back(temp.at(i));
}
}
ct++;
}
- 按学号查找findById():学号规定时唯一的,因此直接使用折半查找即可。
while (left <= right) {
mid = (left + right) / 2;
if (students.at(mid)->getId() == id) {
index = mid;
return students.at(mid);
}
else if (id < students.at(mid)->getId())
right = mid - 1;
else
left = mid + 1;
}
index = -1; //-1代表查询失败
- 按分数查找findByScore():由于分数不是唯一的,可能会有多个结果,因此这里使用的是折半查找的递归,先找到一个结果,然后再对其左半部分和右半部分再进行折半查找,找出所有符合条件的结果。
while (left <= right) {
mid = (left + right) / 2;
if (students.at(mid)->getScore(flag) == score) {
//index=mid;
temp.push_back(students.at(mid));
Search_bin(temp, flag, score, low, mid - 1); //递归左半部分
Search_bin(temp, flag, score, mid + 1, high); //递归右半部分
break; //退出循环
}
else if (score > students.at(mid)->getScore(flag))
right = mid - 1;
else
left = mid + 1;
}
源代码
include.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<fstream> //文件流
#include<cstdlib> //atoi、system
#include<vector> //可变长数组
#include<iomanip> //控制cout输出格式
#include<windows.h> //Sleep(),和颜色设置
using namespace std;
score.h
#pragma once
#include "include.h"
class Score {
private:
double score_m; //数学成绩
double score_c; //语文成绩
double score_e; //英语成绩
double avg; //平均成绩
double sum; //总成绩
public:
Score(); //无参构造
Score(double m, double c, double e);
void setScore_m(double score_m);
double getScore_m();
void setScore_c(double score_c);
double getScore_c();
void setScore_e(double score_e);
double getScore_e();
double getAvg(); //获取平均分
double getSum(); //获取总分
void show_score(); //展示分数
};
score.cpp
#include"score.h"
Score::Score() { //无参构造函数
}
//构造函数
Score::Score(double m, double c, double e) {
this->score_m = m;
this->score_c = c;
this->score_e = e;
this->avg = 1; //getAvg();
this->sum = 1;//getSum();
}
void Score::setScore_m(double score_m) {
this->score_m = score_m;
}
double Score::getScore_m() {
return this->score_m;
}
void Score::setScore_c(double score_c) {
this->score_c = score_c;
}
double Score::getScore_c() {
return this->score_c;
}
void Score::setScore_e(double score_e) {
this->score_e = score_e;
}
double Score::getScore_e() {
return this->score_e;
}
//展示分数信息
void Score::show_score() {
cout << "数学成绩:" << this->score_m << endl;
cout << "语文成绩:" << this->score_c << endl;
cout << "英语成绩:" << this->score_e << endl;
printf("平均成绩:%.2lf\n", getAvg());
printf("总成绩:%.2lf\n", getSum());
}
//计算平均成绩
double Score::getAvg() {
double avg = (score_m + score_c + score_e) / 3;
return avg;
}
//计算总成绩
double Score::getSum() {
double sum = score_m + score_c + score_e;
return sum;
}
student.h
#pragma once
#include "score.h"
class Student {
private:
long id; //学号
string name; //姓名
string grade; //年级
string pf; //专业
Score score; //成绩
public:
Student();
Student(long id, string name, string grade, string pf, Score score);
void setId(long id);
long getId();
void setName(string name);
string getName();
void setGrade(string grade);
string getGrade();
void setPf(string pf);
string getPf();
void setScore(Score score);
void setScore(double score1, double score2, double score3);
Score getScore(); //获取成绩对象
double getScore(int num); //获取对应序号的成绩
};
student.cpp
#include "student.h"
//无参构造函数
Student::Student() {
}
//有参构造函数
Student::Student(long id, string name, string grade, string pf, Score score) {
this->id = id;
this->name = name;
this->grade = grade;
this->pf = pf;
this->score = score;
}
void Student::setId(long id) {
this->id = id;
}
long Student::getId() {
return this->id;
}
void Student::setName(string name) {
this->name = name;
}
string Student::getName() {
return this->name;
}
void Student::setGrade(string grade) {
this->grade = grade;
}
string Student::getGrade() {
return this->grade;
}
void Student::setPf(string pf) {
this->pf = pf;
}
string Student::getPf() {
return this->pf;
}
void Student::setScore(Score score) {
this->score = score;
}
void Student::setScore(double score1, double score2, double score3) {
if (score1 > 0)
this->score.setScore_m(score1);
if (score2 > 0)
this->score.setScore_c(score2);
if (score3 > 0)
this->score.setScore_e(score3);
}
Score Student::getScore() {
return this->score;
}
double Student::getScore(int num) {
switch (num) {
case 1: //总分
return getScore().getSum();
case 2: //数学成绩
return getScore().getScore_m();
case 3: //语文成绩
return getScore().getScore_c();
case 4: //英语成绩
return getScore().getScore_e();
}
}
SMS.h
#pragma once
#include "student.h"
#define _CRT_SECURE_NO_WARNINGS
class Manager {
public:
static const char* getPath(); //获取储存记录的文件路径
vector<Student*> getStudents(); //获取全部记录
void clear(); //清屏
void show_back(int flag = 0); //展示返回信息
void show_to(); //展示进入信息
int checkId(long id); //检查学号是否相同
double read_Number(long& id); //读取分数并检查分数是否合理
void add(); //添加记录
void delete_(); //删除记录
int read_choose(int left, int right); //读取选择
int update(long id = -1); //修改记录
int update(Student* stu); //修改记录
int isEmpty(); //判断记录是否为空
void getFilename(string name, char filename[]); //获取文件名
void Search_bin(vector<Student*>& temp, int flag, double score, int left, int right); //折半查找
Student* findById(long id, int& index); //查找记录
vector<Student*> findByScore(double score, int& index, int flag);
vector<Student*> findByName(string name, int& index);
void query(int cho); //查询功能
void show(Student* stu); //显示单个记录
void color(int i);
void showHeader(); //展示表头
void display(vector<Student*> stus, int flag = 0);//显示全部记录
void swap(int i, int j); //交换
void order(int ch, int flag = 0); //排序功能
string isSave(); //是否要保存
void save(Student* stu, int flag, const char* filename = "stu_score.txt"); //保存单条记录
void saveAll(vector<Student*> stus, const char* filename = "stu_score.txt"); //保存多条记录
int getAll(); //获取全部记录
private:
static const char* path; //储存记录的文件的路径
vector<Student*> students; //保存着全部记录
};
SMS.cpp
#include "SMS.h"
//全局变量,整个程序只有一个Manager对象
Manager manager;
//初始化类静态成员变量。所有记录的保存路径
const char* Manager::path = "stu_score.txt";
//获取储存记录的文件路径
const char* Manager::getPath() {
return path;
}
//获取全部记录
vector<Student*> Manager::getStudents() {
return students;
}
//清屏
void Manager::clear() {
system("pause");
system("cls");
}
//获取全部记录,并封装,返回总记录数
int Manager::getAll() {
long id;
string name, grade, pf;
double score_1, score_2, score_3, avg, sum; //成绩
int i = 0;
fstream fs;
fs.open(getPath(), ios::in); //第一个参数需要的是 char* 类型的,不能用string类型
while (fs >> id >> name >> grade >> pf >> score_1 >> score_2 >> score_3 >> avg >> sum) {
Score score(score_1, score_2, score_3);
students.push_back(new Student(id, name, grade, pf, score));
i++;
}
fs.close();
return students.size(); //返回总记录数
}
//检查学号是否已被使用
int Manager::checkId(long id) {
order(5, 1); //先按学号排序
int count = students.size();
int index;
findById(id, index); //以该学号查找
if (index != -1) { //查找成功,则说明学号已被占用
cout << "该学号的学生已有记录,是否要修改?\n输入'y'修改,输入其他返回。";
string choose;
cin >> choose;
if (choose == "y" || choose == "Y") {
system("cls");
update(id); //修改记录
}
}
return index;
}
//使用正则表达式判断输入是否正确
//id==0时,读取学号;否则,读取分数
double Manager::read_Number(long& id)
{
long flag = id; //0:学号;1:成绩;2:总分
double score = 101;
int ct = 3;
while (true) {
char temp[32]; //保存非数字字符
//获取到数字,返回1;否则,返回0
int ret1;
if (!flag)
ret1 = scanf("%ld", &id);
else
ret1 = scanf("%lf", &score);
if (!ret1) { //没有读取到数字,则把输入的内容全部获取过滤掉
scanf("%*[^'\n']");
}
scanf("%*[' ']"); //把空格读取了
//^表示非。即获取所有不是换行符的字符
//获取到,返回1;否则,返回0
int ret2 = scanf("%[^\n]", temp);
scanf("%*c"); //读取一个字符,*表示不保存到变量中。即获取一个换行符
//读取到数字,并且数字后面没有非换行符的字符
if (ret1 && !ret2 && flag == 0 && (id >= 1000000 && id <= 9999999))
return score; //学号
else if (ret1 && !ret2 && flag == 1 && (score >= 0 && score <= 100))
return score; //成绩
else if (ret1 && !ret2 && flag == 2 && (score >= 0 && score <= 300))
return score; //总分
else if (ct == 0) {
return -1; //输入错误三次,返回1
}
else {
cout << "输入错误,请重新输入(还有" << ct-- << "次机会):";
}
}
}
//添加记录
void Manager::add() {
Student* stu = new Student; //学生对象
long id = 0; //学号
long temp = 1; //临时变量
string name; //姓名
string grade; //年级
string pf; //专业
double score1; //数学成绩
double score2; //语文成绩
double score3; //英语成绩
cout << "请输入学生学号的7位数字(如:2021000):";
if (read_Number(id) == -1) {
clear();
return;
}
if (checkId(id) == -1) { //该学号未被占用
stu->setId(id);
cout << "请分别输入学生的姓名、年级、专业: \n";
cin >> name >> grade >> pf;
stu->setName(name);
stu->setGrade(grade);
stu->setPf(pf);
cout << "请输入学生的数学成绩:";
score1 = read_Number(temp);
if (score1 == -1) {
cout << "添加失败!\n";
clear();
return;
}
cout << "请输入学生的语文成绩:";
score2 = read_Number(temp);
if (score2 == -1) {
cout << "添加失败!\n";
clear();
return;
}
cout << "请输入学生的英语成绩:";
score3 = read_Number(temp);
if (score3 == -1) {
cout << "添加失败!\n";
clear();
return;
}
Score score(score1, score2, score3); //成绩对象
stu->setScore(score);
students.push_back(stu); //添加到全局变量students中
save(stu, 1); //保存记录
cout << "\n添加的记录的信息如下:\n";
show(stu); //显示记录
clear();
}
else
system("cls");
}
//显示单条记录
void Manager::show(Student* stu) {
cout << "学号:" << stu->getId() << endl;
cout << "姓名:" << stu->getName() << endl;
cout << "年级:" << stu->getGrade() << endl;
cout << "专业:" << stu->getPf() << endl;
stu->getScore().show_score();
}
//保存单条记录
//flag==1,为追加保存,flag==0,则为覆盖上一次的记录
void Manager::save(Student* stu, int flag, const char* filename) {
fstream fs;
if (flag == 1) {
//追加写入,在原来 ios::out 的基础上加了ios::app
fs.open(filename, ios::out | ios::app);
}
else {
//会覆盖原来存在的记录
fs.open(filename, ios::out);
}
//写入到文件中
fs << stu->getId() << "\t"
<< stu->getName() << "\t"
<< stu->getGrade() << "\t"
<< stu->getPf() << "\t"
<< stu->getScore().getScore_m() << "\t"
<< stu->getScore().getScore_c() << "\t"
<< stu->getScore().getScore_e() << "\t"
<< stu->getScore().getAvg() << "\t"
<< stu->getScore().getSum() << endl;
fs.close();
}
//保存多条记录
void Manager::saveAll(vector<Student*> stus, const char* filename) {
for (int i = 0; i < stus.size(); i++) {
if (i == 0)
save(stus.at(i), 0, filename); //先覆盖原有记录
else
save(stus.at(i), 1, filename); //追加记录
}
}
//判断记录是否为空
//空:返回0;否则,返回1
int Manager::isEmpty() {
if (students.empty()) { //没有记录
cout << "暂时还没有记录,是否要添加记录?(输入y进行添加操作,输入其他返回)";
string ch;
cin >> ch;
if (ch == "y" || ch == "Y") {
add(); //执行添加操作
}
return 0;
}
return 1;
}
//展示表头
void Manager::showHeader() {
cout << left; //左对齐,默认为右对齐
cout << "-----------+---------+---------+---------------+-----------+-----------+-----------+---------+---------+\n";
cout << "| "; color(14); cout << setw(9) << "学号"; color(3); cout << "| ";
color(14); cout << setw(8) << "姓名"; color(3); cout << "| ";
color(14); cout << setw(8) << "年级"; color(3); cout << "| ";
color(14); cout << setw(14) << "专业"; color(3); cout << "| ";
color(14); cout << setw(10) << "数学成绩"; color(3); cout << "| ";
color(14); cout << setw(10) << "语文成绩"; color(3); cout << "| ";
color(14); cout << setw(10) << "英语成绩"; color(3); cout << "| ";
color(14); cout << setw(8) << "平均分"; color(3); cout << "| ";
color(14); cout << setw(8) << "总成绩"; color(3); cout << "| \n";
}
//设置颜色
void Manager::color(int i) {
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), i);
}
//显示多条记录
//flag默认为0
//flag==1时,是显示查询结果,返回的时候不清屏
void Manager::display(vector<Student*> stus, int flag) {
if (!isEmpty()) //判断是否有记录
return;
system("cls");
showHeader(); //展示表头
int count = stus.size(); //总记录数
int i = 0;
//分页操作
while (i < count) {
cout << "-----------+---------+---------+---------------+-----------+-----------+-----------+---------+---------+\n";
cout << left; //左对齐,默认为右对齐
cout << "| " << setw(9) << stus.at(i)->getId() << "| "
<< setw(8) << stus.at(i)->getName() << "| "
<< setw(8) << stus.at(i)->getGrade() << "| "
<< setw(14) << stus.at(i)->getPf() << "| ";
printf("%-10.1lf| ", stus.at(i)->getScore().getScore_m());
printf("%-10.1lf| ", stus.at(i)->getScore().getScore_c());
printf("%-10.1lf| ", stus.at(i)->getScore().getScore_e());
printf("%-8.1lf| %-8.1lf|\n", stus.at(i)->getScore().getAvg(), stus.at(i)->getScore().getSum());
Sleep(25);
//输出页码以及检查输入是否正确
if ((i + 1) % 10 == 0 || i == count - 1) {
cout << "-----------+---------+---------+---------------+-----------+-----------+-----------+---------+---------+\n";
cout << right << setw(41) << "第" << i / 10 + 1 << "页/共" << (count - 1) / 10 + 1 << "页\n";
cout << "\n输入's'查看上一页,输入'n'查看下一页,输入1-" << (count - 1) / 10 + 1 << "转页,输入其他返回。";
string cho;
cin >> cho;
if (atoi(cho.c_str()) >= 1 && atoi(cho.c_str()) <= ((count - 1) / 10 + 1)) {
i = (atoi(cho.c_str()) - 1) * 10 - 1; //将字符串转换为数字,跳到对应的页码
}
else if (cho == "n" || cho == "N") { //输入n或N表示下一页
if (i == count - 1) {
cout << "当前已是最后一页,没有下一页了!\n";
//count是从1开始计数的,而i是从0开始计数,因此count先-1
i = (count - 1) - ((count - 1) % 10) - 1;
system("pause");
}
}
else if (cho == "s" || cho == "S") { //输入s或S表示上一页
i = i - (i % 10) - 11; //上一页的第一条记录的下标-1
if (i < -1) {
cout << "当前已是第一页,没有上一页了!\n";
i = -1; //重新赋值为第一页的第一条记录的下标-1
system("pause");
}
}
else { //输入其他直接退出
if (!flag) //flag==1时,是显示查询结果,返回的时候不清屏
show_back();
break;
}
system("cls");
showHeader(); //没有退出循环,则要展示下一页。这里是展示表头
}
i++;
}
}
//查找记录(按学号查询)
Student* Manager::findById(long id, int& index) {
//查找记录前先按学号排好序,第二个参数为1,代表排序后要进行的是查询
order(5, 1);
int count = students.size(); //总数
//二分查询
int left = 0;
int right = count - 1;
int mid = 0;
while (left <= right) {
mid = (left + right) / 2;
if (students.at(mid)->getId() == id) {
index = mid;
return students.at(mid);
}
else if (id < students.at(mid)->getId())
right = mid - 1;
else
left = mid + 1;
}
index = -1; //-1代表查询失败
return NULL;
}
//折半查找
//temp:保存查找结果的数组
//flag:标志查找方式(以总分、数学成绩等来查找)
//score:需要查找的分数
//left:查找区间的左边界
//right:查找区间的右边界
void Manager::Search_bin(vector<Student*>& temp, int flag, double score, int left, int right) {
int low = left; //保存最右边的下标
int high = right; //保存最左边的下标
int mid;
while (left <= right) {
mid = (left + right) / 2;
if (students.at(mid)->getScore(flag) == score) {
//index=mid;
temp.push_back(students.at(mid));
Search_bin(temp, flag, score, low, mid - 1); //递归左半部分
Search_bin(temp, flag, score, mid + 1, high); //递归右半部分
break; //退出循环
}
else if (score > students.at(mid)->getScore(flag))
right = mid - 1;
else
left = mid + 1;
}
}
//查找记录(按分数查询)
//参数flag代表排序方式:
//1:按总分排序
//2:按数学成绩排序
//3:按语文成绩排序
//4:按英语成绩排序
vector<Student*> Manager::findByScore(double score, int& index, int flag) {
vector<Student*> temp;
//查找记录前先按成绩排好序,第二个参数为1,代表排序后要进行的是查询
order(flag - 1, 1);
int count = students.size(); //总数
Search_bin(temp, flag - 1, score, 0, count - 1); //二分查找
if (temp.size() == 0)
index = -1; //查找失败
return temp;
}
//按名字查询
vector<Student*> Manager::findByName(string name, int& index) {
cout << "请稍后";
Sleep(200); cout << "."; Sleep(200); cout << "."; Sleep(200); cout << ".\n";
vector<Student*> result;
int len = name.length();
result = students;
vector<Student*> temp, temp_null; //temp临时保存所有可能的结果
int ct = 0; //名字字符串的下标
while (ct < len) {
temp = result;
result = temp_null;
for (int i = 0; i < temp.size(); i++) {
string name_ = temp.at(i)->getName();
if (name_.length() == len) { //长度相同就比较
if (name_[ct] == name[ct])
result.push_back(temp.at(i));
}
}
ct++;
}
if (result.size() == 0)
index = -1; //查找失败
return result;
}
//询问是否要保存
string Manager::isSave() {
cout << "是否要将查询结果保存至文件当中?\n输入y保存,输入其他返回。";
string cho;
cin >> cho;
return cho;
}
//获取文件名
void Manager::getFilename(string name, char filename[]) {
char name_c[16];
for (int i = 0; i < name.length(); i++)
//string类型转换成char数组类型
name_c[i] = name[i];
name_c[name.length()] = '\0';
sprintf(filename, "name=%s.txt", name_c);
}
//查询功能
void Manager::query(int cho) {
if (cho == 0) {
system("cls");
return; //直接返回
}
long id = 0; //学号
double score; //分数
string name; //名字
Student* stu = new Student; //学生对象
vector<Student*> stus;
char* filename = (char*)malloc(sizeof(char) * 64); //文件名
int index; //返回下标,-1代表查询失败
switch (cho) {
case 1:
cout << "请输入名字:";
cin >> name;
stus = findByName(name, index);
break;
case 2: //总分
cout << "请输入成绩:";
id = 2;
score = read_Number(id);
if (score == -1) {
system("cls");
return;
}
stus = findByScore(score, index, cho);
break;
case 3: //成绩
case 4:
case 5:
cout << "请输入成绩:";
id = 1;
score = read_Number(id);
if (score == -1) {
system("cls");
return;
}
stus = findByScore(score, index, cho);
break;
case 6:
cout << "请输入要查询的学号的7位数字(如:2021000):";
if (read_Number(id) == -1) {
system("cls");
return;
}
stu = findById(id, index);
break;
}
if (index == -1) {
cout << "查询失败,没有该学生!" << endl;
}
else if (cho == 6) {
cout << "查询成功!该学生成绩信息如下:\n\n";
show(stu);
sprintf(filename, "id=%.2ld.txt", id);
string ret = isSave();
if (ret == "y" || ret == "Y") {
save(stu, 0, filename); //参数0为覆盖原有数据
cout << "数据已保存至文件: " << filename << endl;
}
}
else {
cout << "查询成功!" << endl;
system("pause"); //先暂停一下
display(stus, 1);
if (cho == 1) {
getFilename(name, filename);
}
else
sprintf(filename, "score=%.2lf.txt", score);
string ret = isSave();
if (ret == "y" || ret == "Y") {
saveAll(stus, filename);
cout << "数据已保存至文件: " << filename << endl;
}
}
clear();
}
//删除记录
void Manager::delete_() {
if (!isEmpty()) { //判断是否有记录
system("cls");
return;
}
int count = students.size(); //总数
long temp = 0;
cout << "请输入要删除的记录的学号的7位数字(如:2021000):";
if (read_Number(temp) == -1) { //学号读取失败
system("cls");
return;
}
cout << "请稍后"; cout << "."; Sleep(200); cout << "."; Sleep(200); cout << ".\n"; Sleep(200);
int index; //待删除记录下标
findById(temp, index);
if (index == -1) {
cout << "记录删除失败!没有该学号的学生。\n";
clear();
return;
}
vector<Student*>::iterator it; //获取迭代器
it = students.begin();
while (index > 0) { //找到迭代器的位置
it++;
index--;
}
students.erase(it); //在数组中删除记录
saveAll(students); //保存全部记录
string cho;
cout << "记录删除成功!\n查看全部记录请输入y,返回请输入其他字符:";
cin >> cho;
if (cho == "y" || cho == "Y")
display(students); //显示删除后的记录
system("cls");
}
//使用正则表达式判断输入是否正确
int Manager::read_choose(int left, int right) {
int ct = 3;
int choose = -1;
cout << "请选择对应功能的编号(" << left << " - " << right << "):";
while (true) {
char temp[32]; //保存非数字字符
//获取到数字,返回1;否则,返回0
int ret1 = scanf("%d", &choose);
if (!ret1) //如果读取不到数字,则把输入的内容全部读取,过滤掉
scanf("%*[^'\n']");
scanf("%*[' ']"); //把空格读取了
//^表示非。即获取所有不是换行符的字符
//获取到,返回1;否则,返回0
int ret2 = scanf("%[^'\n']", temp);
scanf("%*c"); //读取一个字符,*表示不保存到变量中。即获取一个换行符
//读取到数字,并且数字后面没有非换行符的字符
if (ret1 && !ret2 && choose >= left && choose <= right) {
return choose;
}
else if (ct == 0)
return -1; //输入错误三次,返回-1
else {
cout << "输入错误,请重新输入(还有" << ct-- << "次机会):";
}
}
}
//修改记录
int Manager::update(Student* stu) {
cout << "该学生的信息如下:\n\n";
show(stu);
int choose;
cout << "\n=======================================================" << endl;
//cout << "----------------欢迎使用修改功能-----------------------" << endl;
cout << " @ 【1】修改名字 @ " << endl;
cout << " @ 【2】修改年级 @ " << endl;
cout << " @ 【3】修改专业 @ " << endl;
cout << " @ 【4】修改数学成绩 @ " << endl;
cout << " @ 【5】修改语文成绩 @ " << endl;
cout << " @ 【6】修改英语成绩 @ " << endl;
cout << " @ 【0】返回管理系统 @ " << endl;
cout << "+++++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
choose = read_choose(0, 6);
if (choose == 0 || choose == -1) {
manager.show_back();
return -2;
}
string name, grade, pf; //名字,年级,专业
long temp = 1; //临时变量。1表示读取成绩
double score1, score2, score3; //成绩
switch (choose) {
case 0:
return -2;
case 1:
cout << "\n请输入学生新的姓名: ";
cin >> name; stu->setName(name);
break;
case 2:
cout << "\n请输入学生新的年级: ";
cin >> grade; stu->setGrade(grade);
break;
case 3:
cout << "\n请输入学生新的专业: ";
cin >> pf; stu->setPf(pf);
break;
case 4:
cout << "请输入学生新的数学成绩:";
score1 = read_Number(temp);
if (score1 == -1)
return -1;
stu->setScore(score1, -1, -1);
break;
case 5:
cout << "请输入学生新的语文成绩:";
score2 = read_Number(temp);
if (score2 == -1)
return -1;
stu->setScore(-1, score2, -1);
break;
case 6:
cout << "请输入学生新的英语成绩:";
score3 = read_Number(temp);
if (score3 == -1)
return -1;
stu->setScore(-1, -1, score3);
break;
}
return 0;
}
//修改记录功能。id默认为-1,若有其他传进来的学号,则使用其他学号
int Manager::update(long id) {
if (!isEmpty()) {
system("cls");
return 0;
}
long temp;
if (id == -1) {
temp = 0; //0表示读取学号
cout << "请输入要修改的记录的学号的7位数字(如:2021000):";
if (read_Number(temp) == -1) {
system("cls");
return 0;
}
}
else
temp = id;
int index;
findById(temp, index);
if (index != -1) {
string choose;
do {
int ret = update(students.at(index)); //返回值
if (ret == -1) {
cout << "修改失败!\n";
clear();
return 0;
}
else if (ret == -2) {
system("cls");
return 0;
}
saveAll(students); //保存全部记录
system("cls");
cout << "\n修改成功!新信息如下:\n\n";
show(students.at(index)); //显示修改后的记录
cout << "是否要继续修改?\n输入 y 继续,输入其他返回。";
cin >> choose;
system("cls");
} while (choose == "y" || choose == "Y");
}
else {
cout << "修改失败,没有该学号的学生!\n";
system("pause");
}
system("cls");
return 1;
}
//交换两个对象
void Manager::swap(int i, int j) {
Student* stu = students.at(i);
students.at(i) = students.at(j);
students.at(j) = stu;
}
//排序功能(选择排序) ,返回一个数组,方便查找
//参数ch为指定排序方式,参数flag为1,则下一步将进行的是查询或添加
void Manager::order(int ch, int flag) { //flag 默认为0(默认参数要放到后面)
if (ch == 0) {
system("cls");
return; //直接返回
}
//冒泡排序
int count = students.size(); //总数
if (ch != 5) { //按成绩排序(从大到小)
for (int i = 0; i < count - 1; i++) {
int flag_break = true;
for (int j = 0; j < count - 1 - i; j++) {
if (students.at(j + 1)->getScore(ch) > students.at(j)->getScore(ch)) {
swap(j, j + 1); //交换两个对象
flag_break = false;
}
}
if (flag_break)
break;
}
}
else { //按学号排序(从小到大)
for (int i = 0; i < count - 1; i++) {
int flag_break = true;
for (int j = 0; j < count - 1 - i; j++) {
if (students.at(j + 1)->getId() < students.at(j)->getId()) {
swap(j, j + 1); //交换两个对象
flag_break = false;
}
}
if (flag_break)
break;
}
}
if (!flag) {
cout << "请稍后";
Sleep(200); cout << "."; Sleep(200); cout << "."; Sleep(200); cout << ".\n";
}
saveAll(students); //保存记录
if (!flag) { //排序后的操作不是查询,则显示排序后的全部内容
display(students);
system("cls");
}
}
//展示返回信息
void Manager::show_back(int flag) {
flag == 1 ? cout << "即将退出管理系统" : cout << "即将返回";
cout << "."; Sleep(300);
cout << "."; Sleep(300);
cout << "."; Sleep(300);
system("cls");
}
//展示进入信息
void Manager::show_to() {
system("cls");
cout << "请稍后";
cout << "."; Sleep(200);
cout << "."; Sleep(200);
cout << "."; Sleep(200);
system("cls");
}
//选择排序的方式
int order_ch() {
if (!manager.isEmpty())
return 0;
int choose;
cout << "=======================================================" << endl;
cout << "----------------欢迎使用排序功能-----------------------" << endl;
cout << " @ 【1】按总成绩排序 @ " << endl;
cout << " @ 【2】按数学成绩排序 @ " << endl;
cout << " @ 【3】按语文成绩排序 @ " << endl;
cout << " @ 【4】按英语成绩排序 @ " << endl;
cout << " @ 【5】按学号排序 @ " << endl;
cout << " @ 【0】返回管理系统 @ " << endl;
cout << "+++++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
choose = manager.read_choose(0, 5);
if (choose == 0 || choose == -1) {
manager.show_back();
return 0;
}
return choose;
}
//选择查询的方式
int query_ch() {
if (!manager.isEmpty())
return 0;
int choose;
cout << "=======================================================" << endl;
cout << "----------------欢迎使用查询功能-----------------------" << endl;
cout << " @ 【1】按名字查询 @ " << endl;
cout << " @ 【2】按总成绩查询 @ " << endl;
cout << " @ 【3】按数学成绩查询 @ " << endl;
cout << " @ 【4】按语文成绩查询 @ " << endl;
cout << " @ 【5】按英语成绩查询 @ " << endl;
cout << " @ 【6】按学号查询 @ " << endl;
cout << " @ 【0】返回管理系统 @ " << endl;
cout << "+++++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
choose = manager.read_choose(0, 6);
if (choose == 0 || choose == -1) {
manager.show_back();
return 0;
}
return choose;
}
int menu() {
int choose;
cout << "=======================================================" << endl;
cout << "----------------欢迎使用学生成绩管理系统---------------" << endl;
cout << " @ 【1】添加成绩记录 @ " << endl;
cout << " @ 【2】删除成绩记录 @ " << endl;
cout << " @ 【3】修改成绩记录 @ " << endl;
cout << " @ 【4】查询成绩记录 @ " << endl;
cout << " @ 【5】显示全部成绩记录 @ " << endl;
cout << " @ 【6】进行排名 @ " << endl;
cout << " @ 【0】退出管理系统 @ " << endl;
cout << "+++++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
choose = manager.read_choose(0, 6);
if (choose == -1 || choose == 0) {
manager.show_back(1);
cout << "感谢使用,再见!";
Sleep(700);
exit(0);
}
system("cls");
return choose;
}
void start() {
manager.getAll(); //先获取到全部记录
while (true) {
switch (menu()) {
case 1:
manager.show_to();
manager.add();
break;
case 2:
manager.show_to();
manager.delete_();
break;
case 3:
manager.show_to();
manager.update();
break;
case 4:
manager.show_to();
manager.query(query_ch());
break;
case 5:
manager.show_to();
manager.display(manager.getStudents());
manager.clear();
break;
case 6:
manager.show_to();
manager.order(order_ch());
break;
}
}
}
int main() {
manager.color(3);
start();
return 0;
}
---------------欢迎使用学生成绩管理系统---------------" << endl;
cout << " @ 【1】添加成绩记录 @ " << endl;
cout << " @ 【2】删除成绩记录 @ " << endl;
cout << " @ 【3】修改成绩记录 @ " << endl;
cout << " @ 【4】查询成绩记录 @ " << endl;
cout << " @ 【5】显示全部成绩记录 @ " << endl;
cout << " @ 【6】进行排名 @ " << endl;
cout << " @ 【0】退出管理系统 @ " << endl;
cout << “+++++++++++++++++++++++++++++++++++++++++++++++++++++++” << endl;
choose = manager.read_choose(0, 6);
if (choose == -1 || choose == 0) {
manager.show_back(1);
cout << "感谢使用,再见!";
Sleep(700);
exit(0);
}
system("cls");
return choose;
}
void start() {
manager.getAll(); //先获取到全部记录
while (true) {
switch (menu()) {
case 1:
manager.show_to();
manager.add();
break;
case 2:
manager.show_to();
manager.delete_();
break;
case 3:
manager.show_to();
manager.update();
break;
case 4:
manager.show_to();
manager.query(query_ch());
break;
case 5:
manager.show_to();
manager.display(manager.getStudents());
manager.clear();
break;
case 6:
manager.show_to();
manager.order(order_ch());
break;
}
}
}
int main() {
manager.color(3);
start();
return 0;
}