(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.mzph.cn/diannao/82853.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu----100,常用命令2

目录 文件与目录管理系统信息与管理用户与权限管理网络配置与管理软件包管理打包与压缩系统服务与任务调度硬件信息查看系统操作高级工具开发相关其他实用命令 在 Ubuntu 系统中&#xff0c;掌握常用命令可以大幅提升操作效率。以下是一些常用的命令&#xff0c;涵盖了文件管理…

WiFi密码查看器打开软件自动获取数据

相信有很大一部分人都不知道怎么看已经连过的WiFi密码。 你还在手动查询自己的电脑连接过得WiFi密码吗&#xff1f; —————【下 载 地 址】——————— 【本章单下载】&#xff1a;https://drive.uc.cn/s/dbbedf933dad4 【百款黑科技】&#xff1a;https://ucnygalh6…

开目新一代MOM:AI赋能高端制造的破局之道

导读 INTRODUCTION 在高端制造业智能化转型的深水区&#xff0c;企业正面临着个性化定制、多工艺场景、动态生产需求的敏捷响应以及传统MES柔性不足的考验……在此背景下&#xff0c;武汉开目信息技术股份有限公司&#xff08;简称“开目软件”&#xff09;正式发布新一代开目…

Android开发-视图基础

在Android应用开发中&#xff0c;视图&#xff08;View&#xff09;是构建用户界面的基本元素。无论是按钮、文本框还是复杂的自定义控件&#xff0c;它们都是基于View类或其子类实现的。掌握视图的基础知识对于创建功能强大且美观的应用至关重要。本文将深入探讨Android中的视…

无人机信号线被电磁干扰导致停机

问题描述&#xff1a; 无人机飞控和电调之间使用PWM信号控制时候&#xff0c;无人机可以正常起飞&#xff0c;但是在空中悬停的时候会出现某一个电机停机&#xff0c;经排查电调没有启动过流过压等保护&#xff0c;定位到电调和飞控之间的信号线被干扰问题。 信号线被干扰&am…

VSCode设置SSH免密登录

引言 2025年05月13日20:21:14 原来一直用的PyCharn来完成代码在远程服务器上的运行&#xff0c;但是PyCharm时不时同步代码会有问题。因此&#xff0c;尝试用VSCode来完成代码SSH远程运行。由于VSCode每次进行SSH连接的时候都要手动输入密码&#xff0c;为了解决这个问题在本…

硬密封保温 V 型球阀:恒温工况下复杂介质控制的性价比之选-耀圣

硬密封保温 V 型球阀&#xff1a;恒温工况下复杂介质控制的性价比之选 在沥青储运、化学原料加工、食品油脂输送等工业领域&#xff0c;带颗粒高粘度介质与料浆的恒温输送一直是生产的关键环节。普通阀门在应对此类介质时&#xff0c;常因温度流失导致介质凝结堵塞、密封失效&…

最终一致性和强一致性

最终一致性和强一致性是分布式系统中两种不同的数据一致性模型&#xff0c;它们在数据同步的方式和适用场景上有显著区别&#xff1a; 1. 强一致性&#xff08;Strong Consistency&#xff09; 定义&#xff1a;所有节点&#xff08;副本&#xff09;的数据在任何时刻都保持一…

基于单应性矩阵变换的图像拼接融合

单应性矩阵变换 单应性矩阵是一个 3x3 的可逆矩阵&#xff0c;它描述了两个平面之间的投影变换关系。在图像领域&#xff0c;单应性矩阵可以将一幅图像中的点映射到另一幅图像中的对应点&#xff0c;前提是这两幅图像是从不同视角拍摄的同一平面场景。 常见的应用场景&#x…

如何同步虚拟机文件夹

以下是一些常见的同步虚拟机文件夹的方法&#xff1a; 使用共享文件夹&#xff08;以VMware和VirtualBox为例&#xff09; - VMware&#xff1a;打开虚拟机&#xff0c;选择“虚拟机”->“设置”&#xff0c;在“选项”中选择“共享文件夹”&#xff0c;点击“添加”选择…

前端流行框架Vue3教程:15. 组件事件

组件事件 在组件的模板表达式中&#xff0c;可以直接使用$emit方法触发自定义事件 触发自定义事件的目的是组件之间传递数据 我们来创建2个组件。父组件&#xff1a; ComponentEvent.vue,子组件&#xff1a;Child.vue Child.vue <script> export default {// 子组件通…

Python+1688 API 开发教程:实现商品实时数据采集的完整接入方案

在电商行业竞争日益激烈的当下&#xff0c;掌握商品实时数据是企业制定精准营销策略、优化供应链管理的关键。1688 作为国内重要的 B2B 电商平台&#xff0c;其开放平台提供了丰富的 API 接口&#xff0c;借助 Python 强大的数据处理能力&#xff0c;我们能够高效实现商品数据的…

聊一聊Electron中Chromium多进程架构

Chromium 多进程架构概述 Chromium 的多进程架构是其核心设计之一&#xff0c;旨在提高浏览器的稳定性、安全性和性能。Chromium 将不同的功能模块分配到独立的进程中&#xff0c;每个进程相互隔离&#xff0c;避免了单进程架构中一个模块的崩溃导致整个浏览器崩溃的问题。 在…

CodeBuddy 中国版 Cursor 实战:Redis+MySQL双引擎驱动〈王者荣耀〉战区排行榜

文章目录 一、引言二、系统架构设计2.1、整体架构概览2.2、数据库设计2.3、后端服务设计 三、实战&#xff1a;从零构建排行榜3.1、开发环境准备3.2、用户与战区 数据管理3.2.1、MySQL 数据库表创建3.2.2、实现用户和战区数据的 CURD 操作 3.3、实时分数更新3.4、排行榜查询3.5…

Oracle OCP认证考试考点详解083系列15

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 71. 第71题&#xff1a; 题目 解析及答案&#xff1a; 关于在 Oracle 18c 及更高版本中基于 Oracle 黄金镜像的安装&#xff0c;以下哪…

LS-NET-012-TCP的交互过程详解

LS-NET-012-TCP的交互过程详解 附加&#xff1a;TCP如何保障数据传输 TCP的交互过程详解 一、TCP协议核心交互流程 TCP协议通过三次握手建立连接、数据传输、四次挥手终止连接三大阶段实现可靠传输。整个过程通过序列号、确认应答、窗口控制等机制保障传输可靠性。 1.1 三次…

【Pandas】pandas DataFrame cumprod

Pandas2.2 DataFrame Computations descriptive stats 方法描述DataFrame.abs()用于返回 DataFrame 中每个元素的绝对值DataFrame.all([axis, bool_only, skipna])用于判断 DataFrame 中是否所有元素在指定轴上都为 TrueDataFrame.any(*[, axis, bool_only, skipna])用于判断…

C语言之旅5---分支与循环【2】

&#x1f4ab;只有认知的突破&#x1f4ab;才来带来真正的成长&#x1f4ab;编程技术的学习&#x1f4ab;没有捷径&#x1f4ab;一起加油&#x1f4ab; &#x1f341;感谢各位的观看&#x1f341;欢迎大家留言&#x1f341;咱们一起加油&#x1f341;努力成为更好的自己&#x…

docker大镜像优化实战

在 Docker 镜像优化方面&#xff0c;有许多实战技巧可以显著减小镜像体积、提高构建效率和运行时性能。以下是一些实用的优化策略和具体操作方法&#xff1a; 1. 选择合适的基础镜像 策略 使用 Alpine 版本&#xff1a;Alpine 镜像通常只有 5-10MB&#xff0c;比 Ubuntu/Deb…

Java面试终极篇:Sentinel+Seata+Kafka Streams高并发架构实战

面试官&#xff1a;张总&#xff08;严肃脸&#xff09; 程序员&#xff1a;小王&#xff08;紧张冒冷汗&#xff09; 第一轮&#xff1a;分布式基础 张总&#xff1a;说说Spring Cloud Alibaba的Sentinel和Nacos的区别&#xff1f; 小王&#xff1a;&#xff08;结巴&#…