C 语言从 0 入门(十七)|结构体指针 + 动态内存 + 文件综合实战

大家好,我是网域小星球。

前面我们已经学习了结构体、指针、动态内存、文件操作以及简易项目框架。这一篇我们把所有知识点彻底打通融合,写一个更完善、更灵活、可长期保存数据的小型综合程序。

本篇依然全程适配 VS2022,代码可直接复制运行,逻辑清晰、注释完整,适合作为课程设计、实验报告、博客实战案例。

目录

一、本章学习目标

二、功能设计(菜单)

三、完整可运行代码

四、本章重点知识点总结

五、高频易错点

六、本章核心总结

下期预告

一、本章学习目标

  1. 学会用结构体指针管理一批学生数据
  2. 使用动态内存实现可自动扩容的学生列表
  3. 结合文件操作实现数据持久化(重启程序不丢失)
  4. 完善菜单功能:展示、添加、查找、修改、保存、退出
  5. 掌握大型综合程序的编写思路与规范
  6. 学会常见 bug 排查与健壮性处理

二、功能设计(菜单)

cpp 复制代码
=========================
    学生综合管理系统
=========================
 1 展示所有学生
 2 添加学生信息
 3 按学号查找学生
 4 修改学生成绩
 5 保存数据到文件
 6 从文件加载数据
 0 退出系统
=========================

三、完整可运行代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

// 学生结构体
struct Student {
    int id;
    char name[20];
    float score;
};

// 动态管理全局变量
struct Student* stus = NULL;
int count = 0;      // 当前人数
int capacity = 0;   // 总容量

// 函数声明
void showMenu();
void checkCapacity();
void showAll();
void addStu();
void findStu();
void modifyScore();
void saveToFile();
void loadFromFile();

int main() {
    int choice;
    while (1) {
        showMenu();
        printf("请输入选择:");
        scanf("%d", &choice);

        switch (choice) {
        case 1: showAll();            break;
        case 2: addStu();             break;
        case 3: findStu();            break;
        case 4: modifyScore();        break;
        case 5: saveToFile();         break;
        case 6: loadFromFile();       break;
        case 0:
            printf("感谢使用,再见!\n");
            free(stus);
            stus = NULL;
            return 0;
        default:
            printf("输入错误,请重新选择!\n");
        }
        printf("\n");
    }
}

// 显示菜单
void showMenu() {
    printf("=========================\n");
    printf("    学生综合管理系统\n");
    printf("=========================\n");
    printf(" 1 展示所有学生\n");
    printf(" 2 添加学生信息\n");
    printf(" 3 按学号查找学生\n");
    printf(" 4 修改学生成绩\n");
    printf(" 5 保存数据到文件\n");
    printf(" 6 从文件加载数据\n");
    printf(" 0 退出系统\n");
    printf("=========================\n");
}

// 检查容量,满了就扩容
void checkCapacity() {
    if (count < capacity) return;

    int newCap = (capacity == 0) ? 5 : capacity * 2;
    struct Student* temp = (struct Student*)realloc(stus, newCap * sizeof(struct Student));

    if (temp == NULL) {
        printf("内存扩容失败!\n");
        return;
    }
    stus = temp;
    capacity = newCap;
}

// 展示所有
void showAll() {
    printf("当前学生数:%d\n", count);
    if (count == 0) {
        printf("暂无学生数据!\n");
        return;
    }
    printf("学号\t姓名\t成绩\n");
    for (int i = 0; i < count; i++) {
        printf("%d\t%s\t%.1f\n", stus[i].id, stus[i].name, stus[i].score);
    }
}

// 添加学生
void addStu() {
    checkCapacity();
    printf("请输入 学号 姓名 成绩:");
    scanf("%d%s%f", &stus[count].id, stus[count].name, &stus[count].score);
    count++;
    printf("添加成功!\n");
}

// 按学号查找
void findStu() {
    int findId;
    printf("请输入要查找的学号:");
    scanf("%d", &findId);

    for (int i = 0; i < count; i++) {
        if (stus[i].id == findId) {
            printf("找到:%d %s %.1f\n", stus[i].id, stus[i].name, stus[i].score);
            return;
        }
    }
    printf("未找到该学生!\n");
}

// 修改成绩
void modifyScore() {
    int id;
    printf("请输入要修改的学号:");
    scanf("%d", &id);

    for (int i = 0; i < count; i++) {
        if (stus[i].id == id) {
            printf("当前成绩:%.1f\n", stus[i].score);
            printf("请输入新成绩:");
            scanf("%f", &stus[i].score);
            printf("修改成功!\n");
            return;
        }
    }
    printf("未找到该学生!\n");
}

// 保存到文件
void saveToFile() {
    FILE* fp = fopen("stu_data.txt", "w");
    if (fp == NULL) {
        printf("文件打开失败!\n");
        return;
    }
    for (int i = 0; i < count; i++) {
        fprintf(fp, "%d %s %.1f\n", stus[i].id, stus[i].name, stus[i].score);
    }
    fclose(fp);
    printf("已保存到 stu_data.txt\n");
}

// 从文件加载
void loadFromFile() {
    FILE* fp = fopen("stu_data.txt", "r");
    if (fp == NULL) {
        printf("文件不存在!\n");
        return;
    }

    count = 0;
    int id;
    char name[20];
    float score;

    while (fscanf(fp, "%d%s%f", &id, name, &score) != EOF) {
        checkCapacity();
        stus[count].id = id;
        snprintf(stus[count].name, sizeof(stus[count].name), "%s", name);
        stus[count].score = score;
        count++;
    }
    fclose(fp);
    printf("加载成功!共 %d 条数据\n", count);
}

四、本章重点知识点总结

  1. 动态内存扩容 使用 realloc 自动扩容,不再受固定数组大小限制。

  2. 结构体指针访问 直接用 stus[i] 即可,等价于指针偏移访问。

  3. 数据持久化保存 / 加载文件,关闭程序再打开数据依然存在。

  4. 程序健壮性

    • 容量自动检查
    • 文件判空
    • 退出时 free 释放内存
  5. 模块化设计一个功能一个函数,结构清晰、易于扩展。


五、高频易错点

  1. 忘记 free 导致内存泄漏
  2. realloc 不判空直接使用
  3. 文件路径错误或权限不足
  4. 加载文件时覆盖原有数据
  5. 学号重复未做判断
  6. 字符串直接赋值导致报错(必须用 strcpy /snprintf)

六、本章核心总结

  1. 结构体 + 指针 + 动态内存 = 灵活高效的数据管理
  2. 文件操作实现真正意义上的 "持久化存储"
  3. 菜单 + switch 是控制台小项目通用框架
  4. 模块化函数让代码更易读、易维护
  5. 本篇可直接作为课程设计、大作业、实验报告使用

下期预告

下一篇我们系统学习字符串核心操作函数,理解底层原理并自己手写实现,为笔试面试打下扎实基础。

相关推荐
aq55356004 小时前
三大编程语言深度对比:C# vs 易语言 vs 汇编
开发语言·汇编·c#
独特的螺狮粉4 小时前
云隙一言:鸿蒙Flutter框架 实现的随机名言应用
开发语言·flutter·华为·架构·开源·harmonyos
光泽雨4 小时前
c# 文件编译的过程
开发语言·c#
赤水无泪4 小时前
09 C++ 11 新增的标准
开发语言
格林威5 小时前
工业相机 SDK 在 Docker 容器中的部署与权限配置(含 USB/GigE)
开发语言·人工智能·数码相机·计算机视觉·docker·容器·工业相机
哎嗨人生公众号5 小时前
手写求导公式,让轨迹优化性能飞升,150ms变成9ms
开发语言·c++·算法·机器人·自动驾驶
code_whiter5 小时前
C++6(模板)
开发语言·c++
lcj25115 小时前
【C语言】数据在内存中的存储
c语言·数据结构
一只旭宝5 小时前
【C++ 入门精讲1】初始化、const、引用、内联函数 | 超详细手写笔记(附完整代码)
开发语言·c++