当前位置: 首页 > news >正文

【数据结构与算法】第10篇:项目实战:学生信息管理系统(线性表版)

一、项目概述

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 可以改进的地方

  1. 数据持久化:把数据保存到文件,下次启动时加载

  2. 排序功能:按学号、成绩排序显示

  3. 模糊查找:按姓名关键字查找

  4. 统计功能:平均分、最高分、最低分

  5. 改用双向链表:实现反向遍历

  6. 改用顺序表:对比两种实现的差异

6.2 练习建议

尝试自己实现以下功能:

  • 保存数据到文件(fwrite/fread)

  • 从文件加载数据

  • 按成绩排序并输出

  • 统计不及格学生名单


七、小结

这一篇我们用单链表实现了一个完整的学生信息管理系统:

内容说明
数据结构带头结点的单链表
核心操作增删改查、遍历
模块化.h和.c分离,符合工程规范
交互方式命令行菜单

这是数据结构学习的第一个完整项目。麻雀虽小,五脏俱全,它涵盖了链表的所有基本操作,也是后续更复杂项目的基础。

下一篇开始,我们会进入新的章节:栈和队列。

http://www.jsqmd.com/news/547797/

相关文章:

  • Neofetch终极主题切换指南:基于时间与系统状态的智能样式调整
  • DSP2812开发必备:手把手教你从TI官网下载标准头文件和例程(附导入CCS教程)
  • Ollama-for-amd实战指南:AMD GPU本地AI部署从入门到精通
  • FastAPI CORS源验证:打造安全灵活的动态允许列表
  • Crawlee性能监控终极指南:7个关键指标收集与可视化展示技巧
  • OpenClaw智能监控:nanobot镜像实时扫描日志文件发送警报
  • 如何实现FastAPI后端API版本控制:full-stack-fastapi-template的完整演进策略
  • OpenClaw任务稳定性优化:nanobot镜像的3个调参技巧
  • Scoop安全更新终极指南:如何及时修复漏洞并保护你的系统
  • AWD竞赛平台实战:从零搭建Cardinal系统
  • 2026年OpenClaw移动云2分钟本地云上安装及使用教程【教程】
  • 如何使用Apache Pulsar实现MongoDB实时数据同步:完整CDC解决方案指南
  • Transformer架构实战:从零实现一个简易版ChatGPT聊天机器人
  • Phi-3-Mini-128K多场景落地:智能硬件语音交互前端+本地大模型语义理解后端
  • Python类型注解工具选型决策树(附Benchmark实测数据:mypy vs pyright vs pylance vs Jedi vs MonkeyType)
  • 5步掌握[特殊字符] Datasets能源AI:电力负荷预测数据处理终极指南
  • Obsidian Tasks插件开发最佳实践:从代码规范到发布流程的完整指南
  • MediaPipe下一代技术预览:揭秘未来AI开发新方向与跨平台机器学习解决方案
  • SeqGPT-560M保姆级教程:处理中文标点歧义、长句嵌套、多义词等典型问题
  • GitLab集成golang-migrate/migrate:远程迁移文件管理完整指南 [特殊字符]
  • 跨平台Obsidian笔记同步:WebDAV与内网穿透的实战指南
  • 3步掌握Python代码可视化:用VizTracer轻松洞察代码执行过程
  • Rocky Linux 9.4桌面应用实战:办公、影音、远程工具一个都不少(附WPS/QQ/ToDesk安装避坑指南)
  • Apache Pulsar资源配额管理终极指南:租户与命名空间级别限制详解
  • Nunchaku FLUX.1-dev在ComfyUI中的两种安装方法详解(CLI与手动)
  • 高效获取Qobuz高品质音乐:QobuzDownloaderX-MOD全流程技术指南
  • awesome-project精选:10个必备前端开发工具提升你的开发效率
  • Fish Speech 1.5企业降本提效案例:替代商用TTS服务年省超8万元
  • OpenClaw+GLM-4.7-Flash:个人财务记录分析
  • Gemma-3-12b-it多卡适配教程:CUDA_VISIBLE_DEVICES与NCCL优化详解