综合项目实战

通过前期的学习,我们已经掌握了C语言的基本语法、指针、结构体、文件操作、动态内存管理等核心知识。 本章将通过一个完整的图书管理系统项目,将这些知识融会贯通,体验从设计到实现的完整流程。 项目具备添加、删除、修改、查询、显示、排序、保存到文件、从文件加载等实用功能,代码模块化、注释清晰。

📋 项目需求分析

设计一个控制台图书管理系统,满足以下功能:

  • 图书信息包括:ID(唯一)、书名作者价格库存数量
  • 支持添加、删除、修改图书信息。
  • 支持按ID或书名查询图书。
  • 支持显示所有图书列表。
  • 支持按价格排序(升序/降序)。
  • 数据持久化:程序启动时从二进制文件加载数据,退出时自动保存。
  • 使用动态数组存储图书,支持动态扩容。

📁 项目结构设计

book_management/
├── book.h // 图书结构体定义、函数声明
├── book.c // 功能实现
├── main.c // 主菜单和程序入口
├── books.dat // 数据文件(运行时生成)
└── Makefile // 编译脚本

采用模块化设计,将数据结构定义和操作函数封装在 book.hbook.c 中,main.c 负责用户交互。

💻 代码实现

🔹 book.h - 头文件

#ifndef BOOK_H
#define BOOK_H

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

#define INIT_CAPACITY 2      // 初始容量
#define FILENAME "books.dat" // 数据文件

// 图书结构体
typedef struct {
    int id;
    char title[100];
    char author[50];
    double price;
    int stock;
} Book;

// 动态数组管理结构
typedef struct {
    Book *books;      // 指向动态数组的指针
    int count;        // 当前图书数量
    int capacity;     // 当前容量
} BookArray;

// 函数声明
void initBookArray(BookArray *arr);
void freeBookArray(BookArray *arr);
int addBook(BookArray *arr, Book *book);
int deleteBookById(BookArray *arr, int id);
int updateBook(BookArray *arr, int id, Book *newInfo);
Book* findBookById(BookArray *arr, int id);
void findBooksByTitle(BookArray *arr, const char *title, BookArray *result);
void displayAllBooks(BookArray *arr);
void sortBooksByPrice(BookArray *arr, int ascending);
int loadFromFile(BookArray *arr, const char *filename);
int saveToFile(BookArray *arr, const char *filename);

#endif

🔹 book.c - 功能实现

#include "book.h"

// 初始化动态数组
void initBookArray(BookArray *arr) {
    arr->books = (Book*)malloc(INIT_CAPACITY * sizeof(Book));
    if (arr->books == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }
    arr->count = 0;
    arr->capacity = INIT_CAPACITY;
}

// 释放动态数组内存
void freeBookArray(BookArray *arr) {
    if (arr->books != NULL) {
        free(arr->books);
        arr->books = NULL;
    }
    arr->count = 0;
    arr->capacity = 0;
}

// 扩容(内部函数)
static void expandCapacity(BookArray *arr) {
    int newCapacity = arr->capacity * 2;
    Book *newBooks = (Book*)realloc(arr->books, newCapacity * sizeof(Book));
    if (newBooks == NULL) {
        printf("扩容失败!\n");
        return;
    }
    arr->books = newBooks;
    arr->capacity = newCapacity;
    printf("容量已扩展至 %d\n", newCapacity);
}

// 添加图书
int addBook(BookArray *arr, Book *book) {
    // 检查ID是否已存在
    if (findBookById(arr, book->id) != NULL) {
        printf("图书ID %d 已存在!\n", book->id);
        return -1;
    }
    // 若容量不足则扩容
    if (arr->count >= arr->capacity) {
        expandCapacity(arr);
    }
    arr->books[arr->count] = *book;
    arr->count++;
    return 0;
}

// 按ID删除图书
int deleteBookById(BookArray *arr, int id) {
    for (int i = 0; i < arr->count; i++) {
        if (arr->books[i].id == id) {
            // 将后面的元素前移
            for (int j = i; j < arr->count - 1; j++) {
                arr->books[j] = arr->books[j + 1];
            }
            arr->count--;
            return 0;
        }
    }
    return -1; // 未找到
}

// 修改图书信息
int updateBook(BookArray *arr, int id, Book *newInfo) {
    Book *book = findBookById(arr, id);
    if (book == NULL) return -1;
    // 保留原ID,修改其他字段
    newInfo->id = id;
    *book = *newInfo;
    return 0;
}

// 按ID查找图书,返回指针
Book* findBookById(BookArray *arr, int id) {
    for (int i = 0; i < arr->count; i++) {
        if (arr->books[i].id == id) {
            return &arr->books[i];
        }
    }
    return NULL;
}

// 按书名查找(支持模糊匹配,结果存入 result 数组)
void findBooksByTitle(BookArray *arr, const char *title, BookArray *result) {
    initBookArray(result);
    for (int i = 0; i < arr->count; i++) {
        if (strstr(arr->books[i].title, title) != NULL) {
            addBook(result, &arr->books[i]);
        }
    }
}

// 显示所有图书
void displayAllBooks(BookArray *arr) {
    if (arr->count == 0) {
        printf("暂无图书数据。\n");
        return;
    }
    printf("\n%-5s %-30s %-20s %-8s %-5s\n", "ID", "书名", "作者", "价格", "库存");
    printf("------------------------------------------------------------\n");
    for (int i = 0; i < arr->count; i++) {
        Book *b = &arr->books[i];
        printf("%-5d %-30s %-20s %-8.2f %-5d\n", b->id, b->title, b->author, b->price, b->stock);
    }
    printf("------------------------------------------------------------\n");
    printf("共 %d 本图书\n", arr->count);
}

// 按价格排序(升序=1,降序=0)
static int comparePriceAsc(const void *a, const void *b) {
    double diff = ((Book*)a)->price - ((Book*)b)->price;
    if (diff > 0) return 1;
    if (diff < 0) return -1;
    return 0;
}
static int comparePriceDesc(const void *a, const void *b) {
    return -comparePriceAsc(a, b);
}
void sortBooksByPrice(BookArray *arr, int ascending) {
    if (ascending)
        qsort(arr->books, arr->count, sizeof(Book), comparePriceAsc);
    else
        qsort(arr->books, arr->count, sizeof(Book), comparePriceDesc);
    printf("已按价格%s排序\n", ascending ? "升序" : "降序");
}

// 从二进制文件加载数据
int loadFromFile(BookArray *arr, const char *filename) {
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL) {
        printf("数据文件不存在,将创建新文件。\n");
        return 0;
    }
    // 读取文件中的图书数量
    int count;
    if (fread(&count, sizeof(int), 1, fp) != 1) {
        fclose(fp);
        return 0;
    }
    // 确保容量足够
    while (arr->capacity < count) {
        expandCapacity(arr);
    }
    arr->count = count;
    fread(arr->books, sizeof(Book), count, fp);
    fclose(fp);
    printf("成功加载 %d 本图书。\n", count);
    return count;
}

// 保存数据到二进制文件
int saveToFile(BookArray *arr, const char *filename) {
    FILE *fp = fopen(filename, "wb");
    if (fp == NULL) {
        perror("保存文件失败");
        return -1;
    }
    // 先写入图书数量
    fwrite(&arr->count, sizeof(int), 1, fp);
    // 写入图书数据
    fwrite(arr->books, sizeof(Book), arr->count, fp);
    fclose(fp);
    printf("成功保存 %d 本图书到文件。\n", arr->count);
    return 0;
}

🔹 main.c - 主程序与用户交互

#include "book.h"

void printMenu() {
    printf("\n========== 图书管理系统 ==========\n");
    printf("1. 添加图书\n");
    printf("2. 删除图书\n");
    printf("3. 修改图书信息\n");
    printf("4. 按ID查找图书\n");
    printf("5. 按书名查找图书\n");
    printf("6. 显示所有图书\n");
    printf("7. 按价格排序\n");
    printf("8. 保存并退出\n");
    printf("==================================\n");
    printf("请选择:");
}

int main() {
    BookArray library;
    initBookArray(&library);
    loadFromFile(&library, FILENAME);

    int choice;
    while (1) {
        printMenu();
        scanf("%d", &choice);
        getchar(); // 清除换行符

        switch (choice) {
            case 1: { // 添加图书
                Book newBook;
                printf("请输入图书ID:");
                scanf("%d", &newBook.id);
                getchar();
                printf("请输入书名:");
                fgets(newBook.title, sizeof(newBook.title), stdin);
                newBook.title[strcspn(newBook.title, "\n")] = '\0';
                printf("请输入作者:");
                fgets(newBook.author, sizeof(newBook.author), stdin);
                newBook.author[strcspn(newBook.author, "\n")] = '\0';
                printf("请输入价格:");
                scanf("%lf", &newBook.price);
                printf("请输入库存数量:");
                scanf("%d", &newBook.stock);
                if (addBook(&library, &newBook) == 0) {
                    printf("添加成功!\n");
                }
                break;
            }
            case 2: { // 删除图书
                int id;
                printf("请输入要删除的图书ID:");
                scanf("%d", &id);
                if (deleteBookById(&library, id) == 0) {
                    printf("删除成功!\n");
                } else {
                    printf("未找到ID为%d的图书。\n", id);
                }
                break;
            }
            case 3: { // 修改图书信息
                int id;
                printf("请输入要修改的图书ID:");
                scanf("%d", &id);
                Book *old = findBookById(&library, id);
                if (old == NULL) {
                    printf("未找到ID为%d的图书。\n", id);
                    break;
                }
                Book newInfo;
                newInfo.id = id; // 保留原ID
                printf("请输入新书名(原:%s):", old->title);
                getchar();
                fgets(newInfo.title, sizeof(newInfo.title), stdin);
                newInfo.title[strcspn(newInfo.title, "\n")] = '\0';
                printf("请输入新作者(原:%s):", old->author);
                fgets(newInfo.author, sizeof(newInfo.author), stdin);
                newInfo.author[strcspn(newInfo.author, "\n")] = '\0';
                printf("请输入新价格(原:%.2f):", old->price);
                scanf("%lf", &newInfo.price);
                printf("请输入新库存(原:%d):", old->stock);
                scanf("%d", &newInfo.stock);
                if (updateBook(&library, id, &newInfo) == 0) {
                    printf("修改成功!\n");
                }
                break;
            }
            case 4: { // 按ID查找
                int id;
                printf("请输入图书ID:");
                scanf("%d", &id);
                Book *b = findBookById(&library, id);
                if (b) {
                    printf("ID: %d, 书名: %s, 作者: %s, 价格: %.2f, 库存: %d\n",
                           b->id, b->title, b->author, b->price, b->stock);
                } else {
                    printf("未找到。\n");
                }
                break;
            }
            case 5: { // 按书名查找
                char title[100];
                printf("请输入书名关键词:");
                getchar();
                fgets(title, sizeof(title), stdin);
                title[strcspn(title, "\n")] = '\0';
                BookArray result;
                findBooksByTitle(&library, title, &result);
                if (result.count == 0) {
                    printf("未找到包含\"%s\"的图书。\n", title);
                } else {
                    displayAllBooks(&result);
                }
                freeBookArray(&result);
                break;
            }
            case 6: // 显示所有
                displayAllBooks(&library);
                break;
            case 7: { // 排序
                int order;
                printf("请选择排序方式:1.升序  2.降序:");
                scanf("%d", &order);
                sortBooksByPrice(&library, order == 1);
                break;
            }
            case 8: // 保存并退出
                saveToFile(&library, FILENAME);
                freeBookArray(&library);
                printf("感谢使用,再见!\n");
                return 0;
            default:
                printf("无效输入,请重新选择。\n");
        }
    }
}

🔹 Makefile(可选)

CC = gcc
CFLAGS = -Wall -g
TARGET = book_manager
OBJS = main.o book.o

$(TARGET): $(OBJS)
	$(CC) $(OBJS) -o $(TARGET)

main.o: main.c book.h
book.o: book.c book.h

clean:
	rm -f $(OBJS) $(TARGET) books.dat

.PHONY: clean

⚙️ 编译与运行

在终端中进入项目目录,执行以下命令:

# 编译
gcc -c main.c -o main.o
gcc -c book.c -o book.o
gcc main.o book.o -o book_manager

# 或者直接使用Makefile
make

# 运行
./book_manager
说明: 程序首次运行时,books.dat 文件不存在,会自动创建。每次退出时自动保存数据,下次启动自动加载。

📚 项目涉及知识点回顾

  • 结构体:定义图书信息,封装动态数组管理结构。
  • 动态内存管理:使用 mallocreallocfree 实现动态数组。
  • 文件操作:二进制读写(fread/fwrite),实现数据持久化。
  • 指针与数组:通过指针操作结构体数组,作为函数参数传递。
  • 函数模块化:将功能拆分为独立函数,头文件声明,源文件实现。
  • 字符串处理:使用 fgetsstrcspn 安全读取输入。
  • 查找与排序:线性查找、qsort 排序。
  • 错误处理:检查返回值,提供用户反馈。

🚀 扩展与改进方向

  • 增加图书分类、出版社、出版日期等字段。
  • 实现借阅和归还功能,记录借阅者信息。
  • 使用链表代替动态数组,便于频繁插入删除。
  • 增加用户登录权限管理(管理员/普通用户)。
  • 采用数据库(如SQLite)替代文件存储。
  • 添加图形界面(GTK、Qt等)。

通过本项目的实践,你应当已经能够综合运用C语言的核心知识解决实际问题。 建议你从头编写一遍代码,并尝试添加新功能,加深理解。至此,C语言教程的主要内容已全部结束,祝你编程之路越走越远!