问题描述
创建学生信息管理系统,具体要求如下:
学生信息包括:学号 姓名 数学成绩 英语成绩 计算机成绩
- 功能1:添加学生信息 执行1时,输入学号,姓名,三门科目成绩;如果添加学生成功则输出“Add success”,如果学生已存在则输出“Students already exist”
- 功能2:删除学生信息 执行2时,输入学号信息;如果学生不存在,输出“Students do not exist”,如果存在,则输出“Delete success”
- 功能3:更改学生成绩信息 执行3时,输入学号信息;如果学生不存在,输出“Students do not exist”,如果存在,输出“Update success”
- 功能4:显示学生平均分成绩 执行4时,输入学号信息;如果学生不存在,输出“Students do not exist”,如果存在,则输出学生信息,如下格式:其中平均分为三门科目相加除以3,保留一位小数,每行之间换行。
- Student ID:2019989890
- Name:Jerry
- Average Score:89.3
问题求解:
- 构建一个学生信息管理系统,实现上述功能。
- 输入数据:第一行为一个整数n(0<n<130),后边共n行,每一行表示执行一种功能。其中1,2,3,4分别对应执行上面4种功能,具体格式见输入样例。 测试用例保证:学号和名字均为长度不超过10的字符串,各门课成绩为0到100之间的整数。
抽象模型
通常,我们遇到的现实世界中的问题都是采用自然语言描述的。当问题比较复杂时,其语义就会比较模糊。因此,我们需要采用数学语言来为之建立抽象模型,明确问题的确切含义。
典型的问题求解方式:
- 问题 \(\Rightarrow\) 抽象模型 \(\Rightarrow\) 模型中求解 \(\Rightarrow\) 对解的解释(问题的解)
- 这是一个典型的信息系统或信息管理系统。通过针对系统的一系列操作实现信息的存储、检索、处理。典型的操作一般包括:增加、删除、修改、查询。操作时通常还会附带一定的附加条件。
如何设计信息系统?
- 信息包括哪些量?
- 学生信息包括:学号 姓名 数学成绩 英语成绩 计算机成绩
- 学号本质上是数值,但是由于长度可能到10位,无法用int表示,所以采用字符串来描述。
typedef struct Student
{
char ID[11];
char name[11];
int math;
int eng;
int cs;
} STUDENT;
- 系统最多能存储多少学生信息?
- 第一行为一个整数n(0<n<130),后边共n行,每一行表示执行一种功能。从这里看出,最多129次操作,如果都是增加,则系统需要存储129位学生的信息。
- 一种典型的方法是采用数组来存储多个学生的信息,这里就是结构体数组。如果要考虑可扩展性,我们可以设置一个SIZE = 129,或者更大的数值。
STUDENT stu[SIZE];
- 系统如何设计,影响到具体操作如何实现!
进一步思考
- 数组本质上是一个有前后顺序的线性表。要不要排序?
- 排序的话,查询会很快。插入(增加)时需要腾位子,删除时也需要压缩位子。
- 不排序的话,查询只能线性查,插入可以在尾部,删除依然需要压缩位子。
- 题目没有要求,我们可以选择不排序。
- 注意:不同的选择,会影响到后续功能的实现方式!
模型中求解
信息管理系统,后续操作之间没有必然关联。因此,可以独立处理每一个操作。
- 在信息系统中进行操作,本质上就是在实现数组中查找、添加、删除、更新、输出操作。
但是,注意到每个操作都需要查看一下这个记录是否已经存在。所以,可以把这个共同的东西抽取出来,用一个函数来实现。这就是一种复用。
int locate(STUDENT stu[SIZE], char sid[LEN], int count);
为了在系统中查找记录,我们需要知道表中到底有多少学生记录。也就是数组中有多少元素是学生记录,有多少元素是空着的。所以,引入一个变量来存放:
int count;
每次增加或删除操作后,需要对这个变量进行调整。
- 注意:每个操作都需要判断记录是否存在,并据此进行相应的动作。特别是,当不需要进一步操作时,如果还有输入数据未被读取,需要读取后丢弃!
编码
基于上述分析,可以着手编码工作。
- 代码级的设计包括:函数原型设计、变量名命名、变量类型设计、状态变量设计
- 函数:不同功能打包成不同的函数,共同的功能设计为一个函数供上层函数调用。
addRecord
deleteRocrd
updateRecord
printRecord
locate
- 结构体类型:
STUDENT
- 宏定义常量:
SIZE,LEN
- 变量:系统中记录数
count
,操作符op
,学号sid
addRecoed
和deleteRecord
的第三个参数是int *count
,因为在函数中需要对count
作调整。
- 函数:不同功能打包成不同的函数,共同的功能设计为一个函数供上层函数调用。
- 输入数据处理:
scanf
应该可以处理一切。- 是否能用
"%s"
读取姓名? - 是否需要用
getchar
来处理whitespace
?
- 是否能用
- 完整的C语言代码
#include <stdio.h>
#include <string.h>
#define SIZE 129
#define LEN 11
// student record structure
typedef struct Student
{
char ID[LEN];
char name[LEN];
int math;
int eng;
int cs;
} STUDENT;
// check if the record is in the system
// nedded by other 4 functions
int locate(STUDENT stu[SIZE], char sid[LEN], int count);
// Followings are 4 functionalities
void addRecord(STUDENT stu[SIZE], char sid[LEN], int *count);
void deleteRecord(STUDENT stu[SIZE], char sid[LEN], int *count);
void updateRecord(STUDENT stu[SIZE], char sid[LEN], int count);
void printRecord(STUDENT stu[SIZE], char sid[LEN], int count);
int main()
{
STUDENT stu[SIZE];
int op;
char sid[LEN];
int i, n;
int count = 0; // Total number of records in the system
scanf("%d", &n); // how many actions to do
for (i = 0; i < n; i++)
{
// read in ops and studentID in order to determine what to do
scanf("%d%s", &op, sid);
if (op == 1)
{
addRecord(stu, sid, &count);
}
else if (op == 2)
{
deleteRecord(stu, sid, &count);
}
else if (op == 3)
{
updateRecord(stu, sid, count);
}
else // op == 4
{
printRecord(stu, sid, count);
}
}
return 0;
}
int locate(STUDENT stu[SIZE], char sid[LEN], int count)
{
int loc = 0;
while (loc < count)
{
if (strcmp(stu[loc].ID, sid) == 0)
return loc;
loc++;
}
return -1; // means no record
}
void addRecord(STUDENT stu[SIZE], char sid[LEN], int *count)
{
int loc;
loc = locate(stu, sid, *count);
// read and put the record at the end of the array temporarily
// NECESSARY!!! even if the record already exists
strcpy(stu[*count].ID, sid);
scanf("%s", stu[*count].name);
scanf("%d%d%d", &stu[*count].math, &stu[*count].eng, &stu[*count].cs);
if (loc == -1)
{
// if not already exist, increment count to make the record valid
*count += 1;
printf("Add success\n");
}
else
{
printf("Students already exist\n");
}
}
void deleteRecord(STUDENT stu[SIZE], char sid[LEN], int *count)
{
int i, loc;
loc = locate(stu, sid, *count);
if (loc != -1)
{
// if the record exists, delete it
// by shift the following records one index up,
// and then decrement count by 1
for (i = loc; i < *count - 1; i++)
{
stu[i] = stu[i + 1];
}
*count -= 1;
printf("Delete success\n");
}
else
{
printf("Students do not exist\n");
}
}
void updateRecord(STUDENT stu[SIZE], char sid[LEN], int count)
{
int loc;
loc = locate(stu, sid, count);
if (loc != -1)
{
// if the record exists, update by reading new values
scanf("%d%d%d", &stu[loc].math, &stu[loc].eng, &stu[loc].cs);
printf("Update success\n");
}
else
{
// abandon input value by putting them after the end of list
// NECESSARY!!!
scanf("%d%d%d", &stu[count].math, &stu[count].eng, &stu[count].cs);
printf("Students do not exist\n");
}
}
void printRecord(STUDENT stu[SIZE], char sid[LEN], int count)
{
int loc;
loc = locate(stu, sid, count);
if (loc != -1)
{
// if record exists, print out student info
printf("Student ID:%s\nName:%s\n", stu[loc].ID, stu[loc].name);
printf("Average Score:%.1f\n", (stu[loc].math + stu[loc].eng + stu[loc].cs) / 3.0);
}
else
{
printf("Students do not exist\n");
}
}
另一个版本:
https://onlinegdb.com/DZlIObbin
各位可以看看,你认为哪个版本更好?为什么?
另外,是否还可以做一些小的改善呢?
测试用例
- 边界测试
- 增加新记录
- 试图增加已存在的记录
- 删除存在的记录
- 试图删除不存在的记录
- 更新存在的记录
- 试图更新不存在的记录
- 打印存在的记录
- 试图打印不存在的记录
- 先增加后删除,再。。。
总结
- 这是一个典型的信息管理系统,主要功能就是增、删、改、查。
进一步讨论