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

发布时间:2026/7/1 1:38:37

c语言项目驱动学习--实例化(图书管理)--005-代码对比 一、完整带详细注释 V5 SQLite 代码前文完整注释版不变此处复用c运行/** * * 阶段V5 Windows简化版SQLite数据库持久化 * 前置版本V4 二进制文件fread/fwrite本地存储 * 新增核心知识点SQLite嵌入式数据库、SQL预编译语句(sqlite3_stmt)、参数绑定防SQL注入、外键关联表、库表分离、回调查询、磁盘数据库文件 * CMake编译配套说明配套下方CMakeLists.txt 1. 目录规范 - sqlite3.c 放置项目根目录 - sqlite3.h 放置 include/ 文件夹内 - 源码分层存放 src/v0 ~ src/v5 2. C标准全局启用 C23 3. Windows编译自动关闭 CRT 安全警告自动链接 Threads、ws2_32 依赖 * 环境部署说明Windows MinGW-w64 / MSVC 均可 1. 依赖文件sqlite3.c sqlite3.h 单文件合并包(amalgamation)无需额外dll 2. 编译命令cmake构建后自动处理依赖 * 核心升级对比V4 1. 抛弃自定义二进制文件books.dat/records.dat改用标准关系型数据库library.db 2. 数据结构化分两张表books(图书)、borrow_records(借阅记录)支持外键关联 3. 支持条件查询、删除级联、数据完整性约束(UNIQUE ISBN、主键自增) 4. 使用预编译SQL语句绑定参数杜绝字符串拼接注入风险 5. 内存仅做缓存所有增删改操作实时同步数据库数据结构标准化易扩展 6. 天然支持持久化、事务、索引、多条件筛选无需手动处理序列化/反序列化 * */ #include stdio.h #include string.h #include stdlib.h #include time.h // V5新增SQLite数据库核心头文件由include目录全局引入 #include sqlite3.h // 宏定义 #define MAX_NAME 200 // 书名长度扩容支持更长中文书名 #define MAX_AUTHOR 100 // 作者长度扩容 #define MAX_ISBN 30 // ISBN编号长度 #define MAX_USERNAME 50 // 借阅人姓名 #define MAX_BOOK_CACHE 100 // 内存缓存最大图书数量内存仅做缓存真实数据存在数据库 // 借阅记录单向链表内存缓存展示用结构与V3/V4完全一致 struct BorrowRecord { int userId; char userName[MAX_USERNAME]; time_t borrowDate; time_t returnDate; struct BorrowRecord* next; }; // 图书结构体缓存层字段和V4一致 struct Book { int id; char name[MAX_NAME]; char author[MAX_AUTHOR]; char isbn[MAX_ISBN]; int stock; int borrowed; struct BorrowRecord* borrowHistory; // 内存链表数据库单独存记录表 }; // 全局变量 struct Book* library NULL; // 内存图书缓存数组 int bookCount 0; // 当前缓存图书数量 int maxBooks MAX_BOOK_CACHE;// 缓存容量上限 int nextId 1001; // 图书自增ID sqlite3* db NULL; // V5新增SQLite数据库句柄全局数据库连接 // // V5全新分层数据库底层操作函数V4无独立数据库层 // // 初始化数据库打开library.db int db_init(); // 关闭数据库连接释放句柄 void db_close(); // 创建两张数据表books、borrow_records不存在则新建 int db_create_tables(); // 插入新图书到数据库 int db_add_book(struct Book* b); // 更新已有图书库存/借出量/名称等信息 int db_update_book(struct Book* b); // 根据ID删除图书级联删除关联借阅记录 int db_delete_book(int id); // 读取数据库全部图书加载到内存缓存数组 int db_load_all_books(); // 插入一条借阅记录到记录表 int db_add_record(int bookId, int userId, const char* userName, time_t borrowDate, time_t returnDate); // 读取全部借阅记录重建每本书内存链表 int db_load_records(); // // 原有业务层函数复用V4业务逻辑底层替换为SQLite // void initLibrary(); void destroyLibrary(); void addBook(const char* name, const char* author, const char* isbn, int stock); struct Book* findBookById(int id); void borrowBook(int id, int userId, const char* userName); void returnBook(int id, int userId); void listAllBooks(); void showBorrowHistory(int id); void freeBorrowHistory(struct BorrowRecord* head); void showMenu(); // 主程序入口 int main() { // 第一步初始化打开SQLite数据库 if (db_init() ! 0) { printf(数据库打开失败\n); system(pause); return 1; } // 自动建表不存在则创建books、borrow_records db_create_tables(); // 初始化内存缓存数组 initLibrary(); // 从数据库加载所有图书到内存缓存 bookCount db_load_all_books(); // 加载全部借阅记录重建内存借阅链表 db_load_records(); printf(成功加载 %d 本图书\n, bookCount); int choice; while (1) { showMenu(); scanf(%d, choice); getchar(); // 吸收scanf遗留换行符防止后续输入卡顿 if (choice 1) { // 1. 添加图书 char name[MAX_NAME], author[MAX_AUTHOR], isbn[MAX_ISBN]; int stock; printf(请输入ISBN); scanf(%s, isbn); printf(请输入书名); scanf(%s, name); printf(请输入作者); scanf(%s, author); printf(请输入库存数量); scanf(%d, stock); addBook(name, author, isbn, stock); } else if (choice 2) { // 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); } else if (choice 3) { // 3. 还书操作 int id, userId; printf(图书ID); scanf(%d, id); printf(用户ID); scanf(%d, userId); returnBook(id, userId); } else if (choice 4) { // 4. 查询图书借阅历史 int id; printf(查询图书ID); scanf(%d, id); struct Book* b findBookById(id); if (b NULL) { printf(未找到该图书\n); continue; } printf(\n 图书详情 \n); printf(ID%d\n书名%s\n作者%s\nISBN%s\n库存%d 已借出%d\n, b-id, b-name, b-author, b-isbn, b-stock, b-borrowed); showBorrowHistory(id); } else if (choice 5) { // 5. 列出全部缓存图书 listAllBooks(); } else if (choice 6) { // 6. 删除图书数据库级删除同步刷新缓存 int id; printf(待删除图书ID); scanf(%d, id); db_delete_book(id); // 删除后重新从数据库加载全部图书刷新内存缓存 bookCount db_load_all_books(); printf(删除完成已刷新列表\n); } else if (choice 7) { // 7. 退出循环 break; } else { printf(输入无效请重新选择\n); } printf(\n按回车继续...); getchar(); } // 程序退出资源释放流程 destroyLibrary(); // 释放内存中所有借阅链表 db_close(); // 关闭SQLite数据库连接 free(library); // 释放图书缓存数组 printf(程序退出数据已存入 library.db\n); system(pause); return 0; } // V5 新增SQLite数据库底层实现 /** * db_init打开本地磁盘数据库文件 library.db * SQLite特性文件不存在则自动新建空数据库文件 */ int db_init() { int rc sqlite3_open(library.db, db); if (rc ! SQLITE_OK) { printf(数据库错误%s\n, sqlite3_errmsg(db)); return -1; } return 0; } /** * db_close安全关闭数据库句柄置空防止野指针 */ void db_close() { if (db ! NULL) { sqlite3_close(db); db NULL; } } /** * db_create_tables执行建表SQL两张表外键约束 * 1. books图书主表id主键、ISBN唯一约束防止重复 * 2. borrow_records借阅记录表book_id外键关联图书id存储时间戳(整数time_t) */ int db_create_tables() { char* errMsg NULL; // 创建图书表 const char* sqlBook CREATE TABLE IF NOT EXISTS books( id INTEGER PRIMARY KEY, name TEXT NOT NULL, author TEXT, isbn TEXT UNIQUE, stock INTEGER, borrowed INTEGER);; sqlite3_exec(db, sqlBook, NULL, NULL, errMsg); if (errMsg) { printf(建表失败%s\n, errMsg); sqlite3_free(errMsg); return -1; } // 创建借阅记录表外键绑定图书ID const char* sqlRec CREATE TABLE IF NOT EXISTS borrow_records( id INTEGER PRIMARY KEY AUTOINCREMENT, book_id INTEGER, user_id INTEGER, user_name TEXT, borrow_date INTEGER, return_date INTEGER, FOREIGN KEY(book_id) REFERENCES books(id));; sqlite3_exec(db, sqlRec, NULL, NULL, errMsg); if (errMsg) { printf(建表失败%s\n, errMsg); sqlite3_free(errMsg); return -1; } return 0; } /** * db_add_book预编译SQL插入图书参数绑定防SQL注入 * sqlite3_prepare_v2 预编译语句bind绑定参数避免字符串拼接漏洞 */ int db_add_book(struct Book* b) { sqlite3_stmt* stmt; const char* sql INSERT INTO books(id,name,author,isbn,stock,borrowed) VALUES(?,?,?,?,?,?); int rc sqlite3_prepare_v2(db, sql, -1, stmt, NULL); if (rc ! SQLITE_OK) return -1; // 依次绑定6个占位符参数 sqlite3_bind_int(stmt, 1, b-id); sqlite3_bind_text(stmt, 2, b-name, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 3, b-author, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 4, b-isbn, -1, SQLITE_STATIC); sqlite3_bind_int(stmt, 5, b-stock); sqlite3_bind_int(stmt, 6, b-borrowed); sqlite3_step(stmt); // 执行SQL语句 sqlite3_finalize(stmt);// 释放预编译语句资源 return 0; } /** * db_update_book更新图书库存、借出数量、名称等信息 */ int db_update_book(struct Book* b) { sqlite3_stmt* stmt; const char* sql UPDATE books SET name?,author?,isbn?,stock?,borrowed? WHERE id?; int rc sqlite3_prepare_v2(db, sql, -1, stmt, NULL); if (rc ! SQLITE_OK) return -1; sqlite3_bind_text(stmt, 1, b-name, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, b-author, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 3, b-isbn, -1, SQLITE_STATIC); sqlite3_bind_int(stmt, 4, b-stock); sqlite3_bind_int(stmt, 5, b-borrowed); sqlite3_bind_int(stmt, 6, b-id); sqlite3_step(stmt); sqlite3_finalize(stmt); return 0; } /** * db_delete_book删除图书先级联删除该图书所有借阅记录 * V4二进制文件需要手动遍历链表删除数据库一条SQL即可批量清理 */ int db_delete_book(int id) { sqlite3_stmt* stmt; // 第一步删除关联借阅记录 sqlite3_prepare_v2(db, DELETE FROM borrow_records WHERE book_id?, -1, stmt, NULL); sqlite3_bind_int(stmt, 1, id); sqlite3_step(stmt); sqlite3_finalize(stmt); // 第二步删除图书主数据 sqlite3_prepare_v2(db, DELETE FROM books WHERE id?, -1, stmt, NULL); sqlite3_bind_int(stmt, 1, id); sqlite3_step(stmt); sqlite3_finalize(stmt); return 0; } /** * db_load_all_books查询全量图书填充到内存缓存数组 * sqlite3_step循环读取每一行查询结果sqlite3_column_xxx读取字段值 */ int db_load_all_books() { const char* sql SELECT id,name,author,isbn,stock,borrowed FROM books;; sqlite3_stmt* stmt; sqlite3_prepare_v2(db, sql, -1, stmt, NULL); int cnt 0; // 循环读取每一行图书数据 while (sqlite3_step(stmt) SQLITE_ROW) { struct Book* b library[cnt]; b-id sqlite3_column_int(stmt, 0); strcpy(b-name, (const char*)sqlite3_column_text(stmt, 1)); strcpy(b-author, (const char*)sqlite3_column_text(stmt, 2)); strcpy(b-isbn, (const char*)sqlite3_column_text(stmt, 3)); b-stock sqlite3_column_int(stmt, 4); b-borrowed sqlite3_column_int(stmt, 5); b-borrowHistory NULL; // 链表先置空后续加载记录重建 // 同步更新全局自增ID避免新增图书ID重复 if (b-id nextId) nextId b-id 1; cnt; } sqlite3_finalize(stmt); return cnt; } /** * db_add_record插入一条完整借阅记录到数据库表 * time_t 使用sqlite3_bind_int64存储64位时间戳整数 */ int db_add_record(int bookId, int userId, const char* userName, time_t borrowDate, time_t returnDate) { sqlite3_stmt* stmt; const char* sql INSERT INTO borrow_records(book_id,user_id,user_name,borrow_date,return_date) VALUES(?,?,?,?,?); sqlite3_prepare_v2(db, sql, -1, stmt, NULL); sqlite3_bind_int(stmt, 1, bookId); sqlite3_bind_int(stmt, 2, userId); sqlite3_bind_text(stmt, 3, userName, -1, SQLITE_STATIC); sqlite3_bind_int64(stmt, 4, borrowDate); sqlite3_bind_int64(stmt, 5, returnDate); sqlite3_step(stmt); sqlite3_finalize(stmt); return 0; } /** * db_load_records读取全部借阅记录重建每本书内存单向链表 * 根据book_id匹配对应图书malloc节点头插法挂载链表 */ int db_load_records() { const char* sql SELECT book_id,user_id,user_name,borrow_date,return_date FROM borrow_records;; sqlite3_stmt* stmt; sqlite3_prepare_v2(db, sql, -1, stmt, NULL); while (sqlite3_step(stmt) SQLITE_ROW) { int bid sqlite3_column_int(stmt, 0); int uid sqlite3_column_int(stmt, 1); const char* uname (const char*)sqlite3_column_text(stmt, 2); time_t bt sqlite3_column_int64(stmt, 3); time_t rt sqlite3_column_int64(stmt, 4); struct Book* b findBookById(bid); if (b NULL) continue; // 新建链表节点 struct BorrowRecord* rec (struct BorrowRecord*)malloc(sizeof(struct BorrowRecord)); rec-userId uid; strcpy(rec-userName, uname); rec-borrowDate bt; rec-returnDate rt; rec-next b-borrowHistory; b-borrowHistory rec; } sqlite3_finalize(stmt); return 0; } // 业务逻辑函数缓存层底层调用SQLite接口 /** * initLibrarycalloc初始化内存缓存数组自动清零 */ void initLibrary() { library (struct Book*)calloc(maxBooks, sizeof(struct Book)); if (library NULL) { printf(内存分配失败\n); exit(1); } bookCount 0; } /** * destroyLibrary释放所有图书挂载的借阅链表内存 */ void destroyLibrary() { for (int i 0; i bookCount; i) { freeBorrowHistory(library[i].borrowHistory); } } /** * addBook新增图书先内存缓存再写入SQLite数据库 * ISBN重复校验仅校验内存缓存数据库自带UNIQUE约束双重保障 */ void addBook(const char* name, const char* author, const char* isbn, int stock) { // 内存层判重 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; // 写入数据库持久化 db_add_book(b); bookCount; printf(图书添加成功ID%d\n, b-id); } /** * findBookById遍历内存缓存查找图书和V4逻辑完全一致 */ struct Book* findBookById(int id) { for (int i 0; i bookCount; i) { if (library[i].id id) return library[i]; } return NULL; } /** * borrowBook借书逻辑 * 1. 内存创建借阅节点挂载链表 * 2. 插入一条借阅记录到数据库 * 3. 更新图书库存借出字段到数据库 */ 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(库存不足无法借阅\n); return; } time_t now time(NULL); // 内存链表新增节点 struct BorrowRecord* rec (struct BorrowRecord*)malloc(sizeof(struct BorrowRecord)); rec-userId userId; strcpy(rec-userName, userName); rec-borrowDate now; rec-returnDate 0; rec-next b-borrowHistory; b-borrowHistory rec; b-borrowed; // 同步数据库 db_add_record(id, userId, userName, now, 0); db_update_book(b); printf(借书成功\n); } /** * returnBook还书逻辑 * V4二进制文件仅修改内存记录V5新增一条完整归还记录存入数据库 */ void returnBook(int id, int userId) { struct Book* b findBookById(id); if (b NULL) { printf(图书不存在\n); return; } struct BorrowRecord* p b-borrowHistory; while (p ! NULL) { if (p-userId userId p-returnDate 0) { time_t now time(NULL); p-returnDate now; b-borrowed--; // 新增一条带归还时间的记录存入数据库 db_add_record(id, userId, p-userName, p-borrowDate, now); db_update_book(b); printf(还书成功\n); return; } p p-next; } printf(未找到该用户未归还记录\n); } /** * listAllBooks打印内存缓存图书列表 */ void listAllBooks() { if (bookCount 0) { printf(暂无图书\n); return; } printf(\nID\t书名\t作者\t库存/已借\n); for (int i 0; i bookCount; i) { struct Book* b library[i]; printf(%d\t%s\t%s\t%d/%d\n, b-id, b-name, b-author, b-stock, b-borrowed); } } /** * showBorrowHistory遍历内存链表打印借阅历史时间格式化逻辑不变 */ void showBorrowHistory(int id) { struct Book* b findBookById(id); if (b-borrowHistory NULL) { printf(无借阅记录\n); return; } printf(\n 借阅历史 \n); struct BorrowRecord* p b-borrowHistory; int idx 1; while (p) { char bt[64], rt[64]; strftime(bt, 64, %Y-%m-%d %H:%M, localtime(p-borrowDate)); if (p-returnDate 0) strcpy(rt, 未归还); else strftime(rt, 64, %Y-%m-%d %H:%M, localtime(p-returnDate)); printf(%d. 用户%s(ID:%d) 借:%s 还:%s\n, idx, p-userName, p-userId, bt, rt); p p-next; } } /** * freeBorrowHistory释放单向链表内存逻辑同V4 */ void freeBorrowHistory(struct BorrowRecord* head) { struct BorrowRecord* cur head; while (cur) { struct BorrowRecord* tmp cur; cur cur-next; free(tmp); } } /** * showMenuV5菜单标题更新为SQLite数据库版本 */ void showMenu() { printf(\n 图书管理系统V5(Windows简化SQLite版) \n); printf(1 添加图书 | 2 借书 | 3 还书 | 4 查询图书 | 5 全部图书 | 6 删除图书 | 7 退出\n); printf(请输入功能序号); }二、完整带注释 CMakeLists.txt适配整套 V0~V5 测试程序cmake# CMake最低版本要求 cmake_minimum_required(VERSION 3.30) # 项目名称C语言项目 project(booksPm C) # 全局编译标准配置 # 强制使用 C23 标准 set(CMAKE_C_STANDARD 23) set(CMAKE_C_STANDARD_REQUIRED ON) # 禁止标准降级 set(CMAKE_C_EXTENSIONS OFF) # Windows平台消除scanf/strcpy等函数安全警告 if (WIN32) add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif() # 全局头文件检索目录include 存放 sqlite3.h、公共头文件 include_directories(include) # SQLite依赖线程库V5版本编译必需 find_package(Threads REQUIRED) # 全局公共源码 # 所有版本共用公共工具代码 set(COMMON_SOURCES src/common.c ) # V0 基础语法版 set(SOURCE_FILES_v0 ${COMMON_SOURCES} src/v0/booksv0.c ) add_executable(v0 ${SOURCE_FILES_v0}) # V1 结构体静态数组版 set(BOOK_v1 ${COMMON_SOURCES} src/v1/booksv1.c ) # 原版无注释程序 add_executable(B_V1 ${BOOK_v1}) # 带详细注释版本程序 add_executable(B_V1.1 src/v1/booksv1-zhushi.c) # V2 动态内存malloc/realloc版 set(BOOK_v2 ${COMMON_SOURCES} src/v2/booksv2.c ) add_executable(B_V2 ${BOOK_v2}) add_executable(B_V2.1 src/v2/booksv2-zhushi.c) # V3 链表借阅历史版 set(BOOK_v3 ${COMMON_SOURCES} src/v3/booksv3.c ) add_executable(B_V3 ${BOOK_v3}) add_executable(B_V3.1 src/v3/booksv3-zhushi.c) # V4 二进制文件持久化版 set(BOOK_v4 ${COMMON_SOURCES} src/v4/booksv4.c ) add_executable(B_V4 ${BOOK_v4}) # V5 SQLite嵌入式数据库版新增核心模块 # 路径规范sqlite3.c 放置项目根目录sqlite3.h 在 include 下 set(BOOK_v5 ${COMMON_SOURCES} sqlite3.c src/v5/booksv5.c ) # 生成可执行文件 B_V5 add_executable(B_V5 ${BOOK_v5}) # 链接线程依赖库SQLite运行必需 target_link_libraries(B_V5 PRIVATE Threads::Threads) # Windows平台额外链接网络ws2_32库适配SQLite网络/IO依赖 if (WIN32) target_link_libraries(B_V5 PRIVATE ws2_32) endif() # 基础语法测试小程序 # for循环测试 add_executable(t0-for tests/t_vo/t0-for.c) # while循环测试 add_executable(t0-while tests/t_vo/t0-while.c)三、目录结构规范CMake 配套完整项目布局plaintextbooksPm/ ├── CMakeLists.txt # 上方cmake构建脚本 ├── sqlite3.c # SQLite amalgamation 单文件实现根目录 ├── include/ │ └── sqlite3.h # SQLite头文件全局include引入 ├── src/ │ ├── common.c # 全版本公共工具代码 │ ├── v0/ │ │ └── booksv0.c │ ├── v1/ │ │ ├── booksv1.c │ │ └── booksv1-zhushi.c │ ├── v2/ │ │ ├── booksv2.c │ │ └── booksv2-zhushi.c │ ├── v3/ │ │ ├── booksv3.c │ │ └── booksv3-zhushi.c │ ├── v4/ │ │ └── booksv4.c │ └── v5/ │ └── booksv5.c # 本次V5 SQLite源码 └── tests/ └── t_vo/ ├── t0-for.c └── t0-while.c四、V4→V5 CMake 配套新增改动汇总1. 代码层面新增引入sqlite3.h头文件新增整套 SQLite 数据库 CRUD 底层接口新增数据库全局句柄sqlite3* db新增建表、打开库、关闭库、增删改图书、读写借阅记录全套数据库函数存储架构从二进制文件切换为library.db关系数据库归还逻辑改为每次新增一条完整记录完整留存全部借还流水删除图书自动级联清理对应借阅记录删除后自动重载数据库刷新内存缓存2. CMake 构建脚本新增内容全局查找Threads线程库V5 强制链接新增 V5 编译目标B_V5编译时纳入根目录sqlite3.c源码Windows 系统自动追加-lws2_32链接依赖全局统一include/头文件目录sqlite3.h 无需手动拷贝到 src 目录全局统一启用 C23 标准统一关闭 Windows CRT 安全警告区分原版程序与带注释版程序后缀.1一键编译所有版本3. 编译运行优势无需手动敲长 gcc 编译命令cmake 一键构建全部版本自动处理 SQLite 线程、Windows 网络依赖不会出现链接报错分层编译V0~V5 独立可执行文件互不干扰统一 C23 标准全项目语法规范统一源码目录分层清晰公共代码抽离复用便于维护迭代

相关新闻