【数据结构与算法】第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分离,符合工程规范
交互方式 命令行菜单

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

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

相关推荐
q_35488851532 小时前
计算机毕业设计:汽车数据可视化与智能分析平台 Django框架 Scrapy爬虫 可视化 车辆 懂车帝大数据 数据分析 机器学习(建议收藏)✅
算法·信息可视化·django·flask·汽车·课程设计·美食
MyBFuture2 小时前
Halcon模板匹配核心技术解析大全
开发语言·人工智能·计算机视觉·halcon·机器视觉
精神小伙就是猛2 小时前
使用go-zero快速搭建一个微服务(一)
开发语言·后端·微服务·golang
不会聊天真君6472 小时前
基础语法·下(golang笔记第三期)
开发语言·笔记·golang
FakeOccupational2 小时前
【电路笔记 通信】IEEE 1588精密时间协议(PTP):数学假设+时间同步链路建模+消除主从偏差算法
笔记·算法
客卿1232 小时前
最小生成树(贪心)--构造回文串(字符串 + 回文判断 + 构造)
java·开发语言·算法
Bert.Cai2 小时前
Python input函数作用
开发语言·python
88号技师2 小时前
2026年3月中科院一区SCI-赏金猎人优化算法Bounty Hunter Optimizer-附Matlab免费代码
开发语言·算法·数学建模·matlab·优化算法
再卷也是菜2 小时前
算法基础篇(12)Trie树
算法·字典树