一、项目概述
1.1 功能需求
一个简单的学生信息管理系统,支持以下操作:
-
添加学生(尾插)
-
删除学生(按学号)
-
修改学生信息(按学号)
-
查找学生(按学号)
-
显示所有学生
-
退出系统
1.2 学生信息
每个学生包含:
-
学号(int,作为唯一标识)
-
姓名(char数组)
-
年龄(int)
-
成绩(float)
1.3 技术选型
用带头结点的单链表存储数据。选择链表的原因:
-
学生数量不确定,动态增删
-
主要操作是按学号查找和删除,链表可以胜任
二、模块化设计
项目分成三个文件:
text
student_system/
├── main.c # 主程序,菜单和交互
├── student.h # 头文件,结构体定义和函数声明
└── student.c # 实现文件,函数具体实现
这样做的好处:
-
代码结构清晰
-
方便复用和维护
-
符合工程化开发规范
三、代码实现
3.1 student.h(头文件)
c
#ifndef STUDENT_H
#define STUDENT_H
#define MAX_NAME 20
// 学生结构体
typedef struct Student {
int id; // 学号
char name[MAX_NAME]; // 姓名
int age; // 年龄
float score; // 成绩
} Student;
// 链表节点结构体
typedef struct Node {
Student data; // 学生数据
struct Node *next; // 指向下一个节点
} Node, *PNode;
// 链表结构体
typedef struct {
PNode head; // 头结点(哑结点)
int size; // 节点个数
} LinkedList;
// 初始化链表
void initList(LinkedList *list);
// 销毁链表
void destroyList(LinkedList *list);
// 添加学生(尾插)
int addStudent(LinkedList *list, Student stu);
// 按学号删除学生
int deleteStudent(LinkedList *list, int id);
// 按学号修改学生信息
int updateStudent(LinkedList *list, int id, Student newStu);
// 按学号查找学生,返回节点指针
PNode findStudent(LinkedList *list, int id);
// 显示所有学生
void displayAll(LinkedList *list);
// 获取链表长度
int getSize(LinkedList *list);
#endif
3.2 student.c(实现文件)
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "student.h"
// 初始化链表(带头结点)
void initList(LinkedList *list) {
list->head = (PNode)malloc(sizeof(Node));
if (list->head == NULL) {
printf("初始化失败\n");
exit(1);
}
list->head->next = NULL;
list->size = 0;
}
// 销毁链表,释放所有节点
void destroyList(LinkedList *list) {
PNode cur = list->head;
while (cur != NULL) {
PNode temp = cur;
cur = cur->next;
free(temp);
}
list->head = NULL;
list->size = 0;
}
// 添加学生(尾插法)
int addStudent(LinkedList *list, Student stu) {
// 检查学号是否已存在
if (findStudent(list, stu.id) != NULL) {
printf("学号 %d 已存在,添加失败\n", stu.id);
return -1;
}
// 创建新节点
PNode newNode = (PNode)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败\n");
return -1;
}
newNode->data = stu;
newNode->next = NULL;
// 找到尾节点
PNode cur = list->head;
while (cur->next != NULL) {
cur = cur->next;
}
cur->next = newNode;
list->size++;
printf("添加成功\n");
return 0;
}
// 按学号删除学生
int deleteStudent(LinkedList *list, int id) {
// 找到要删除节点的前一个节点
PNode prev = list->head;
while (prev->next != NULL && prev->next->data.id != id) {
prev = prev->next;
}
if (prev->next == NULL) {
printf("学号 %d 不存在\n", id);
return -1;
}
PNode toDelete = prev->next;
prev->next = toDelete->next;
free(toDelete);
list->size--;
printf("删除成功\n");
return 0;
}
// 按学号修改学生信息
int updateStudent(LinkedList *list, int id, Student newStu) {
PNode node = findStudent(list, id);
if (node == NULL) {
printf("学号 %d 不存在\n", id);
return -1;
}
// 如果修改学号,需要检查新学号是否已被占用
if (newStu.id != id && findStudent(list, newStu.id) != NULL) {
printf("新学号 %d 已存在,修改失败\n", newStu.id);
return -1;
}
node->data = newStu;
printf("修改成功\n");
return 0;
}
// 按学号查找学生,返回节点指针
PNode findStudent(LinkedList *list, int id) {
PNode cur = list->head->next;
while (cur != NULL) {
if (cur->data.id == id) {
return cur;
}
cur = cur->next;
}
return NULL;
}
// 显示所有学生
void displayAll(LinkedList *list) {
if (list->size == 0) {
printf("暂无学生数据\n");
return;
}
printf("\n========================================\n");
printf("学号\t姓名\t\t年龄\t成绩\n");
printf("========================================\n");
PNode cur = list->head->next;
while (cur != NULL) {
printf("%d\t%-10s\t%d\t%.1f\n",
cur->data.id,
cur->data.name,
cur->data.age,
cur->data.score);
cur = cur->next;
}
printf("========================================\n");
printf("共 %d 名学生\n\n", list->size);
}
// 获取链表长度
int getSize(LinkedList *list) {
return list->size;
}
3.3 main.c(主程序)
c
#include <stdio.h>
#include <stdlib.h>
#include "student.h"
// 清空输入缓冲区
void clearInput() {
while (getchar() != '\n');
}
// 打印菜单
void showMenu() {
printf("\n======== 学生信息管理系统 ========\n");
printf("1. 添加学生\n");
printf("2. 删除学生\n");
printf("3. 修改学生信息\n");
printf("4. 查找学生\n");
printf("5. 显示所有学生\n");
printf("6. 查看学生总数\n");
printf("0. 退出系统\n");
printf("===================================\n");
printf("请选择操作: ");
}
// 添加学生界面
void addStudentUI(LinkedList *list) {
Student stu;
printf("\n--- 添加学生 ---\n");
printf("请输入学号: ");
scanf("%d", &stu.id);
printf("请输入姓名: ");
scanf("%s", stu.name);
printf("请输入年龄: ");
scanf("%d", &stu.age);
printf("请输入成绩: ");
scanf("%f", &stu.score);
addStudent(list, stu);
}
// 删除学生界面
void deleteStudentUI(LinkedList *list) {
int id;
printf("\n--- 删除学生 ---\n");
printf("请输入要删除的学号: ");
scanf("%d", &id);
deleteStudent(list, id);
}
// 修改学生界面
void updateStudentUI(LinkedList *list) {
int id;
Student newStu;
printf("\n--- 修改学生信息 ---\n");
printf("请输入要修改的学号: ");
scanf("%d", &id);
// 先检查学生是否存在
if (findStudent(list, id) == NULL) {
printf("学号 %d 不存在\n", id);
return;
}
printf("请输入新的信息:\n");
printf("学号: ");
scanf("%d", &newStu.id);
printf("姓名: ");
scanf("%s", newStu.name);
printf("年龄: ");
scanf("%d", &newStu.age);
printf("成绩: ");
scanf("%f", &newStu.score);
updateStudent(list, id, newStu);
}
// 查找学生界面
void findStudentUI(LinkedList *list) {
int id;
printf("\n--- 查找学生 ---\n");
printf("请输入要查找的学号: ");
scanf("%d", &id);
PNode node = findStudent(list, id);
if (node == NULL) {
printf("学号 %d 不存在\n", id);
return;
}
printf("\n找到学生信息:\n");
printf("学号: %d\n", node->data.id);
printf("姓名: %s\n", node->data.name);
printf("年龄: %d\n", node->data.age);
printf("成绩: %.1f\n", node->data.score);
}
int main() {
LinkedList list;
initList(&list);
int choice;
printf("欢迎使用学生信息管理系统!\n");
while (1) {
showMenu();
scanf("%d", &choice);
clearInput(); // 清空缓冲区
switch (choice) {
case 1:
addStudentUI(&list);
break;
case 2:
deleteStudentUI(&list);
break;
case 3:
updateStudentUI(&list);
break;
case 4:
findStudentUI(&list);
break;
case 5:
displayAll(&list);
break;
case 6:
printf("当前共有 %d 名学生\n", getSize(&list));
break;
case 0:
printf("感谢使用,再见!\n");
destroyList(&list);
return 0;
default:
printf("无效选择,请重新输入\n");
}
}
return 0;
}
四、编译与运行
4.1 编译命令
在命令行中执行(假设三个文件在同一目录):
bash
gcc main.c student.c -o student_system
或者使用 Makefile:
makefile
# Makefile
student_system: main.c student.c student.h
gcc main.c student.c -o student_system
clean:
rm -f student_system
4.2 运行效果
text
欢迎使用学生信息管理系统!
======== 学生信息管理系统 ========
1. 添加学生
2. 删除学生
3. 修改学生信息
4. 查找学生
5. 显示所有学生
6. 查看学生总数
0. 退出系统
===================================
请选择操作: 1
--- 添加学生 ---
请输入学号: 1001
请输入姓名: 张三
请输入年龄: 20
请输入成绩: 85.5
添加成功
======== 学生信息管理系统 ========
...
请选择操作: 5
========================================
学号 姓名 年龄 成绩
========================================
1001 张三 20 85.5
========================================
共 1 名学生
五、代码模块化说明
5.1 为什么要模块化
| 优点 | 说明 |
|---|---|
| 可读性 | 每个文件职责单一,代码更清晰 |
| 可维护性 | 修改链表实现不影响主程序 |
| 可复用性 | student.h可以被其他项目包含使用 |
| 编译效率 | 修改一个文件只需重新编译该文件 |
5.2 模块划分原则
-
头文件(.h):放声明,结构体定义,函数原型,宏定义
-
实现文件(.c):放具体实现,包含对应的头文件
-
主文件(main.c):放主逻辑,只调用接口,不关心内部实现
5.3 防止头文件重复包含
c
#ifndef STUDENT_H
#define STUDENT_H
// 头文件内容
#endif
这是头文件保护宏,防止重复包含导致编译错误。
六、扩展思考
6.1 可以改进的地方
-
数据持久化:把数据保存到文件,下次启动时加载
-
排序功能:按学号、成绩排序显示
-
模糊查找:按姓名关键字查找
-
统计功能:平均分、最高分、最低分
-
改用双向链表:实现反向遍历
-
改用顺序表:对比两种实现的差异
6.2 练习建议
尝试自己实现以下功能:
-
保存数据到文件(fwrite/fread)
-
从文件加载数据
-
按成绩排序并输出
-
统计不及格学生名单
七、小结
这一篇我们用单链表实现了一个完整的学生信息管理系统:
| 内容 | 说明 |
|---|---|
| 数据结构 | 带头结点的单链表 |
| 核心操作 | 增删改查、遍历 |
| 模块化 | .h和.c分离,符合工程规范 |
| 交互方式 | 命令行菜单 |
这是数据结构学习的第一个完整项目。麻雀虽小,五脏俱全,它涵盖了链表的所有基本操作,也是后续更复杂项目的基础。
下一篇开始,我们会进入新的章节:栈和队列。