0
点赞
收藏
分享

微信扫一扫

学生成绩管理系统(C++)

书呆鱼 2022-01-09 阅读 85

学生成绩管理系统

一、设计任务分析

​ 本课题主要的目的是为了管理学生的成绩。可以录入成绩、查询成绩、删除成绩记录、对成绩进行各种排名等操作。方便考完试后对学生的成绩统一处理。

二、学生成绩管理系统总体方案的设计

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;

}

举报

相关推荐

0 条评论