第1章:数据结构综合应用——学生成绩管理系统
说实话,很多初学者学完结构体、链表、文件操作和排序后,总觉得这些知识点是孤立的。我当年也有这种感觉,直到第一次在项目中需要把学生成绩数据存下来、查出来、排好序——才发现,原来这些知识点是串在一起的。
这一章,我们就来做一个完整的学生成绩管理系统。它不复杂,但很实用。你想想看,面试官最喜欢问的就是这种综合题。
1. 系统功能概览
我们要实现的功能其实很清晰:
- 添加学生信息(学号、姓名、成绩)
- 删除指定学生
- 修改成绩
- 按成绩排序(升序/降序)
- 保存到文件
- 从文件加载
嗯,说白了就是一个迷你版的教务系统。我在做嵌入式项目时,就经常用这种结构来管理设备配置信息。
2. 核心数据结构设计
我们先定义学生结构体:
typedef struct Student {
int id; // 学号
char name[32]; // 姓名
float score; // 成绩
struct Student *next; // 指向下一个节点
} Student;
这里我用了链表而不是数组。为什么?因为链表插入和删除更方便,不用搬动大量数据。我在项目中遇到过需要频繁增删的场景,数组真的让人头疼。
核心思路:用链表管理动态数据,用文件做持久化,用排序算法处理展示需求。
3. 链表操作核心函数
我们来看几个关键函数。先看创建节点:
Student* createNode(int id, char *name, float score) {
Student *node = (Student*)malloc(sizeof(Student));
if (!node) {
printf("内存分配失败\n");
return NULL;
}
node->id = id;
strcpy(node->name, name);
node->score = score;
node->next = NULL;
return node;
}
这里有个坑——一定要检查 malloc 返回值。我曾经在一个项目中忘了检查,结果在内存不足时程序直接崩溃,排查了好久才发现。
再看插入函数,我习惯按学号升序插入:
void insertSorted(Student **head, Student *newNode) {
if (*head == NULL || (*head)->id > newNode->id) {
newNode->next = *head;
*head = newNode;
return;
}
Student *cur = *head;
while (cur->next != NULL && cur->next->id < newNode->id) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
}
为什么用二级指针?因为我们要修改头指针本身。嗯,这个细节面试经常考。
4. 排序实现
排序我选择了冒泡排序,虽然效率不是最高,但胜在简单直观,适合教学:
void sortByScore(Student *head, int ascending) {
if (head == NULL) return;
int swapped;
Student *ptr;
Student *last = NULL;
do {
swapped = 0;
ptr = head;
while (ptr->next != last) {
int needSwap = ascending ?
(ptr->score > ptr->next->score) :
(ptr->score < ptr->next->score);
if (needSwap) {
float tempScore = ptr->score;
ptr->score = ptr->next->score;
ptr->next->score = tempScore;
// 同样交换 id 和 name
int tempId = ptr->id;
ptr->id = ptr->next->id;
ptr->next->id = tempId;
char tempName[32];
strcpy(tempName, ptr->name);
strcpy(ptr->name, ptr->next->name);
strcpy(ptr->next->name, tempName);
swapped = 1;
}
ptr = ptr->next;
}
last = ptr;
} while (swapped);
}
小技巧:实际项目中,我更推荐用归并排序处理链表,时间复杂度 O(n log n),而且稳定。但面试时能写出冒泡排序已经及格了。
5. 文件操作
保存到文件,我习惯用二进制方式,读写速度快:
void saveToFile(Student *head, const char *filename) {
FILE *fp = fopen(filename, "wb");
if (!fp) {
printf("无法打开文件 %s\n", filename);
return;
}
Student *cur = head;
while (cur) {
fwrite(cur, sizeof(Student), 1, fp);
cur = cur->next;
}
fclose(fp);
printf("数据保存成功\n");
}
从文件加载时要注意:我们只保存了数据部分,不保存 next 指针。加载后要重新建立链表关系:
Student* loadFromFile(const char *filename) {
FILE *fp = fopen(filename, "rb");
if (!fp) return NULL;
Student *head = NULL;
Student temp;
while (fread(&temp, sizeof(Student), 1, fp) == 1) {
Student *node = createNode(temp.id, temp.name, temp.score);
insertSorted(&head, node);
}
fclose(fp);
return head;
}
注意:二进制文件在不同平台间可能不兼容(大小端问题)。如果需要在不同系统间迁移数据,建议用文本格式(如 CSV)。
6. 系统整体流程
下面这张图展示了整个系统的核心逻辑:
7. 完整代码示例
下面是一个精简但完整的 main 函数框架:
int main() {
Student *head = NULL;
int choice;
// 启动时加载已有数据
head = loadFromFile("students.dat");
while (1) {
printf("\n=== 学生成绩管理系统 ===\n");
printf("1. 添加学生\n");
printf("2. 删除学生\n");
printf("3. 修改成绩\n");
printf("4. 按成绩排序\n");
printf("5. 显示所有学生\n");
printf("6. 保存并退出\n");
printf("请选择: ");
scanf("%d", &choice);
switch (choice) {
case 1: {
int id; char name[32]; float score;
printf("输入学号 姓名 成绩: ");
scanf("%d %s %f", &id, name, &score);
Student *node = createNode(id, name, score);
insertSorted(&head, node);
break;
}
case 2: {
int id;
printf("输入要删除的学号: ");
scanf("%d", &id);
deleteStudent(&head, id);
break;
}
case 3: {
int id; float newScore;
printf("输入学号和新成绩: ");
scanf("%d %f", &id, &newScore);
updateScore(head, id, newScore);
break;
}
case 4: {
int order;
printf("1.升序 2.降序: ");
scanf("%d", &order);
sortByScore(head, order == 1);
displayAll(head);
break;
}
case 5:
displayAll(head);
break;
case 6:
saveToFile(head, "students.dat");
freeList(head);
return 0;
default:
printf("无效选择\n");
}
}
}
8. 避坑指南
我总结几个常见问题:
- 内存泄漏:每次删除节点或程序退出时,一定要 free 所有节点。我曾经忘记释放,程序跑了几天后内存爆了。
- 文件读写错误:打开文件后一定要检查返回值,不要假设文件一定存在。
- 排序稳定性:如果成绩相同,你希望按学号排序吗?那就需要二次排序条件。
- 输入缓冲区:scanf 后记得清空缓冲区,否则后面的 fgets 会读到换行符。
总结一下:这个系统虽然小,但涵盖了 C 语言的核心知识点。你如果能独立写出来,面试时数据结构这块基本就稳了。
嗯,代码就这些。建议你自己动手敲一遍,遇到问题再回头看。动手实践才是最好的学习方式。
公众号:蓝海资料掘金营,微信deep3321