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

news2025/5/14 10:18:07

目录

前言:

源代码:

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":二进制读取模式,文件不存在时返回 NULL
    if (!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":二进制读取模式,文件不存在时返回 NULL
    if (!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.coloradmin.cn/o/2375293.html

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

相关文章

nRF Connect 下载

官方下载路径 点击&#xff0c;或往下拉 选对应的版本 下载成功&#xff0c;数字代表版本好

基于Arduino的贪吃蛇游戏机

3D 打印迷你贪吃蛇游戏机&#xff1a; 在数字娱乐高度发达的今天&#xff0c;我们常常怀念那些经典的复古游戏。其中&#xff0c;贪吃蛇游戏无疑是许多人童年的记忆。今天&#xff0c;我将带你走进一个有趣的 DIY 项目——3D 打印迷你贪吃蛇游戏机。这个项目不仅能够让你重温经…

【PmHub后端篇】Redis分布式锁:保障PmHub流程状态更新的关键

在分布式系统中&#xff0c;确保数据一致性和操作的正确执行是至关重要的。PmHub项目中&#xff0c;通过集成Redis分布式锁来保障流程状态更新&#xff0c;这是一个非常关键的技术点&#xff0c;以下将详细介绍其原理、实现。 1 本地锁的问题 1.1 常见的本地锁 在Java中&…

Starrocks的主键表涉及到的MOR Delete+Insert更新策略

背景 写这个文章的作用主要是做一些总结和梳理&#xff0c;特别是正对大数据场景下的实时写入更新策略 COW 和 MOR 以及 DeleteInsert 的技术策略的演进&#xff0c; 这也适用于其他大数据的计算存储系统。该文章主要参考了Primary Key table. 分析总结 Starrocks 的主键表主…

《操作系统真象还原》第十四章(2)——文件描述符、文件操作基础函数

文章目录 前言文件描述符简介文件描述符原理文件描述符实现修改thread.h修改thread.c 文件操作相关的基础函数inode操作相关函数文件相关函数编写file.h编写file.c 目录相关函数完善fs/dir.h编写fs/dir.c 路径解析相关函数实现文件检索功能修改fs.h继续完善fs.c makefile 结语 …

EMQX v5.0通过连接器和规则同步数据

1 概述 EMQX数据集成功能&#xff0c;帮助用户将所有的业务数据无需额外编写代码即可快速完成处理与分发。 数据集成能力由连接器和规则两部分组成&#xff0c;用户可以使用数据桥接或 MQTT 主题来接入数据&#xff0c;使用规则处理数据后&#xff0c;再通过数据桥接将数据发…

2. 盒模型/布局模块 - 响应式产品展示页_案例:电商产品网格布局

2. 盒模型/布局模块 - 响应式产品展示页 案例&#xff1a;电商产品网格布局 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><style type"text/css">:root {--primary-color…

LVGL的三层屏幕结构

文章目录 &#x1f31f; LVGL 的三层屏幕架构1. **Top Layer&#xff08;顶层&#xff09;**2. **System Layer&#xff08;系统层&#xff09;**3. **Active Screen&#xff08;当前屏幕层&#xff09;** &#x1f9e0; 总结对比&#x1f50d; 整体作用✅ 普通屏幕层对象&…

【PDF】使用Adobe Acrobat dc添加水印和加密

【PDF】使用Adobe Acrobat dc添加水印和加密 文章目录 [TOC](文章目录) 前言一、添加保护加密口令二、添加水印三、实验四、参考文章总结 实验工具&#xff1a; 1.Adobe Acrobat dc 前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、添加保护加…

Windows下安装mysql8.0

一、下载安装离线安装包 &#xff08;下载过了&#xff0c;可以跳过&#xff09; 下载网站&#xff1a;MySQL :: Download MySQL Installerhttps://dev.mysql.com/downloads/installer/ 二、安装mysql 三、安装完成验证

水滴Android面经及参考答案

static 关键字有什么作用&#xff0c;它修饰的方法可以使用非静态的成员变量吗&#xff1f; static关键字在 Java 中有多种作用。首先&#xff0c;它可以用来修饰变量&#xff0c;被static修饰的变量称为静态变量。静态变量属于类&#xff0c;而不属于类的某个具体实例&#xf…

工程师必读! 3 个最常被忽略的 TDR 测试关键细节与原理

TDR真的是一个用来看阻抗跟Delay的好工具&#xff0c;通过一个Port的测试就可以看到通道各个位置的阻抗变化。 可是使用上其实没这么单纯&#xff0c;有很多细节需要非常地小心&#xff0c;才可以真正地看到您想看的信息&#xff01; 就让我们整理3个极为重要的TDR使用小细节&…

C++中的各式类型转换

隐式转换&#xff1a; 基本类型的隐式转换&#xff1a; 当函数参数类型非精确匹配&#xff0c;但是可以转换的时候发生 如&#xff1a; void func1(double x){cout << x << endl; }void func2(char c){cout << c << endl; }int main(){func1(2);//…

Nacos源码—9.Nacos升级gRPC分析七

大纲 10.gRPC客户端初始化分析 11.gRPC客户端的心跳机制(健康检查) 12.gRPC服务端如何处理客户端的建立连接请求 13.gRPC服务端如何映射各种请求与对应的Handler处理类 14.gRPC简单介绍 10.gRPC客户端初始化分析 (1)gRPC客户端代理初始化的源码 (2)gRPC客户端启动的源码…

【计算机视觉】基于深度学习的实时情绪检测系统:emotion-detection项目深度解析

基于深度学习的实时情绪检测系统&#xff1a;emotion-detection项目深度解析 1. 项目概述2. 技术原理与模型架构2.1 核心算法1) 数据预处理流程2) 改进型MobileNetV2 2.2 系统架构 3. 实战部署指南3.1 环境配置3.2 数据集准备3.3 模型训练3.4 实时推理 4. 常见问题与解决方案4.…

【图像处理基石】什么是油画感?

在图像处理中&#xff0c;“油画感”通常指图像呈现出类似油画的块状纹理、笔触痕迹或色彩过渡不自然的现象&#xff0c;表现为细节模糊、边缘不锐利、颜色断层或人工纹理明显。这种问题常见于照片处理、视频帧截图或压缩后的图像&#xff0c;本质是画质受损的一种表现。以下是…

AD PCB布线的常用命令

PCB布线顺序&#xff1a;先信号&#xff0c;再电源&#xff0c;再GNG 1.多根走线的应用 将IC上的引脚分类 更改一类引脚以及引线的颜色&#xff0c;画出走线&#xff08;将脚引出&#xff09; 选中这些走线&#xff0c;点击‘交互式总线布线’&#xff0c;便可以多根拉线 shi…

【3-2】HDLC

前言 前面我们提到了 PSTN&#xff08;Public Switched Telephone Network&#xff09; &#xff0c;今天介绍一种很少见的数据链路层的协议&#xff0c;HDLC&#xff01; 文章目录 前言1. 定义2. 帧边界3. 零比特填充4. 控制字段4.1. 信息帧&#xff08;I帧&#xff09;4.2. …

MySQL 学习(八)如何打开binlog日志

目录 一、默认状态二、如何检查 binlog 状态三、如何开启 binlog3.1 临时开启&#xff08;重启后失效&#xff09;3.2 永久开启&#xff08;需修改配置文件&#xff09;3.3 验证是否开启成功3.4 查看 binlog 内容 四、高级配置建议五、注意事项六、开启后的日常维护 知识回顾&a…

OpenCV进阶操作:光流估计

文章目录 前言一、光流估计1、光流估计是什么&#xff1f;2、光流估计的前提&#xff1f;1&#xff09;亮度恒定2&#xff09;小运动3&#xff09;空间一致 3、OpenCV中的经典光流算法1&#xff09;Lucas-Kanade方法&#xff08;稀疏光流&#xff09;2&#xff09; Farneback方…