- 项目概述与功能需求
- 系统设计(数据结构、函数模块)
- 完整源代码
- 代码详解
- 编译与运行指南
- 项目扩展与优化建议
项目概述与功能需求
项目目标
设计一个基于命令行的、简单的图书信息管理系统,该系统使用文件来持久化存储图书数据,实现了图书信息的增加、删除、修改、查询和显示等基本功能。

(图片来源网络,侵删)
功能需求
- 录入图书信息:将新图书的信息(如ID、书名、作者、库存数量)添加到系统中。
- 删除图书信息:根据图书ID,从系统中删除指定图书。
- 修改图书信息:根据图书ID,修改图书的某一项或多项信息(如书名、作者、库存)。
- 查询图书信息:
- 按ID精确查询。
- 按书名模糊查询。
- 显示所有图书:以表格形式列出系统中所有图书的信息。
- 数据持久化:系统启动时从文件中加载数据,退出时将数据保存到文件中,确保信息不丢失。
- 退出系统:保存所有更改并退出程序。
系统设计
数据结构设计
为了存储图书信息,我们定义一个结构体 Book。
typedef struct {
int id; // 图书ID (唯一标识)
char title[100]; // 书名
char author[50]; // 作者
int quantity; // 库存数量
} Book;
为了管理所有图书,我们使用一个动态数组,数组的大小可以根据图书数量动态增长。
Book *books; // 指向图书动态数组的指针 int book_count = 0; // 当前图书数量 int capacity = 10; // 数组当前容量
函数模块设计
我们将整个系统分解为多个功能函数,每个函数负责一个特定的任务,使代码结构清晰、易于维护。
| 函数名 | 功能描述 |
|---|---|
load_data() |
程序启动时,从文件 books.dat 加载数据到内存。 |
save_data() |
程序退出时,将内存中的图书数据保存到文件 books.dat。 |
show_menu() |
显示主菜单界面,接收用户输入的选项。 |
add_book() |
实现增加图书的功能。 |
delete_book() |
实现删除图书的功能。 |
modify_book() |
实现修改图书的功能。 |
find_book() |
实现查询图书的功能,包含按ID查询和按书名查询。 |
display_all_books() |
实现显示所有图书的功能。 |
main() |
程序入口,负责调用其他函数,控制程序流程。 |
完整源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h> // 用于 getch(),Windows平台下按任意键继续
// 定义图书结构体
typedef struct {
int id;
char title[100];
char author[50];
int quantity;
} Book;
// 全局变量
Book *books = NULL; // 图书数组指针
int book_count = 0; // 当前图书数量
int capacity = 10; // 数组初始容量
// 函数声明
void load_data();
void save_data();
void show_menu();
void add_book();
void delete_book();
void modify_book();
void find_book();
void display_all_books();
void pause_and_clear();
int main() {
load_data(); // 启动时加载数据
int choice;
do {
show_menu();
printf("请输入您的选择: ");
scanf("%d", &choice);
switch (choice) {
case 1:
add_book();
break;
case 2:
delete_book();
break;
case 3:
modify_book();
break;
case 4:
find_book();
break;
case 5:
display_all_books();
break;
case 0:
printf("感谢使用,正在退出...\n");
save_data(); // 退出前保存数据
free(books); // 释放内存
break;
default:
printf("无效的输入,请重新输入!\n");
break;
}
pause_and_clear();
} while (choice != 0);
return 0;
}
// 从文件加载数据
void load_data() {
FILE *fp = fopen("books.dat", "rb");
if (fp == NULL) {
// 文件不存在,是第一次运行,无需加载
books = (Book *)malloc(capacity * sizeof(Book));
return;
}
// 先读取图书数量
fread(&book_count, sizeof(int), 1, fp);
// 根据数量重新分配内存
capacity = book_count > 0 ? book_count : 10;
books = (Book *)malloc(capacity * sizeof(Book));
// 读取所有图书数据
fread(books, sizeof(Book), book_count, fp);
fclose(fp);
printf("成功加载 %d 本图书数据,\n", book_count);
}
// 保存数据到文件
void save_data() {
FILE *fp = fopen("books.dat", "wb");
if (fp == NULL) {
printf("错误:无法打开文件进行保存!\n");
return;
}
// 先写入图书数量
fwrite(&book_count, sizeof(int), 1, fp);
// 再写入所有图书数据
fwrite(books, sizeof(Book), book_count, fp);
fclose(fp);
printf("数据已成功保存到文件,\n");
}
// 显示主菜单
void show_menu() {
printf("\n========== 图书管理系统 ==========\n");
printf(" 1. 录入图书信息\n");
printf(" 2. 删除图书信息\n");
printf(" 3. 修改图书信息\n");
printf(" 4. 查询图书信息\n");
printf(" 5. 显示所有图书\n");
printf(" 0. 退出系统\n");
printf("=================================\n");
}
// 添加图书
void add_book() {
Book new_book;
printf("请输入图书ID: ");
scanf("%d", &new_book.id);
// 检查ID是否已存在
for (int i = 0; i < book_count; i++) {
if (books[i].id == new_book.id) {
printf("错误:该ID的图书已存在!\n");
return;
}
}
printf("请输入书名: ");
scanf("%s", new_book.title); // 注意:这里用%s,书名中不能有空格,若需支持空格,可用fgets并处理换行符。
printf("请输入作者: ");
scanf("%s", new_book.author);
printf("请输入库存数量: ");
scanf("%d", &new_book.quantity);
// 检查是否需要扩容
if (book_count >= capacity) {
capacity *= 2;
books = (Book *)realloc(books, capacity * sizeof(Book));
if (books == NULL) {
printf("内存分配失败!\n");
exit(1);
}
}
books[book_count++] = new_book;
printf("图书添加成功!\n");
}
// 删除图书
void delete_book() {
if (book_count == 0) {
printf("系统中没有图书,无法删除!\n");
return;
}
int id;
printf("请输入要删除的图书ID: ");
scanf("%d", &id);
int found = 0;
for (int i = 0; i < book_count; i++) {
if (books[i].id == id) {
found = 1;
// 将后面的元素前移
for (int j = i; j < book_count - 1; j++) {
books[j] = books[j + 1];
}
book_count--;
printf("图书删除成功!\n");
break;
}
}
if (!found) {
printf("未找到ID为 %d 的图书!\n", id);
}
}
// 修改图书
void modify_book() {
if (book_count == 0) {
printf("系统中没有图书,无法修改!\n");
return;
}
int id;
printf("请输入要修改的图书ID: ");
scanf("%d", &id);
int found = 0;
for (int i = 0; i < book_count; i++) {
if (books[i].id == id) {
found = 1;
printf("找到图书: %s, %s, %d\n", books[i].title, books[i].author, books[i].quantity);
printf("请输入新的书名 (直接回车保持不变): ");
char new_title[100];
scanf("%s", new_title); // 简化处理,实际应用中应更健壮
if (strlen(new_title) > 0) strcpy(books[i].title, new_title);
printf("请输入新的作者 (直接回车保持不变): ");
char new_author[50];
scanf("%s", new_author);
if (strlen(new_author) > 0) strcpy(books[i].author, new_author);
printf("请输入新的库存数量 (输入-1保持不变): ");
int new_quantity;
scanf("%d", &new_quantity);
if (new_quantity != -1) books[i].quantity = new_quantity;
printf("图书信息修改成功!\n");
break;
}
}
if (!found) {
printf("未找到ID为 %d 的图书!\n", id);
}
}
// 查询图书
void find_book() {
if (book_count == 0) {
printf("系统中没有图书,无法查询!\n");
return;
}
int choice;
printf("请选择查询方式:\n");
printf(" 1. 按ID查询\n");
printf(" 2. 按书名查询\n");
printf("请输入您的选择: ");
scanf("%d", &choice);
if (choice == 1) {
int id;
printf("请输入要查询的图书ID: ");
scanf("%d", &id);
int found = 0;
for (int i = 0; i < book_count; i++) {
if (books[i].id == id) {
printf("查询结果:\n");
printf("ID: %d, 书名: %s, 作者: %s, 库存: %d\n",
books[i].id, books[i].title, books[i].author, books[i].quantity);
found = 1;
break;
}
}
if (!found) printf("未找到ID为 %d 的图书!\n", id);
} else if (choice == 2) {
char keyword[100];
printf("请输入要查询的书名关键词: ");
scanf("%s", keyword);
int found = 0;
printf("查询结果:\n");
for (int i = 0; i < book_count; i++) {
if (strstr(books[i].title, keyword) != NULL) {
printf("ID: %d, 书名: %s, 作者: %s, 库存: %d\n",
books[i].id, books[i].title, books[i].author, books[i].quantity);
found = 1;
}
}
if (!found) printf("未找到包含关键词 \"%s\" 的图书!\n", keyword);
} else {
printf("无效的选择!\n");
}
}
// 显示所有图书
void display_all_books() {
if (book_count == 0) {
printf("系统中没有图书!\n");
return;
}
printf("\n%-10s %-30s %-20s %-10s\n", "ID", "书名", "作者", "库存");
printf("------------------------------------------------------------\n");
for (int i = 0; i < book_count; i++) {
printf("%-10d %-30s %-20s %-10d\n",
books[i].id, books[i].title, books[i].author, books[i].quantity);
}
}
// 暂停并清屏 (Windows平台)
void pause_and_clear() {
printf("\n按任意键继续...");
getch(); // 等待用户按键
system("cls"); // 清屏
}
代码详解
main()函数:程序的入口,它首先调用load_data()加载数据,然后进入一个do-while循环,不断显示菜单并处理用户输入,直到用户选择退出(0),退出时会调用save_data()保存数据并释放malloc分配的内存。load_data()和save_data():这两个函数是实现数据持久化的核心。load_data以二进制读取模式 ("rb") 打开文件,先读入一个int类型的book_count,再根据这个数量读取Book结构体数组。save_data以二进制写入模式 ("wb") 打开文件,先写入book_count,再写入整个数组,使用二进制文件读写比文本文件读写更高效,且能直接保存结构体数据。- 动态内存管理:使用
malloc和realloc来管理books数组,当图书数量达到数组容量上限时,add_book函数会调用realloc将数组容量翻倍,这是一种常见的动态扩容策略。 find_book()函数:提供了两种查询方式,按ID查询是精确匹配,按书名查询使用了 C 标准库函数strstr(),它可以在一个字符串中查找另一个子字符串是否存在,从而实现了模糊查询。pause_and_clear()函数:这是一个用户体验函数。getch()会等待用户按下任意一个键后才继续执行,system("cls")会清空控制台屏幕,使得每次操作后的界面更整洁。注意:system("cls")和getch()是平台相关的(Windows),在 Linux 或 macOS 上,getch()可能需要用#include <ncurses.h>库替代,system("cls")应改为system("clear")。
编译与运行指南
- 保存代码:将上面的完整源代码复制并粘贴到一个文本编辑器中,保存为
library.c。 - 编译:
- 如果你使用的是 GCC 编译器(在 Linux、macOS 或 Windows 的 MinGW/Cygwin 环境下),打开终端或命令提示符,进入
library.c所在的目录,然后运行:gcc library.c -o library
- 如果你使用的是 GCC 编译器(在 Linux、macOS 或 Windows 的 MinGW/Cygwin 环境下),打开终端或命令提示符,进入
- 运行:
- 在 Windows 上:
library.exe
- 在 Linux 或 macOS 上:
./library
- 在 Windows 上:
- 使用:
- 程序启动后,会先尝试加载
books.dat文件,如果是第一次运行,会提示“成功加载 0 本图书数据。” - 然后会显示主菜单,你可以根据提示输入数字(1-5, 0)来执行相应操作。
- 当你选择退出(0)时,程序会将当前内存中的所有图书信息保存到
books.dat文件中,下次启动时,这些数据会被自动加载。
- 程序启动后,会先尝试加载
项目扩展与优化建议
如果你已经完成了基本功能,并希望进一步提升这个项目,可以考虑以下扩展方向:

(图片来源网络,侵删)
-
更健壮的输入处理:
- 当前代码使用
scanf("%s", ...)来读取书名和作者,这无法处理包含空格的字符串,可以改用fgets(),并注意清除输入缓冲区中的残留换行符。 - 对数字输入(如ID、库存)进行有效性检查,防止用户输入非数字导致程序崩溃。
- 当前代码使用
-
更友好的用户界面:
- 使用颜色来突出显示菜单选项或错误信息(在Windows上可以使用
SetConsoleTextAttribute)。 - 在显示表格时,自动计算并调整列宽,以适应不同长度的书名和作者。
- 使用颜色来突出显示菜单选项或错误信息(在Windows上可以使用
-
增加新功能:
- 借阅与归还:为每本图书增加一个
is_borrowed状态字段,并实现借书和还书功能,借书时检查库存,还书时增加库存。 - 用户管理:增加一个
User结构体,实现简单的用户登录功能,区分管理员和普通用户(普通用户只能查询和借书)。 - 排序功能:增加按书名、作者、入库时间等对图书列表进行排序的功能。
- 数据备份与恢复:增加将数据备份到另一个文件,以及从备份文件恢复数据的功能。
- 借阅与归还:为每本图书增加一个
-
代码结构优化:
(图片来源网络,侵删)- 将每个功能模块(如增、删、改、查)封装到单独的
.c和.h文件中,形成多文件项目,这对于大型项目来说是更好的实践。
- 将每个功能模块(如增、删、改、查)封装到单独的
这个项目非常适合作为C语言课程设计,它涵盖了结构体、指针、动态内存分配、文件I/O、函数封装等核心知识点,并且具有良好的扩展性,祝你项目顺利!
