尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

C语言图书管理系统:链表+文件实现增删查改

C语言图书管理系统:链表+文件实现增删查改 简介这是一份面向C语言初学者的书店图书管理系统实战项目资源聚焦基础数据结构与文件操作能力训练适用于高校编程入门课程设计、课设实践或自学巩固。资源以C语言实现完整控制台版系统涵盖图书录入、多条件查询、借阅归还、库存统计等核心功能通过结构体封装图书信息、链表管理数据、文件读写实现持久化帮助学习者深入理解内存管理、指针应用与模块化编程逻辑。压缩包为ZIP格式大小2.86MB包含可直接编译运行的源码文件.c/.h、配套说明文档及项目结构组织无冗余资源便于快速上手调试与二次开发。目前已有454人学习下载适合需要从零构建小型管理系统、夯实C语言工程实践能力的学习者尤其利于掌握结构体嵌套、链表增删查改、fread/fwrite文件存取等关键技能点。1. 用C语言写一个能存书、查书、删书的书店图书管理系统不是玩具代码而是能跑在Linux或Windows控制台里的真实可执行程序你手头有一堆纸质图书要登记没有现成数据库也不想装MySQL——这时候一个用标准C写的命令行图书管理系统就是最轻量、最可控的选择。它不依赖图形界面不调用第三方库只靠stdio.h和stdlib.h就能完成增删改查、按书名/作者检索、数据持久化到文本文件。很多高校C语言课程设计、嵌入式设备本地管理模块、甚至小型社区图书馆的临时台账都从这类系统起步。它不是“教学Demo”而是真正能解决“300本书怎么不丢不乱”的实际问题结构体定义图书实体链表动态管理内存文件读写实现关机不丢数据菜单驱动提供清晰交互路径。如果你刚学完指针和文件操作又想立刻写出有业务逻辑的程序这个系统就是你C语言能力落地的第一块试金石。2. 用结构体链表构建图书数据模型让每本书都有唯一ID、书名、作者、库存和价格字段2.1 定义图书结构体与全局链表头指针图书管理系统的核心是数据组织方式。C语言中结构体struct是描述实体的天然工具。我们定义Book结构体包含业务必需字段id自增编号避免重复、title书名最长50字符、author作者最长30字符、stock库存数量整型、price单价浮点型。为支持动态增删采用单向链表而非固定数组——链表节点包含Book数据和指向下一节点的指针。全局头指针head初始化为NULL作为整个链表的入口。#include stdio.h #include stdlib.h #include string.h #define MAX_TITLE 50 #define MAX_AUTHOR 30 typedef struct Book { int id; char title[MAX_TITLE]; char author[MAX_AUTHOR]; int stock; float price; struct Book* next; } Book; Book* head NULL; // 全局链表头指针初始为空提示MAX_TITLE和MAX_AUTHOR定义为宏便于后续统一调整字符串长度限制next指针类型必须是struct Book*不能省略struct关键字这是C语言结构体自引用的语法要求。2.2 实现链表插入新书自动分配ID并插入链表头部插入操作需保证新书ID唯一且递增。我们遍历现有链表找到最大ID新书ID在此基础上加1。为简化逻辑并提升效率尤其在小规模数据下选择头插法新节点直接链接到head之后head指向新节点。这样每次插入时间复杂度为O(1)无需遍历找尾。Book* create_book(const char* title, const char* author, int stock, float price) { Book* new_node (Book*)malloc(sizeof(Book)); if (!new_node) { printf(内存分配失败\n); return NULL; } // 初始化新节点 new_node-id get_max_id() 1; // 调用辅助函数获取当前最大ID strncpy(new_node-title, title, MAX_TITLE - 1); new_node-title[MAX_TITLE - 1] \0; strncpy(new_node-author, author, MAX_AUTHOR - 1); new_node-author[MAX_AUTHOR - 1] \0; new_node-stock stock; new_node-price price; new_node-next NULL; return new_node; } int get_max_id() { if (!head) return 0; int max_id 0; Book* current head; while (current) { if (current-id max_id) max_id current-id; current current-next; } return max_id; } void insert_book(const char* title, const char* author, int stock, float price) { Book* new_node create_book(title, author, stock, price); if (!new_node) return; new_node-next head; // 头插法新节点指向原头节点 head new_node; // 更新头指针 printf(图书 %s 添加成功ID: %d\n, title, new_node-id); }注意strncpy用于安全复制字符串防止缓冲区溢出-1留出\0终止符空间get_max_id()遍历链表确保ID连续性虽非最优但逻辑清晰可靠malloc失败时必须检查返回值避免后续空指针解引用崩溃。2.3 实现链表遍历与打印按格式输出所有图书信息显示全部图书是基础功能需按列对齐增强可读性。使用printf的格式化占位符控制字段宽度%-10s左对齐10字符宽字符串%8d右对齐8字符宽整数%8.2f右对齐8字符宽、保留2位小数的浮点数。遍历从head开始逐个打印直到next为NULL。void display_all_books() { if (!head) { printf(暂无图书记录。\n); return; } printf(\n%-4s %-20s %-15s %-8s %-10s\n, ID, 书名, 作者, 库存, 价格(元)); printf(------------------------------------------------------------\n); Book* current head; while (current) { printf(%-4d %-20s %-15s %-8d %8.2f\n, current-id, current-title, current-author, current-stock, current-price); current current-next; } printf(------------------------------------------------------------\n); }提示表头使用%-4s等格式确保标题与数据列对齐printf中换行符\n位置决定输出节奏此处放在表头后和结尾前形成清晰分隔空链表时提前返回避免无效循环。3. 用文件读写实现数据持久化让系统重启后图书记录不丢失3.1 设计文本文件存储格式用制表符分隔字段每行一条图书记录持久化意味着将内存链表数据保存到磁盘文件并在程序启动时重新加载。选择纯文本格式如books.txt而非二进制因为易读、易调试、兼容性强。每行存储一条图书记录字段间用制表符\t分隔ID\t书名\t作者\t库存\t价格。例如1\t《深入理解计算机系统》\tRandal E. Bryant\t45\t99.00。制表符比空格更可靠书名含空格时不会错位且fscanf能精准识别。字段类型说明ID整数自增唯一标识书名字符串最长49字符含\0作者字符串最长29字符含\0库存整数非负整数价格浮点数精确到分注意文件格式必须与内存结构严格对应否则读取时字段错位fscanf读取字符串时需指定最大长度如%49[^\t]防止缓冲区溢出。3.2 实现保存功能遍历链表将每条记录写入文件save_to_file()函数打开文件w模式覆盖写入遍历链表用fprintf按格式写入每条记录。关键点在于fprintf中%s直接输出字符串%d和%f输出数值\t作为分隔符\n换行。写入完成后关闭文件确保数据刷入磁盘。void save_to_file(const char* filename) { FILE* fp fopen(filename, w); if (!fp) { printf(无法打开文件 %s 进行写入\n, filename); return; } Book* current head; while (current) { fprintf(fp, %d\t%s\t%s\t%d\t%.2f\n, current-id, current-title, current-author, current-stock, current-price); current current-next; } fclose(fp); printf(所有图书数据已保存至 %s。\n, filename); }提示w模式会清空原文件内容适合全量保存fclose()必不可少否则部分数据可能滞留在缓冲区未写入磁盘fprintf的格式字符串与printf一致但输出目标是文件流。3.3 实现加载功能启动时读取文件重建链表load_from_file()在程序初始化时调用打开文件r模式读取逐行解析。使用fscanf配合%d\t%49[^\t]\t%29[^\t]\t%d\t%f格式串%d读ID\t跳过制表符%49[^\t]读取最多49个非制表符字符即书名依此类推。成功读取后调用insert_book()插入链表自动分配ID但文件中ID已存在需稍作调整——见下节。void load_from_file(const char* filename) { FILE* fp fopen(filename, r); if (!fp) { printf(文件 %s 不存在将创建新系统。\n, filename); return; } int id, stock; float price; char title[MAX_TITLE], author[MAX_AUTHOR]; while (fscanf(fp, %d\t%49[^\t]\t%29[^\t]\t%d\t%f, id, title, author, stock, price) 5) { // 关键文件中的ID已存在需绕过create_book的ID生成逻辑 Book* new_node (Book*)malloc(sizeof(Book)); if (!new_node) { printf(内存分配失败跳过图书 %s\n, title); continue; } new_node-id id; strncpy(new_node-title, title, MAX_TITLE - 1); new_node-title[MAX_TITLE - 1] \0; strncpy(new_node-author, author, MAX_AUTHOR - 1); new_node-author[MAX_AUTHOR - 1] \0; new_node-stock stock; new_node-price price; new_node-next head; head new_node; } fclose(fp); printf(已从 %s 加载 %d 条图书记录。\n, filename, count_books()); }注意fscanf返回值为成功匹配的字段数必须等于5才认为一行完整count_books()是辅助函数遍历链表计数用于反馈加载数量strncpy后手动置\0是防御性编程确保字符串安全。4. 实现核心业务操作按书名/作者模糊查询、按ID删除、库存修改4.1 模糊查询支持子字符串匹配返回所有符合条件的图书用户常记不清全名需支持模糊搜索。search_books()接收关键词keyword遍历链表对title和author字段分别调用strstr()判断是否包含关键词大小写敏感。匹配时打印该图书信息并统计总数。若无匹配提示“未找到”。void search_books(const char* keyword) { if (!keyword || strlen(keyword) 0) { printf(搜索关键词不能为空\n); return; } printf(\n搜索结果关键词: %s:\n, keyword); printf(%-4s %-20s %-15s %-8s %-10s\n, ID, 书名, 作者, 库存, 价格(元)); printf(------------------------------------------------------------\n); Book* current head; int found 0; while (current) { if (strstr(current-title, keyword) || strstr(current-author, keyword)) { printf(%-4d %-20s %-15s %-8d %8.2f\n, current-id, current-title, current-author, current-stock, current-price); found; } current current-next; } if (!found) { printf(未找到包含 %s 的图书。\n, keyword); } else { printf(------------------------------------------------------------\n); printf(共找到 %d 条匹配记录。\n, found); } }提示strstr()返回子串首次出现地址非空即匹配strlen(keyword)检查空输入避免strstr对空指针操作found计数器区分“无结果”和“有结果”两种状态。4.2 按ID删除定位节点并调整前后指针释放内存删除操作需处理三种情况删除头节点、删除中间/尾节点、ID不存在。delete_book_by_id()遍历链表用prev指针记录前驱节点。若head-id匹配直接head head-next否则在循环中检查current-id匹配时prev-next current-next再free(current)。全程需检查空指针。void delete_book_by_id(int target_id) { if (!head) { printf(图书列表为空无法删除。\n); return; } // 情况1删除头节点 if (head-id target_id) { Book* temp head; head head-next; free(temp); printf(ID为 %d 的图书已删除。\n, target_id); return; } // 情况2删除中间或尾节点 Book* current head; Book* prev NULL; while (current current-id ! target_id) { prev current; current current-next; } if (!current) { printf(未找到ID为 %d 的图书。\n, target_id); return; } prev-next current-next; free(current); printf(ID为 %d 的图书已删除。\n, target_id); }注意prev初始化为NULL循环中赋值current为NULL表示遍历结束未找到free()释放内存后原指针变为悬空指针但此处current是局部变量无需置NULL。4.3 库存修改定位图书后更新stock字段支持补货与售罄库存管理是书店核心需求。update_stock()先通过ID查找图书找到后提示当前库存允许用户输入新库存值。输入负数则拒绝库存不能为负否则更新stock字段并确认。void update_stock(int target_id, int new_stock) { Book* current head; while (current) { if (current-id target_id) { if (new_stock 0) { printf(库存数量不能为负数\n); return; } printf(图书 %s 当前库存: %d更新为: %d\n, current-title, current-stock, new_stock); current-stock new_stock; printf(库存更新成功。\n); return; } current current-next; } printf(未找到ID为 %d 的图书。\n, target_id); }提示业务逻辑要求库存非负new_stock 0检查必不可少printf输出当前值帮助用户确认操作对象函数不返回布尔值而是通过printf直接反馈结果。5. 构建主菜单与交互流程用switch-case实现清晰的用户操作路径5.1 主循环持续显示菜单接收用户选择调用对应函数主函数main()是程序入口。首先调用load_from_file(books.txt)加载历史数据然后进入while(1)无限循环打印菜单选项1-7用scanf(%d, choice)读取用户输入。switch(choice)分支处理各操作case 7执行save_to_file(books.txt)并break退出循环。default处理非法输入。int main() { printf( 书店图书管理系统C语言版\n); load_from_file(books.txt); // 启动时加载数据 int choice; while (1) { printf(\n请选择操作\n); printf(1. 添加图书\n); printf(2. 查看所有图书\n); printf(3. 搜索图书书名/作者\n); printf(4. 删除图书按ID\n); printf(5. 修改库存按ID\n); printf(6. 统计图书总数\n); printf(7. 保存并退出\n); printf(请输入选项1-7: ); if (scanf(%d, choice) ! 1) { printf(输入错误请输入数字\n); while (getchar() ! \n); // 清空输入缓冲区 continue; } switch (choice) { case 1: { char title[MAX_TITLE], author[MAX_AUTHOR]; int stock; float price; printf(请输入书名: ); scanf(%49s, title); // 限制输入长度 printf(请输入作者: ); scanf(%29s, author); printf(请输入库存: ); scanf(%d, stock); printf(请输入价格: ); scanf(%f, price); insert_book(title, author, stock, price); break; } case 2: display_all_books(); break; case 3: { char keyword[MAX_TITLE]; printf(请输入搜索关键词: ); scanf(%49s, keyword); search_books(keyword); break; } case 4: { int id; printf(请输入要删除的图书ID: ); scanf(%d, id); delete_book_by_id(id); break; } case 5: { int id, stock; printf(请输入图书ID: ); scanf(%d, id); printf(请输入新库存数量: ); scanf(%d, stock); update_stock(id, stock); break; } case 6: printf(当前图书总数: %d\n, count_books()); break; case 7: save_to_file(books.txt); printf(感谢使用再见。\n); return 0; default: printf(无效选项请输入1-7之间的数字。\n); } } return 0; }注意scanf读取字符串时必须限制长度%49s防止缓冲区溢出scanf(%d)失败时如输入字母getchar()循环清空缓冲区避免后续scanf卡住case内用{}包裹局部变量声明符合C99标准。5.2 辅助函数统计总数、清空链表、安全退出补充count_books()统计节点数clear_all_books()释放全部内存用于测试或重置以及get_max_id()已在2.2节定义。这些函数增强系统健壮性。int count_books() { int count 0; Book* current head; while (current) { count; current current-next; } return count; } void clear_all_books() { Book* current head; Book* next; while (current) { next current-next; free(current); current next; } head NULL; printf(所有图书记录已清除。\n); }提示clear_all_books()是free内存的标准模式先保存next指针再free(current)避免访问已释放内存head NULL确保链表彻底清空。6. 关键参数调优与常见陷阱规避字符串长度、内存泄漏、文件编码兼容性6.1 字符串长度参数MAX_TITLE与MAX_AUTHOR的设定依据MAX_TITLE设为50、MAX_AUTHOR设为30并非随意。中文图书名常见长度在20-40字如《三体全集套装共3册》50字符足够覆盖绝大多数情况作者名通常1-5字单名或复姓30字符可容纳长笔名或外文名如“J.R.R. Tolkien”。关键在于strncpy目标缓冲区大小必须大于源字符串长度且预留\0空间。若MAX_TITLE50则strncpy(dest, src, 49)确保末尾有\0。若设为MAX_TITLE10则strncpy(dest, src, 9)但实际应用中10字符远不够会导致截断。参数建议值说明MAX_TITLE50覆盖99%中文书名留足余量MAX_AUTHOR30支持多字姓名及外文名MAX_FILENAME100文件路径长度避免fopen失败注意这些宏定义在文件顶部修改一处即可全局生效若需支持超长书名应同步增大数组和strncpy长度参数否则strncpy会截断。6.2 内存泄漏检查确保每个malloc都有对应freeC语言手动内存管理易出错。本系统中create_book()和load_from_file()调用malloc()对应free()出现在delete_book_by_id()和clear_all_books()。检查点insert_book()调用create_book()但create_book()内部mallocinsert_book()不负责freedelete_book_by_id()找到节点后free(current)且current是malloc分配的clear_all_books()遍历释放所有节点遗漏风险search_books()或display_all_books()中未malloc故无需free。验证方法在main()末尾添加clear_all_books()运行后用valgrindLinux或Application VerifierWindows检测内存泄漏。若valgrind ./booksys输出definitely lost: 0 bytes则无泄漏。6.3 文件编码与跨平台兼容性UTF-8 vs GBK换行符LF vs CRLF文本文件在不同系统表现不同。Linux/macOS用LF\n换行Windows用CRLF\r\n。fopen以文本模式w/r打开时C标准库自动转换Windows写入\n实际存为\r\n读取时\r\n转为\n。因此同一份books.txt在Linux和Windows下均可正确读写无需额外处理。编码方面若图书名含中文需确保源文件.c和数据文件books.txt同为UTF-8编码。现代编辑器VS Code、Vim默认UTF-8编译时GCC/Clang无需特殊参数。若在Windows旧终端CMD显示中文乱码可执行chcp 65001切换到UTF-8代码页。场景推荐方案说明Linux开发UTF-8编码LF换行标准POSIX环境Windows开发UTF-8编码文本模式fopenC库自动处理换行符中文显示异常检查终端代码页chcpCMD需chcp 65001提示避免使用wb二进制模式写入文本会导致换行符不兼容fscanf读取UTF-8中文无问题因%s和%[^\t]按字节解析UTF-8多字节序列被整体视为字符。本文还有配套的精品资源点击获取
返回列表