下面是一个基于双向链表实现的学生管理系统,采用C语言编写,严格按照模块化设计,包含详细注释,并在关键操作中描述了链表的指针变化过程。

---

## 系统设计说明

- **数据结构**:双向链表,每个节点包含学生信息(学号、姓名、成绩)以及指向前驱和后继的指针。
- **功能模块**:
  - 添加学生(按学号自动升序插入,保证链表有序)
  - 删除学生(按学号查找并删除,释放内存)
  - 修改学生信息(先查找,后修改)
  - 查找学生(按学号返回节点)
  - 显示所有学生(遍历链表)
  - 退出系统(释放整个链表)
- **模块划分**:
  - `student.h`:头文件,定义结构体和函数原型。
  - `student.c`:实现所有链表操作函数,内部包含详细注释说明指针变化。
  - `main.c`:主程序,提供菜单交互,调用链表函数。

代码中每个关键步骤都附有注释,解释指针的重新连接过程,直观展示双向链表的“运动”方式。

---

## 代码实现

### 1. student.h

```c
#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.c

```c
#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->next
    newNode->next = current->next;
    // 如果current->next存在,则将其prev指向新节点
    if (current->next != NULL) {
        current->next->prev = newNode;
    }
    // 将current的next指向新节点
    current->next = newNode;
    // 新节点的prev指向current
    newNode->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置NULL
        current->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.c

```c
#include <stdio.h>
#include <stdlib.h>
#include "student.h"

int 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` 之间插入新节点 `X`:
  1. `X->next = B`(新节点指向后驱)
  2. `B->prev = X`(后驱指回新节点)
  3. `A->next = X`(前驱指向新节点)
  4. `X->prev = A`(新节点指向前驱)
- **插入到尾部**:最后一个节点的 `next` 指向新节点,新节点的 `prev` 指向最后一个节点。

删除操作则是上述过程的逆过程,同时要注意释放被删除节点的内存,防止泄漏。

通过代码中的详细注释和运行时打印的位置信息,可以清晰观察每个操作如何改变链表结构。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐