当前位置: 首页 > news >正文

c语言项目驱动学习--实例化(图书管理)--002-代码对比

V2 完整逐行详细注释版 + V1/V2 版本差异对比

一、带全覆盖注释完整代码

c

运行

// // Created by Administrator on 2026/6/28. // /** * ============================================================ * 阶段V2:指针深化版 - 动态内存管理 * ============================================================ * 知识点:结构体指针、动态内存分配malloc、内存扩容realloc、内存释放free、堆内存管理 * 功能:书架支持动态扩容,不再固定最大100本,理论可存放大量图书(受物理内存限制) * 版本迭代说明:基于V1结构体数组版本升级,解决静态数组容量固定的缺陷 * ============================================================ */ #include <stdio.h> #include <string.h> #include <stdlib.h> // malloc/realloc/free/exit 动态内存函数依赖头文件 // ======================================== // 1. 宏定义:字符串长度限制,与V1保持一致 // ======================================== #define MAX_NAME 100 #define MAX_AUTHOR 50 #define MAX_ISBN 20 // ======================================== // 2. 图书结构体:完全复用V1结构,无改动 // ======================================== struct Book { int id; // 图书唯一ID char name[MAX_NAME]; // 书名 char author[MAX_AUTHOR];// 作者 char isbn[MAX_ISBN]; // ISBN编号 int stock; // 总库存 int borrowed; // 已借出数量 void* borrowHistory; // 预留字段,V3实现借阅记录链表 }; // ======================================== // 3. 全局数据(V2重大改动:静态数组改为动态堆内存指针) // ======================================== struct Book* library = NULL; // 指向堆上动态结构体数组的指针,初始为空 int bookCount = 0; // 当前实际存储图书数量 int maxBooks = 10; // 当前动态数组总容量,初始分配10本空间 int nextId = 1001; // 自增图书ID,和V1逻辑一致 // ======================================== // 4. 函数声明(新增3个内存管理专属函数) // ======================================== 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); void returnBook(int id); void listAllBooks(); void deleteBook(int id); void showMenu(); // ======================================== // 5. 主程序入口 // ======================================== int main() { // V2新增:程序启动先初始化动态内存书架 initLibrary(); // 预置3本测试图书,逻辑与V1完全一致 addBook("C程序设计语言", "Kernighan", "978-7-111-00101-0", 5); addBook("数据结构与算法", "Weiss", "978-7-111-00202-0", 3); addBook("深入理解计算机系统", "Bryant", "978-7-111-00555-0", 2); int choice; // 循环菜单 while (1) { showMenu(); scanf("%d", &choice); if (choice == 1) { // 1.添加图书,输入逻辑不变 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); } else if (choice == 2) { // 2.借书 int id; printf("请输入图书ID:"); scanf("%d", &id); borrowBook(id); } else if (choice == 3) { // 3.还书 int id; printf("请输入图书ID:"); scanf("%d", &id); returnBook(id); } else if (choice == 4) { // 4.按ID查询单本图书 int id; printf("请输入图书ID:"); scanf("%d", &id); struct Book* b = findBookById(id); if (b != NULL) { printf("\n找到图书:\n"); printf(" ID: %d\n", b->id); printf(" 书名: %s\n", b->name); printf(" 作者: %s\n", b->author); printf(" ISBN: %s\n", b->isbn); printf(" 库存: %d,已借出: %d,可借: %d\n", b->stock, b->borrowed, b->stock - b->borrowed); } else { printf("未找到ID为 %d 的图书\n", id); } } else if (choice == 5) { // 5.列出全部图书 listAllBooks(); } else if (choice == 6) { // 6.删除图书 int id; printf("请输入要删除的图书ID:"); scanf("%d", &id); deleteBook(id); } else if (choice == 7) { // 7.退出菜单循环 break; } else { // 非法输入提示 printf("无效选择,请重新输入!\n"); } } // V2新增:程序退出前释放堆内存,避免内存泄漏 destroyLibrary(); printf("\n感谢使用图书管理系统,再见!\n"); return 0; } // ======================================== // 6. 函数实现(新增内存管理3个核心函数,原有业务函数适配动态指针) // ======================================== /** * initLibrary:初始化动态书架 * 作用:程序启动时malloc分配初始堆内存,创建动态数组 * 异常处理:内存分配失败直接退出程序 */ void initLibrary() { // malloc:分配 maxBooks 个Book结构体大小的堆内存,强制转换为Book*指针 library = (struct Book*)malloc(maxBooks * sizeof(struct Book)); // 判断内存分配是否失败(malloc失败返回NULL) if (library == NULL) { printf("✗ 内存分配失败!\n"); exit(1); // 异常退出程序,返回错误码1 } printf("✓ 初始化书架,容量:%d 本\n", maxBooks); } /** * destroyLibrary:销毁书架、释放堆内存 * 作用:程序结束前free释放动态分配的内存,解决内存泄漏 * 安全判断:先判断指针非空再释放,防止重复free崩溃 */ void destroyLibrary() { if (library != NULL) { free(library); // 释放堆内存 library = NULL; // 置空野指针 } printf("✓ 已释放所有内存\n"); } /** * ensureCapacity:动态扩容核心函数 * 触发时机:每次添加图书前调用 * 逻辑:当前图书数量等于总容量时,容量翻倍,realloc重新分配更大内存 */ void ensureCapacity() { // 容量充足,无需扩容,直接返回 if (bookCount < maxBooks) { return; } // 新容量 = 当前容量 * 2 int newMax = maxBooks * 2; // realloc:重新分配更大内存,自动拷贝原有数据 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); } /** * addBook:新增图书(适配动态内存) * 改动:函数开头先调用ensureCapacity自动扩容,V1无此逻辑 */ void addBook(const char* name, const char* author, const char* isbn, int stock) { ensureCapacity(); // V2新增:先校验容量,不足自动扩容 // 校验ISBN重复,逻辑与V1完全一致 for (int i = 0; i < bookCount; i++) { if (strcmp(library[i].isbn, isbn) == 0) { printf("✗ ISBN %s 已存在\n", isbn); 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(当前共 %d 本)\n", b->id, bookCount); } /** * findBookById:按ID查找图书,逻辑与V1完全无改动 */ struct Book* findBookById(int id) { for (int i = 0; i < bookCount; i++) { if (library[i].id == id) { return &library[i]; } } return NULL; } /** * findBookIndexById:返回图书下标,逻辑与V1完全无改动 */ int findBookIndexById(int id) { for (int i = 0; i < bookCount; i++) { if (library[i].id == id) { return i; } } return -1; } /** * borrowBook:借书业务逻辑不变,仅输出文字小幅简化 */ void borrowBook(int id) { struct Book* b = findBookById(id); if (b == NULL) { printf("✗ 未找到ID为 %d 的图书\n", id); return; } if (b->borrowed >= b->stock) { printf("✗ 借书失败!《%s》库存不足\n", b->name); return; } b->borrowed++; printf("✓ 借书成功!《%s》已借出:%d 本\n", b->name, b->borrowed); } /** * returnBook:还书业务逻辑不变,输出文字小幅简化 */ void returnBook(int id) { struct Book* b = findBookById(id); if (b == NULL) { printf("✗ 未找到ID为 %d 的图书\n", id); return; } if (b->borrowed <= 0) { printf("✗ 还书失败!《%s》没有借出记录\n", b->name); return; } b->borrowed--; printf("✓ 还书成功!《%s》已借出:%d 本\n", b->name, b->borrowed); } /** * listAllBooks:展示全部图书 * 改动:末尾打印当前动态数组总容量maxBooks,V1无容量展示 */ 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]; printf("%d\t%-12s\t%-12s\t%d/%d\n", b->id, b->name, b->author, b->stock, b->stock - b->borrowed); } printf("========================================\n"); printf("总计:%d 本书(容量:%d)\n", bookCount, maxBooks); } /** * deleteBook:删除图书,数组移位逻辑完全复用V1,无改动 */ void deleteBook(int id) { int index = findBookIndexById(id); if (index == -1) { printf("✗ 未找到ID为 %d 的图书\n", id); return; } if (library[index].borrowed > 0) { printf("✗ 删除失败!《%s》还有 %d 本借出\n", library[index].name, library[index].borrowed); return; } // 末尾元素覆盖删除位置,逻辑不变 library[index] = library[bookCount - 1]; bookCount--; printf("✓ 删除成功!\n"); } /** * showMenu:菜单界面 * 改动:标题改为V2动态内存版,新增打印当前容量maxBooks */ void showMenu() { printf("\n========================================\n"); printf(" 图书管理系统 V2(指针+动态内存)\n"); printf("========================================\n"); printf("图书总数:%d 本(容量:%d)\n", bookCount, maxBooks); printf("----------------------------------------\n"); printf("1. 添加图书\n"); printf("2. 借书\n"); printf("3. 还书\n"); printf("4. 查询图书(按ID)\n"); printf("5. 显示所有图书\n"); printf("6. 删除图书\n"); printf("7. 退出程序\n"); printf("----------------------------------------\n"); printf("请选择操作(1-7):"); }

二、V1(静态结构体数组) VS V2(动态内存指针)新增 / 改动内容汇总

1. 头文件新增

  • V1:仅#include <stdio.h>#include <string.h>
  • V2:新增#include <stdlib.h>,提供malloc / realloc / free / exit动态内存函数

2. 全局存储结构核心改动(最大升级点)

  1. V1:固定大小静态数组struct Book library[MAX_BOOKS];,最大 100 本,栈 / 全局静态内存,容量写死无法扩展
  2. V2:堆内存动态指针struct Book* library = NULL;
    • 移除固定宏MAX_BOOKS
    • 新增变量maxBooks:记录当前动态数组分配的容量
    • 初始容量仅 10 本,图书增多自动扩容

3. 新增 3 个专属内存管理函数(V1 完全不存在)

  1. void initLibrary()
    • 程序启动时调用,malloc分配初始堆内存
    • 内存分配失败直接退出程序,做异常容错
  2. void ensureCapacity()(核心扩容函数)
    • 添加图书前自动检测容量
    • 存满时容量翻倍,realloc重新分配更大内存,自动迁移原有数据
  3. void destroyLibrary()
    • 程序退出前调用,free释放堆内存
    • 避免长期运行产生内存泄漏,释放后置空指针防止野指针

4. main 函数流程改动

  1. 程序开头新增initLibrary();初始化动态内存
  2. 退出循环后新增destroyLibrary();释放内存,V1 无内存释放操作
  3. V1 菜单选项 7 退出直接结束,V2 增加内存清理步骤

5. addBook 函数逻辑修改

  • V1:开头判断bookCount >= MAX_BOOKS固定上限
  • V2:替换为ensureCapacity();自动扩容,无固定上限

6. 界面输出细节改动

  1. showMenu 菜单:
    • 标题更新为 V2 动态内存版本
    • 新增打印当前动态数组容量maxBooks
  2. listAllBooks 列表底部:
    • 新增(容量:%d)显示当前分配内存大小
  3. addBook 成功提示:增加当前总图书数量展示

7. 业务函数细微优化(无逻辑变更,仅文字)

  • borrowBook /returnBook 错误提示文字精简,去掉多余括号信息,业务判断逻辑完全不变

8. 结构体、查找、删除逻辑完全复用无改动

  • struct Book 结构体定义和 V1 一模一样
  • findBookById /findBookIndexById 遍历查找代码无修改
  • deleteBook 数组移位删除逻辑完全复用 V1
  • 借书、还书、单本查询核心业务逻辑无变更

三、V2 版本核心优势对比 V1

  1. 不再限制固定图书数量,按需分配内存,内存利用率更高
  2. 学习堆内存、指针、malloc/realloc/free,夯实 C 语言内存管理知识点
  3. 手动释放内存,养成无内存泄漏的规范编程习惯
  4. 容量自动翻倍扩容,模拟真实项目动态容器设计思路

四、内容梳理

在C语言中,malloc、realloc 和 free 是三个用于动态内存管理的核心函数,它们都定义在 <stdlib.h> 头文件中。 简单来说,它们的作用是: malloc 申请内存,realloc 调整已申请的内存大小,free 释放内存。 1. malloc(内存分配) 作用:在堆(Heap)上申请一块连续的、指定大小的内存空间,但不会初始化其中的内容(里面是垃圾值)。 原型:void *malloc(size_t size); 参数:size 是你想要申请的字节数。 返回值:成功时返回指向该内存块的指针(void* 类型,可强制转为任何类型);失败时返回 NULL。 如何使用: c #include <stdio.h> #include <stdlib.h> int main() { int *arr; int n = 5; // 申请可以存放5个int类型的数据的内存空间 // 总字节数 = 元素个数 * 每个元素大小 arr = (int *)malloc(n * sizeof(int)); // 重要:必须检查申请是否成功 if (arr == NULL) { printf("内存分配失败!\n"); return 1; } // 使用这块内存(此时里面的值是随机的,需要手动赋值) for (int i = 0; i < n; i++) { arr[i] = i + 1; // 初始化 printf("%d ", arr[i]); } // 使用完毕后必须释放,防止内存泄漏 free(arr); return 0; } 2. realloc(重新分配内存) 作用:调整(扩大或缩小)之前通过 malloc 或 calloc 申请的内存块大小。它会尽量在原地扩展,如果原地空间不够,会找新位置并自动复制旧数据过去。 原型:void *realloc(void *ptr, size_t new_size); 参数: ptr:指向之前申请的内存块的指针(如果传 NULL,行为等同于 malloc)。 new_size:新的目标大小(字节数)。 返回值:成功返回新内存的指针(可能与 ptr 不同),失败返回 NULL(此时原内存块保持不变)。 如何使用(重要:必须用临时指针接收返回值): c #include <stdio.h> #include <stdlib.h> int main() { int *arr; int n = 3; // 1. 先申请3个int的空间 arr = (int *)malloc(n * sizeof(int)); if (arr == NULL) return 1; for (int i = 0; i < n; i++) arr[i] = i; // 存入 0,1,2 // 2. 现在需要扩展到能存5个int int new_n = 5; int *temp; // 关键:用临时指针接收realloc的返回值 temp = (int *)realloc(arr, new_n * sizeof(int)); // 必须检查realloc是否成功 if (temp == NULL) { printf("扩展内存失败!原数据 arr 依然有效。\n"); // 这里不能将 arr 置为 NULL,因为它还指向原内存 // 继续使用 arr 或者 free(arr) 后退出 free(arr); return 1; } else { // 成功:将 arr 指向新内存 arr = temp; } // 3. 此时旧数据 (0,1,2) 被自动保留,新增的空间(索引3,4)是未初始化的 for (int i = n; i < new_n; i++) { arr[i] = i; // 初始化新空间 } // 打印所有数据:0 1 2 3 4 for (int i = 0; i < new_n; i++) { printf("%d ", arr[i]); } free(arr); return 0; } 3. free(释放内存) 作用:将之前通过动态分配函数申请的内存归还给操作系统,以便系统重新利用这些内存。 原型:void free(void *ptr); 参数:ptr 指向要释放的内存块的指针。 使用要点(极其重要): 只能释放动态分配的内存:不能对栈上的变量(如 int a;)或静态变量调用 free。 只能释放一次:释放后,该指针变为“悬空指针”。再次释放(Double Free)会导致程序崩溃。 释放后指针置 NULL(好习惯):释放后,原指针仍然保存着已经无效的地址,容易误用。建议立即置为 NULL。 c free(arr); // 释放内存 arr = NULL; // 将指针置空,防止野指针 4. 总结对比与核心注意事项 函数 核心作用 是否初始化 失败时返回 注意事项 malloc 申请新内存 否(垃圾值) NULL 必须检查返回值;sizeof 计算大小 realloc 调整已有内存大小 新增部分不初始化 NULL 必须用临时指针接收返回值,防止原指针丢失 free 释放内存 不涉及 无返回值 只能释放一次;释放后指针应置 NULL 三个致命错误(务必避免): 内存泄漏:申请了内存但没有 free,程序长期运行会耗尽内存。 悬空指针:free 后继续使用该指针(访问已释放的内存)。 重复释放:对同一个指针调用两次 free。 标准使用模板: c // 申请 type *p = (type *)malloc(n * sizeof(type)); if (p == NULL) { /* 处理错误 */ } // ... 使用 p ... // 释放 free(p); p = NULL;
http://www.jsqmd.com/news/1093475/

相关文章:

  • 学完各类AI课程仍无法落地企业项目?核心短板从来不是工具操作
  • 录音转写太慢效率低?语音识别软件性价比关键评估
  • 为什么 CPU/内存指标不足以支撑真实业务伸缩
  • 软硬一体销售会话分析软硬件一体方案选型与落地参考
  • 长春新房除甲醛避坑!普尔净教你分清通风和专业治理的差距
  • PG 日报|PGConf.EU 2026 开启预约
  • GPT 付款失败怎么办?国内信用卡无法绑定时有哪些替代方案
  • MITK在windows平台的构建
  • SystemVerilog包(package)的三大引用方式与实战场景解析
  • 如何将 HTML 转换为可编辑的 Word 文档(无需安装软件)
  • 从零搭建最简pytest+Playwright UI自动化测试框架
  • Python自动化工具实战:从零构建B站抢票脚本的完整指南
  • 【课程设计/毕业设计】基于 SpringBoot 的餐厅前台点餐后台管理系统 轻量化餐饮订单服务管理系统设计与实现【附源码、数据库、万字文档】
  • 未来真正赚钱的AI项目,往往都长得不像“AI项目”
  • 如何从Redmi恢复已删除的文件:4种简单方法
  • vitest + vue3 踩坑记录
  • Java计算机毕设之基于 SpringBoot 的毕业课题进程督导管理平台(完整前后端代码+说明文档+LW,调试定制等)
  • vide coding软件开发流程
  • wireshark学习小结
  • 一人创业时,内容、开发、客户跟进分别适合用哪些AI工具辅助开篇:一人创业为什么最容易卡在任务切换和推进节奏上
  • 6个真实用户反馈 森优时铁锌维 白发转黑发 改善周期测评
  • 2026 私域全面严打,无层级矩阵拼团为什么能安稳做
  • LEADTOOLS 医疗套件开发人员工具包
  • 2026 APP竞品分析怎么做?一套完整流程分享
  • 高速ADC外围电路设计精要:增益、时钟与接口配置实战指南
  • 二层三层交换机选型
  • 如何从三星帐户恢复联系人?分步指南
  • 2026 年命理排盘工具隐私与数据管理榜:玄易为何更适合长期执业
  • ESXi 直通与共享模式
  • 嵌入式低功耗子系统(LFSS)实战:RTC、看门狗与安全监控设计