通过前期的学习,我们已经掌握了C语言的基本语法、指针、结构体、文件操作、动态内存管理等核心知识。 本章将通过一个完整的图书管理系统项目,将这些知识融会贯通,体验从设计到实现的完整流程。 项目具备添加、删除、修改、查询、显示、排序、保存到文件、从文件加载等实用功能,代码模块化、注释清晰。
设计一个控制台图书管理系统,满足以下功能:
采用模块化设计,将数据结构定义和操作函数封装在 book.h 和 book.c 中,main.c 负责用户交互。
#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
#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;
}
#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");
}
}
}
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 文件不存在,会自动创建。每次退出时自动保存数据,下次启动自动加载。
malloc、realloc、free 实现动态数组。fread/fwrite),实现数据持久化。fgets、strcspn 安全读取输入。qsort 排序。通过本项目的实践,你应当已经能够综合运用C语言的核心知识解决实际问题。 建议你从头编写一遍代码,并尝试添加新功能,加深理解。至此,C语言教程的主要内容已全部结束,祝你编程之路越走越远!