目录
前言:
源代码:
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); }
关键知识点
二进制文件操作
"wb"
模式直接写入内存数据,保持精确性(如浮点数不会丢失精度)文件内容不可直接阅读,但读写效率高
数据存储顺序
文件结构如下:┌──────────────┬───────────────────────────┐ │ 4字节整数 │ N个Product结构体 │ │ (商品数量count) │ (每个占sizeof(Product)字节) │ └──────────────┴───────────────────────────┘错误处理
perror
会输出类似保存失败: Permission denied
的详细信息
exit(EXIT_FAILURE)
立即终止程序,防止后续操作破坏数据
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); }
关键知识点
安全初始化
进入函数后立刻初始化
list
,避免残留值导致错误文件打开模式
"rb"
表示二进制读取模式,文件不存在时不会创建新文件动态内存管理
malloc
根据文件中的商品数量分配精确内存计算公式:
数量 × sizeof(Product)
确保内存足够存放所有商品
扩展知识:二进制文件 vs 文本文件
特性 二进制文件 文本文件 存储方式 直接存储内存数据 存储字符编码 可读性 不可直接阅读 可用文本编辑器查看 浮点数存储 精确(IEEE 754格式) 可能有精度损失 结构体存储 直接整体存储 需要序列化/反序列化 跨平台兼容性 需保证结构体内存布局一致 更通用 读写效率 高(无格式转换) 低(需解析格式)
mian模块 (free释放内存)
在 C 语言中,动态分配的内存(通过
malloc
、calloc
或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
本身。