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

(C语言)超市管理系统(测试版)(指针)(数据结构)(二进制文件读写)

目录

前言:

源代码:

product.h

 product.c

 fileio.h

 fileio.c

 main.c

代码解析:

fileio模块(文件(二进制))

 写文件(保存)

函数功能

代码逐行解析

关键知识点

读文件(加载) 

函数功能

代码逐行解析

关键知识点

mian模块 (free释放内存)

1. 为什么需要这行代码?

内存泄漏问题

代码中的具体场景

2. free(list.Data) 的作用

释放堆内存

内存示意图

3. 何时调用 free()?

正确时机

忘记释放的后果

4. 为什么不需要释放 List 变量本身?


前言:

当前这篇博客是测试版,仅仅教大家读写二进制文件的相关知识点!

共6个文件(加上二进制文件);

源代码:

product.h

//product.h
#pragma once //防止头文件重复定义#define NAME_LEN 50 //商品名称最大容量//单个商品结构体
typedef struct {int id;//商品编号char name[NAME_LEN];//商品名字float price;//商品单价int stock;//商品库存
}Product;//商品列表表结构体
typedef struct {Product* Data;//指向单个商品数组的指针int count;//当前商品数量
}ProductList;// 函数原型
void Init_products(ProductList* list);//初始化商品列表结构体

 product.c

//product.c
#include "product.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>void Init_products(ProductList* list) {list->Data = NULL;//指针置空,防止野指针list->count = 0;//商品数量归0
}

 fileio.h

//fileio.h
#pragma once
#include "product.h"// 文件操作函数原型
void save_to_file(const char* filename, const ProductList* list);
void load_from_file(const char* filename, ProductList* list);

 fileio.c

//fileio.c
//引用头文件
#include <stdio.h>
#include <stdlib.h>
#include "product.h"// 保存数据到文件(二进制写入)
void save_to_file(const char* filename, const ProductList* list) {//1.打开文件(二进制写入模式)FILE* fp = fopen(filename, "wb");// "wb":二进制写入模式,会清空原文件内容// 若文件不存在则创建新文件if (!fp) { // fp == NULL 表示打开失败perror("保存失败"); // 输出错误信息(包含具体原因,如权限不足)exit(EXIT_FAILURE); // 终止程序,EXIT_FAILURE 表示异常退出}//2.先写入商品数量(int 类型)fwrite(&list->count,sizeof(int),1,fp);// &list->count:取商品数量的内存地址// sizeof(int):每个元素的大小(4字节)// 1:写入1个元素// fp:文件指针//3.再写入所有商品数据(Product 结构体数组)fwrite(list->Data, sizeof(Product), list->count, fp);// list->Data:商品数组首地址// sizeof(Product):每个商品占用的字节数// list->count:要写入的商品数量//4.关闭文件fclose(fp);
}// 从文件加载数据(二进制读取)
void load_from_file(const char* filename, ProductList* list) {//1.初始化结构体(防御性编程)Init_products(&list);//初始化商品列表结构体//2.尝试打开文件(二进制读取模式)FILE* fp = fopen(filename, "rb");// "rb":二进制读取模式,文件不存在时返回 NULLif (!fp) {//文件打开失败处理return; // 保持 list 的初始状态(count=0, Data=NULL)}//3.读取商品数量(int 类型)fread(&list->count,sizeof(int),1,fp);// 从文件中读取4字节到 list->count//4.根据数量分配内存list->Data = malloc(list->count * sizeof(Product));// 计算总字节数 = 商品数量 × 单个商品大小//检查是否分配成功if (list->Data == NULL) { // list->Data == NULL 表示失败printf("内存分配失败\n");exit(EXIT_FAILURE); // 终止程序}//5.读取所有商品数据fread(list->Data, sizeof(Product), list->count, fp);// 将文件内容直接读入 Data 数组//6.关闭文件fclose(fp);
}

 main.c

//mian.c
#include <stdio.h>
#include <stdlib.h>
#include "product.h"
#include "fileio.h"#define FILENAME "products.dat"//宏定义文件名// 显示主菜单(用户界面)
void display_menu() {printf("\n超市管理系统\n");printf("1. 添加商品\n");printf("2. 显示所有商品\n");printf("3. 修改商品信息\n");printf("4. 删除商品\n");printf("5. 搜索商品\n");printf("6. 保存并退出\n");printf("请选择操作:");
}int mian() {//1.创建结构体并初始化ProductList list;//创建商品列表结构体Init_products(&list);//初始化//2.读文件load_from_file(FILENAME, &list);//读文件//3.选择模块int choice;//选择选项while (1) {display_menu();//显示菜单scanf("%d", &choice);//输入选项switch (choice) {case 1://add_product(&list);break;case 2://display_products(&list);break;case 6:save_to_file(FILENAME, &list); // 保存数据free(list.Data); // 释放动态内存printf("系统已退出\n");return 0; // 正确退出default:printf("无效输入\n");}}
}

代码解析:

fileio模块(文件(二进制))

 写文件(保存)

save_to_file 函数解析

函数功能

将 ProductList 中的商品数据保存到二进制文件中。

代码逐行解析
// 保存数据到文件(二进制写入)
void save_to_file(const char* filename, const ProductList* list) {//1.打开文件(二进制写入模式)FILE* fp = fopen(filename, "wb");// "wb":二进制写入模式,会清空原文件内容// 若文件不存在则创建新文件if (!fp) { // fp == NULL 表示打开失败perror("保存失败"); // 输出错误信息(包含具体原因,如权限不足)exit(EXIT_FAILURE); // 终止程序,EXIT_FAILURE 表示异常退出}//2.先写入商品数量(int 类型)fwrite(&list->count,sizeof(int),1,fp);// &list->count:取商品数量的内存地址// sizeof(int):每个元素的大小(4字节)// 1:写入1个元素// fp:文件指针//3.再写入所有商品数据(Product 结构体数组)fwrite(list->Data, sizeof(Product), list->count, fp);// list->Data:商品数组首地址// sizeof(Product):每个商品占用的字节数// list->count:要写入的商品数量//4.关闭文件fclose(fp);
}
关键知识点
  1. 二进制文件操作

    • "wb" 模式直接写入内存数据,保持精确性(如浮点数不会丢失精度)

    • 文件内容不可直接阅读,但读写效率高

  2. 数据存储顺序
    文件结构如下:

    ┌──────────────┬───────────────────────────┐
    │ 4字节整数     │ N个Product结构体          │
    │ (商品数量count) │ (每个占sizeof(Product)字节) │
    └──────────────┴───────────────────────────┘
  3. 错误处理

    • perror 会输出类似 保存失败: Permission denied 的详细信息

    • exit(EXIT_FAILURE) 立即终止程序,防止后续操作破坏数据

  4. const 修饰符

    • const ProductList* list 保证函数内不会修改结构体内容

读文件(加载) 

load_from_file 函数解析

函数功能

从二进制文件中加载数据到 ProductList 结构体。

代码逐行解析
// 从文件加载数据(二进制读取)
void load_from_file(const char* filename, ProductList* list) {//1.初始化结构体(防御性编程)Init_products(&list);//初始化商品列表结构体//2.尝试打开文件(二进制读取模式)FILE* fp = fopen(filename, "rb");// "rb":二进制读取模式,文件不存在时返回 NULLif (!fp) {//文件打开失败处理return; // 保持 list 的初始状态(count=0, Data=NULL)}//3.读取商品数量(int 类型)fread(&list->count,sizeof(int),1,fp);// 从文件中读取4字节到 list->count//4.根据数量分配内存list->Data = malloc(list->count * sizeof(Product));// 计算总字节数 = 商品数量 × 单个商品大小//检查是否分配成功if (list->Data == NULL) { // list->Data == NULL 表示失败printf("内存分配失败\n");exit(EXIT_FAILURE); // 终止程序}//5.读取所有商品数据fread(list->Data, sizeof(Product), list->count, fp);// 将文件内容直接读入 Data 数组//6.关闭文件fclose(fp);
}
关键知识点
  1. 安全初始化

    • 进入函数后立刻初始化 list,避免残留值导致错误

  2. 文件打开模式

    • "rb" 表示二进制读取模式,文件不存在时不会创建新文件

  3. 动态内存管理

    • malloc 根据文件中的商品数量分配精确内存

    • 计算公式:数量 × sizeof(Product) 确保内存足够存放所有商品

扩展知识:二进制文件 vs 文本文件

特性二进制文件文本文件
存储方式直接存储内存数据存储字符编码
可读性不可直接阅读可用文本编辑器查看
浮点数存储精确(IEEE 754格式)可能有精度损失
结构体存储直接整体存储需要序列化/反序列化
跨平台兼容性需保证结构体内存布局一致更通用
读写效率高(无格式转换)低(需解析格式)

mian模块 (free释放内存)

在 C 语言中,动态分配的内存(通过 malloccalloc 或 realloc 申请的内存)不会自动释放,必须由程序员手动调用 free() 函数释放。free(list.Data); 这行代码的作用是释放 ProductList 结构体中动态分配的 Data 数组内存。以下是详细解释:


1. 为什么需要这行代码?
内存泄漏问题
  • 动态内存的生命周期:通过 malloc 分配的内存会一直存在,直到程序退出或显式调用 free() 释放。

  • 若不释放:程序每次运行时分配的内存会累积,最终导致内存耗尽(称为“内存泄漏”)。

  • 示例
    假设每次运行程序都添加 1000 个商品,但退出时不释放内存:

    • 第一次运行:占用 1000 × sizeof(Product) 字节内存(未释放)。

    • 第二次运行:再占用 1000 × sizeof(Product) 字节内存(未释放)。

    • 最终程序会因内存不足崩溃。

代码中的具体场景
  • Data 数组的内存来源
    在 load_from_file 函数中,通过 malloc 分配内存:

    list->Data = malloc(list->count * sizeof(Product));
  • Data 的所有权
    该内存由 ProductList 结构体管理,退出时必须归还系统。


2. free(list.Data) 的作用
释放堆内存
free(list.Data); // 释放 Data 数组占用的堆内存
  • 操作对象list.Data 是指向动态分配的数组的指针。

  • 结果
    操作系统回收该内存区域,程序不再能访问 Data 的内容(访问会引发未定义行为)。

内存示意图
程序内存布局:
┌─────────────┐
│  栈区       │ ← list 变量(包括 Data 指针和 count)
├─────────────┤
│  堆区       │ ← list.Data 指向的动态内存(需手动释放)
└─────────────┘

3. 何时调用 free()
正确时机
  • 在程序不再需要 Data 数组时调用(如退出前)。

  • 在您的主函数中,当用户选择“保存并退出”(选项 6)时释放内存:

    case 6:save_to_file(FILENAME, &List); // 保存数据free(List.Data);              // 释放内存printf("系统已退出\n");return 0;
忘记释放的后果
  • 内存泄漏:程序每次运行都会“吃掉”更多内存,最终导致系统资源耗尽。

  • 性能下降:长期运行的程序(如服务器)会逐渐变慢甚至崩溃。


4. 为什么不需要释放 List 变量本身?
  • List 的内存来源
    ProductList List; 是局部变量,在栈上分配,由系统自动管理。

  • 栈内存特性
    函数退出时,栈内存(包括 List 变量)会自动释放,无需手动操作。

  • 重点
    只需释放 List.Data 指向的堆内存,无需释放 List 本身。

http://www.xdnf.cn/news/5833.html

相关文章:

  • Android支持离线功能的复杂业务场景(如编辑、同步):设计数据同步策略的解决方案
  • 基于大模型的腰椎管狭窄术前、术中、术后全流程预测与治疗方案研究报告
  • 数据服务包括哪些内容?一文讲清数据服务模块的主要功能!
  • 【HarmonyOs鸿蒙】七种传参方式
  • IoTDB集群的一键启停功能详解
  • 裸机开发的核心技术:轮询、中断与DMA
  • PowerShell 实现 conda 懒加载
  • MUSE Pi Pro 编译kernel内核及创建自动化脚本进行环境配置
  • 什么是IoT长连接服务?
  • 最终一致性和强一致性
  • Datawhale 5月coze-ai-assistant 笔记1
  • 免费实用的远程办公方案​
  • Spark的缓存
  • 麦肯锡110页PPT企业组织效能提升调研与诊断分析指南
  • 从0到1上手Kafka:开启分布式消息处理之旅
  • ES6中的解构
  • 【SpringBoot】集成kafka之生产者、消费者、幂等性处理和消息积压
  • c语言第一个小游戏:贪吃蛇小游戏08(贪吃蛇完结)
  • 本地的ip实现https访问-OpenSSL安装+ssl正式的生成(Windows 系统)
  • 职坐标AIoT开发技能精讲培训
  • Tomcat的调优
  • 【用「概率思维」重新理解生活】
  • RabbitMQ 核心概念与消息模型深度解析(二)
  • 开源模型应用落地-qwen模型小试-Qwen3-8B-融合VLLM、MCP与Agent(七)
  • 六、Hive 分桶
  • OpenHarmony平台驱动开发(十五),SDIO
  • tomcat与nginx之间实现多级代理
  • DeepSeek、B(不是百度)AT、科大讯飞靠什么坐上中国Ai牌桌?
  • css iconfont图标样式修改,js 点击后更改样式
  • 哈希表:数据世界的超级索引