基于C语言(B 树索引)实现(控制台) 图书管理系统【100010728】
基于 B 树为索引的图书管理系统
一、需求分析
1.1 问题描述
图书管理基本业务活动包括:对一本书的采编入库、清除库存、借阅和归还等等。试设计一个图书管理系统,将上述业务活动借助于计算机系统完成。
1.2 基本要求
每种书的登记内容至少包括书号、书名、著者、现存量和总库存量等五项。
作为演示系统,不必使用文件,全部数据可以都在内存存放。但是由于上述四项基本业务活动都是通过书号(即关键字)进行的,所以要用 B 树(2-3 树)对书号建立索引,以获得高效率。
系统应实现的操作及其功能定义如下:
① 采编入库:新购入一种书,经分类和确定书号之后登记到图书账目中去。如果这种书在帐中已有,则只将总库存量增加。
② 清除库存:某种书已无保留价值,将它从图书账目中注销。
③ 借阅:如果一种书的现存量大于零,则借出一本,登记借阅者的图书证号和归还期限。
④ 归还:注销对借阅者的登记,改变该书的现存量。
⑤ 显示:以凹入表的形式显示 B 树。这个操作是为了调试和维护的目的而设置的。下列 B 树的打印格式如下所示:
1.3 已完成情况
基本功能 | 基本功能 | 选做功能 | 选做功能 |
---|---|---|---|
图书入库 | 已完成 | 操作日志 | 已完成 |
删除图书 | 已完成 | 查询作者著作 | 已完成 |
借书 | 已完成 | 查询书籍详细信息和借阅记录 | 已完成 |
还书 | 已完成 | 数据存储文件 | 已完成 |
总结 | 总结 | 总结 | 总结 |
1、图书管理系统的四个基本功能已经全部完成。;2、选择内容四个功能已完成三个功能。 | 1、图书管理系统的四个基本功能已经全部完成。;2、选择内容四个功能已完成三个功能。 | 1、图书管理系统的四个基本功能已经全部完成。;2、选择内容四个功能已完成三个功能。 | 1、图书管理系统的四个基本功能已经全部完成。;2、选择内容四个功能已完成三个功能。 |
1.4 程序亮点
亮点一:实现了输入判断。当用户输入异常数据,系统会给出提示。
比如在功能菜单页面输入了 999,以及已存在图书号和借阅者号,更新库存时出错等提示
亮点二:菜单页面和打印索引结构页面更为直观
二、概要设计
2.1 数据类型的定义
2.2 功能模块关系
三、详细设计
3.1 数据类型定义
B 树的数据结构定义
/** B树的结点类型 **/
typedef struct BTNode {
int keynum; // 关键字个数
KeyType key[M+1]; // 关键字数组, key[0]未用
struct BTNode *parent; // 双亲结点指针
struct BTNode *ptr[M+1]; // 孩子结点指针数组
void *record[M+1]; // 记录指针向量,0号单元未用, 指向保存的数据
} BTNode, *BTree; // B树的结点及指针类型
/** 返回的结果信息类型 **/
typedef struct result {
BTree pt; // 指向找到的结点
int i; // 在结点中的关键字位序(1 <= i <= m)
int tag; // 1:查找成功 0:查找失败
} result, *resultptr;
/** 辅助打印索引结构类型 **/
typedef struct arr {
int *thelast; // 用于标记分隔符
int max; // 最大值/层数
int n; // 当前位/层
} arr, *array;
图书相关的数据结构定义
/** 借阅者结构体类型 **/
typedef struct reader {
int readerId; // 借阅者id
char name[30]; // 借阅者姓名
} reader, *readerptr; // 借阅者
/** 借阅记录结构类型 **/
typedef struct rcd {
readerptr reader; // 借阅者
char borrowTime[25]; // 借书时间
char returnTime[25]; // 归还时间
} rcd, *rcdptr;
/** 借阅记录的队列结点结构类型 **/
typedef struct rcdQueueNode {
rcdptr data; // 借阅记录的数据
struct rcdQueueNode *next; // 指向下一借阅记录的指针
} qNode;
/** 借阅记录队列结构类型 **/
typedef struct que {
int count; // 借阅数量
qNode *front; // 头指针
qNode *rear; // 尾指针
} que, *queue;
/** 图书结构类型 **/
typedef struct book {
int bookId; // 书号
char name[30]; // 书名
char writer[30]; // 著者
int count; // 现存量
int total; // 总库存量
queue borrower; // 借阅记录
} book, *bookptr; // 结构体和指针的别名
/** 图书结点类型 **/
typedef struct bookNode {
bookptr data; // 图书数据
struct bookNode *next; // 指向下一结点指针
} bookNode, *bookNodeptr;
/** hash存储表结构 **/
typedef struct {
bookNodeptr *rcd; // 通过hash存放图书结点的数组
int size; // 容量
int count; // 已存在数
} HashTable, *Hashptr;
3.2 函数功能具体设计
- 图书入库
- 删除图书
- 借书
- 还书
- 查看图书
- 查看某著者所有著作
- 查看图书列表
- 查看借阅者列表
四、调试分析
4.1 问题分析与解决
难题一:借阅者的数据存储定义
本来是不打算使用 B 树来存储读者信息的,但是一想到当借阅者数量足够多时查找借阅者的难度应该挺大的,而且查询借阅者的难度和查询图书的难度应该保持一致,恰好 B 树能作为索引优化查询效率,因此决定使用 B 树来存储借阅者信息。
难题二:查询某一著者的所有著作
决定使用 B 树来存储借阅者信息时,我也想将著者也使用借阅者信息存储的,后面发现如果这样,那么在查询某一著者的所有著作时,难度会非常高,因为你需要去判断 B 树中的所有图书,有哪些图书的著者与要查找的著者相同的,即使实现了,效率上也不会很高。因此我决定用 hash 表存储著者的所有著者信息。在 hash 表中只需要存带有图书结点的链表,结点的成员是指向图书信息结构体的指针,这样一来占用的空间不用很大,而且在效率上也提高了很多。
难题三:数据结构体的定义
基于以往写大作业的经验来谈,我更倾向于将 B 树划分为更通用的数据结构,而不是将图书、借阅者等与其结合,这样会造成耦合过高,不利于维护,而且也不利于扩展。因此我借助 B 树中的 record 字段来标识关键字所指向的外部数据(图书和借阅者信息)。
难题四:数据存储入文件的处理
由于向文件中存储数据很难做到从尾部追加和删除,因此决定每次保存时都彻底的覆盖文件。
4.2 经验体会
最大的体会就是,如果要设计一个整体性功能完善的系统的话,一开始一定要构思好整体的逻辑,包括各种功能的具体定义、实现的合理性、交互的实用性以及功能和功能之间整体性。如果没有最初的构思,对于整个程序根本是无从下手的。首先你得定义好基础的数据结构,再到业务数据结构,然后设计功能函数,最后是设计好界面和调试试错,而功能与功能模块之间的关联性又是很强,而这些都是基于数据结构存储定义的,需要有很完全可用的数据结构才能够实现。
其次是 c 语言里,进行单元测试(单个函数的调试)是非常困难且非常麻烦的事情,因为函数与函数之间的关联性很强,耦合度非常高,如果你想单独调试一个函数,而这函数又与其他函数相连,首先你得有完整和合理的测试数据输入,再是测试函数,只有存在 main.c 函数时能完成链接生成 exe,这样你需要写很多冗余的测试函数。最好的方式当然是将底层全部铺好后一起测试,但是这样后期暴露的小 bug 会非常多。
最后是耐心非常重要。当测试数据需要重复输入,调试的断点需要在各处打时,耐心就显得尤为重要了。
五、用户使用说明
5.1 功能菜单
如果已经存在了之前的数据文件(index.dat, queue.dat, reader.dat),会出现以下提示
然后进入菜单界面
5.2 使用说明
根据输入的序号来进行各种操作
图中显示的序号意义
序号 | 功能简介 |
---|---|
1 | 添加图书 |
2 | 添加借阅者 |
3 | 删除图书 |
4 | 更新图书 |
5 | 查看图书详细信息 |
6 | 打印索引结构 |
7 | 查找某一著者的所有图书 |
8 | 查看图书列表 |
9 | 查看订阅者列表 |
10 | 摧毁 B 树 |
11 | 借书 |
12 | 还书 |
13 | 退出 |
测试数据:
书名随机输入
著者名为 1 到 111 不等
库存为 1 到 111 不等
借阅者:
借阅者号 | 借阅者姓名 |
---|---|
1 | 王明明 |
2 | 王嘉然 |
3 | 杜向晚 |
六、测试结果
6.1 图书入库
插入测试数据 35, 16, 18, 70, 5, 50, 22, 60, 13, 17, 12 , 45, 25, 42, 15, 90, 30, 7
- 插入书号为 35 的图书
- 插入书号为 16 的图书
- 插入书号为 18 的图书
- 插入书号为 70 的图书
- 插入书号为 5 的图书
- 插入书号为 50 的图书
- 插入书号为 22 的图书
- 插入书号为 60 的图书
- 插入书号为 13 的图书
- 插入书号为 17 的图书
- 插入书号为 12 的图书
- 插入书号为 45 的图书
- 插入书号为 25 的图书
- 插入书号为 42 的图书
- 插入书号为 15 的图书
- 插入书号为 90 的图书
- 插入书号为 30 的图书
- 插入书号为 7 的图书
6.2 删除图书
删除测试数据 45, 90, 50, 22, 42
插入完成后索引图为
- 删除书号为 45 的图书
- 删除书号为 90 的图书
- 删除书号为 50 的图书
- 删除书号为 22 的图书
- 删除书号为 42 的图书
至此删除操作完成
6.3 添加借阅者
借阅者号 | 借阅者姓名 |
---|---|
1 | 王明明 |
2 | 王嘉然 |
3 | 杜向晚 |
6.4 查看图书列表
6.5 借书
3 号借阅者杜向晚借书(书号为 5)
查看图书列表
6.6 查看图书详细信息
6.7 查看订阅者列表
6.8 还书
七、附录
7.1 头文件
BTree.h
# ifndef BTREE_H_INCLUDED
# define BTREE_H_INCLUDED
# include <stdio.h>
# endif // BTREE_H_INCLUDED
# define M 3 // B树的阶,设为3
typedef int KeyType; // 关键字类型
typedef enum Status { // 表状态的枚举类
SUCCESS, // 0-成功
ERROR // 1-失败/错误
} Status;
/** B树的结点类型 **/
typedef struct BTNode {
int keynum; // 关键字个数
KeyType key[M+1]; // 关键字数组, key[0]未用
struct BTNode *parent; // 双亲结点指针
struct BTNode *ptr[M+1]; // 孩子结点指针数组
void *record[M+1]; // 记录指针向量,0号单元未用, 指向保存的数据
} BTNode, *BTree; // B树的结点及指针类型
/** 返回的结果信息类型 **/
typedef struct result {
BTree pt; // 指向找到的结点
int i; // 在结点中的关键字位序(1 <= i <= m)
int tag; // 1:查找成功 0:查找失败
} result, *resultptr;
/** 辅助打印索引结构类型 **/
typedef struct arr {
int *thelast; // 用于标记分隔符
int max; // 最大值/层数
int n; // 当前位/层
} arr, *array;
// 返回关键字k的位序
int search(BTree t, KeyType k);
// 分裂结点
void split(BTree t, int s, BTree *ap, void *rec);
// 创建新结点
void newRoot(BTree *p, BTree left, KeyType x, BTree right, void *rec);
// 插入
void insert(BTree t, int i, KeyType x, BTree p, void *rec);
// 找出Ai子树最下层非终端结点的最小关键字代替Ki
void successor(BTree *t, int i);
// 从结点中删除关键字key[i]
void removeKey(BTree t, int i);
// 调整BTree
void restore(BTree *root, BTree *t, int i);
// 为该位的索引结构添加标识并向下一层迁移
array append(array a, int last);
// 初始化一个array
array getArray();
// BTree的查找
resultptr searchBTree(BTree t, int k);
// 插入结点
Status insertBTree(BTree *t, KeyType k, BTree q, int i, void *rec);
// 删除结点
void deleteBTree(BTree *root, BTree *t, int i);
// 销毁BTree
Status destroyBTree(BTree t);
// 打印BTree索引结构
void printBTree(BTree t);
// 打印索引结构的功能函数
void print(BTree t, int depth, array last);
// 弹出当前位,回溯到上一层
array pop(array a);
// 初始化BTree结点
BTree getBTree();
// 初始化搜索结果信息
resultptr getResult(BTree t, int index, int tag);
Librarian.h
# ifndef LIBRARIAN_H_INCLUDED
# define LIBRARIAN_H_INCLUDED
# include <time.h>
# include <string.h>
# include <stdarg.h>
# include <stdlib.h>
# endif // LIBRARIAN_H_INCLUDED
# define HASH_TABLE_SIZE 1000
/** 借阅者结构体类型 **/
typedef struct reader {
int readerId; // 借阅者id
char name[30]; // 借阅者姓名
} reader, *readerptr; // 借阅者
/** 借阅记录结构类型 **/
typedef struct rcd {
readerptr reader; // 借阅者
char borrowTime[25]; // 借书时间
char returnTime[25]; // 归还时间
} rcd, *rcdptr;
/** 借阅记录的队列结点结构类型 **/
typedef struct rcdQueueNode {
rcdptr data; // 借阅记录的数据
struct rcdQueueNode *next; // 指向下一借阅记录的指针
} qNode;
/** 借阅记录队列结构类型 **/
typedef struct que {
int count; // 借阅数量
qNode *front; // 头指针
qNode *rear; // 尾指针
} que, *queue;
/** 图书结构类型 **/
typedef struct book {
int bookId; // 书号
char name[30]; // 书名
char writer[30]; // 著者
int count; // 现存量
int total; // 总库存量
queue borrower; // 借阅记录
} book, *bookptr; // 结构体和指针的别名
/** 图书结点类型 **/
typedef struct bookNode {
bookptr data; // 图书数据
struct bookNode *next; // 指向下一结点指针
} bookNode, *bookNodeptr;
/** hash存储表结构 **/
typedef struct {
bookNodeptr *rcd; // 通过hash存放图书结点的数组
int size; // 容量
int count; // 已存在数
} HashTable, *Hashptr;
// 把整形(int)转换成字符串(char*)的形式
char *int2str(int num);
// 初始化队列
void initQueue(queue *q);
// 入队
int enQueue(queue q, rcdptr rdr);
// 出队
void deQueue(queue q);
// 获取队列头结点的数据
rcdptr getFront(queue q);
// 移除某借阅者id队列元素
int removeElem(queue q, int readerId);
// **********************
// 菜单
void menu();
// 清空缓冲区
void flush();
// 初始化图书信息
bookptr getBook(int bookId, char *name, char *writer, int total);
// 检查输入数据以及插入图书信息
int checkAndInsert(BTree *t, Hashptr table, KeyType k, void *rec);
// 打印所有图书信息
void printAllBook(BTree t);
// 打印所有图书信息功能类
void printBookIndex(BTree t);
// 打印图书详细信息
void printBookDetail(bookptr book);
// 打印单条图书信息
void printCompletBook(bookptr book);
// 打印单条图书信息2 差别只在于宽度
void printBook(bookptr book);
// 打印某著者的所有图书信息
int printBookByWriter(Hashptr table, char *name);
// 更新图书信息
int updateBook(Hashptr tb, bookptr book, char *nm, char *wrtr, int tot);
// 保存图书信息
void saveBTree(BTree t, FILE *index, FILE *queue);
// **********************
// 插入借阅者
int insertReader(BTree *t, void *rec);
// 打印所有借阅者信息
void printAllReader(BTree rt);
// 打印所有借阅者信息-功能函数
void printReaderIndex(BTree rt);
// 借书
int borrowBook(bookptr book, readerptr rdr);
// 还书
int returnBook(bookptr book, int readerid);
// 保存借阅者信息
void saveReader(BTree rt, FILE *fp_reader);
// **********************
// hash
int addHash(Hashptr table, bookptr book);
// hash函数
int hashFunc(char *data);
// 检测是否存在hash
int existHash(Hashptr table, bookptr book);
// 移除hash
int removeHash(Hashptr table, bookptr book);
// 拼接字符串
char *catchString(int n, ...);
// 写入日志
void writeLog(char *msg);
// 保存至文件
void save(BTree t, BTree rt);
// 从文件中加载数据
int load(BTree *t, BTree *rt, Hashptr tb);
// 从文件中加载借阅者数据
int loadReader(BTree *rt);
// 从文件中加载图书数据
int loadBTree(BTree *t, bookptr book);
7.2 树功能函数
BTree.c
# include "header/BTree.h"
# include <stdio.h>
# include <stdlib.h>
/**
* @brief 返回关键字k的大致位序(p->key[i-1]<k<=p->key[i])
*
* @param t BTree
- @param k 关键字
* @return int (p->key[i-1]<k<=p->key[i])
*/
int search(BTree t, KeyType k) {
int i = 1;
// 关键字范围内查找
while(i <= t->keynum && t->key[i] < k) {
i++;
}
return i;
}
/**
- @brief 分裂结点,前一半留在原结点,后一半移入 ap 指向的结点
*
* @param t BTree
* @param s mid index
* @param ap targe BTree
* @param rec record data
*/
void split(BTree t, int s, BTree *ap, void *rec) {
int i, j, n = t->keynum;
// 生成新结点
(*ap) = getBTree();
// 改变孩子指针数组指向
(*ap)->ptr[0] = t->ptr[s];
// 将结点后一半移向ap所指向的结点
for (i = s + 1, j = 1; i <= n; i++, j++) {
(*ap)->key[j] = t->key[i];
(*ap)->record[j] = t->record[i];
(*ap)->ptr[j] = t->ptr[i];
}
(*ap)->parent = t->parent;
(*ap)->keynum = n - s;
for (i = 0; i <= n - s; i++) {
// 修改子结点的双亲指针指向
if ((*ap)->ptr[i]) {
(*ap)->ptr[i]->parent = *ap;
}
}
// 前一半保留
t->keynum = s - 1;
}
/**
* @brief
*
* @param a
* @param last
* @return array
*/
array append(array a, int last) {
// 如果满了则扩容
if (a->n == a->max) {
a->thelast = (int *)realloc(a->thelast, sizeof(int) * ((a->max) + 10));
}
a->max += 10;
a->thelast[(a->n)++] = last;
return a;
}
/**
* @brief Get the Array object
*
* @return array
*/
array getArray() {
array a = (array)malloc(sizeof(struct arr));
a->max = 10;
a->n = 0;
a->thelast = (int *)malloc(sizeof(int) * (a->max));
return a;
}
/**
* @brief
*
* @param t BTree
- @param k 待查关键字
- @return resultptr 结果信息指针
*/
resultptr searchBTree(BTree t, int k) {
// p-指向待查结点 q-指向双亲
BTree p = t, q = NULL;
if(t == NULL) {
return NULL;
}
// isFound-记录是否找到结点, i-结点关键字大致位序
int isFound = 0, i = 0;
while (p != NULL && isFound == 0) {
// 结点关键字大致位序
= search(p, k);
if (i <= p->keynum && p->key[i] == k) {
// 找到待查关键字
isFound = 1;
} else {
// 指针下移
= p;
// 从0开始
= p->ptr[i-1];
}
}
if (isFound) {
return getResult(p, i, 1);
} else {
return getResult(q, i, 0);
}
}
/**
- @brief 创建新结点
*
* @param p new BTNode
* @param left the left part
- @param x 关键字
* @param right the right part
* @param rec record data
*/
void newRoot(BTree *p, BTree left, KeyType x, BTree right, void *rec) {
(*p) = getBTree();
(*p)->keynum = 1;
(*p)->key[1] = x;
(*p)->record[1] = rec;
(*p)->ptr[0] = left;
(*p)->ptr[1] = right;
// 如果左边部分需要接入新结点
if(left) {
left->parent = *p;
}
// 如果右边部分需要接入新结点
if(right) {
right->parent = *p;
}
// 双亲结点置空
(*p)->parent = NULL;
}
/**
* @brief 插入结点, 将关键字x和新结点指针ap分别插入t->key[i], q->ptr[i]
*
-
- @param t 待插入结点
-
- @param i 插入的位序(i-1 < position <= i)
-
- @param x 关键字
-
- @param p 新结点指针
* @param rec record data
*/
void insert(BTree t, int i, KeyType x, BTree p, void *rec) {
int j, n = t->keynum;
// 右移为新结点腾出空间
for (j = n; j >= i; j--) {
t->ptr[j + 1] = t->ptr[j];
t->key[j + 1] = t->key[j];
t->record[j + 1] = t->record[j];
}
t->ptr[i] = p;
t->key[i] = x;
t->record[i] = rec;
if (p) {
p->parent = t;
}
t->keynum++;
}
/**
- @brief 插入, 将关键字 k 插入 t 树的 q 结点的第 i-1~i 之间
*
* @param t BTree
-
- @param k 关键字
-
- @param q 插入的目标结点
-
- @param i 关键字插入位序
* @param rec record data
- @return Status 成功返回 SUCCESS 否则返回 ERROR
*/
Status insertBTree(BTree *t, KeyType k, BTree q, int i, void *rec) {
// s-中位数, finished-是否完成插入操作, need-是否需要改变根结点
int s, finished = 0, need = 0;
// x-关键字
KeyType x;
// 新结点指针
BTree ap;
void *recd;
// 目标结点指向若空则置新
if (q == NULL) {
newRoot(t, NULL, k, NULL, rec);
} else {
= k;
ap = NULL;
recd = rec;
while (need == 0 && finished == 0) {
insert(q, i, x, ap, recd);
if (q->keynum < M) {
// 关键字个数未超出M,标记完成插入操作
finished = 1;
} else {
// 关键字总数达到M,需要分裂结点
= (M + 1) / 2;
split(q, s, &ap, recd);
// 中位数位序的关键字, 下一个循环插入到双亲结点中
= q->key[s];
recd = q->record[s];
if (q->parent) {
// 未分裂到根节点, 向上检查关键字总数是否超出M
= q->parent;
= search(q, x);
} else {
// 分裂到根节点,需要产生新的根节点
need = 1;
}
}
}
// 产生新的根节点
if (need) {
newRoot(t, q, x, ap, recd);
}
}
return SUCCESS;
}
/**
- @brief 找出 Ai 子树最下层非终端结点的最小关键字代替 Ki
*
* @param t
* @param i
*/
void successor(BTree *t, int i) {
if((*t) == NULL) {
return;
}
BTree temp = (*t)->ptr[i];
// 下移至最下层
while (temp->ptr[0]) {
temp = temp->ptr[0];
}
// 替换Ki
(*t)->key[i] = temp->key[1];
(*t)->record[i] = temp->record[1];
// t转为最下层结点返回
(*t) = temp;
}
/**
- @brief 从结点中删除关键字 key[i]
*
- @param t 目标节点
- @param i 关键字位序
*/
void removeKey(BTree t, int i) {
// 左移关键字覆盖
while (i < t->keynum) {
t->key[i] = t->key[i + 1];
t->record[i] = t->record[i + 1];
t->ptr[i] = t->ptr[i + 1];
i++;
}
t->keynum--;
}
/**
- @brief 调整 BTree
*
-
- @param root 根节点
-
- @param t 目标节点
-
- @param i 当前节点在双亲的位序
*/
void restore(BTree *root, BTree *t, int i) {
// 目标节点的双亲结点
BTree par = (*t)->parent;
if (par == NULL) {
// 若为根节点
if(par == NULL) {
if((*t)->keynum == 0) {
(*root) = (*t);
(*root)->parent = NULL;
// free(*t);
}
}
return;
}
// 目标节点在双亲结点的位序
int j = 0;
while (par->ptr[j] != (*t)) {
j++;
}
if (j > 0 && par->ptr[j - 1]->keynum > (M - 1) / 2) {
// 左兄弟结点个数足够
// 目标节点的左兄弟结点
BTree sib = par->ptr[j - 1];
(*t)->keynum++;
int n = (*t)->keynum;
int k;
// 目标节点的关键字右移,为调整所得的新节点腾出空间
for (k = n; k > 0; k--) {
(*t)->key[k] = (*t)->key[k - 1];
(*t)->record[k] = (*t)->record[k - 1];
(*t)->ptr[k] = (*t)->ptr[k - 1];
}
(*t)->ptr[1] = (*t)->ptr[0];
(*t)->ptr[0] = sib->ptr[sib->keynum];
if((*t)->ptr[0] != NULL) {
(*t)->ptr[0]->parent = (*t);
}
// 将大于该关键字的关键字下移
(*t)->key[1] = par->key[j];
(*t)->record[1] = par->record[j];
// 将左兄弟结点中最大的关键字上移至双亲结点
par->key[j] = sib->key[sib->keynum];
par->record[j] = sib->record[sib->keynum];
// 删除左兄弟结点中的最大关键字
removeKey(sib, sib->keynum);
} else if (j >= 0 && par->keynum >= j + 1 && par->ptr[j + 1]->keynum > (M - 1) / 2) {
// 右兄弟结点个数足够
BTree sib = par->ptr[j + 1];
(*t)->keynum++;
// 直接把双亲结点的右一个关键字放入目标节点的最大位
(*t)->key[(*t)->keynum] = par->key[j + 1];
(*t)->record[(*t)->keynum] = par->record[j + 1];
(*t)->ptr[(*t)->keynum] = sib->ptr[0];
if((*t)->ptr[(*t)->keynum] != NULL) {
(*t)->ptr[(*t)->keynum]->parent = (*t);
}
// 右兄弟结点最小的上移
par->key[j + 1] = sib->key[1];
par->record[j + 1] = sib->record[1];
// 删除右兄弟结点的最小关键字(实际上是覆盖了位序为1的关键字, 因为左移后为0而0未使用)
removeKey(sib, 0);
} else {
// 左右兄弟结点都不够
if (j != 0) {
// 目标节点是位于右边的结点
// 目标节点的左兄弟节点
BTree sib = par->ptr[j - 1];
++sib->keynum;
// 双亲结点中分割目标节点和左兄弟结点的关键字下移至左兄弟结点
sib->key[sib->keynum] = par->key[j];
sib->record[sib->keynum] = par->record[j];
sib->ptr[sib->keynum] = (*t)->ptr[0];
par->keynum--;
// 循环变量
int k;
// 双亲结点的关键字左移
for (k = j; k < par->keynum; k++) {
par->key[k] = par->key[k + 1];
par->record[k] = par->record[k + 1];
par->ptr[k] = par->ptr[k + 1];
}
// 将目标节点的剩余信息合并到左兄弟结点中
for (k = 1; k <= (*t)->keynum; k++) {
sib->keynum++;
sib->key[sib->keynum] = (*t)->key[k];
sib->record[sib->keynum] = (*t)->record[k];
sib->ptr[sib->keynum] = (*t)->ptr[k];
}
// free(*t);
(*t) = sib;
for (int i = 0; i <= sib->keynum; i++) {
if(sib->ptr[i]) {
sib->ptr[i]->parent = sib;
}
}
} else if (j != M) {
// 目标节点是位于左边的结点
// 目标节点的右兄弟节点
BTree sib = par->ptr[j + 1];
int count = (*t)->keynum + 1;
par->keynum--;
sib->keynum += count;
// 循环变量
int k;
// 右兄弟结点关键字右移
for (k = sib->keynum; k > count; k--) {
sib->key[k] = sib->key[k - count];
sib->record[k] = sib->record[k - count];
sib->ptr[k] = sib->ptr[k - count];
}
// 双亲结点关键字下移到右兄弟结点
sib->ptr[count] = sib->ptr[0];
sib->key[count] = par->key[j + 1];
sib->record[count] = par->record[j + 1];
// 双亲结点指向子节点的指针左移
par->ptr[j] = par->ptr[j + 1];
// 双亲结点关键字左移
for (k = j + 1; k <= par->keynum; k++) {
par->key[k] = par->key[k + 1];
par->record[k] = par->record[k + 1];
par->ptr[k] = par->ptr[k + 1];
}
// // 目标节点的剩余信息并入右兄弟结点
for (k = 1; k <= (*t)->keynum; k++) {
sib->key[k] = (*t)->key[k];
sib->record[k] = (*t)->record[k];
sib->ptr[k] = (*t)->ptr[k];
}
sib->ptr[0] = (*t)->ptr[0];
// free(*t);
(*t) = sib;
for (int i = 0; i <= sib->keynum; i++) {
if(sib->ptr[i]) {
sib->ptr[i]->parent = sib;
}
}
}
(*t) = (*t)->parent;
if((*t) == (*root) && (*root)->keynum == 0) {
(*root) = (*root)->ptr[0];
(*root)->parent = NULL;
free(*t);
}
}
// 如果双亲结点也如此,以双亲结点为目标节点调整树
if (par->keynum < (M - 1) / 2) {
restore(root, &par, j);
}
}
/**
- @brief 删除,删除 B 树 root 上 t 结点的第 i 个关键字
*
-
- @param root 根节点
-
- @param t 目标节点
-
- @param i 删除关键字位序
*/
void deleteBTree(BTree *root, BTree *t, int i) {
// 不是最下层的非终端节点
if((*t)->ptr[i]) {
// 找出Ai子树最下层非终端结点的最小关键字代替Ki, t已转为最下层结点
successor(t, i);
// 删除最下层非终端节点中的最小关键字
deleteBTree(root, t, 1);
} else {
// 是最下层非终端节点
removeKey((*t), i);
if((*t)->keynum < (M - 1) / 2) {
restore(root, t, i);
}
}
}
/**
- @brief 销毁 BTree
*
* @param t BTree
* @return Status
*/
Status destroyBTree(BTree t) {
if(t == NULL) {
return ERROR;
}
int i = 0;
for (i = 0; i <= t->keynum; i++) {
if(t->ptr[i]) {
// 递归销毁
destroyBTree(t->ptr[i]);
}
}
free(t);
return SUCCESS;
}
/**
- @brief 打印 BTree 索引结构
*
* @param t BTree
*/
void printBTree(BTree t) {
if (t == NULL) {
return;
}
array last = getArray();
print(t, 1, last);
}
/**
- @brief 将层数返回一层
*
* @param a
* @return array
*/
array pop(array a) {
if (a == NULL || a->n == 0) {
return a;
}
a->n -= 1;
return a;
}
/**
- @brief 打印索引结构如下所示
|
|------>|
| |------>|
| | |->1
| |->2
| |------>|
| |->3
|->4
|------>|
|------>|
| |->5
|->6
|------>|
| |->7
|->8
|------>|
|->9
|->10
*
* @param t BTree
- @param depth 打印的深度
- @param last 存放关键字位序的数组
*/
void print(BTree t, int depth, array last) {
int i, j;
if(t == NULL) {
return;
}
// 第一个|
printf("|\n");
for (i = 0; i <= t->keynum; i++) {
// 打印最下层结点关键字
if(i != 0 && (t->key[i] != -1)) {
for (j = 0; j < depth - 1; j++) {
if(!(last->thelast[j])) {
printf("|");
} else {
printf(" ");
}
printf("\t");
}
printf("|->");
printf("%d\n", t->key[i]);
}
if((t->ptr[i]) != NULL) {
for (j = 0; j < depth - 1; j++) {
if(!(last->thelast[j])) {
printf("|");
} else {
printf(" ");
}
printf("\t");
}
printf("|------>");
if(i == t->keynum) {
append(last, 1);
} else {
append(last, 0);
}
print(t->ptr[i], depth + 1, last);
pop(last);
}
}
}
/**
- @brief 初始化 BTree 结点
*
- @return BTree BTree 结点
*/
BTree getBTree() {
BTree t = (BTree)malloc(sizeof(BTNode));
if(t == NULL) {
return NULL;
}
t->parent = NULL;
t->keynum = 0;
for (int i = 0; i <= M; i++) {
t->ptr[i] = NULL;
t->key[i] = -1;
t->record[i] = NULL;
}
return t;
}
/**
* @brief Get the Result object
*
-
- @param t 指向找到的结点
-
- @param index 结点位序
-
- @param tag 是否找到,1:查找成功 0:查找失败
* @return resultptr
*/
resultptr getResult(BTree t, int index, int tag) {
resultptr result = (resultptr)malloc(sizeof(struct result));
result->pt = t;
result->i = index;
result->tag = tag;
return result;
}
7.3 图书管理功能函数
Librarian.c
# include "header/BTree.h"
# include "header/Librarian.h"
/**
- @brief 把整形(int)转换成字符串(char*)的形式
*
- @param num 整形数字
- @return char* 字符串形式
*/
char *int2str(int num) {
char *res = (char *)malloc(sizeof(char) * 50);
itoa(num, res, 10);
return res;
}
/**
* @brief Get the Book object
*
* @param name
* @param writer
* @param total
* @return bookptr
*/
bookptr getBook(int bookId, char *name, char *writer, int total) {
bookptr p = (bookptr)malloc(sizeof(book));
p->bookId = bookId;
p->count = p->total = total;
strcpy(p->name, name);
strcpy(p->writer, writer);
initQueue(&(p->borrower));
return p;
}
/**
- @brief 初始化队列
*
* @param q
*/
void initQueue(queue *q) {
(*q) = (queue)malloc(sizeof(que));
(*q)->count = 0;
(*q)->rear = (*q)->front = NULL;
}
/**
- @brief 入队
*
- @param q 队列
* @param rdr the node data
* @return int 1-success 0-error
*/
int enQueue(queue q, rcdptr rdr) {
if (q == NULL || rdr == NULL) {
return 0;
}
// save the node data
qNode *node = (qNode *)malloc(sizeof(qNode));
node->data = rdr;
node->next = NULL;
if (q->rear) {
q->rear->next = node;
}
q->rear = node;
if (q->front == NULL) {
q->front = node;
}
q->count++;
return 1;
}
/**
- @brief 出队
*
- @param q 队列
*/
void deQueue(queue q) {
if(q == NULL || q->front == NULL) {
return;
}
qNode *p = q->front;
q->front = p->next;
free(p);
= NULL;
q->count--;
}
/**
* @brief Get the Front object
*
* @param q the queue
* @return rcdptr the front data of the queue
*/
rcdptr getFront(queue q) {
if(q == NULL || q->front == NULL) {
return NULL;
}
return q->front->data;
}
/**
* @brief remove the elem of the queue by readerId
*
* @param q the queue
- @param readerId 借阅者 id
* @return int success return 1 else return 0
*/
int removeElem(queue q, int readerId) {
if(q == NULL || q->front == NULL) {
return 0;
}
qNode *p = q->front;
if(p->data->reader->readerId == readerId) {
q->front = p->next;
free(p);
q->count--;
return 1;
} else {
while(p->next) {
if(p->next->data->reader->readerId == readerId) {
qNode *temp = p->next;
p->next = temp->next;
free(temp);
q->count--;
return 1;
}
= p->next;
}
}
return 0;
}
/**
- @brief 菜单
*
*/
void menu() {
printf("\n ||****************************************MENU****************************************||\n");
printf(" || ||\n");
printf(" ||*********************************Librarian System***********************************||\n");
printf(" || ||\n");
printf(" ||====================================================================================||\n");
printf(" || ||\n");
printf(" ||>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Please enter your choice<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<||\n");
printf(" || ||\n");
printf(" ||====================================================================================||\n");
printf(" || 1.Add Book 2.Add Reader ||\n");
printf(" || ||\n");
printf(" || 3.Delete Book 4.Update Book ||\n");
printf(" || ||\n");
printf(" || 5.Check Book Detail Message 6.Print Tree Struction ||\n");
printf(" || ||\n");
printf(" || 7.Find Book List of Writer 8.Check Book List ||\n");
printf(" || ||\n");
printf(" || 9.Check Reader List 10.Destory BTree ||\n");
printf(" || ||\n");
printf(" || 11.Borrow Book 12.Return Book ||\n");
printf(" || ||\n");
printf(" || 13.Exit ||\n");
printf(" || ||\n");
printf(" ||====================================================================================||\n");
printf(" || ||\n");
printf(" ||****************************************END*****************************************||\n");
printf(" ||->> Please input your option:");
}
/**
* @brief fulsh
*
*/
void flush() {
char c;
while ((c = getchar()) != 10 && c != EOF);
}
/**
- @brief 检查输入并插入数据
*
* @param t BTree
* @param table hashtable
- @param k 关键字 k
* @param rec
*/
int checkAndInsert(BTree *t, Hashptr table, KeyType k, void *rec) {
resultptr p = searchBTree(*t, k);
if (p->tag) {
printf("\n\n\n\n\n||->> insert book failed! %d exist\n", k);
free(p);
return 0;
} else {
insertBTree(t, k, p->pt, p->i, rec);
// add hash
addHash(table, (bookptr)rec);
time_t t = time(NULL);
char time[25];
// 格式化时间
strftime(time, 25, "%Y-%m-%d %H:%M:%S", localtime(&t));
char *msg = catchString(4, time, " Add new book ", ((bookptr)rec)->name, "\n");
// 打印日志
writeLog(msg);
free(msg);
}
free(p);
return 1;
}
/**
- @brief 打印所有图书信息
*
* @param t BTree
*/
void printAllBook(BTree t) {
printf("\n\t--------------------------------------------------------------------------------------------\n");
printf("\t| bookId | bookName | bookWriter | bookCount | bookTotal |\n");
printf("\t--------------------------------------------------------------------------------------------\n");
printBookIndex(t);
}
/**
- @brief 打印所有图书信息-功能实现类
*
* @param t BTree
*/
void printBookIndex(BTree t) {
if (t) {
printBookIndex(t->ptr[0]);
for (int i = 1; i <= t->keynum; i++) {
printBook((bookptr)(t->record[i]));
printBookIndex(t->ptr[i]);
}
}
}
/**
- @brief 打印图书的详细信息(借阅情况)
*
* @param book book data
*/
void printBookDetail(bookptr book) {
printf("\n\t--------------------------------------------------------------------------------------------\n");
printf("\t| bookId | bookName | bookWriter | bookCount | bookTotal |\n");
printf("\t--------------------------------------------------------------------------------------------\n");
// 图书信息
printCompletBook(book);
printf("\tThe borrower list:\n");
qNode *qp = book->borrower->front;
printf("\t---------------------------------------------------------------------------------\n");
printf("\t| Id | Name | BorrowTime | ReturnTime |\n");
printf("\t---------------------------------------------------------------------------------\n");
// 打印借阅记录信息
while (qp) {
readerptr rdr = qp->data->reader;
printf("\t| %-8d | %-8s | %s | %s |\n", rdr->readerId, rdr->name, qp->data->borrowTime, qp->data->returnTime);
printf("\t---------------------------------------------------------------------------------\n");
qp = qp->next;
}
}
/**
- @brief 打印单条图书信息
*
* @param book book
*/
void printCompletBook(bookptr book) {
printf("\t| %-8d | %-15s | %-15s | %-8d | %-8d |\n", book->bookId, book->name, book->writer, book->count, book->total);
printf("\t--------------------------------------------------------------------------------------------\n");
}
/**
- @brief 打印单条图书信息 2
*
* @param book
*/
void printBook(bookptr book) {
printf("\t| %-8d | %-15.15s | %-15.15s | %-8d | %-8d |\n", book->bookId, book->name, book->writer, book->count, book->total);
printf("\t--------------------------------------------------------------------------------------------\n");
}
/**
- @brief 打印某作者的所有图书信息
- 通过 hash 作者名存储图书数据,可以直接找出该作者的所有图书作品信息
*
* @param table hashtable
* @param name writer name
* @return int success return 1 else return error
*/
int printBookByWriter(Hashptr table, char *name) {
if (table == NULL) {
return 0;
}
// get index
int index = hashFunc(name) % table->size;
bookNodeptr p = table->rcd[index];
printf("\n\t--------------------------------------------------------------------------------------------\n");
printf("\t| bookId | bookName | bookWriter | bookCount | bookTotal |\n");
printf("\t--------------------------------------------------------------------------------------------\n");
while (p) {
printBook(p->data);
= p->next;
}
return 1;
}
/**
- @brief 更新图书信息
*
* @param tb hashtable point
* @param book book point
* @param nm name
* @param wrtr writer
* @param tot total
* @return int success return 1 else return 0
*/
int updateBook(Hashptr tb, bookptr book, char *nm, char *wrtr, int tot) {
// 新的总量小于剩余量时更新失败
if(tb == NULL || book == NULL || !nm || !wrtr || tot < (book->total - book->count)) {
return 0;
}
removeHash(tb, book);
strcpy(book->name, nm);
strcpy(book->writer, wrtr);
// 新增的, 更新库存和总库存
int inc = tot - book->total;
book->count += inc;
book->total = tot;
// update hash data
addHash(tb, book);
return 1;
}
/**
- @brief 保存数据至文件
*
* @param t book BTree
* @param rt reader Btree
*/
void save(BTree t, BTree rt) {
FILE *fp_index = fopen("index.dat", "wb");
FILE *fp_queue = fopen("queue.dat", "wb");
FILE *fp_reader = fopen("reader.dat", "wb");
saveBTree(t, fp_index, fp_queue);
saveReader(rt, fp_reader);
fclose(fp_reader);
fclose(fp_index);
fclose(fp_queue);
}
/**
- @brief 保存图书信息
*
* @param t BTree
* @param index file point index
* @param queue file point queue
*/
void saveBTree(BTree t, FILE *index, FILE *queue) {
if(t == NULL) {
return;
}
bookptr book;
BTree visit;
for (int i = 1; i <= t->keynum; i++) {
book = (bookptr)(t->record[i]);
if(book == NULL || book->bookId == 0) {
continue;
}
// 向index.dat中写入book data
fwrite(book, sizeof(struct book), 1, index);
int count = book->borrower->count;
// 向queue.dat写入借阅数量
fwrite(&count, sizeof(int), 1, queue);
qNode *p = book->borrower->front;
while (p) {
// 向queue.dat写入借阅信息
fwrite(p->data->reader, sizeof(reader), 1, queue);
fwrite(p->data->borrowTime, sizeof(char) * 30, 1, queue);
fwrite(p->data->returnTime, sizeof(char) * 30, 1, queue);
= p->next;
}
}
for (int i = 0; i <= t->keynum && t->ptr[i]; i++) {
// 向下层递归save
visit = t->ptr[i];
saveBTree(visit, index, queue);
}
}
/**
* @brief
*
* @param rt
* @param fp_reader
*/
void saveReader(BTree rt, FILE *fp_reader) {
if (rt == NULL) {
return;
}
readerptr rdr = NULL;
// 访问节点
BTree visit = NULL;
// 向reader.dat写入借阅者信息
for (int i = 1; i <= rt->keynum; i++) {
rdr = (readerptr)(rt->record[i]);
fwrite(rdr, sizeof(reader), 1, fp_reader);
}
// 递归保存借阅者信息
for (int i = 0; i <= rt->keynum && rt->ptr[i]; i++) {
visit = rt->ptr[i];
saveReader(visit, fp_reader);
}
}
/**
- @brief 从文件中加载数据
*
* @param t book BTree
* @param rt reader BTree
* @param tb hashtable
* @return int success return 1 else return 0
*/
int load(BTree *t, BTree *rt, Hashptr tb) {
if(t == NULL || rt == NULL || tb == NULL) {
return 0;
}
FILE *fp_index = fopen("index.dat", "rb");
FILE *fp_reader = fopen("queue.dat", "rb");
// not file point
if(!fp_index || !fp_reader) {
return 0;
}
// load reader data
loadReader(rt);
// 重名
bookptr book = (bookptr)malloc(sizeof(struct book));
int result = 0;
readerptr rdp = (readerptr)malloc(sizeof(reader));
// load book data
while(fread(book, sizeof(struct book), 1, fp_index)) {
result = loadBTree(t, book);
addHash(tb, book);
// 借阅队列
initQueue(&(book->borrower));
int cnt = 0;
fread(&cnt, sizeof(int), 1, fp_reader);
while(cnt-- >= 0) {
// load record
rcdptr rc = (rcdptr)malloc(sizeof(rcd));
fread(rdp, sizeof(reader), 1, fp_reader);
fread(rc->borrowTime, sizeof(char) * 30, 1, fp_reader);
fread(rc->returnTime, sizeof(char) * 30, 1, fp_reader);
resultptr res = searchBTree(*rt, rdp->readerId);
if(res->tag == 1) {
rc->reader = (readerptr)(res->pt->record[res->i]);
enQueue(book->borrower, rc);
} else {
// 已经load了,rt里应该都有
insertReader(rt, rdp);
rc->reader = rdp;
enQueue(book->borrower, rc);
rdp = (readerptr)malloc(sizeof(reader));
}
}
book = (bookptr)malloc(sizeof(struct book));
}
free(rdp);
free(book);
fclose(fp_reader);
fclose(fp_index);
return result;
}
/**
- @brief 加载借阅者数据
*
* @param rt BTree point of reader
* @return int success return 1 else return 0
*/
int loadReader(BTree *rt) {
FILE *fp_reader = fopen("queue.dat", "rb");
// not exist the file
if(fp_reader == NULL) {
return 0;
}
readerptr reader = (readerptr)malloc(sizeof(struct reader));
// overflow
if(reader == NULL) {
return 0;
}
int res = 0;
// 先查找,找不到再插入
while(fread(reader, sizeof(reader), 1, fp_reader)) {
resultptr result = (resultptr) searchBTree(*rt, reader->readerId);
// not find
if(result->tag == 0) {
res = insertBTree(rt, reader->readerId, result->pt, result->i, reader);
reader = (readerptr)malloc(sizeof(struct reader));
}
}
// close the file stream
fclose(fp_reader);
free(reader);
return res;
}
/**
- @brief 加载图书数据
*
* @param t BTree
* @param book book data point
* @return int success return 1 else return 0
*/
int loadBTree(BTree *t, bookptr book) {
resultptr res = searchBTree(*t, book->bookId);
int result = 1;
if (res->tag == 0) {
result = (insertBTree(t, book->bookId, res->pt, res->i, book) == SUCCESS);
}
return result;
}
/**
- @brief 拼接字符串
*
- @param n 数目
- @param … 可变参
* @return char*
*/
char *catchString(int n, ...) {
// 获取变参
va_list list;
// 读取变参,遍历和读取栈区中的参数列表
va_start(list, n);
char *msg = (char *)malloc(sizeof(char) * 70);
char *p = NULL;
int i = 0;
for (; i < 70; i++) {
msg[i] = 0;
}
while (n--) {
// 使list指针指向变参列表中的下一个参数, 并返回其值
= va_arg(list, char *);
// 拼接字符串
strcat(msg, p);
}
// 允许函数返回
va_end(list);
return msg;
}
/**
- @brief 打印日志
*
* @param msg msg
*/
void writeLog(char *msg) {
FILE *p = fopen("Librarian.log", "a");
if (p) {
fputs(msg, p);
}
fclose(p);
}
/**
* @brief hash
*
* @param table hashtable
- @param book 图书信息
* @return int if exist return 0 else return 1
*/
int addHash(Hashptr table, bookptr book) {
if (table == NULL || book == NULL) {
return 0;
}
// check if is exist
if (existHash(table, book)) {
return 0;
}
int index = hashFunc(book->writer) % table->size;
bookNodeptr p = (bookNodeptr)malloc(sizeof(bookNode));
// save data to hashtable
p->data = book;
p->next = table->rcd[index];
table->rcd[index] = p;
table->count++;
return 1;
}
/**
* @brief check is exist hash
*
* @param table hashtable
* @param book data
* @return int 1-exist 0-unexist
*/
int existHash(Hashptr table, bookptr book) {
// hash
int index = hashFunc(book->writer) % table->size;
bookNodeptr p = table->rcd[index];
if (p == NULL) {
return 0;
}
while (p->next) {
if (p->next->data->bookId == book->bookId) {
return 1;
}
= p->next;
}
return 0;
}
/**
* @brief hash func
*
* @param data hash data
* @return int result
*/
int hashFunc(char *data) {
int t = 0;
for (int i = 0; data[i]; i++) {
+= data[i];
}
return t ^ (t >> 16);
}
/**
- @brief 移除 hash
*
* @param table hashtable
* @param book book data
* @return int success return 1 else return 0
*/
int removeHash(Hashptr table, bookptr book) {
// get index
int index = hashFunc(book->writer) % table->size;
bookNodeptr p = table->rcd[index];
if (p == NULL) {
return 0;
}
// find it
if (p->data->bookId == book->bookId) {
bookNodeptr temp = p;
table->rcd[index] = p->next;
free(temp);
return 1;
}
// not find but can find the next to find it
while (p->next) {
if (p->next->data->bookId == book->bookId) {
bookNodeptr temp = p->next;
p->next = temp->next;
free(temp);
return 1;
}
= p->next;
}
return 0;
}
/**
- @brief 新增借阅者信息
*
* @param t BTree
* @param rec reader data point
*/
int insertReader(BTree *t, void *rec) {
readerptr rdp = (readerptr)rec;
resultptr res = searchBTree(*t, rdp->readerId);
// 如果找到
if(res->tag) {
printf("\n\n\n\n\n||->> insert reader failed! %d exist\n", rdp->readerId);
free(res);
return 0;
} else {
insertBTree(t, rdp->readerId, res->pt, res->i, rec);
time_t t = time(NULL);
char time[25];
// format time style
strftime(time, 25, "%Y-%m-%d %H:%M:%S", localtime(&t));
char id[10];
itoa(rdp->readerId, id, 10);
char *msg = catchString(4, time, " Add new reader ", id, "\n");
// write log
writeLog(msg);
free(msg);
free(res);
return 1;
}
}
/**
- @brief 打印所有借阅者信息
*
* @param rt BTree
*/
void printAllReader(BTree rt) {
printf("\t------------------------------\n");
printf("\t| Id | Name |\n");
printf("\t------------------------------\n");
printReaderIndex(rt);
}
/**
- @brief 打印所有借阅者信息-功能信息
*
* @param rt BTree
*/
void printReaderIndex(BTree rt) {
if (rt) {
printReaderIndex(rt->ptr[0]);
int i = 1;
for (; i <= rt->keynum; i++) {
readerptr rdr = (readerptr)(rt->record[i]);
printf("\t| %-8d | %-8s |\n", rdr->readerId, rdr->name);
printf("\t------------------------------\n");
// 递归打印子节点
printReaderIndex(rt->ptr[i]);
}
}
}
/**
- @brief 借书
*
* @param book book data
* @param rdr reader data point
* @return int success return 1 else return 0
*/
int borrowBook(bookptr book, readerptr rdr) {
// 库存不足
if (book == NULL || book->count <= 0) {
return 0;
}
rcdptr rc = (rcdptr)malloc(sizeof(rcd));
rc->reader = rdr;
time_t t = time(NULL);
strftime(rc->borrowTime, 25, "%Y-%m-%d %H:%M:%S", localtime(&t));
// 借书时限为30天
time_t t2 = t + 30 * 24 * 60 * 60;
strftime(rc->returnTime, 25, "%Y-%m-%d %H:%M:%S", localtime(&t2));
enQueue(book->borrower, rc);
book->count--;
return 1;
}
/**
- @brief 归还图书
*
* @param book book data
- @param readerid 借阅者 id
* @return int success return 1 else return 0
*/
int returnBook(bookptr book, int readerid) {
if (book == NULL) {
return 0;
}
if (removeElem(book->borrower, readerid)) {
book->count++;
return 1;
}
return 0;
}
7.4 主函数
main.c
# include <stdio.h>
# include <stdlib.h>
# include "header/BTree.h"
# include "header/Librarian.h"
int main() {
int option, res;
BTree t = getBTree();
BTree rdrt = getBTree();
Hashptr tb = (Hashptr)malloc(sizeof(HashTable));
tb->count = 0;
tb->size = HASH_TABLE_SIZE;
tb->rcd = (bookNodeptr *)malloc(sizeof(bookNodeptr) * tb->size);
for(int i = 0; i < HASH_TABLE_SIZE; i++) {
tb->rcd[i] = NULL;
}
if(load(&t, &rdrt, tb)) {
printf("\n\n\n\n\n||->> load data success! Please enter to get the menu!");
flush();
system("cls");
}
menu();
scanf("%d", &option);
while(option != 13) {
switch (option) {
// 添加图书
case 1: {
int bookId;
char name[30];
char writer[30];
int total;
system("cls");
flush(stdin);
printf("the bookId is ?\n");
scanf("%d", &bookId);
printf("the book name is ?\n");
scanf("%s", name);
printf("the writer name is ?\n");
scanf("%s", writer);
printf("what is the total?\n");
scanf("%d", &total);
bookptr book = getBook(bookId, name, writer, total);
system("cls");
res = checkAndInsert(&t, tb, book->bookId, book);
save(t, rdrt);
if(res == 1) {
printf("\n\n\n\n\n||->> insert book success! Please enter to get the menu!");
}
break;
}
// 添加借阅者
case 2: {
int userId;
char readerName[20];
system("cls");
printf("the reader id is ?\n");
scanf("%d", &userId);
printf("the reader name is ?\n");
scanf("%s", readerName);
system("cls");
readerptr reader = (readerptr)malloc(sizeof(struct reader));
reader->readerId = userId;
strcpy(reader->name, readerName);
res = insertReader(&rdrt, reader);
save(t, rdrt);
if(res == 1) {
printf("\n\n\n\n\n||->> insert reader success! Please enter to get the menu!");
}
break;
}
// 删除图书
case 3: {
system("cls");
printf("input the bookId of which you want to delete:\n");
int bookId;
scanf("%d", &bookId);
system("cls");
resultptr result = searchBTree(t, bookId);
if(result->tag == 0) {
printf("not find the book(id is %d)\n", bookId);
} else {
char bookName[50];
strcpy(bookName, ((bookptr)(result->pt->record[result->i]))->name);
removeHash(tb, (bookptr)((result)->pt->record[result->i]));
deleteBTree(&t, &(result->pt), result->i);
printf("\n\n\n\n\n||->> delete book success! Please enter to get the menu!");
time_t tm = time(NULL);
char time[25];
strftime(time, 25, "%Y-%m-%d %H:%M:%S", localtime(&tm));
char *msg = catchString(4, time, " delete book ", bookName, "\n");
writeLog(msg);
free(msg);
}
save(t, rdrt);
break;
}
// 更新图书
case 4: {
system("cls");
printf("input the booId of which you want to update:\n");
int updateId, updateCount;
char name[50];
char writer[50];
scanf("%d", &updateId);
resultptr updateResult = searchBTree(t, updateId);
if(updateResult->tag == 0) {
printf("not find the book(id is %d)\n", updateId);
} else {
system("cls");
printf("the book name is ?\n");
scanf("%s", name);
printf("the writer name is ?\n");
scanf("%s", writer);
printf("what is the total?\n");
scanf("%d", &updateCount);
if(updateBook(tb, (bookptr)(updateResult->pt->record[updateResult->i]), name, writer, updateCount)) {
time_t tm = time(NULL);
char time[25],num[10];
itoa(updateId,num,10);
strftime(time, 25, "%Y-%m-%d %H:%M:%S", localtime(&tm));
char *msg = catchString(4, time, " Update book ", num, "\n");
writeLog(msg);
free(msg);
printf("\n\n\n\n\n||->> update book success! Please enter to get the menu!");
} else {
printf("\n\n\n\n\n||->> update book failed! Please enter to get the menu!");
}
}
save(t, rdrt);
break;
}
// 查看图书详情
case 5: {
system("cls");
printf("please input the bookId of which you want to see detail:\n");
int bookId;
scanf("%d", &bookId);
resultptr result = searchBTree(t, bookId);
if(result->tag == 0) {
printf("not find the book(id is %d)\n", bookId);
} else {
printBookDetail((bookptr)(result->pt->record[result->i]));
}
break;
}
// 打印索引结构
case 6: {
system("cls");
printf("choose the book(B) or the reader(R): \n");
char choose;
flush(stdin);
while((choose = getchar()) != 'B' && choose != 'b' && choose != 'R' && choose != 'r') {
flush();
printf("please input B or R \n");
}
printf("\n");
BTree temp = (choose == 'B' || choose == 'b') ? t : rdrt;
printBTree(temp);
break;
}
// 查看某个作者的所有图书
case 7: {
system("cls");
char writer[30];
printf("input the writer you want to see \n");
scanf("%s", writer);
system("cls");
printBookByWriter(tb, writer);
break;
}
// 查看图书列表
case 8: {
system("cls");
printAllBook(t);
break;
}
// 查看借阅者列表
case 9: {
system("cls");
printAllReader(rdrt);
break;
}
// 销毁索引
case 10: {
system("cls");
printf("if you want to destroy the index, which will remove all data(y/n):\n");
char choose;
flush(stdin);
while((choose = getchar()) != 'Y' && choose != 'y' && choose != 'N' && choose != 'n') {
flush();
printf("please input y or n \n");
}
if(choose == 'Y' || choose == 'y') {
destroyBTree(t);
= getBTree();
printf("\n\n\n\n\n||->> destroy successfully! \n");
save(t, rdrt);
time_t tm = time(NULL);
char time[25];
strftime(time, 25, "%Y-%m-%d %H:%M:%S", localtime(&tm));
char *msg = catchString(2,time," Destroy all.\n");
writeLog(msg);
free(msg);
}
break;
}
// 借书
case 11: {
system("cls");
int bookId;
int userId;
printf("input the readerId: \n");
scanf("%d", &userId);
resultptr res = searchBTree(rdrt, userId);
if(res->tag == 0) {
printf("not find the reader(id is %d)\n", userId);
break;
} else {
readerptr rdp = (readerptr)(res->pt->record[res->i]);
free(res);
flush(stdin);
printf("input bookId. until enter [ctrl+z] to complete \n");
while(~scanf("%d", &bookId)) {
resultptr result = searchBTree(t, bookId);
if(result->tag == 0) {
printf("not find the book(id is %d) \n", bookId);
} else {
if(borrowBook((bookptr)(result->pt->record[result->i]), rdp)) {
time_t tm = time(NULL);
char time[25];
strftime(time, 25, "%Y-%m-%d %H:%M:%S", localtime(&tm));
char *msg = catchString(6, time, " Borrow ", ((bookptr)(result->pt->record[result->i]))->name, " readerId : ", int2str(userId),"\n");
writeLog(msg);
free(msg);
printf("borrow book successfully \n");
} else {
printf("borrow book failed \n");
}
}
free(result);
printf("input bookId. until enter [ctrl+z] to complete \n");
}
}
save(t, rdrt);
break;
}
// 还书
case 12: {
system("cls");
int userId;
printf("input the readerId:\n");
scanf("%d", &userId);
printf("input bookId. until enter [ctrl+z] to complete \n");
int bookId;
int cnt;
while(~(cnt = scanf("%d", &bookId))) {
if(!cnt) {
flush();
printf("illegal input \n");
continue;
}
resultptr result = searchBTree(t, bookId);
if(result->tag == 0) {
printf("not find the book(id is %d) \n", bookId);
} else {
if(returnBook((bookptr)(result->pt->record[result->i]), userId)) {
printf("return book successfully \n");
time_t tm = time(NULL);
char time[25];
strftime(time, 25, "%Y-%m-%d %H:%M:%S", localtime(&tm));
char *msg = catchString(6, time, " Return ", ((bookptr)(result->pt->record[result->i]) )->name, " readerId : ", int2str(userId),"\n");
writeLog(msg);
free(msg);
}
}
printf("input bookId. until enter [ctrl+z] to complete \n");
}
save(t, rdrt);
break;
}
// 退出
case 13:
return 0;
// input overflow option range
default: {
system("cls");
printf("\n\n\n\n\n||->> illegal command! [enter] to continue to input order! \n");
break;
}
}
flush();
getchar();
system("cls");
menu();
scanf("%d", &option);
}
getchar();
return 0;
}
♻️ 资源
大小: 2.04MB
➡️ 资源下载:https://download.csdn.net/download/s1t16/87430274
注:如当前文章或代码侵犯了您的权益,请私信作者删除!