二、设计与实现
1、设计思想
设计一个Student类,Student类中包括对学生信息的各种操作函数,并设计一个StuMessage结构,用以存储学生信息,Student类公有继承用StuMessage结构实例化的List模板类。
Student类中有实现录入、修改、添加、删除、显示、查询、分析、清空学生成绩,学生成绩排序,学生信息存档的成员函数。
StuMessage结构中有表示学生五门成绩、排名、学号、姓名的变量。
List类即单链表类,其中包括对单链表的各种操作函数以及有关的变量。
最终,在主函数中生成Student类的对象,实现相应的功能。
2、类结构
图2-1 类结构
- 主要数据结构
学生成绩用单链表存储,方便插入和删除等对学生成绩的处理操作,实现动态管理。一个学生对应一个结点,使用链表的基本算法实现对学生成绩表管理功能。
- 算法设计
图4.1 主函数流程图
图4-2 删除成绩和修改成绩的流程图
图4-3 录入学生成绩和添加成绩成员函数的流程图
图4-4 成绩排序及成绩分析成员函数的流程图
图4-5 重载运算符<<和查找学生成绩成员函数的流程图
图4-6 保存学生信息和清空信息的流程图
5、核心代码展示
struct StuMessage//存储学生信息的结构类型
{
int order=-1;
string number="";
string name="";
int Math=-1;
int OPP=-1;
int English=-1;
int History=-1;
int Physics=-1;
};
void Student::Sort_Sum(int subject, int order) //成绩排名(subject为按何种成绩排序的代号,order为升序和降序的代号)
{
int i, j, sum1, sum2, lastorder;
class ListNode<struct StuMessage>* head = GetHead();
class ListNode<struct StuMessage>* p;
struct StuMessage stu1, stu2;
int length = GetLength();
if (length > 1)
{
for (i = 2;i <= length;i++)
{
for (j = 1;j < i;j++)
{
stu1 = Get_Order(j);
stu2 = Get_Order(i);
switch (subject) // 选择排序依据的数据
{
case 1:sum1 = stu1.English + stu1.History + stu1.Math + stu1.OPP + stu1.Physics;
sum2 = stu2.English + stu2.History + stu2.Math + stu2.OPP + stu2.Physics;break;
case 2:sum1 = stu1.English;sum2 = stu2.English;break;
……//sum1,sum2中存入相应成绩
default:cout << "Error\n";exit(1); //遇到非法输入则退出
}
if (order == 1) //排名升序下的直插排序
{
if (sum1 < sum2)
{
Remove_Order(i);
Insert(stu2, j);
}
}
else if (order == 2) //排名降序下的直插排序
{
……//类似于升序下的代码
}
else //遇到非法输入退出程序
{ cout << "Error!";exit(1); }
}
}
}
p = head->next;
sum1 = sum2 = -1;
if (order == 1) //排名升序下为每个学生写入排名
{
for (i = 1;i <= length;i++)
{
sum2 = sum1;
stu1 = p->data; //第i个学生的学生信息存入stu1
switch (subject) // 选择排序依据的数据
{
case 1:sum1 = stu1.English + stu1.History + stu1.Math + stu1.OPP + stu1.Physics;break;
case 2:sum1 = stu1.English;break;
……
default:{ cout << "Error\n";
exit(1); //遇到非法输入则退出
}
}
if (sum1 == sum2) //同上一个人的成绩相同
{
p->data.order = lastorder; //上个人的排名即为此人的排名
p = p->next;
}
else
{
p->data.order = i; //成绩与前一个人不同时,排名为序号
p = p->next;
lastorder = i;
}
}
}
else if (order == 2)//降序下,为每个学生写入排名
{ ……//同升序下的代码相似
}
else //遇到非法输入,程序退出
{ cout << "Error!";exit(1); }
}
void Student::Analyse() //成绩分析
{
int a;
while (1)
{
cout << "\n想要分析哪一门成绩?请输入相应序号(1:物理 2:数学 3:英语 4:历史 5:OPP 6:总分): ";
cin >> a;
if (a != 1 && a != 2 && a != 3 && a != 4 && a != 5 && a != 6) { cout << "\n输入序号有误,请重新输入!" << endl; }
else {break;}
}
cout << "\n\t成绩分析如下: \n" << endl;
class ListNode<struct StuMessage>* head = GetHead();
class ListNode<struct StuMessage>* p = head->next;
struct StuMessage stu;
int you = 0, liang = 0, zhong = 0, jige = 0, bujige = 0, num = 0,b,max = -1, min = 1000;
double average = 0;
{
while (p)
{
stu = p->data;
switch (a)
{
case 1:b = stu.Physics;break;
……//将要分析的科目的成绩保存到b中
}
if (a <= 5) //当只有一门科目时
switch (b / 10)
{
case 10:
case 9:you++;break;
case 8:liang++;break;
case 7:zhong++;break;
case 6:jige++;break;
default:bujige++;
}
else //当分析总分时
switch (b / 50)
{
……//代码和a<<5时相似
}
average += b;
max = (b > max) ? b : max; min = (b < min) ? b : min;
p = p->next;
num++;
}
average = average / num;
……//显示成绩分析结果
}
}
void Student::AlterGrade() //修改学生成绩
{
int i,a;
string num;
float newgrade;
ListNode<struct StuMessage>* head = GetHead(); ListNode<struct StuMessage>* p = NULL;
cout << "请输入修改成绩对应的学号";
cin >> num;
cout << "请输入修改科目(1:物理 2:数学 3:英语 4:历史 5:OPP)";
cin >> a;
cout << "请输入修改后的成绩";
cin >> newgrade;
for (i = 1; i <= length; i++)//找该学号学生
{
p = Find_Order(i); if (num == p->data.number) {break;}
}
if (i > length) {cout << "未找到该学号的学生\n" << endl;}
else
{
struct StuMessage& stu = p->data;
if (p == NULL || p == head)
{
cout << "Error!" << endl;
exit(1);
}
else
{
switch (a)
{case 1: stu.Physics = newgrade;break;
……//改变对应科目的成绩
case 5: stu.OOP = newgrade;break;
default:cout << "序号错误\n" << endl;
}
cout << "修改成功\n" << endl;
}
}
}
void Student::AddGrade() //添加成绩
{
struct StuMessage stu;
cout << "请输入学号:";
cin >> stu.number;
cout << "请输入姓名:";
cin >> stu.name;
cout << "请依次输入成绩(1:物理 2:数学 3:英语 4:历史 5:OPP)\n";
cin >> stu.Physics >> stu.Math >> stu.English >> stu.History >> stu.OOP;
Insert(stu, length + 1);
cout << "添加成功\n" << endl;
}
void Student::DeleteGrade() //删除成绩
{
ListNode<StuMessage>* p;
int i;
string num; //待删除成绩对应学号
cout << "请输入学号";
cin >> num;
for (i = 1; i <= length; i++)
{
p = Find_Order(i);
if (num == p->data.number)
{ Remove_Order(i); break; }
}
if (i > length)
{ cout << "未找到该学号的学生\n" << endl; }
else
{ cout << "删除成功\n" << endl; }
}
void Student::Save() //将学生信息保存到指定的txt文件
{
int i;
struct StuMessage stu;
ofstream fw;
char a[100];
cout << "输入文件路径(.txt文件)(不含空格):";
cin >> a;
fw.open(a); //打开该文件
if (fw.is_open()) //判断是否打开成功
{
if (a[strlen(a) - 1] == 't' && a[strlen(a) - 2] == 'x' && a[strlen(a) - 3] == 't' && a[strlen(a) - 4] == '.') //判断是不是txt文件
{
for (i = 1; i <= length; i++)
{
stu = Get_Order(i);
fw << stu.number << " " << stu.name << " " << stu.Physics << " " << stu.Math << " " << stu.History << " " << stu.OOP << " " << stu.English << endl; //将学生信息写入文件
}
cout << "已保存成功!\n";
}
else //不是txt文件,输出警告
{ cout << "您输入的不是txt文件!\n" << endl; }
fw.close(); //关闭文件
}
else //未成功打开文件
{ cout << "打开文件失败!\n" << endl;}
}
void Student::Find() //查询学生信息
{
int i;
string num;
struct StuMessage stu;
cout << "请输入学号:";
cin >> num;
for (i = 1; i <= length; i++)
{
stu = Get_Order(i);
if (stu.number == num) //从链表开头找,直到找到该学号的学生
{
……//省略显示学生信息有关的代码
break;
}
}
if (i > length) //未找到该学号的学生
{ cout << "未找到该学生!\n" << endl; }
else if (stu.order == -1) //如果还没有排名,输出警告
{ cout << "尚未排名,请返回主菜单排名" << endl; }
}
void Student::EnterStuInformation()
{
struct StuMessage stu;
int point = 0,i, j = 0;
ifstream fr;
char a[100],line[100];
float score;
cout << "输入文件路径(txt文件)(不含空格):";
cin >> a;
fr.open(a); //打开该文件
if (fr.is_open()) //判断是否打开
{
if (a[strlen(a) - 1] == 't' && a[strlen(a) - 2] == 'x' && a[strlen(a) - 3] == 't'&& a[strlen(a) - 4] == '.') //判断是不是txt文件
{
while (!fr.eof()) //判断是否读到末尾
{
fr >> stu.number;
if (fr.eof()) //判断是否读到末尾
{break;}
fr >> stu.name;
for (i = 1; i <= 5; i++)
{
fr >> line;
for (j = 0, score = 0,point=0; line[j]; j++)
{
if (point == 0 && line[j] >= '0' && line[j] <= '9')
{ score = score * 10 + line[j] - '0'; //没碰到小数点,将字符串表示的数转化为数字
}
else if (point !=0 && line[j] >= '0' && line[j] <= '9')
{ score = score + pow(10, -(j - point))*(line[j]-'0'); }
else if(line[j]=='.')
{ point = j; }
}
switch (i)
{
case 1:stu.Physics = score; break;
……}
}
Insert(stu, length + 1);
}
cout << "录入成功\n" << endl;
}
else
{ cout << "您输入的不是txt文件!\n" << endl; }
fr.close();
}
else
{ cout << "打开文件失败!\n" << endl; }
}
三、测试与结论
1、测试环境与数据
1. 测试环境:Visual Studio 2019
2. 测试数据:
学号 | 姓名 | 物理 | 数学 | 历史 | OOP | 英语 |
1 | a | 89 | 90.5 | 86 | 49 | 65 |
2 | b | 38 | 49 | 89 | 99 | 76.5 |
3 | c | 49.5 | 99.5 | 59 | 47 | 90 |
2、测试用例
1.数据输入
1)手动输入如图1:
图1 手动输入成绩
2)从文件读取如图2:
![]() |
图2 读取成绩
2.信息添加和删除后输出如图3
![]() |
图3 添加、删除成绩
3.信息修改如图4
![]() |
图4 修改成绩
4.信息查询输出如图5
![]() |
图5 查询信息
5.成绩按原顺序或排名输出如图6,成绩相同时按学号如图7
![]() |
图6 按顺序输出成绩
![]() |
图7 相同分数排序
6.保存文件如图8
![]() |
图8 保存文件
7.成绩分析,获得包括成绩分布、最高分、最低分和平均分等信息如图9
![]() |
图9 分析成绩
8.程序容错:文件打开失败,未查找到对应学生信息如图10,无学生信息时提示错误如图11,输入重复信息提示如图12
图10
图 11
![]() |
图12
3、测试结论
经过测试,代码圆满完成了题目的各项要求,实现了数据的输入、输出、修改、排序、统计和存储。