用C语言手搓一个图书管理系统:从顺序表到链表的完整实现
第一次接触数据结构时,总觉得那些抽象的概念离实际开发很远。直到某天在图书馆借书,看着管理员在电脑上快速检索、入库、出库,突然意识到这不就是线性表的完美应用场景吗?于是决定用C语言实现一个完整的图书管理系统,把课本上的顺序表和链表真正用起来。
这个项目不仅帮我通过了数据结构考试,还成了简历上的亮点。下面我会分享从零开始的完整实现过程,包含可直接运行的源码,特别适合正在学习《数据结构》的C语言初学者。
1. 系统设计与数据结构选型
图书管理系统需要处理的核心操作包括:
- 图书信息的存储与检索
- 新书入库与旧书出库
- 图书信息修改
- 价格排序与统计
- 重复书籍检测
数据结构对比分析:
| 特性 | 顺序表 | 链表 |
|---|---|---|
| 内存占用 | 连续空间,可能浪费 | 动态分配,无浪费 |
| 插入/删除效率 | O(n) | O(1) |
| 随机访问效率 | O(1) | O(n) |
| 实现难度 | 简单 | 中等 |
// 图书信息结构体定义 typedef struct { char isbn[20]; // 国际标准书号 char name[50]; // 书名 float price; // 价格 } Book;2. 顺序表实现方案
顺序表适合数据量固定、查询频繁的场景。我们先实现基础功能:
2.1 顺序表初始化与内存管理
#define MAX_SIZE 1000 typedef struct { Book *elements; // 存储空间基地址 int length; // 当前图书数量 } SeqList; int InitList(SeqList *L) { L->elements = (Book*)malloc(MAX_SIZE * sizeof(Book)); if(!L->elements) exit(OVERFLOW); L->length = 0; return OK; }内存管理要点:
- 预分配固定大小的连续内存空间
- 需要检查分配是否成功
- 记得在程序结束时释放内存
2.2 核心功能实现
图书入库操作示例:
int InsertBook(SeqList *L, int position, Book book) { if (position < 1 || position > L->length + 1) { return ERROR; // 非法位置 } if (L->length >= MAX_SIZE) { return OVERFLOW; // 存储空间已满 } // 移动元素腾出空间 for(int i = L->length; i >= position; i--) { L->elements[i] = L->elements[i-1]; } L->elements[position-1] = book; L->length++; return OK; }价格调整算法:
void AdjustPrices(SeqList *L) { float total = 0; for(int i = 0; i < L->length; i++) { total += L->elements[i].price; } float avg = total / L->length; for(int i = 0; i < L->length; i++) { if(L->elements[i].price >= avg) { L->elements[i].price *= 1.1; // 高于平均价涨10% } else { L->elements[i].price *= 1.2; // 低于平均价涨20% } } }3. 链表实现方案
当需要频繁插入删除时,链表是更好的选择。我们采用带头结点的单链表实现:
3.1 链表结构定义
typedef struct LNode { Book data; struct LNode *next; } LNode, *LinkList; int InitList(LinkList *L) { *L = (LinkList)malloc(sizeof(LNode)); if(!*L) exit(OVERFLOW); (*L)->next = NULL; return OK; }3.2 链表特色操作
链表逆序存储实现:
void ReverseList(LinkList *L) { LNode *prev = NULL; LNode *current = (*L)->next; LNode *next = NULL; while(current != NULL) { next = current->next; current->next = prev; prev = current; current = next; } (*L)->next = prev; }链表去重算法:
void RemoveDuplicates(LinkList *L) { LNode *current = (*L)->next; while(current != NULL) { LNode *runner = current; while(runner->next != NULL) { if(strcmp(runner->next->data.isbn, current->data.isbn) == 0) { LNode *temp = runner->next; runner->next = runner->next->next; free(temp); } else { runner = runner->next; } } current = current->next; } }4. 性能优化实战技巧
在实际开发中,我们还需要考虑以下优化点:
4.1 混合存储策略
结合顺序表和链表的优点:
- 主存储使用顺序表便于快速检索
- 最近操作记录使用链表实现LRU缓存
typedef struct { SeqList main_storage; // 主存储 LinkList recent_ops; // 最近操作缓存 int cache_size; // 缓存大小 } HybridStorage;4.2 索引优化
为常用查询字段建立索引:
typedef struct { char isbn[20]; int position; // 在顺序表中的位置 } ISBN_Index; ISBN_Index *CreateISBNIndex(SeqList *L) { ISBN_Index *index = (ISBN_Index*)malloc(L->length * sizeof(ISBN_Index)); for(int i=0; i<L->length; i++) { strcpy(index[i].isbn, L->elements[i].isbn); index[i].position = i; } // 按ISBN排序索引 qsort(index, L->length, sizeof(ISBN_Index), CompareISBN); return index; }4.3 文件持久化
将数据保存到文件实现持久化:
void SaveToFile(SeqList *L, const char *filename) { FILE *fp = fopen(filename, "wb"); if(fp == NULL) return; fwrite(&(L->length), sizeof(int), 1, fp); fwrite(L->elements, sizeof(Book), L->length, fp); fclose(fp); } int LoadFromFile(SeqList *L, const char *filename) { FILE *fp = fopen(filename, "rb"); if(fp == NULL) return ERROR; fread(&(L->length), sizeof(int), 1, fp); L->elements = (Book*)malloc(L->length * sizeof(Book)); fread(L->elements, sizeof(Book), L->length, fp); fclose(fp); return OK; }5. 完整系统实现
将各个模块组合成完整系统:
int main() { SeqList bookList; InitList(&bookList); // 加载初始数据 if(LoadFromFile(&bookList, "books.dat") != OK) { printf("初始化图书数据...\n"); // 添加示例图书 Book sampleBooks[5] = { {"9787302257646", "程序设计基础", 25.00}, {"9787302164340", "程序设计基础(第2版)", 20.00}, {"9787302219972", "单片机技术及应用", 32.00}, {"9787302203513", "单片机原理与应用技术", 26.00}, {"7810827430", "工业计算机控制技术", 29.00} }; for(int i=0; i<5; i++) { InsertBook(&bookList, i+1, sampleBooks[i]); } } // 主菜单界面 while(1) { printf("\n图书管理系统\n"); printf("1. 添加新书\n"); printf("2. 删除图书\n"); printf("3. 查询图书\n"); printf("4. 价格调整\n"); printf("5. 保存退出\n"); int choice; scanf("%d", &choice); switch(choice) { case 1: AddBook(&bookList); break; case 2: DeleteBook(&bookList); break; case 3: SearchBook(&bookList); break; case 4: AdjustPrices(&bookList); break; case 5: SaveToFile(&bookList, "books.dat"); free(bookList.elements); return 0; default: printf("无效选择\n"); } } }实现这个系统的过程中,最深的体会是:数据结构的选择会极大影响系统性能。当图书数量超过5000本时,顺序表的插入操作明显变慢,这时就需要考虑改用链表或其他更高效的数据结构。