一、代码/** * * 阶段V4文件持久化版 - 数据保存到文件 * * 前置版本V3 动态数组 单向链表借阅记录 * 新增核心知识点二进制文件读写 fread/fwrite、数据序列化、版本校验、链表持久化存储 * 解决V3痛点程序关闭后内存数据全部丢失无法留存借阅与图书信息 * 功能 * 1. 程序启动自动读取 books.dat、records.dat 恢复所有图书借阅历史 * 2. 每一次增删改操作自动写入文件持久化 * 3. 程序退出前再次保存数据防止丢失 * 4. 数据版本号校验兼容后续升级避免新旧文件格式错乱 * 文件说明 * books.dat 二进制文件存储所有图书基础信息 * records.dat 二进制文件存储所有图书对应的借阅链表记录 * */ #include stdio.h #include string.h #include stdlib.h #include time.h // // 1. 宏与结构体定义完全复用V3新增版本宏DATA_VERSION // #define MAX_NAME 100 #define MAX_AUTHOR 50 #define MAX_ISBN 20 #define MAX_USERNAME 50 #define DATA_VERSION 1 // V4新增数据文件格式版本号用于兼容校验 // 借阅记录链表节点与V3无改动 struct BorrowRecord { int userId; char userName[MAX_USERNAME]; time_t borrowDate; time_t returnDate; struct BorrowRecord* next; }; // 图书结构体与V3无改动 struct Book { int id; char name[MAX_NAME]; char author[MAX_AUTHOR]; char isbn[MAX_ISBN]; int stock; int borrowed; struct BorrowRecord* borrowHistory; }; // // 2. 全局数据同步V3无增减 // struct Book* library NULL; int bookCount 0; int maxBooks 10; int nextId 1001; // // 3. 函数声明 // // V3原有基础业务函数 void initLibrary(); void destroyLibrary(); void ensureCapacity(); void addBook(const char* name, const char* author, const char* isbn, int stock); struct Book* findBookById(int id); int findBookIndexById(int id); void borrowBook(int id, int userId, const char* userName); void returnBook(int id, int userId); void listAllBooks(); void showBorrowHistory(int id); void deleteBook(int id); void freeBorrowHistory(struct BorrowRecord* head); void showMenu(); // V4全新新增文件持久化接口 // 统一保存入口同时保存图书借阅记录 int saveData(); // 统一加载入口先加载图书再加载所有借阅链表 int loadData(); // 写入图书二进制文件 books.dat int saveBooks(); // 从books.dat读取图书数据到内存动态数组 int loadBooks(); // 写入全部借阅记录到 records.dat int saveRecords(); // 读取records.dat重建每本书的借阅链表 int loadRecords(); // // 4. 主程序V4大量新增文件加载、自动保存逻辑 // int main() { initLibrary(); // V4新增程序启动优先读取本地存档文件恢复数据 if (loadData() 0) { printf( 成功加载历史数据\n); } else { // 文件不存在/版本不兼容则加载默认测试图书 printf( 未找到数据文件使用初始测试数据\n); addBook(C程序设计语言, Kernighan, 978-7-111-00101-0, 5); addBook(数据结构与算法, Weiss, 978-7-111-00202-0, 3); } int choice; while (1) { showMenu(); scanf(%d, choice); if (choice 1) { // 添加图书执行完成自动调用saveData持久化 char name[MAX_NAME], author[MAX_AUTHOR], isbn[MAX_ISBN]; int stock; printf(请输入书名); scanf(%s, name); printf(请输入作者); scanf(%s, author); printf(请输入ISBN); scanf(%s, isbn); printf(请输入库存数量); scanf(%d, stock); addBook(name, author, isbn, stock); saveData(); // V4新增操作后自动保存 } else if (choice 2) { // 借书完成自动保存 int id, userId; char userName[MAX_USERNAME]; printf(请输入图书ID); scanf(%d, id); printf(请输入用户ID); scanf(%d, userId); printf(请输入用户姓名); scanf(%s, userName); borrowBook(id, userId, userName); saveData(); } else if (choice 3) { // 还书完成自动保存 int id, userId; printf(请输入图书ID); scanf(%d, id); printf(请输入用户ID); scanf(%d, userId); returnBook(id, userId); saveData(); } else if (choice 4) { // 查询仅读取数据无修改无需保存 int id; printf(请输入图书ID); scanf(%d, id); struct Book* b findBookById(id); if (b ! NULL) { printf(\n 图书信息 \n); printf(ID: %d\n书名: %s\n作者: %s\nISBN: %s\n, b-id, b-name, b-author, b-isbn); printf(库存: %d已借出: %d可借: %d\n, b-stock, b-borrowed, b-stock - b-borrowed); showBorrowHistory(id); } else { printf(未找到ID为 %d 的图书\n, id); } } else if (choice 5) { // 列表展示只读不保存 listAllBooks(); } else if (choice 6) { // 删除图书后自动持久化 int id; printf(请输入图书ID); scanf(%d, id); deleteBook(id); saveData(); } else if (choice 7) { break; } else { printf(无效选择\n); } } // V4新增退出循环前强制保存全部数据 saveData(); destroyLibrary(); printf(\n感谢使用数据已保存至 books.dat / records.dat\n); return 0; } // // 5. 基础业务函数完整复制V3实现逻辑无改动仅注释微调 // void initLibrary() { library (struct Book*)malloc(maxBooks * sizeof(struct Book)); if (library NULL) { printf(内存分配失败程序退出\n); exit(1); } printf(✓ 初始化书架初始容量%d 本\n, maxBooks); } void destroyLibrary() { // 释放每本书的借阅链表内存 for (int i 0; i bookCount; i) { if (library[i].borrowHistory ! NULL) { freeBorrowHistory(library[i].borrowHistory); } } if (library ! NULL) { free(library); library NULL; } printf(✓ 已释放所有内存\n); } void ensureCapacity() { if (bookCount maxBooks) return; int newMax maxBooks * 2; struct Book* newLibrary (struct Book*)realloc(library, newMax * sizeof(struct Book)); if (newLibrary NULL) { printf(扩容内存分配失败\n); return; } library newLibrary; maxBooks newMax; printf(✓ 书架自动扩容至 %d 本\n, maxBooks); } void addBook(const char* name, const char* author, const char* isbn, int stock) { ensureCapacity(); for (int i 0; i bookCount; i) { if (strcmp(library[i].isbn, isbn) 0) { printf(✗ 添加失败该ISBN图书已存在\n); return; } } struct Book* b library[bookCount]; b-id nextId; strcpy(b-name, name); strcpy(b-author, author); strcpy(b-isbn, isbn); b-stock stock; b-borrowed 0; b-borrowHistory NULL; bookCount; printf(✓ 图书添加成功分配ID%d\n, b-id); } struct Book* findBookById(int id) { for (int i 0; i bookCount; i) { if (library[i].id id) { return library[i]; } } return NULL; } int findBookIndexById(int id) { for (int i 0; i bookCount; i) { if (library[i].id id) { return i; } } return -1; } void borrowBook(int id, int userId, const char* userName) { struct Book* b findBookById(id); if (b NULL) { printf(✗ 未找到该图书\n); return; } if (b-borrowed b-stock) { printf(✗ 借书失败《%s》库存已全部借出\n, b-name); return; } struct BorrowRecord* record (struct BorrowRecord*)malloc(sizeof(struct BorrowRecord)); if (record NULL) { printf(✗ 借阅记录内存分配失败\n); return; } record-userId userId; strcpy(record-userName, userName); record-borrowDate time(NULL); record-returnDate 0; record-next b-borrowHistory; b-borrowHistory record; b-borrowed; char timeStr[64]; struct tm* tm_info localtime(record-borrowDate); strftime(timeStr, sizeof(timeStr), %Y-%m-%d %H:%M:%S, tm_info); printf(✓ 借书成功\n); printf( 用户%sID:%d借阅《%s》\n, userName, userId, b-name); printf( 借阅时间%s\n, timeStr); printf( 当前已借出%d 本\n, b-borrowed); } void returnBook(int id, int userId) { struct Book* b findBookById(id); if (b NULL) { printf(✗ 未找到该图书\n); return; } if (b-borrowed 0) { printf(✗ 该图书无借出记录\n); return; } struct BorrowRecord* p b-borrowHistory; int found 0; while (p ! NULL) { if (p-userId userId p-returnDate 0) { p-returnDate time(NULL); found 1; b-borrowed--; char timeStr[64]; struct tm* tm_info localtime(p-returnDate); strftime(timeStr, sizeof(timeStr), %Y-%m-%d %H:%M:%S, tm_info); printf(✓ 还书成功\n); printf( 用户 %sID:%d归还《%s》\n, p-userName, userId, b-name); printf( 归还时间%s\n, timeStr); break; } p p-next; } if (!found) { printf(✗ 未找到该用户未归还的借阅记录\n); } } void showBorrowHistory(int id) { struct Book* b findBookById(id); if (b NULL) return; if (b-borrowHistory NULL) { printf( 该图书暂无借阅记录\n); return; } printf(\n 借阅历史 \n); struct BorrowRecord* p b-borrowHistory; int count 1; while (p ! NULL) { char borrowStr[64], returnStr[64]; struct tm* tm_info; tm_info localtime(p-borrowDate); strftime(borrowStr, sizeof(borrowStr), %Y-%m-%d %H:%M, tm_info); if (p-returnDate 0) { strcpy(returnStr, 未归还); } else { tm_info localtime(p-returnDate); strftime(returnStr, sizeof(returnStr), %Y-%m-%d %H:%M, tm_info); } printf( %d. 用户%sID:%d\n, count, p-userName, p-userId); printf( 借阅%s\n, borrowStr); printf( 归还%s\n, returnStr); printf( ---\n); p p-next; count; } printf( 共计 %d 条借阅记录\n, count - 1); } void freeBorrowHistory(struct BorrowRecord* head) { struct BorrowRecord* current head; struct BorrowRecord* next; while (current ! NULL) { next current-next; free(current); current next; } } void deleteBook(int id) { int index findBookIndexById(id); if (index -1) { printf(✗ 未找到该图书\n); return; } if (library[index].borrowed 0) { printf(✗ 删除失败该图书还有 %d 本未归还\n, library[index].borrowed); return; } if (library[index].borrowHistory ! NULL) { freeBorrowHistory(library[index].borrowHistory); } library[index] library[bookCount - 1]; bookCount--; printf(✓ 图书删除成功\n); } void listAllBooks() { if (bookCount 0) { printf(\n 书架暂无图书\n); return; } printf(\n\n); printf(ID\t书名\t\t作者\t\t库存/可借/历史条数\n); printf(\n); for (int i 0; i bookCount; i) { struct Book* b library[i]; int historyCount 0; struct BorrowRecord* p b-borrowHistory; while (p ! NULL) { historyCount; p p-next; } printf(%d\t%-12s\t%-12s\t%d/%d/%d\n, b-id, b-name, b-author, b-stock, b-stock - b-borrowed, historyCount); } printf(\n); } void showMenu() { printf(\n\n); printf( 图书管理系统 V4文件持久化\n); printf(\n); printf(当前图书总数%d 本\n, bookCount); printf(----------------------------------------\n); printf(1. 添加图书\n); printf(2. 借书自动保存记录\n); printf(3. 还书\n); printf(4. 查询图书完整借阅历史\n); printf(5. 列出全部图书\n); printf(6. 删除图书\n); printf(7. 退出程序自动保存\n); printf(----------------------------------------\n); printf(请选择操作序号(1-7)); } // // 6. V4 全新模块文件持久化完整实现 // /** * saveData统一保存入口函数 * 功能同时调用保存图书、保存借阅记录统一返回保存结果 * 返回0全部保存成功返回-1至少一项保存失败 */ int saveData() { int ret1 saveBooks(); int ret2 saveRecords(); if (ret1 ! 0 || ret2 ! 0) { printf(✗ 部分数据保存失败\n); return -1; } return 0; } /** * loadData统一加载入口函数 * 执行顺序先加载图书基础数据再加载所有借阅记录重建链表 */ int loadData() { if (loadBooks() ! 0) { return -1; } loadRecords(); return 0; } /** * saveBooks写入图书二进制文件 books.dat * 写入顺序版本号version → 图书总数bookCount → 下一本ID nextId → 循环写入每本图书固定字段 * 说明borrowHistory是内存链表指针不写入文件借阅记录单独存records.dat */ int saveBooks() { // wb 二进制写模式清空原有文件内容 FILE* fp fopen(books.dat, wb); if (fp NULL) { printf(无法打开 books.dat 写入\n); return -1; } int version DATA_VERSION; fwrite(version, sizeof(int), 1, fp); fwrite(bookCount, sizeof(int), 1, fp); fwrite(nextId, sizeof(int), 1, fp); // 循环序列化每一本图书 for (int i 0; i bookCount; i) { struct Book* b library[i]; fwrite(b-id, sizeof(int), 1, fp); fwrite(b-name, sizeof(char), MAX_NAME, fp); fwrite(b-author, sizeof(char), MAX_AUTHOR, fp); fwrite(b-isbn, sizeof(char), MAX_ISBN, fp); fwrite(b-stock, sizeof(int), 1, fp); fwrite(b-borrowed, sizeof(int), 1, fp); } fclose(fp); return 0; } /** * loadBooks读取图书文件 books.dat恢复内存图书数组 * 逻辑 * 1. rb 二进制只读打开文件文件不存在返回-1 * 2. 读取版本号与DATA_VERSION对比版本不一致直接放弃加载 * 3. 读取图书数量、下一个图书ID * 4. 动态扩容library数组保证能放下读取到的图书数量 * 5. 循环读取每本图书字段链表头指针borrowHistory初始化为NULL */ int loadBooks() { FILE* fp fopen(books.dat, rb); if (fp NULL) { return -1; } int version; size_t rd fread(version, sizeof(int), 1, fp); // 读取失败说明文件损坏 if (rd ! 1) { fclose(fp); return -1; } // 版本校验防止新旧数据格式不兼容 if (version ! DATA_VERSION) { printf(⚠ 文件版本不兼容放弃加载\n); fclose(fp); return -1; } fread(bookCount, sizeof(int), 1, fp); fread(nextId, sizeof(int), 1, fp); // 动态扩容保证数组容量足够存放读取到的图书 while (bookCount maxBooks) { maxBooks * 2; } struct Book* temp (struct Book*)realloc(library, maxBooks * sizeof(struct Book)); if (temp NULL) { printf(加载图书扩容内存失败\n); fclose(fp); return -1; } library temp; // 循环读取每一本图书信息 for (int i 0; i bookCount; i) { struct Book* b library[i]; fread(b-id, sizeof(int), 1, fp); fread(b-name, sizeof(char), MAX_NAME, fp); fread(b-author, sizeof(char), MAX_AUTHOR, fp); fread(b-isbn, sizeof(char), MAX_ISBN, fp); fread(b-stock, sizeof(int), 1, fp); fread(b-borrowed, sizeof(int), 1, fp); b-borrowHistory NULL; // 刚加载图书链表先置空后续loadRecords重建 } fclose(fp); return 0; } /** * saveRecords序列化所有借阅链表存入 records.dat * 存储格式总记录条数totalRecords → 每条记录绑定对应图书ID BorrowRecord完整字段 * 原理链表指针不能存文件只存有效业务数据加载时根据bookId重新挂载到对应图书链表 */ int saveRecords() { FILE* fp fopen(records.dat, wb); if (fp NULL) { printf(无法打开 records.dat 写入\n); return -1; } // 先统计全局所有借阅记录总条数 int totalRecords 0; for (int i 0; i bookCount; i) { struct BorrowRecord* p library[i].borrowHistory; while (p) { totalRecords; p p-next; } } fwrite(totalRecords, sizeof(int), 1, fp); // 遍历所有图书循环写入每一条借阅记录 for (int i 0; i bookCount; i) { struct BorrowRecord* p library[i].borrowHistory; while (p) { fwrite(library[i].id, sizeof(int), 1, fp); fwrite(p-userId, sizeof(int), 1, fp); fwrite(p-userName, sizeof(char), MAX_USERNAME, fp); fwrite(p-borrowDate, sizeof(time_t), 1, fp); fwrite(p-returnDate, sizeof(time_t), 1, fp); p p-next; } } fclose(fp); return 0; } /** * loadRecords读取records.dat重建每本书的借阅单向链表 * 1. 读取总记录条数 * 2. 循环读取每条记录对应的图书ID、用户、时间等数据 * 3. 根据bookId找到对应图书malloc新建节点头插法插入链表 */ int loadRecords() { FILE* fp fopen(records.dat, rb); if (fp NULL) { return -1; } int totalRecords; size_t rd fread(totalRecords, sizeof(int), 1, fp); if (rd ! 1) { fclose(fp); return -1; } // 逐条读取并重建链表 for (int i 0; i totalRecords; i) { int bookId; struct BorrowRecord record; fread(bookId, sizeof(int), 1, fp); fread(record.userId, sizeof(int), 1, fp); fread(record.userName, sizeof(char), MAX_USERNAME, fp); fread(record.borrowDate, sizeof(time_t), 1, fp); fread(record.returnDate, sizeof(time_t), 1, fp); record.next NULL; // 根据图书ID找到对应图书 struct Book* b findBookById(bookId); if (!b) continue; // 分配新链表节点 struct BorrowRecord* newNode (struct BorrowRecord*)malloc(sizeof(struct BorrowRecord)); if (!newNode) { printf(加载记录内存不足跳过本条\n); continue; } // 拷贝读取到的记录头插法挂载到图书链表头部 *newNode record; newNode-next b-borrowHistory; b-borrowHistory newNode; } fclose(fp); return 0; }二、V3 → V4 完整新增 / 改动内容汇总1. 宏定义新增新增#define DATA_VERSION 1作用二进制文件版本校验防止后续修改结构体后旧文件读取错乱。2. 函数声明全新 6 个文件操作接口V3 完全不存在c运行int saveData(); int loadData(); int saveBooks(); int loadBooks(); int saveRecords(); int loadRecords();分层设计saveData/loadData上层统一入口业务代码只调用这两个saveBooks/loadBooks负责图书基础二进制读写saveRecords/loadRecords负责借阅链表序列化、反序列化重建3. main 主程序大量业务逻辑改动1程序启动阶段新增自动加载c运行if (loadData() 0) 加载本地文件 else 加载默认测试图书V3 启动直接创建测试数据无文件读取逻辑。2所有修改类操作执行后自动持久化添加 / 借书 / 还书 / 删除图书操作末尾统一添加saveData();V3 所有操作仅存在内存关闭程序数据全部丢失。3退出前强制保存while 循环 break 后调用saveData()保证退出前数据落盘。4菜单界面更新showMenu 标题改为 V4文件持久化功能标注自动保存、退出自动保存。4. 新增两大二进制文件存储逻辑文件 1books.dat存储内容版本号、图书总数、nextId、每本图书 id / 书名 / 作者 /isbn/ 库存 / 已借出不存储 borrowHistory 链表指针指针是内存地址无法持久化读写模式wb写二进制 /rb读二进制文件 2records.dat存储内容总借阅记录条数、每条记录绑定对应图书 ID 用户 ID、姓名、借阅 / 归还时间持久化思路链表不能直接存文件只存业务数据加载时根据 bookId 重建链表5. 新增核心技术点说明V3 无二进制序列化 fwrite将结构体整块字节写入文件无需字符串解析读写速度快。二进制反序列化 fread从文件读取字节直接还原结构体变量。链表持久化方案分离图书与借阅记录两个文件加载时动态 malloc 节点重建单向链表。数据版本兼容机制文件头部写入版本号升级结构体后可判断旧文件并拒绝加载避免内存错乱。自动持久化机制所有修改操作实时落盘断电 / 闪退最大限度减少数据丢失。6. 原有 V3 代码变更点少量适配showMenu 菜单文字全部更新标注自动保存特性所有只读操作查询、列表无 saveData写操作全部追加保存调用loadBooks 加载图书时自动置空 borrowHistory交由 loadRecords 重建链表7. V4 对比 V3 核心优势数据永久保存关闭程序、重启电脑数据不丢失解决 V3 内存数据临时存储缺陷分层文件存储图书基础信息、借阅记录分离存储便于后期扩展版本容错文件损坏 / 版本不兼容自动降级不会直接崩溃自动落地用户无需手动保存所有增删改操作自动写入本地文件完整二进制 IO 实战掌握 fread/fwrite、结构体序列化、链表持久化工业级方案
c语言项目驱动学习--实例化(图书管理)--004-代码对比
发布时间:2026/7/1 20:40:02
一、代码/** * * 阶段V4文件持久化版 - 数据保存到文件 * * 前置版本V3 动态数组 单向链表借阅记录 * 新增核心知识点二进制文件读写 fread/fwrite、数据序列化、版本校验、链表持久化存储 * 解决V3痛点程序关闭后内存数据全部丢失无法留存借阅与图书信息 * 功能 * 1. 程序启动自动读取 books.dat、records.dat 恢复所有图书借阅历史 * 2. 每一次增删改操作自动写入文件持久化 * 3. 程序退出前再次保存数据防止丢失 * 4. 数据版本号校验兼容后续升级避免新旧文件格式错乱 * 文件说明 * books.dat 二进制文件存储所有图书基础信息 * records.dat 二进制文件存储所有图书对应的借阅链表记录 * */ #include stdio.h #include string.h #include stdlib.h #include time.h // // 1. 宏与结构体定义完全复用V3新增版本宏DATA_VERSION // #define MAX_NAME 100 #define MAX_AUTHOR 50 #define MAX_ISBN 20 #define MAX_USERNAME 50 #define DATA_VERSION 1 // V4新增数据文件格式版本号用于兼容校验 // 借阅记录链表节点与V3无改动 struct BorrowRecord { int userId; char userName[MAX_USERNAME]; time_t borrowDate; time_t returnDate; struct BorrowRecord* next; }; // 图书结构体与V3无改动 struct Book { int id; char name[MAX_NAME]; char author[MAX_AUTHOR]; char isbn[MAX_ISBN]; int stock; int borrowed; struct BorrowRecord* borrowHistory; }; // // 2. 全局数据同步V3无增减 // struct Book* library NULL; int bookCount 0; int maxBooks 10; int nextId 1001; // // 3. 函数声明 // // V3原有基础业务函数 void initLibrary(); void destroyLibrary(); void ensureCapacity(); void addBook(const char* name, const char* author, const char* isbn, int stock); struct Book* findBookById(int id); int findBookIndexById(int id); void borrowBook(int id, int userId, const char* userName); void returnBook(int id, int userId); void listAllBooks(); void showBorrowHistory(int id); void deleteBook(int id); void freeBorrowHistory(struct BorrowRecord* head); void showMenu(); // V4全新新增文件持久化接口 // 统一保存入口同时保存图书借阅记录 int saveData(); // 统一加载入口先加载图书再加载所有借阅链表 int loadData(); // 写入图书二进制文件 books.dat int saveBooks(); // 从books.dat读取图书数据到内存动态数组 int loadBooks(); // 写入全部借阅记录到 records.dat int saveRecords(); // 读取records.dat重建每本书的借阅链表 int loadRecords(); // // 4. 主程序V4大量新增文件加载、自动保存逻辑 // int main() { initLibrary(); // V4新增程序启动优先读取本地存档文件恢复数据 if (loadData() 0) { printf( 成功加载历史数据\n); } else { // 文件不存在/版本不兼容则加载默认测试图书 printf( 未找到数据文件使用初始测试数据\n); addBook(C程序设计语言, Kernighan, 978-7-111-00101-0, 5); addBook(数据结构与算法, Weiss, 978-7-111-00202-0, 3); } int choice; while (1) { showMenu(); scanf(%d, choice); if (choice 1) { // 添加图书执行完成自动调用saveData持久化 char name[MAX_NAME], author[MAX_AUTHOR], isbn[MAX_ISBN]; int stock; printf(请输入书名); scanf(%s, name); printf(请输入作者); scanf(%s, author); printf(请输入ISBN); scanf(%s, isbn); printf(请输入库存数量); scanf(%d, stock); addBook(name, author, isbn, stock); saveData(); // V4新增操作后自动保存 } else if (choice 2) { // 借书完成自动保存 int id, userId; char userName[MAX_USERNAME]; printf(请输入图书ID); scanf(%d, id); printf(请输入用户ID); scanf(%d, userId); printf(请输入用户姓名); scanf(%s, userName); borrowBook(id, userId, userName); saveData(); } else if (choice 3) { // 还书完成自动保存 int id, userId; printf(请输入图书ID); scanf(%d, id); printf(请输入用户ID); scanf(%d, userId); returnBook(id, userId); saveData(); } else if (choice 4) { // 查询仅读取数据无修改无需保存 int id; printf(请输入图书ID); scanf(%d, id); struct Book* b findBookById(id); if (b ! NULL) { printf(\n 图书信息 \n); printf(ID: %d\n书名: %s\n作者: %s\nISBN: %s\n, b-id, b-name, b-author, b-isbn); printf(库存: %d已借出: %d可借: %d\n, b-stock, b-borrowed, b-stock - b-borrowed); showBorrowHistory(id); } else { printf(未找到ID为 %d 的图书\n, id); } } else if (choice 5) { // 列表展示只读不保存 listAllBooks(); } else if (choice 6) { // 删除图书后自动持久化 int id; printf(请输入图书ID); scanf(%d, id); deleteBook(id); saveData(); } else if (choice 7) { break; } else { printf(无效选择\n); } } // V4新增退出循环前强制保存全部数据 saveData(); destroyLibrary(); printf(\n感谢使用数据已保存至 books.dat / records.dat\n); return 0; } // // 5. 基础业务函数完整复制V3实现逻辑无改动仅注释微调 // void initLibrary() { library (struct Book*)malloc(maxBooks * sizeof(struct Book)); if (library NULL) { printf(内存分配失败程序退出\n); exit(1); } printf(✓ 初始化书架初始容量%d 本\n, maxBooks); } void destroyLibrary() { // 释放每本书的借阅链表内存 for (int i 0; i bookCount; i) { if (library[i].borrowHistory ! NULL) { freeBorrowHistory(library[i].borrowHistory); } } if (library ! NULL) { free(library); library NULL; } printf(✓ 已释放所有内存\n); } void ensureCapacity() { if (bookCount maxBooks) return; int newMax maxBooks * 2; struct Book* newLibrary (struct Book*)realloc(library, newMax * sizeof(struct Book)); if (newLibrary NULL) { printf(扩容内存分配失败\n); return; } library newLibrary; maxBooks newMax; printf(✓ 书架自动扩容至 %d 本\n, maxBooks); } void addBook(const char* name, const char* author, const char* isbn, int stock) { ensureCapacity(); for (int i 0; i bookCount; i) { if (strcmp(library[i].isbn, isbn) 0) { printf(✗ 添加失败该ISBN图书已存在\n); return; } } struct Book* b library[bookCount]; b-id nextId; strcpy(b-name, name); strcpy(b-author, author); strcpy(b-isbn, isbn); b-stock stock; b-borrowed 0; b-borrowHistory NULL; bookCount; printf(✓ 图书添加成功分配ID%d\n, b-id); } struct Book* findBookById(int id) { for (int i 0; i bookCount; i) { if (library[i].id id) { return library[i]; } } return NULL; } int findBookIndexById(int id) { for (int i 0; i bookCount; i) { if (library[i].id id) { return i; } } return -1; } void borrowBook(int id, int userId, const char* userName) { struct Book* b findBookById(id); if (b NULL) { printf(✗ 未找到该图书\n); return; } if (b-borrowed b-stock) { printf(✗ 借书失败《%s》库存已全部借出\n, b-name); return; } struct BorrowRecord* record (struct BorrowRecord*)malloc(sizeof(struct BorrowRecord)); if (record NULL) { printf(✗ 借阅记录内存分配失败\n); return; } record-userId userId; strcpy(record-userName, userName); record-borrowDate time(NULL); record-returnDate 0; record-next b-borrowHistory; b-borrowHistory record; b-borrowed; char timeStr[64]; struct tm* tm_info localtime(record-borrowDate); strftime(timeStr, sizeof(timeStr), %Y-%m-%d %H:%M:%S, tm_info); printf(✓ 借书成功\n); printf( 用户%sID:%d借阅《%s》\n, userName, userId, b-name); printf( 借阅时间%s\n, timeStr); printf( 当前已借出%d 本\n, b-borrowed); } void returnBook(int id, int userId) { struct Book* b findBookById(id); if (b NULL) { printf(✗ 未找到该图书\n); return; } if (b-borrowed 0) { printf(✗ 该图书无借出记录\n); return; } struct BorrowRecord* p b-borrowHistory; int found 0; while (p ! NULL) { if (p-userId userId p-returnDate 0) { p-returnDate time(NULL); found 1; b-borrowed--; char timeStr[64]; struct tm* tm_info localtime(p-returnDate); strftime(timeStr, sizeof(timeStr), %Y-%m-%d %H:%M:%S, tm_info); printf(✓ 还书成功\n); printf( 用户 %sID:%d归还《%s》\n, p-userName, userId, b-name); printf( 归还时间%s\n, timeStr); break; } p p-next; } if (!found) { printf(✗ 未找到该用户未归还的借阅记录\n); } } void showBorrowHistory(int id) { struct Book* b findBookById(id); if (b NULL) return; if (b-borrowHistory NULL) { printf( 该图书暂无借阅记录\n); return; } printf(\n 借阅历史 \n); struct BorrowRecord* p b-borrowHistory; int count 1; while (p ! NULL) { char borrowStr[64], returnStr[64]; struct tm* tm_info; tm_info localtime(p-borrowDate); strftime(borrowStr, sizeof(borrowStr), %Y-%m-%d %H:%M, tm_info); if (p-returnDate 0) { strcpy(returnStr, 未归还); } else { tm_info localtime(p-returnDate); strftime(returnStr, sizeof(returnStr), %Y-%m-%d %H:%M, tm_info); } printf( %d. 用户%sID:%d\n, count, p-userName, p-userId); printf( 借阅%s\n, borrowStr); printf( 归还%s\n, returnStr); printf( ---\n); p p-next; count; } printf( 共计 %d 条借阅记录\n, count - 1); } void freeBorrowHistory(struct BorrowRecord* head) { struct BorrowRecord* current head; struct BorrowRecord* next; while (current ! NULL) { next current-next; free(current); current next; } } void deleteBook(int id) { int index findBookIndexById(id); if (index -1) { printf(✗ 未找到该图书\n); return; } if (library[index].borrowed 0) { printf(✗ 删除失败该图书还有 %d 本未归还\n, library[index].borrowed); return; } if (library[index].borrowHistory ! NULL) { freeBorrowHistory(library[index].borrowHistory); } library[index] library[bookCount - 1]; bookCount--; printf(✓ 图书删除成功\n); } void listAllBooks() { if (bookCount 0) { printf(\n 书架暂无图书\n); return; } printf(\n\n); printf(ID\t书名\t\t作者\t\t库存/可借/历史条数\n); printf(\n); for (int i 0; i bookCount; i) { struct Book* b library[i]; int historyCount 0; struct BorrowRecord* p b-borrowHistory; while (p ! NULL) { historyCount; p p-next; } printf(%d\t%-12s\t%-12s\t%d/%d/%d\n, b-id, b-name, b-author, b-stock, b-stock - b-borrowed, historyCount); } printf(\n); } void showMenu() { printf(\n\n); printf( 图书管理系统 V4文件持久化\n); printf(\n); printf(当前图书总数%d 本\n, bookCount); printf(----------------------------------------\n); printf(1. 添加图书\n); printf(2. 借书自动保存记录\n); printf(3. 还书\n); printf(4. 查询图书完整借阅历史\n); printf(5. 列出全部图书\n); printf(6. 删除图书\n); printf(7. 退出程序自动保存\n); printf(----------------------------------------\n); printf(请选择操作序号(1-7)); } // // 6. V4 全新模块文件持久化完整实现 // /** * saveData统一保存入口函数 * 功能同时调用保存图书、保存借阅记录统一返回保存结果 * 返回0全部保存成功返回-1至少一项保存失败 */ int saveData() { int ret1 saveBooks(); int ret2 saveRecords(); if (ret1 ! 0 || ret2 ! 0) { printf(✗ 部分数据保存失败\n); return -1; } return 0; } /** * loadData统一加载入口函数 * 执行顺序先加载图书基础数据再加载所有借阅记录重建链表 */ int loadData() { if (loadBooks() ! 0) { return -1; } loadRecords(); return 0; } /** * saveBooks写入图书二进制文件 books.dat * 写入顺序版本号version → 图书总数bookCount → 下一本ID nextId → 循环写入每本图书固定字段 * 说明borrowHistory是内存链表指针不写入文件借阅记录单独存records.dat */ int saveBooks() { // wb 二进制写模式清空原有文件内容 FILE* fp fopen(books.dat, wb); if (fp NULL) { printf(无法打开 books.dat 写入\n); return -1; } int version DATA_VERSION; fwrite(version, sizeof(int), 1, fp); fwrite(bookCount, sizeof(int), 1, fp); fwrite(nextId, sizeof(int), 1, fp); // 循环序列化每一本图书 for (int i 0; i bookCount; i) { struct Book* b library[i]; fwrite(b-id, sizeof(int), 1, fp); fwrite(b-name, sizeof(char), MAX_NAME, fp); fwrite(b-author, sizeof(char), MAX_AUTHOR, fp); fwrite(b-isbn, sizeof(char), MAX_ISBN, fp); fwrite(b-stock, sizeof(int), 1, fp); fwrite(b-borrowed, sizeof(int), 1, fp); } fclose(fp); return 0; } /** * loadBooks读取图书文件 books.dat恢复内存图书数组 * 逻辑 * 1. rb 二进制只读打开文件文件不存在返回-1 * 2. 读取版本号与DATA_VERSION对比版本不一致直接放弃加载 * 3. 读取图书数量、下一个图书ID * 4. 动态扩容library数组保证能放下读取到的图书数量 * 5. 循环读取每本图书字段链表头指针borrowHistory初始化为NULL */ int loadBooks() { FILE* fp fopen(books.dat, rb); if (fp NULL) { return -1; } int version; size_t rd fread(version, sizeof(int), 1, fp); // 读取失败说明文件损坏 if (rd ! 1) { fclose(fp); return -1; } // 版本校验防止新旧数据格式不兼容 if (version ! DATA_VERSION) { printf(⚠ 文件版本不兼容放弃加载\n); fclose(fp); return -1; } fread(bookCount, sizeof(int), 1, fp); fread(nextId, sizeof(int), 1, fp); // 动态扩容保证数组容量足够存放读取到的图书 while (bookCount maxBooks) { maxBooks * 2; } struct Book* temp (struct Book*)realloc(library, maxBooks * sizeof(struct Book)); if (temp NULL) { printf(加载图书扩容内存失败\n); fclose(fp); return -1; } library temp; // 循环读取每一本图书信息 for (int i 0; i bookCount; i) { struct Book* b library[i]; fread(b-id, sizeof(int), 1, fp); fread(b-name, sizeof(char), MAX_NAME, fp); fread(b-author, sizeof(char), MAX_AUTHOR, fp); fread(b-isbn, sizeof(char), MAX_ISBN, fp); fread(b-stock, sizeof(int), 1, fp); fread(b-borrowed, sizeof(int), 1, fp); b-borrowHistory NULL; // 刚加载图书链表先置空后续loadRecords重建 } fclose(fp); return 0; } /** * saveRecords序列化所有借阅链表存入 records.dat * 存储格式总记录条数totalRecords → 每条记录绑定对应图书ID BorrowRecord完整字段 * 原理链表指针不能存文件只存有效业务数据加载时根据bookId重新挂载到对应图书链表 */ int saveRecords() { FILE* fp fopen(records.dat, wb); if (fp NULL) { printf(无法打开 records.dat 写入\n); return -1; } // 先统计全局所有借阅记录总条数 int totalRecords 0; for (int i 0; i bookCount; i) { struct BorrowRecord* p library[i].borrowHistory; while (p) { totalRecords; p p-next; } } fwrite(totalRecords, sizeof(int), 1, fp); // 遍历所有图书循环写入每一条借阅记录 for (int i 0; i bookCount; i) { struct BorrowRecord* p library[i].borrowHistory; while (p) { fwrite(library[i].id, sizeof(int), 1, fp); fwrite(p-userId, sizeof(int), 1, fp); fwrite(p-userName, sizeof(char), MAX_USERNAME, fp); fwrite(p-borrowDate, sizeof(time_t), 1, fp); fwrite(p-returnDate, sizeof(time_t), 1, fp); p p-next; } } fclose(fp); return 0; } /** * loadRecords读取records.dat重建每本书的借阅单向链表 * 1. 读取总记录条数 * 2. 循环读取每条记录对应的图书ID、用户、时间等数据 * 3. 根据bookId找到对应图书malloc新建节点头插法插入链表 */ int loadRecords() { FILE* fp fopen(records.dat, rb); if (fp NULL) { return -1; } int totalRecords; size_t rd fread(totalRecords, sizeof(int), 1, fp); if (rd ! 1) { fclose(fp); return -1; } // 逐条读取并重建链表 for (int i 0; i totalRecords; i) { int bookId; struct BorrowRecord record; fread(bookId, sizeof(int), 1, fp); fread(record.userId, sizeof(int), 1, fp); fread(record.userName, sizeof(char), MAX_USERNAME, fp); fread(record.borrowDate, sizeof(time_t), 1, fp); fread(record.returnDate, sizeof(time_t), 1, fp); record.next NULL; // 根据图书ID找到对应图书 struct Book* b findBookById(bookId); if (!b) continue; // 分配新链表节点 struct BorrowRecord* newNode (struct BorrowRecord*)malloc(sizeof(struct BorrowRecord)); if (!newNode) { printf(加载记录内存不足跳过本条\n); continue; } // 拷贝读取到的记录头插法挂载到图书链表头部 *newNode record; newNode-next b-borrowHistory; b-borrowHistory newNode; } fclose(fp); return 0; }二、V3 → V4 完整新增 / 改动内容汇总1. 宏定义新增新增#define DATA_VERSION 1作用二进制文件版本校验防止后续修改结构体后旧文件读取错乱。2. 函数声明全新 6 个文件操作接口V3 完全不存在c运行int saveData(); int loadData(); int saveBooks(); int loadBooks(); int saveRecords(); int loadRecords();分层设计saveData/loadData上层统一入口业务代码只调用这两个saveBooks/loadBooks负责图书基础二进制读写saveRecords/loadRecords负责借阅链表序列化、反序列化重建3. main 主程序大量业务逻辑改动1程序启动阶段新增自动加载c运行if (loadData() 0) 加载本地文件 else 加载默认测试图书V3 启动直接创建测试数据无文件读取逻辑。2所有修改类操作执行后自动持久化添加 / 借书 / 还书 / 删除图书操作末尾统一添加saveData();V3 所有操作仅存在内存关闭程序数据全部丢失。3退出前强制保存while 循环 break 后调用saveData()保证退出前数据落盘。4菜单界面更新showMenu 标题改为 V4文件持久化功能标注自动保存、退出自动保存。4. 新增两大二进制文件存储逻辑文件 1books.dat存储内容版本号、图书总数、nextId、每本图书 id / 书名 / 作者 /isbn/ 库存 / 已借出不存储 borrowHistory 链表指针指针是内存地址无法持久化读写模式wb写二进制 /rb读二进制文件 2records.dat存储内容总借阅记录条数、每条记录绑定对应图书 ID 用户 ID、姓名、借阅 / 归还时间持久化思路链表不能直接存文件只存业务数据加载时根据 bookId 重建链表5. 新增核心技术点说明V3 无二进制序列化 fwrite将结构体整块字节写入文件无需字符串解析读写速度快。二进制反序列化 fread从文件读取字节直接还原结构体变量。链表持久化方案分离图书与借阅记录两个文件加载时动态 malloc 节点重建单向链表。数据版本兼容机制文件头部写入版本号升级结构体后可判断旧文件并拒绝加载避免内存错乱。自动持久化机制所有修改操作实时落盘断电 / 闪退最大限度减少数据丢失。6. 原有 V3 代码变更点少量适配showMenu 菜单文字全部更新标注自动保存特性所有只读操作查询、列表无 saveData写操作全部追加保存调用loadBooks 加载图书时自动置空 borrowHistory交由 loadRecords 重建链表7. V4 对比 V3 核心优势数据永久保存关闭程序、重启电脑数据不丢失解决 V3 内存数据临时存储缺陷分层文件存储图书基础信息、借阅记录分离存储便于后期扩展版本容错文件损坏 / 版本不兼容自动降级不会直接崩溃自动落地用户无需手动保存所有增删改操作自动写入本地文件完整二进制 IO 实战掌握 fread/fwrite、结构体序列化、链表持久化工业级方案