C: 综合项目:图书管理系统

造房子不能只学搬砖——现在该把所有技能组合起来,从地基到屋顶完整地盖一栋楼了。

1. 需求分析

我们要开发一个命令行图书管理系统,功能如下:

  1. 添加图书(书名、作者、ISBN、价格)
  2. 删除图书(按ISBN)
  3. 修改图书信息
  4. 查询图书(按书名或ISBN)
  5. 显示所有图书
  6. 数据保存到文件(持久化)
  7. 从文件加载数据

2. 数据结构设计

图书信息用一个结构体表示,所有图书用动态数组管理:

C
typedef struct {
    char isbn[14];
    char title[128];
    char author[64];
    double price;
} Book;

typedef struct {
    Book *books;
    int count;
    int capacity;
} Library;

Library 管理一个动态数组:books 是数据指针,count 是当前数量,capacity 是已分配容量。当 count 达到 capacity 时自动扩容。


3. 项目文件组织

C
bookmanager/
├── Makefile
├── main.c
├── library.h
├── library.c
├── storage.h
└── storage.c

4. library.h

C
#ifndef LIBRARY_H
#define LIBRARY_H

typedef struct {
    char isbn[14];
    char title[128];
    char author[64];
    double price;
} Book;

typedef struct {
    Book *books;
    int count;
    int capacity;
} Library;

void library_init(Library *lib);
void library_free(Library *lib);
int library_add(Library *lib, const Book *book);
int library_remove(Library *lib, const char *isbn);
Book *library_find_by_isbn(Library *lib, const char *isbn);
void library_find_by_title(Library *lib, const char *keyword,
                           Book **results, int *result_count);
int library_update(Library *lib, const char *isbn, const Book *new_info);
void library_list(const Library *lib);

#endif

5. library.c

C
#include "library.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int ensure_capacity(Library *lib) {
    if (lib->count < lib->capacity) {
        return 1;
    }
    int new_cap = lib->capacity == 0 ? 4 : lib->capacity * 2;
    Book *new_books = realloc(lib->books, new_cap * sizeof(Book));
    if (new_books == NULL) {
        return 0;
    }
    lib->books = new_books;
    lib->capacity = new_cap;
    return 1;
}

void library_init(Library *lib) {
    lib->books = NULL;
    lib->count = 0;
    lib->capacity = 0;
}

void library_free(Library *lib) {
    free(lib->books);
    lib->books = NULL;
    lib->count = 0;
    lib->capacity = 0;
}

int library_add(Library *lib, const Book *book) {
    if (!ensure_capacity(lib)) {
        return 0;
    }
    lib->books[lib->count] = *book;
    lib->count++;
    return 1;
}

int library_remove(Library *lib, const char *isbn) {
    for (int i = 0; i < lib->count; i++) {
        if (strcmp(lib->books[i].isbn, isbn) == 0) {
            for (int j = i; j < lib->count - 1; j++) {
                lib->books[j] = lib->books[j + 1];
            }
            lib->count--;
            return 1;
        }
    }
    return 0;
}

Book *library_find_by_isbn(Library *lib, const char *isbn) {
    for (int i = 0; i < lib->count; i++) {
        if (strcmp(lib->books[i].isbn, isbn) == 0) {
            return &lib->books[i];
        }
    }
    return NULL;
}

void library_find_by_title(Library *lib, const char *keyword,
                           Book **results, int *result_count) {
    *result_count = 0;
    for (int i = 0; i < lib->count; i++) {
        if (strstr(lib->books[i].title, keyword) != NULL) {
            results[*result_count] = &lib->books[i];
            (*result_count)++;
        }
    }
}

int library_update(Library *lib, const char *isbn, const Book *new_info) {
    Book *existing = library_find_by_isbn(lib, isbn);
    if (existing == NULL) {
        return 0;
    }
    *existing = *new_info;
    return 1;
}

void library_list(const Library *lib) {
    if (lib->count == 0) {
        printf("图书馆中没有图书。\n");
        return;
    }
    printf("%-14s %-30s %-20s %-8s\n", "ISBN", "书名", "作者", "价格");
    printf("--------------------------------------------------------------\n");
    for (int i = 0; i < lib->count; i++) {
        printf("%-14s %-30s %-20s %-8.2f\n",
               lib->books[i].isbn,
               lib->books[i].title,
               lib->books[i].author,
               lib->books[i].price);
    }
    printf("共 %d 本图书\n", lib->count);
}
💡 ensure_capacity 是内部函数,用 static 修饰限制作用域在本文件内。动态数组扩容策略是翻倍——这是标准做法,保证均摊 O(1) 的插入效率。


6. storage.h

C
#ifndef STORAGE_H
#define STORAGE_H

#include "library.h"

int storage_save(const Library *lib, const char *filename);
int storage_load(Library *lib, const char *filename);

#endif

7. storage.c

C
#include "storage.h"
#include <stdio.h>
#include <string.h>

int storage_save(const Library *lib, const char *filename) {
    FILE *fp = fopen(filename, "w");
    if (fp == NULL) {
        return 0;
    }
    for (int i = 0; i < lib->count; i++) {
        fprintf(fp, "%s|%s|%s|%.2f\n",
                lib->books[i].isbn,
                lib->books[i].title,
                lib->books[i].author,
                lib->books[i].price);
    }
    fclose(fp);
    return 1;
}

int storage_load(Library *lib, const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        return 1;
    }
    Book book;
    while (fscanf(fp, "%13[^|]|%127[^|]|%63[^|]|%lf\n",
                  book.isbn, book.title, book.author, &book.price) == 4) {
        if (!library_add(lib, &book)) {
            fclose(fp);
            return 0;
        }
    }
    fclose(fp);
    return 1;
}
⚠️ fscanf%[^|] 格式读取直到 | 的字符串。%13[^|] 限制最多读 13 个字符,防止溢出。这是格式化读写的安全写法。


8. main.c

C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "library.h"
#include "storage.h"

#define DATA_FILE "library.dat"

static void input_string(const char *prompt, char *buf, int size) {
    printf("%s", prompt);
    fflush(stdout);
    if (fgets(buf, size, stdin) == NULL) {
        buf[0] = '\0';
        return;
    }
    buf[strcspn(buf, "\n")] = '\0';
}

static void cmd_add(Library *lib) {
    Book book;
    input_string("请输入ISBN: ", book.isbn, sizeof(book.isbn));
    input_string("请输入书名: ", book.title, sizeof(book.title));
    input_string("请输入作者: ", book.author, sizeof(book.author));

    char price_str[32];
    input_string("请输入价格: ", price_str, sizeof(price_str));
    book.price = atof(price_str);

    if (library_find_by_isbn(lib, book.isbn) != NULL) {
        printf("错误:ISBN %s 已存在\n", book.isbn);
        return;
    }

    if (library_add(lib, &book)) {
        printf("添加成功!\n");
    } else {
        printf("添加失败:内存不足\n");
    }
}

static void cmd_remove(Library *lib) {
    char isbn[14];
    input_string("请输入要删除的ISBN: ", isbn, sizeof(isbn));

    if (library_remove(lib, isbn)) {
        printf("删除成功!\n");
    } else {
        printf("未找到该ISBN的图书\n");
    }
}

static void cmd_find(Library *lib) {
    printf("1. 按ISBN查找  2. 按书名查找\n");
    char choice[8];
    input_string("请选择: ", choice, sizeof(choice));

    if (strcmp(choice, "1") == 0) {
        char isbn[14];
        input_string("请输入ISBN: ", isbn, sizeof(isbn));
        Book *book = library_find_by_isbn(lib, isbn);
        if (book != NULL) {
            printf("ISBN:  %s\n", book->isbn);
            printf("书名:  %s\n", book->title);
            printf("作者:  %s\n", book->author);
            printf("价格:  %.2f\n", book->price);
        } else {
            printf("未找到\n");
        }
    } else if (strcmp(choice, "2") == 0) {
        char keyword[128];
        input_string("请输入关键词: ", keyword, sizeof(keyword));
        Book *results[100];
        int result_count = 0;
        library_find_by_title(lib, keyword, results, &result_count);
        if (result_count == 0) {
            printf("未找到包含\"%s\"的图书\n", keyword);
        } else {
            for (int i = 0; i < result_count; i++) {
                printf("%-14s %-30s %.2f\n",
                       results[i]->isbn, results[i]->title, results[i]->price);
            }
            printf("找到 %d 本\n", result_count);
        }
    }
}

static void cmd_update(Library *lib) {
    char isbn[14];
    input_string("请输入要修改的ISBN: ", isbn, sizeof(isbn));

    Book *existing = library_find_by_isbn(lib, isbn);
    if (existing == NULL) {
        printf("未找到该ISBN的图书\n");
        return;
    }

    Book new_info = *existing;
    printf("当前书名: %s(直接回车保持不变)\n", existing->title);
    input_string("新书名: ", new_info.title, sizeof(new_info.title));
    if (new_info.title[0] == '\0') {
        strcpy(new_info.title, existing->title);
    }

    printf("当前作者: %s(直接回车保持不变)\n", existing->author);
    input_string("新作者: ", new_info.author, sizeof(new_info.author));
    if (new_info.author[0] == '\0') {
        strcpy(new_info.author, existing->author);
    }

    char price_str[32];
    printf("当前价格: %.2f(直接回车保持不变)\n", existing->price);
    input_string("新价格: ", price_str, sizeof(price_str));
    if (price_str[0] != '\0') {
        new_info.price = atof(price_str);
    } else {
        new_info.price = existing->price;
    }

    strcpy(new_info.isbn, isbn);
    if (library_update(lib, isbn, &new_info)) {
        printf("修改成功!\n");
    }
}

static void show_menu(void) {
    printf("\n===== 图书管理系统 =====\n");
    printf("1. 添加图书\n");
    printf("2. 删除图书\n");
    printf("3. 查询图书\n");
    printf("4. 修改图书\n");
    printf("5. 显示所有图书\n");
    printf("6. 保存数据\n");
    printf("0. 退出\n");
    printf("========================\n");
}

int main(void) {
    Library lib;
    library_init(&lib);

    if (!storage_load(&lib, DATA_FILE)) {
        printf("警告:加载数据失败,从空库开始\n");
    }

    char choice[8];
    while (1) {
        show_menu();
        input_string("请选择操作: ", choice, sizeof(choice));

        if (strcmp(choice, "1") == 0) {
            cmd_add(&lib);
        } else if (strcmp(choice, "2") == 0) {
            cmd_remove(&lib);
        } else if (strcmp(choice, "3") == 0) {
            cmd_find(&lib);
        } else if (strcmp(choice, "4") == 0) {
            cmd_update(&lib);
        } else if (strcmp(choice, "5") == 0) {
            library_list(&lib);
        } else if (strcmp(choice, "6") == 0) {
            if (storage_save(&lib, DATA_FILE)) {
                printf("保存成功!\n");
            } else {
                printf("保存失败!\n");
            }
        } else if (strcmp(choice, "0") == 0) {
            printf("是否保存数据?(y/n): ");
            char confirm[8];
            if (fgets(confirm, sizeof(confirm), stdin) != NULL) {
                if (confirm[0] == 'y' || confirm[0] == 'Y') {
                    storage_save(&lib, DATA_FILE);
                    printf("已保存\n");
                }
            }
            break;
        } else {
            printf("无效选择\n");
        }
    }

    library_free(&lib);
    return 0;
}

9. Makefile

MAKEFILE
CC = gcc
CFLAGS = -Wall -Wextra -std=c99 -O2
SRCS = main.c library.c storage.c
OBJS = $(SRCS:.c=.o)
TARGET = bookmanager

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

main.o: main.c library.h storage.h
library.o: library.c library.h
storage.o: storage.c storage.h library.h

clean:
	rm -f $(OBJS) $(TARGET)

.PHONY: clean

编译和运行:

BASH
make
./bookmanager

10. 运行效果

TEXT 📖 仅展示
===== 图书管理系统 =====
1. 添加图书
2. 删除图书
3. 查询图书
4. 修改图书
5. 显示所有图书
6. 保存数据
0. 退出
========================
请选择操作: 1
请输入ISBN: 9787115279460
请输入书名: C Primer Plus
请输入作者: Stephen Prata
请输入价格: 89.00
添加成功!

请选择操作: 5
ISBN           书名                           作者                 价格
--------------------------------------------------------------
9787115279460  C Primer Plus                  Stephen Prata        89.00
共 1 本图书

11. 项目要点解析

(1) 动态数组扩容

ensure_capacity 采用翻倍扩容策略。每次 count 达到 capacity 时,容量翻倍。这样 n 次插入的总扩容次数是 O(log n),每次插入均摊 O(1)。

(2) 文件格式选择

存储格式用 | 分隔的文本格式,而不是二进制格式。好处是:

(3) 模块划分

librarystorage 分离——核心逻辑不依赖具体存储方式。将来换成数据库存储,只需修改 storage.c,核心代码不需要改。

(4) 输入安全

input_stringfgets 读取输入,自动限制长度,再用 strcspn 去掉换行符。这是比 scanf 更安全的输入方式。

▶ 示例

C
#include <stdio.h>
#include <string.h>

typedef struct {
    char title[64];
    char author[32];
    int id;
} Book;

int main(void) {
    Book b = {"C Primer Plus", "Stephen Prata", 1};
    printf("编号:%d 书名:%s 作者:%s\n", b.id, b.title, b.author);
    return 0;
}
▶ 试一试
TEXT 📖 仅展示
编号:1 书名:C Primer Plus 作者:Stephen Prata

❓ 常见问题

Q 为什么退出时不自动保存?
A 用户可能做了误操作,退出时询问是否保存给用户一个反悔的机会。自动保存可能覆盖掉之前的数据。
Q 动态数组删除元素为什么要移动后面的元素?
A 因为数组在内存中连续存储,删除中间的元素后必须把后面的元素前移填补空隙,否则查找和遍历会出问题。
Q 这个项目能支持多少本图书?
A 理论上受内存限制。每本图书约 210 字节,1GB 内存可存约 500 万本。实际瓶颈在搜索效率——线性查找 O(n) 在数据量大时会很慢,应改用哈希表或二叉搜索树。
Q Makefile 中 %.o 那行是什么意思?
A 这是模式规则,表示所有 .c 文件用同样的命令编译成 .o 文件。$< 是第一个依赖(.c文件),$@ 是目标(.o文件)。

📖 小节

📝 作业

  1. 为图书管理系统增加"按作者查找"功能
  2. 增加排序功能,可以按书名、价格或ISBN排序显示
  3. 改进存储模块,改用二进制文件格式存储,对比文本格式的优劣
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏