下面是一个基于双向链表实现的学生管理系统采用C语言编写严格按照模块化设计包含详细注释并在关键操作中描述了链表的指针变化过程。---## 系统设计说明- **数据结构**双向链表每个节点包含学生信息学号、姓名、成绩以及指向前驱和后继的指针。- **功能模块**- 添加学生按学号自动升序插入保证链表有序- 删除学生按学号查找并删除释放内存- 修改学生信息先查找后修改- 查找学生按学号返回节点- 显示所有学生遍历链表- 退出系统释放整个链表- **模块划分**- student.h头文件定义结构体和函数原型。- student.c实现所有链表操作函数内部包含详细注释说明指针变化。- main.c主程序提供菜单交互调用链表函数。代码中每个关键步骤都附有注释解释指针的重新连接过程直观展示双向链表的“运动”方式。---## 代码实现### 1. student.hc#ifndef STUDENT_H#define STUDENT_H// 学生结构体同时也是双向链表节点typedef struct student {int id; // 学号char name[20]; // 姓名float score; // 成绩struct student *prev; // 指向前一个节点struct student *next; // 指向后一个节点} Student;// 函数声明Student* createStudent(int id, const char *name, float score);void insertStudent(Student **head, Student *newNode);void deleteStudent(Student **head, int id);Student* findStudent(Student *head, int id);void updateStudent(Student *head, int id, const char *newName, float newScore);void displayStudents(Student *head);void freeList(Student **head);#endif### 2. student.cc#include stdio.h#include stdlib.h#include string.h#include student.h/*** 创建新学生节点* 分配内存初始化数据前后指针置NULL*/Student* createStudent(int id, const char *name, float score) {Student *newNode (Student*)malloc(sizeof(Student));if (newNode NULL) {printf(内存分配失败\n);return NULL;}newNode-id id;strcpy(newNode-name, name);newNode-score score;newNode-prev NULL;newNode-next NULL;return newNode;}/*** 按学号升序插入新节点保证链表始终有序* 参数 head 为二级指针因为可能修改头指针* 插入过程详细注释描述了双向链表指针的变化*/void insertStudent(Student **head, Student *newNode) {// 情况1链表为空新节点成为头结点if (*head NULL) {*head newNode;printf(链表为空新节点 %d 作为头结点插入。\n, newNode-id);return;}// 情况2新节点学号小于头结点插入到头部if (newNode-id (*head)-id) {// 新节点的next指向原头结点newNode-next *head;// 原头结点的prev指向新节点(*head)-prev newNode;// 更新头指针为新节点*head newNode;printf(新节点 %d 学号最小插入到链表头部。\n, newNode-id);return;}// 情况3寻找插入位置在有序链表中找到第一个大于新学号的节点的前一个位置Student *current *head;// 遍历直到当前节点的下一个节点为空或者下一个节点的学号大于新学号while (current-next ! NULL current-next-id newNode-id) {current current-next;}// 此时有两种可能// 1) current-next NULL说明新节点学号最大插入到尾部// 2) current-next-id newNode-id插入到current之后current-next之前// 将新节点的next指向current-nextnewNode-next current-next;// 如果current-next存在则将其prev指向新节点if (current-next ! NULL) {current-next-prev newNode;}// 将current的next指向新节点current-next newNode;// 新节点的prev指向currentnewNode-prev current;if (newNode-next NULL) {printf(新节点 %d 学号最大插入到链表尾部。\n, newNode-id);} else {printf(新节点 %d 插入到学号 %d 和学号 %d 之间。\n,newNode-id, current-id, newNode-next-id);}}/*** 按学号删除节点* 释放被删除节点的内存* 注释说明了不同情况下指针的重连*/void deleteStudent(Student **head, int id) {if (*head NULL) {printf(链表为空无法删除。\n);return;}Student *current *head;// 查找要删除的节点while (current ! NULL current-id ! id) {current current-next;}if (current NULL) {printf(未找到学号为 %d 的学生。\n, id);return;}// 根据位置分情况处理if (current *head) {// 删除头结点头指针指向下一个节点新头结点的prev置NULL*head current-next;if (*head ! NULL) {(*head)-prev NULL;}printf(删除头结点学号 %d。\n, id);}else if (current-next NULL) {// 删除尾节点将前一个节点的next置NULLcurrent-prev-next NULL;printf(删除尾节点学号 %d。\n, id);}else {// 删除中间节点// 前一个节点的next指向当前节点的下一个current-prev-next current-next;// 下一个节点的prev指向当前节点的上一个current-next-prev current-prev;printf(删除中间节点学号 %d。\n, id);}free(current); // 释放内存}/*** 根据学号查找节点* 返回节点指针若未找到返回NULL*/Student* findStudent(Student *head, int id) {Student *current head;while (current ! NULL) {if (current-id id) {return current;}current current-next;}return NULL;}/*** 更新学生信息姓名、成绩* 先查找找到后直接修改数据链表结构不变*/void updateStudent(Student *head, int id, const char *newName, float newScore) {Student *stu findStudent(head, id);if (stu NULL) {printf(学号为 %d 的学生不存在无法更新。\n, id);return;}strcpy(stu-name, newName);stu-score newScore;printf(学生信息更新成功。\n);}/*** 显示所有学生信息从头到尾遍历*/void displayStudents(Student *head) {if (head NULL) {printf(当前学生列表为空。\n);return;}printf(学生列表按学号升序\n);Student *current head;while (current ! NULL) {printf(学号: %d, 姓名: %s, 成绩: %.2f\n,current-id, current-name, current-score);current current-next;}}/*** 释放整个链表防止内存泄漏*/void freeList(Student **head) {Student *current *head;while (current ! NULL) {Student *temp current;current current-next;free(temp);}*head NULL;printf(链表内存已释放。\n);}### 3. main.cc#include stdio.h#include stdlib.h#include student.hint main() {Student *head NULL; // 链表头指针初始为空int choice, id;char name[20];float score;do {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:printf(请输入学号: );scanf(%d, id);// 检查学号是否已存在if (findStudent(head, id) ! NULL) {printf(学号已存在添加失败。\n);break;}printf(请输入姓名: );scanf(%s, name);printf(请输入成绩: );scanf(%f, score);Student *newStu createStudent(id, name, score);if (newStu ! NULL) {insertStudent(head, newStu);printf(学生添加成功。\n);}break;case 2:printf(请输入要删除的学号: );scanf(%d, id);deleteStudent(head, id);break;case 3:printf(请输入要修改的学号: );scanf(%d, id);printf(请输入新的姓名: );scanf(%s, name);printf(请输入新的成绩: );scanf(%f, score);updateStudent(head, id, name, score);break;case 4:printf(请输入要查找的学号: );scanf(%d, id);Student *stu findStudent(head, id);if (stu ! NULL) {printf(找到学生学号: %d, 姓名: %s, 成绩: %.2f\n,stu-id, stu-name, stu-score);} else {printf(未找到该学号的学生。\n);}break;case 5:displayStudents(head);break;case 6:freeList(head);printf(感谢使用学生管理系统再见\n);break;default:printf(无效选择请重新输入。\n);}} while (choice ! 6);return 0;}---## 链表实际运动过程描述在双向链表的插入和删除操作中指针的重新连接是核心。以插入操作为例其过程如下参考 insertStudent 函数中的注释- **插入到头部**新节点的 next 指向原头结点原头结点的 prev 指向新节点然后头指针指向新节点。- **插入到中间**假设要在节点 A 和节点 B 之间插入新节点 X1. X-next B新节点指向后驱2. B-prev X后驱指回新节点3. A-next X前驱指向新节点4. X-prev A新节点指向前驱- **插入到尾部**最后一个节点的 next 指向新节点新节点的 prev 指向最后一个节点。删除操作则是上述过程的逆过程同时要注意释放被删除节点的内存防止泄漏。通过代码中的详细注释和运行时打印的位置信息可以清晰观察每个操作如何改变链表结构。