C语言图书管理系统如何实现核心功能?

99ANYc3cd6
预计阅读时长 41 分钟
位置: 首页 C语言 正文
  1. 项目概述与功能需求
  2. 系统设计(数据结构、函数模块)
  3. 完整源代码
  4. 代码详解
  5. 编译与运行指南
  6. 项目扩展与优化建议

项目概述与功能需求

项目目标

设计一个基于命令行的、简单的图书信息管理系统,该系统使用文件来持久化存储图书数据,实现了图书信息的增加、删除、修改、查询和显示等基本功能。

c语言课程设计 图书管理系统
(图片来源网络,侵删)

功能需求

  1. 录入图书信息:将新图书的信息(如ID、书名、作者、库存数量)添加到系统中。
  2. 删除图书信息:根据图书ID,从系统中删除指定图书。
  3. 修改图书信息:根据图书ID,修改图书的某一项或多项信息(如书名、作者、库存)。
  4. 查询图书信息
    • 按ID精确查询。
    • 按书名模糊查询。
  5. 显示所有图书:以表格形式列出系统中所有图书的信息。
  6. 数据持久化:系统启动时从文件中加载数据,退出时将数据保存到文件中,确保信息不丢失。
  7. 退出系统:保存所有更改并退出程序。

系统设计

数据结构设计

为了存储图书信息,我们定义一个结构体 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,再写入整个数组,使用二进制文件读写比文本文件读写更高效,且能直接保存结构体数据。
  • 动态内存管理:使用 mallocrealloc 来管理 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")

编译与运行指南

  1. 保存代码:将上面的完整源代码复制并粘贴到一个文本编辑器中,保存为 library.c
  2. 编译
    • 如果你使用的是 GCC 编译器(在 Linux、macOS 或 Windows 的 MinGW/Cygwin 环境下),打开终端或命令提示符,进入 library.c 所在的目录,然后运行:
      gcc library.c -o library
  3. 运行
    • 在 Windows 上:
      library.exe
    • 在 Linux 或 macOS 上:
      ./library
  4. 使用
    • 程序启动后,会先尝试加载 books.dat 文件,如果是第一次运行,会提示“成功加载 0 本图书数据。”
    • 然后会显示主菜单,你可以根据提示输入数字(1-5, 0)来执行相应操作。
    • 当你选择退出(0)时,程序会将当前内存中的所有图书信息保存到 books.dat 文件中,下次启动时,这些数据会被自动加载。

项目扩展与优化建议

如果你已经完成了基本功能,并希望进一步提升这个项目,可以考虑以下扩展方向:

c语言课程设计 图书管理系统
(图片来源网络,侵删)
  1. 更健壮的输入处理

    • 当前代码使用 scanf("%s", ...) 来读取书名和作者,这无法处理包含空格的字符串,可以改用 fgets(),并注意清除输入缓冲区中的残留换行符。
    • 对数字输入(如ID、库存)进行有效性检查,防止用户输入非数字导致程序崩溃。
  2. 更友好的用户界面

    • 使用颜色来突出显示菜单选项或错误信息(在Windows上可以使用 SetConsoleTextAttribute)。
    • 在显示表格时,自动计算并调整列宽,以适应不同长度的书名和作者。
  3. 增加新功能

    • 借阅与归还:为每本图书增加一个 is_borrowed 状态字段,并实现借书和还书功能,借书时检查库存,还书时增加库存。
    • 用户管理:增加一个 User 结构体,实现简单的用户登录功能,区分管理员和普通用户(普通用户只能查询和借书)。
    • 排序功能:增加按书名、作者、入库时间等对图书列表进行排序的功能。
    • 数据备份与恢复:增加将数据备份到另一个文件,以及从备份文件恢复数据的功能。
  4. 代码结构优化

    c语言课程设计 图书管理系统
    (图片来源网络,侵删)
    • 将每个功能模块(如增、删、改、查)封装到单独的 .c.h 文件中,形成多文件项目,这对于大型项目来说是更好的实践。

这个项目非常适合作为C语言课程设计,它涵盖了结构体、指针、动态内存分配、文件I/O、函数封装等核心知识点,并且具有良好的扩展性,祝你项目顺利!

-- 展开阅读全文 --
头像
C语言能处理哪些文件类型?
« 上一篇 02-14
高端服装企业织梦模板如何助力品牌升级?
下一篇 » 02-14
取消
微信二维码
支付宝二维码

目录[+]