目錄
前言:
源代碼:
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); }
關鍵知識點
二進制文件操作
"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":二進制讀取模式,文件不存在時返回 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); }
關鍵知識點
安全初始化
進入函數后立刻初始化?
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
?本身。