本专栏目的
- 更新C/C++的相关的项目
前言
- C语言的图书权限管理系统完结(进阶的一点后面更新),1000多行代码(核心代码5、600行);
- 本设计是一个比较综合的练习,用到数据结构(顺序表、链表、静态链表)、文件、排序、查找、字符串操作等方面的知识;
- 由于本项目是本人一步一步边做边写笔记,难免会有失误,有失误看最后下载网盘代码即可;
- 制作很不容易,望大家点赞 + 收藏 + 关注。
简要说明
本设计是一个比较综合的练习,用到顺序表、链表、静态链表、文件、排序、查找、字符串操作等方面的知识。
- 大体架构
- 简要关系
- 编译器:vs2022
前置知识
-
之前更新的知识点
-
void*,如下:
-
int a = 10; void* p = a; // 说明p存储的是** 字节序列 **,告诉编译器不关注int类型,只是存储字节序列 printf("%d\n", (int)p); // 告诉编译器说,p存储的字节序列转化成int类型输出
-
从上可以说明,void* 在C语言中是万能数据类型,可以存储任意数据
文章目录
- Part1
- 0、创建入口函数main
- 1、菜单
- 2、数据存储--顺序表
- 3、数据存储--链表
- 4、用户管理需求
- 5、用户管理框架搭建
- 用户信息
- 用户管理
- 检验框架
- 6、用户功能的实现
- 从文件加载用户信息成功
- 用户信息显示实现
- 用户信息修改
- 用户信息输入
- 用户密码修改
- 用户信息删除
- 小结
- 7、框架的整理
- part2
- 0、读者管理需求
- 1、读者管理框架搭建
- 读者信息
- 读者管理
- 2、读者功能的实现
- 从文件加载数据
- 读者信息显示
- 读者信息输入
- 读者信息查询
- 读者信息删除
- 读者信息修改
- 小结
- Part3
- 0、图书管理需求
- 1、图书模块搭建
- 图书信息
- 框架的搭建
- 2、图书功能实现
- 图书信息输入实现
- 图书信息修改实现
- 图书查询功能实现
- 按书号查询
- 按书名查询
- 按作者名查询
- 按出版社查询
- 汇总功能实现
- 小结
- Part4
- 0、图书流通子系统需求
- 1、图书流通信息
- 2、框架搭建
- 3、功能实现
- 4、测试
- Part5
- 0、权限需求
- 1、权限分析与准备
- 2、功能实现
- 图书流通
- 图书管理
- 读者模块
- 用户模块
- 3、保存完善
- 4、进阶
- 代码网盘
Part1
0、创建入口函数main
创建**“main.c”**
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
return 0;
}
1、菜单
创建**"Menu.h"和“Menu.c”**文件,目录如下:
在"Menu.h"文件中,创建不同菜单的API接口
#ifndef MENU_H_
#define MENU_H_
/*@Autor: sheep*/
/*@菜单*/
// 首页欢迎菜单
int welcomeMenu();
// 主菜单
int mainMenu();
// 用户管理
int userMenu();
// 读者管理
int readerMenu();
// 图书管理
int managerMenu();
// 图书流通
int circulate();
// 图书查询:图书管理中的功能
int searchMenu();
#endif // !MENU_H_
API实现在"Menu.c"文件中
#define _CRT_SECURE_NO_WARNINGS
#include "Menu.h"
#include <stdlib.h>
#include <stdio.h>
// 选择
static int select()
{
// 定义要输入的选择变量,赋值为-1的原因:返回未初始化的变量很危险
int n = -1;
// 清屏
printf("请输入您的选项>>: ");
int res = scanf("%d", &n);
return n;
}
int welcomeMenu()
{
system("cls");
printf("******欢迎使用图书信息馆管理系统****\n");
printf("* 1.用户登录 *\n");
printf("* 2.退出系统 *\n");
printf("************************************\n");
return select();
}
int mainMenu()
{
system("cls");
printf("************************************\n");
printf("* 1.用户管理 *\n");
printf("* 2.读者管理 *\n");
printf("* 3.图书管理 *\n");
printf("* 4.图书流通管理 *\n");
printf("* 5.退出管理 *\n");
printf("************************************\n");
return select();
}
int userMenu()
{
system("cls");
printf("************************************\n");
printf("* 1.用户信息输入 *\n");
printf("* 2.用户信息修改 *\n");
printf("* 3.用户信息删除 *\n");
printf("* 4.用户信息显示 *\n");
printf("* 5.用户密码修改 *\n");
printf("* 0.返回主菜单 *\n");
printf("************************************\n");
return select();
}
int readerMenu()
{
system("cls");
printf("************************************\n");
printf("* 1.读者信息输入 *\n");
printf("* 2.读者信息修改 *\n");
printf("* 3.读者信息删除 *\n");
printf("* 4.读者信息显示 *\n");
printf("* 5.读者密码修改 *\n");
printf("* 0.返回主菜单 *\n");
printf("************************************\n");
return select();
}
int managerMenu()
{
system("cls");
printf("************************************\n");
printf("* 1.图书信息输入 *\n");
printf("* 2.图书信息修改 *\n");
printf("* 3.图书信息查询 *\n");
printf("* 4.统计汇总 *\n");
printf("* 0.返回主菜单 *\n");
printf("************************************\n");
return select();
}
int searchMenu()
{
system("cls");
printf("************************************\n");
printf("* 1.按书号查询 *\n");
printf("* 2.按书名查询 *\n");
printf("* 3.按作者查询 *\n");
printf("* 4.按出版社查询 *\n");
printf("* 0.返回主菜单 *\n");
printf("************************************\n");
return select();
}
int circulate()
{
system("cls");
printf("************************************\n");
printf("* 1.借书处理 *\n");
printf("* 2.还书处理 *\n");
printf("* 0.返回主菜单 *\n");
printf("************************************\n");
return select();
}
效果
在main函数中分别调用这些API接口,如下:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "Menu.h"
int main()
{
welcomeMenu();
mainMenu();
userMenu();
readerMenu();
managerMenu();
circulate();
searchMenu();
return 0;
}
效果如下:
因为在上面实现菜单的时候,在每一个输出前都调用了**"system(“cls)”**这个API,为了展示,我们可以先注释掉这些,这个时候输出就可以看到全部菜单显示了:
2、数据存储–顺序表
实现功能:增删
创建**”SeqList.h“和”SeqList.c"**,目录如下:
"SeqList.h"文件中创建‘增删’API
#ifndef SEQLIST_H_
#define SEQLIST_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SEQLIST_INIT_SIZE 10
typedef struct SeqList {
int size; //当前元素
int capacity; //容量大小
void** data; //数据
}SeqList;
// 初始化顺序表
void SeqList_init(SeqList* plist);
// 插入数据
void SeqList_pushBack(SeqList* plist, void* value);
// 清空顺序表
void SeqList_clear(SeqList* plist);
// 销毁顺序表
void SeqList_destory(SeqList* plist);
#endif // !SEQLIST_H_
"SeqList.c"文件中实现
#include "SeqList.h"
// 初始化顺序表
void SeqList_init(SeqList* plist)
{
plist->size = 0;
plist->capacity = SEQLIST_INIT_SIZE;
plist->data = malloc((plist->capacity) * sizeof(void*));
memset(plist->data, 0, sizeof(void*) * plist->capacity);
}
// 插入数据
void SeqList_pushBack(SeqList* plist, void* value)
{
if (plist->size == plist->capacity) {
// 扩容
plist->data = realloc(plist->data, (plist->capacity + 10) * sizeof(void*));
if (!plist->data) {
printf("%s 扩容申请内存失败\n", __FUNCTION__);
return;
}
// 数据变化
plist->capacity += 10;
plist->data[plist->size++] = value;
}
else {
plist->data[plist->size++] = value;
}
}
// 清空顺序表
void SeqList_clear(SeqList* plist)
{
plist->size = 0;
}
void SeqList_destory(SeqList* plist)
{
// 判断是否是空指针
if (!plist) return;
// 清空数据
plist->capacity = 0;
plist->size = 0;
free(plist->data);
plist->data = NULL;
}
插入数据验证
3、数据存储–链表
暂时先实现增删
创建"List.h"和”List.c“文件,结构如图:
在”List.h"文件中定义API接口,如下:
#ifndef LIST_H_
#define LIST_H_
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 有头链表
/*@节点*/
typedef struct Node {
void* data; // 数据
struct Node* next; // 下一个节点
}Node;
/*@链表*/
typedef struct List {
int size;
Node* front; // 头节点
Node* tail; // 尾节点
}List;
// 初始化链表
void list_init(List* plist);
// 插入元素
void list_pushBack(List* plist, void* data);
// 删除元素
void list_removeOne(List* plist, void* value);
// 销毁链表
void list_destory(List* plist);
#endif // !LIST_H_
在“List.c”文件中实现API接口
#include "List.h"
#include <stdbool.h>
#include <stdlib.h>
// 本文件用
static bool isVailed(List* plist)
{
if (NULL == plist) {
printf("function: %s List* is NULL\n", __FUNCTION__);
return false;
}
return true;
}
Node* createNode(void* data)
{
if (NULL == data) {
printf("function is: %s, data is NULL\n", __FUNCTION__);
return NULL;
}
// 创建节点
Node* new_node = malloc(sizeof(Node));
if (!new_node) {
printf("function is %s, malloc failed\n", __FUNCTION__);
return;
}
new_node->next = NULL; // 防止为空指针
// 赋值数据
new_node->data = data;
return new_node;
}
void list_init(List* plist)
{
// 检查是否为空指针
if (!isVailed(plist)) {
return;
}
// calloc会将里面参数赋值为 0
plist->front = plist->tail = calloc(1, sizeof(Node*));
if (NULL == plist->front) {
printf("function: %s 申请内存失败\n", __FUNCTION__);
return;
}
// 赋值
plist->size = 0;
}
void list_pushBack(List* plist, void* data)
{
if (!isVailed(plist)) {
return;
}
// 尾插法
Node* new_node = createNode(data);
plist->tail->next = new_node; // 插入
plist->tail = new_node; // 尾节点移位
plist->size++;
}
void list_removeOne(List* plist, void* value)
{
if (!isVailed(plist)) {
printf("function is %s, list is null\n", __FUNCTION__);
return;
}
// 获取指针
Node* cur = plist->front->next;
Node* pre = plist->front;
while (cur) {
if (cur->data == value) {
pre->next = cur->next;
break;
}
// 更新节点
pre = cur;
cur = cur->next;
}
// 删除
if (NULL != cur) {
pre->next = cur->next;
free(cur);
}
}
void list_destory(List* plist)
{
if (!isVailed(plist)) {
printf("function is: %s, list is null\n", __FUNCTION__);
return;
}
Node* cur = plist->front->next;
Node* pre = plist->front;
// 删除每一个节点
while (cur) {
free(pre);
pre = cur;
cur = cur->next;
}
// 销毁最后一个
free(cur);
}
插入数据验证
4、用户管理需求
应包括用户信息输入、用户信息修改、用户信息删除、用户信息显示、用户密码修改等功能。其中”系统管理员“可以使用上述全部功能,”图书管理员“和”普通读者“只能使用”用户密码修改功能
“。
用户管理菜单要求包括如下选项
**********************************
1.用户信息输入
2.用户信息修改
3.用户信息删除
4.用户信息显示
5.用户密码修改
0.返回主菜单
**********************************
"用户管理子系统"涉及到的”用户信息文件“要求如下表所示,其中用户名时学号或教工号,用户密码要求由八位字母和数字组成。系统开始运行时要将该文件打开,将其内容读进来,形成一个单链表,在系统运行结束时再将单链表中的内容写回相应文件。
- 用户信息文件
用户ID | 用户密码 | 用户类型 |
---|---|---|
1998017 | 1234567q | 1(系统管理员) |
2001021 | S1234567 | 2(图书管理员) |
1988003 | 123W5678 | 3(普通读者) |
20172568 | ZZ123qq9 | 3(普通读者) |
5、用户管理框架搭建
用户信息
**创建“User.h"和”User.c"**文件,目录如下:
用户信息储存与处理相关API定义”User.h“
#ifndef USER_H_
#define USER_H_
// 用户类型编号
enum UserType {
SYS_MANAGER = 1,
BOOK_MANAGER,
READER
};
typedef struct User {
unsigned long long user_id; // 用户ID
char user_password[10]; // 用户密码
int type; // 用户类型
}User;
//创建一个未初始化用户
User* createUser();
//打印一个用户信息
void userMessPrintf(User* user);
#endif // !USER_H_
用户信息储存API实现"User.c"
#include "User.h"
#include <stdio.h>
#include <malloc.h>
User* createUser()
{
User* new_user = calloc(1, sizeof(User));
if (!new_user) {
printf("function is: %s, calloc failed\n", __FUNCTION__);
return new_user;
}
return new_user;
}
void userMessPrintf(User* user)
{
printf("%-10llu %-10s %d\n", user->user_id, user->user_password, user->type);
}
用户管理
更具要求,用链表来储存用户信息、用户管理菜单一共有6个功能实现,这里先搭好框架。
创建”UserManager.h"和“UserManager.c”文件
UserManager.h的实现
#ifndef USERMANAGER_H_
#define USERMANAGER_H_
#include "List.h"
// 创建一个用户管理结构体
typedef struct UserManage {
List listManager; // 用户用链表存储
}UserManage;
// 用户信息初始化,如:创建链表,从文件中读取用户信息
void userManage_init(UserManage* userm, const char* filename);
// 用户管理处理工作
void userManage_operator(UserManage* userm);
// 用户信息输入
void userManage_input(UserManage* userm);
// 用户信息修改
void userManage_modify(UserManage* userm);
// 用户信息删除
void userManage_delete(UserManage* userm);
// 用户信息显示
void userManage_show(UserManage* userm);
// 用户密码修改
void userManage_modifyPassword(UserManage* userm);
#endif // !USERMANAGER_H_
UserManager.c的实现
#include "UserManager.h"
#include "List.h"
#include "Menu.h"
#include <Windows.h>
void userManage_init(UserManage* userm, const char* filename)
{
// 初始化链表
list_init(&userm->listManager);
}
void userManage_operator(UserManage* userm)
{
bool isQuit = false;
while (!isQuit) {
int op = userMenu();
switch (op)
{
case 1: // 用户信息输入
userManage_input(userm);
break;
case 2: // 用户信息修改
userManage_modify(userm);
break;
case 3: // 用户信息删除
userManage_delete(userm);
break;
case 4: // 用户信息显示
userManage_show(userm);
break;
case 5: // 用户密码修改
userManage_modifyPassword(userm);
break;
case 0:
printf("欢迎下次使用\n");
Sleep(1000); //暂停1s
isQuit = true;
break;
}
}
}
void userManage_input(UserManage* userm)
{
}
void userManage_modify(UserManage* userm)
{
}
void userManage_delete(UserManage* userm)
{
}
void userManage_show(UserManage* userm)
{
}
void userManage_modifyPassword(UserManage* userm)
{
}
检验框架
在**“main.c”中测试**
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "UserManager.h"
int main()
{
UserManage manage;
userManage_init(&manage, NULL);
// 开始管理
userManage_operator(&manage);
return 0;
}
结果
注意:这里菜单依然注释了**“system(“cls”)”**这条语句
6、用户功能的实现
从文件加载用户信息成功
首先准备测试数据,新建数据文件:
在user.txt中写入部分数据,中间用Tab键隔开
账号 密码 用户类型
1998017 1234567q 1
2001021 S1234567 2
1988003 123W5678 3
20172568 ZZ123qq9 3
根据要求,要加载文件中数据到用户【链表】,故在“userManage.h”中添加新的API
// 用户信息加载(从文件中加载)
void userManage_load_from_file(UserManage* userm, const char* filename);
在**”userManage.c“**文件中实现:
// 在初始化中调用
void userManage_init(UserManage* userm, const char* filename)
{
// 初始化链表
list_init(&userm->listManager);
// 加载数据
userManage_load_from_file(userm, filename);
}
void userManage_load_from_file(UserManage* userm, const char* filename)
{
// 打开文件
FILE* fp = fopen(filename, "r");
if (!fp) {
perror("打开文件失败\n");
return;
}
// 读取文件数据
char buff[BUFSIZ] = { 0 }; // 定义缓冲区
// 读取标题数据: buff 0x00000044680ff470 "账号\t密码\t用户类型\n" char[512]
fgets(buff, BUFSIZ, fp);
// 字符串形式读取数据
while (!feof(fp)) //读取到文件末尾
{
// 一行一行读取
fgets(buff, BUFSIZ, fp);
// 分割
User* user = createUser();
int res = sscanf(buff, "%llu %s %d", &user.user_id, user.user_password, &user.type);
if (res < 0) {
printf("function is %s, file read failed\n", __FUNCTION__);
return;
}
// 插入链表中
list_pushBack(&userm->listManager, user);
}
// 读完关闭文件
fclose(fp);
}
用户信息显示实现
由于后面经常需要用链表中的数据进行不同处理,故在链表中进行添加回调函数API,在**”List.h“**中添加:
// 处理每个数据的回调函数
typedef void (*LISTCALLBACK)(void*);
void list_transform(List* plist, LISTCALLBACK callback);
在”List.c“文件中进行实现
void list_transform(List* plist, LISTCALLBACK callback)
{
if (!isVailed(plist)) {
printf("function is %s, list is null\n", __FUNCTION__);
return;
}
Node* cur = plist->front->next;
// 将每一个数据通过回调函数进行处理
while (cur) {
callback(cur->data);
cur = cur->next;
}
}
测试,实现用户查询功能,看是否真的在文件中读取到了用户信息:
// userManage.c
void userManage_show(UserManage* userm)
{
// 输出标题
printf("%-10s %-10s %-10s\n", "用户ID", "密码", "用户类型");
// 运用回调函数输出
list_transform(&userm->listManager, userMessPrintf); // userMessPrintf在User.h/c文件中实现了
}
效果展示:
-
main函数传递文件代码:
-
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include "UserManager.h" int main() { UserManage manage; userManage_init(&manage, "./data/user.txt"); // 传入文件代码 // 开始管理 userManage_operator(&manage); return 0; }
-
效果展示:
-
用户信息加载成功
用户信息修改
用户信息有三个字段,用户ID、密码、类型,其中只能修改类型,ID是不可能修改的,密码能修改但是后面5
是专门修改密码的,故只能修改类型。
修改就要去查找,通过用户ID查找无疑是最准确的选择,又由于我们链表不只是为了储存用户信息,还可能存储其他信息,故为了应对不同的查找,在List.h中定义查找函数,用过什么查找可以通过不同回调函数决定查找什么。
List.h定义查找API:
// 查找,等于data的数据,然后做处理
typedef bool (*CMPCALLBACK)(void*, void*);
void* list_find(List* plist, CMPCALLBACK cmp, void* data);
List.c实现
void* list_find(List* plist, CMPCALLBACK cmp, void* data)
{
if (!isVailed(plist)) {
printf("function is %s, plist is null\n", __FUNCTION__);
return;
}
Node* cur = plist->front->next;
while (cur) {
if (cmp(cur->data, data)) {
return cur->data;
}
cur = cur->next;
}
}
实现对信息的修改
// 比较回调函数
bool user_id_cmp(void* v1, void* v2)
{
User* u1 = (User*)v1;
unsigned long long find = (unsigned long long)v2;
if (u1->user_id == find) {
return true;
}
return false;
}
void userManage_modify(UserManage* userm)
{
unsigned long long find_userid;
int modify_type = -1;
printf("请输入你要修改的用户名ID >>: ");
int res = scanf("%llu", &find_userid);
if (res != 1) {
return;
}
// 寻找对于的数据
User* target_data = (User*)list_find(&userm->listManager, user_id_cmp, find_userid);
if (!target_data) {
printf("这个id不存在\n");
return;
}
// 修改数据
printf("请输入要修改的身份类型: ");
res = scanf("%d", &modify_type);
if (res != 1) {
return;
}
target_data->type = modify_type; // 修改类型
}
结果
修改成功,但是没有修改文件中的数据,这个后面我们再统一修改。
用户信息输入
userManage.c完成用户输入部分
void userManage_input(UserManage* userm)
{
User* new_user = createUser();
printf("请输入要创建用户的用户名ID>> ");
int res = scanf("%llu", &new_user->user_id);
if (res != 1) {
return;
}
printf("请输入要创建用户的密码(8位数,数字+字母)>> ");
res = scanf("%s", new_user->user_password);
if (res != 1) {
return;
}
printf("请输入要创建用户的用户类型(1:系统管理员, 2:图书管理员, 3:普通读者)>> ");
res = scanf("%d", &new_user->type);
// 插入最后
list_pushBack(&userm->listManager, new_user);
}
结果
用户密码修改
用户密码修改,需要先登录用户,故先实现用户的登入,这里先模拟,后面在按模块封装,一步一步来。
- 首先在userManage.h 和 userManage.c中添加检验是否登录成功的函数:
// userManage.h中
// 检查用户登录是否成功
bool check_user_login(UserManage* userm, unsigned long long user_id, const char* user_password);
// userManage.c中
// 检查用户是否能登录成功
static bool cmp_user(void* v1, void* v2)
{
User* u1 = (User*)v1;
User* u2 = (User*)v2;
return (u1->user_id == u2->user_id) && (strcmp(u1->user_password, u2->user_password) == 0);
}
bool check_user_login(UserManage* userm, unsigned long long user_id, const char* user_password)
{
User user;
user.user_id = user_id;
strcpy(user.user_password, user_password);
// 查找
User* res = list_find(&userm->listManager, cmp_user, &user);
if (!res) {
printf("用户名、密码输入错误\n");
return false;
}
return true;
}
- 接着,在“main.c”中模拟登录
int main()
{
UserManage manage;
userManage_init(&manage, "./data/user.txt");
int countMax = 3;
int curCount = 0;
while (true)
{
unsigned long long user_id = -1;
char passward[10] = { 0 };
printf("请输入登录用户ID>> ");
int res = scanf("%llu", &user_id);
if (res <= 0) exit(0);
printf("请输入登录的密码>> ");
res = scanf("%s", passward);
// 验证是否登录成功
if (check_user_login(&manage, user_id, passward)) { // 成功
printf("login success~~\n");
break;
}
else { // 失败
curCount++;
if (curCount >= countMax) {
printf("超过三次输入错误\n");
exit(0);
}
printf("登录失败, 您还剩下%d次机会\n", countMax - curCount);
}
}
// 开始管理
userManage_operator(&manage);
return 0;
}
- 运行,登录输出结果如下:
- 这个时候可以修改密码了,在修改密码前,我们首先需要存储当下登录的用户信息
// 在** userManage.h ** 文件中添加用户
typedef struct UserManage {
List listManager; // 用户用链表存储
User user; // 保存当前登录用户信息,由于是单机的,故只有一个用户
}UserManage;
在登录成功后进行储存
bool check_user_login(UserManage* userm, unsigned long long user_id, const char* user_password)
{
…………………………
// 登陆成功,保存当下用户信息
memcpy(&userm->user, res, sizeof(User));
return true;
}
- 最后就可以实现修改函数了
void userManage_modifyPassword(UserManage* userm)
{
char password[10] = { 0 };
printf("请输入要修改的密码>> ");
int res = scanf("%s", password);
if (res <= 0) {
return;
}
// 查找
User* user = list_find(&userm->listManager, cmp_user, &userm->user);
strcpy(user->user_password, password); // 修改密码
printf("修改密码成功~~\n");
}
实现结果:
用户信息删除
通过上面的做法,这个就简单了,如下:
void userManage_delete(UserManage* userm)
{
// 先不加权限
unsigned long long user_id;
printf("请输入要删除的用户ID>> ");
int res = scanf("%llu", &user_id);
if (res <= 0) {
return;
}
// 查找
User* user = list_find(&userm->listManager, user_id_cmp, user_id);
if (!user) {
printf("该用户ID不存在\n");
return;
}
// 删除
list_removeOne(&userm->listManager, user);
}
效果检查:
小结
这里,我们就完成第一个部分,不知道有多少人跟下来,大概功能基本实现了,后面就是不同功能之间的配合,权限之间的添加了。
7、框架的整理
main函数有些乱,我们可以进一步进行封装,封装比较简单,我就不一一讲解了,封装如下:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdbool.h>
#include <Windows.h>
#include "UserManager.h"
#include "Menu.h"
typedef struct ManageMent
{
UserManage userManage; // 用户管理
}ManageMent;
// 初始化
void management_init(ManageMent* m);
// 登录
void management_login(ManageMent* m);
// 操作
void management_run(ManageMent* m);
int main()
{
// 测试
ManageMent manage;
management_init(&manage);
management_run(&manage);
return 0;
}
void management_init(ManageMent* m)
{
userManage_init(&m->userManage, "./data/user.txt");
}
// 登录
void management_login(ManageMent* m)
{
int countMax = 3;
int curCount = 0;
while (true)
{
unsigned long long user_id = -1;
char passward[10] = { 0 };
printf("请输入登录用户ID>> ");
int res = scanf("%llu", &user_id);
if (res <= 0) exit(0);
printf("请输入登录的密码>> ");
res = scanf("%s", passward);
// 验证是否登录成功
if (check_user_login(m, user_id, passward)) { // 成功
printf("login success~~\n");
break;
}
else { // 失败
curCount++;
if (curCount >= countMax) {
printf("超过三次输入错误\n");
exit(0);
}
printf("登录失败, 您还剩下%d次机会\n", countMax - curCount);
}
}
}
// 操作
void management_run(ManageMent* m)
{
int op = welcomeMenu();
if (op == 1)
{
management_login(m);
}
else
{
exit(0);
}
bool isQuit = false;
while (!isQuit) {
int op = mainMenu();
switch (op)
{
case 1: // 用户管理
userManage_operator(&m->userManage.listManager);
break;
case 2: // 读者管理
readerMenu();
break;
case 3: // 图书管理
managerMenu();
break;
case 4: // 图书流通管理
circulateMenu();
break;
case 5: // 退出
isQuit = true;
break;
default:
printf("输入有误\n");
break;
}
if (isQuit) {
break;
}
}
}
part2
0、读者管理需求
读者管理子系统只有图书管理员使用,本模块应该包括读者信息输入、读者信息修改、读者信息删除、读者信息按名查询等功能。
读者管理菜单要求包括如下选项:
**********************************
1.读者信息输入
2.读者信息修改
3.读者信息删除
4.读者信息查询
5.读者信息显示
6.返回主菜单
**********************************
读者管理子系统设计道德读者信息文件要求如下表所示,其中读者号是学号或者教工号,教工可借书数为10本,学生可借书数为5本。图书管理员登陆时要讲该文件打开,将其内容读进来,形成一个单链表,推出系统时将单链表中的内容写回"读者信息文件"。
- 读者信息文件
读者号 | 读者名 | 单位 | 联系方式 | 可借书数 | 已借书数 |
---|---|---|---|---|---|
1998017 | 丁一 | 网络中心 | 13915082233 | 10 | 5 |
2001021 | 王二 | 图书馆 | 13145092236 | 10 | 3 |
1998003 | 张三 | 计算机学院 | 13745092237 | 10 | 8 |
20172568 | 李四 | 软件学院 | 13945092239 | 5 | 3 |
1、读者管理框架搭建
读者信息
首先创建类,封装书籍信息,“Reader.h"和”Reader.c“,目录如下:
- 定义实现**”Reader.h“**
#ifndef READER_H_
#define READER_H_
typedef struct Reader {
int reader_record_number; //记录号
int reader_number; //书号
char reader_name[20]; //书名
char reader_author[20]; //作者
char reader_publisher[20]; //出版社
int reader_collection_count; //藏书量
int reader_borrow_count; //图书借出数量
}Reader;
// 创建一个没用初始化书籍
Reader* createEmptyReader();
// 打印书籍信息
void printReaderMess(Reader* book);
#endif // !READER_H_
- 实现**“Reader.c”**d的功能
#include "Reader.h"
#include <stdio.h>
#include <stdlib.h>
Reader* createEmptyReader()
{
Reader* new_reader = calloc(1, sizeof(Reader));
if (!new_reader) {
printf("function is %s, calloc failed\n", __FUNCTION__);
return NULL;
}
return new_reader;
}
void printReaderMess(Reader* book)
{
printf("%-10d %-10s %-15s %-15s %-10d %d\n", book->reader_number, book->reader_name, book->reader_company, book->reader_community, book->reader_collection_count, book->reader_borrow_count);
}
读者管理
读者框架搭建其实和用户大差不差。
- 首先创建类,进行封装,创建**"ReaderManage.h"和“ReaderManage.c”**,创建目录如下:
- "ReaderManage.h"搭建
#ifndef BOOKMANAGER_H_
#define BOOKMANAGER_H_
#include "List.h"
#include "Book.h"
typedef struct ReaderManager
{
List bookManageList;
}ReaderManager;
// 读者信息初始化
void readerManage_init(ReaderManager* readerm, const char* filename);
// 读者信息加载
void readerManage_load_from_file(ReaderManager* readerm, const char* filename);
// 读者管理run
void readerManage_operate(ReaderManager* readerm);
// 读者信息输入
void readerManage_imput(ReaderManager* readerm);
// 读者信息修改
void readerManage_modify(ReaderManager* readerm);
// 读者信息删除
void readerManage_remove(ReaderManager* readerm);
// 读者信息查询
void readerManage_search(ReaderManager* readerm);
// 读者信息显示
void readerManage_show(ReaderManager* readerm);
#endif // !BOOKMANAGER_H_
- "ReaderManage.c"的实现
#include "ReaderManager.h"
void readerManage_init(ReaderManager* readerm, const char* filename)
{
}
void readerManage_load_from_file(ReaderManager* readerm, const char* filename)
{
}
void readerManage_operate(ReaderManager* readerm)
{
}
void readerManage_imput(ReaderManager* readerm)
{
}
void readerManage_modify(ReaderManager* readerm)
{
}
void readerManage_remove(ReaderManager* readerm)
{
}
void readerManage_search(ReaderManager* readerm)
{
}
void readerManage_show(ReaderManager* readerm)
{
}
2、读者功能的实现
从文件加载数据
- 首先创建文件,存放读者信息,文件目录如下:
- 显示功能的实现
ReaderManage.h
// 读者信息初始化
void readerManage_init(ReaderManager* readerm, const char* filename);
// 读者信息加载
void readerManage_load_from_file(ReaderManager* readerm, const char* filename);
ReaderManage.c
void readerManage_init(ReaderManager* readerm, const char* filename)
{
// 链表初始化
list_init(&readerm->bookManageList);
// 加载数据
readerManage_load_from_file(&readerm->bookManageList, filename);
}
void readerManage_load_from_file(ReaderManager* readerm, const char* filename)
{
FILE* fp = fopen(filename, "r");
if (!fp) {
printf("function is %s, file open failed\n", __FUNCTION__);
return;
}
// 读取头
char buff[BUFSIZ] = { 0 };
fgets(buff, BUFSIZ, fp);
// 读取内容
while (!feof(fp)) {
memset(buff, 0, sizeof(buff)); // 清空
// 读取一条数据
fgets(buff, BUFSIZ, fp);
// 创建
Reader* r = createEmptyReader();
// 赋值
int res = sscanf(buff, "%d %s %s %s %d %d", &r->reader_number, r->reader_name, r->reader_company, r->reader_community, &r->reader_collection_count, &r->reader_borrow_count);
if (res <= 0) {
return;
}
// 加入链表
list_pushBack(&readerm->bookManageList, r);
}
fclose(fp);
}
- 测试,这一次我们通调试的方法进行查看,是否读取成功,打入两个断点,调试查看
测试前,需要对main创建读者管理,并对其初始化,代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdbool.h>
#include <Windows.h>
#include "UserManager.h"
#include "ReaderManager.h"
#include "Menu.h"
typedef struct ManageMent
{
UserManage userManage; // 用户管理
ReaderManager readerManage; // 读者管理
}ManageMent;
// 初始化
void management_init(ManageMent* m);
// 登录
void management_login(ManageMent* m);
// 操作
void management_run(ManageMent* m);
int main()
{
ManageMent manage;
management_init(&manage);
management_run(&manage);
return 0;
}
void management_init(ManageMent* m)
{
userManage_init(&m->userManage, "./data/user.txt");
readerManage_init(&m->readerManage, "./data/reader.txt");
}
// 登录
void management_login(ManageMent* m)
{
int countMax = 3;
int curCount = 0;
while (true)
{
unsigned long long user_id = -1;
char passward[10] = { 0 };
printf("请输入登录用户ID>> ");
int res = scanf("%llu", &user_id);
if (res <= 0) exit(0);
printf("请输入登录的密码>> ");
res = scanf("%s", passward);
// 验证是否登录成功
if (check_user_login(m, user_id, passward)) { // 成功
printf("login success~~\n");
break;
}
else { // 失败
curCount++;
if (curCount >= countMax) {
printf("超过三次输入错误\n");
exit(0);
}
printf("登录失败, 您还剩下%d次机会\n", countMax - curCount);
}
}
}
// 操作
void management_run(ManageMent* m)
{
int op = welcomeMenu();
if (op == 1)
{
management_login(m);
}
else
{
exit(0);
}
bool isQuit = false;
while (!isQuit) {
int op = mainMenu();
switch (op)
{
case 1: // 用户管理
userManage_operator(&m->userManage.listManager);
break;
case 2: // 读者管理
readerManage_operate(&m->readerManage.bookManageList);
break;
case 3: // 图书管理
managerMenu();
break;
case 4: // 图书流通管理
circulateMenu();
break;
case 5: // 退出
isQuit = true;
break;
default:
printf("输入有误\n");
break;
}
if (isQuit) {
break;
}
}
}
测试结果,断点调试:
读者信息显示
上面方法可以学习vs的调试能力,但是具体要看有没有问题,还需要把他内容全部输出出来,故在这里我们先实现信息展示功能,还是用回调函数进行,代码如下:
void readerManage_show(ReaderManager* readerm)
{
// 标题
printf("%-10s %-10s %-15s %-15s %-10s %-10s\n", "读者号", "读者名", "单位", "联系方式", "可借书数量", "已借书数量");
// 读者信息打印
list_transform(&readerm->bookManageList, printReaderMess);
}
输出结果如下:
读者信息输入
void readerManage_input(ReaderManager* readerm)
{
Reader* reader = createEmptyReader();
printf("请输入读者号>> ");
int res = scanf("%d", &reader->reader_number);
printf("请输入读者名>> ");
res = scanf("%s", reader->reader_name);
printf("请输入单位>> ");
res = scanf("%s", reader->reader_company);
printf("请输入读者联系方式>> ");
res = scanf("%s", reader->reader_community);
printf("请输入读者可借书数量>> ");
res = scanf("%d", &reader->reader_borrow_count);
// 添加用户
list_pushBack(&readerm->bookManageList, reader);
printf("添加用户成功~~\n");
}
结果测试:
读者信息查询
要求:通过读者号查询,其实这一段也很简单,逻辑简单。
void readerManage_search(ReaderManager* readerm)
{
int reader_num = -1;
printf("请输入你要查询读者的读者号>> ");
int res = scanf("%d", &reader_num);
if (res <= 0) {
return;
}
// 查找
Reader* reader = list_find(&readerm->bookManageList, cmp_reader_num, reader_num);
if (!reader) {
printf("不存在该读者信息\n");
return;
}
// 显示
printf("%-10s %-10s %-15s %-15s %-10s %-10s\n", "读者号", "读者名", "单位", "联系方式", "可借书数量", "已借书数量"); // 这一句可以封装,因为不止一次使用了
printReaderMess(reader);
}
运行结果:
读者信息删除
这个和Part1的部分一样。
void readerManage_remove(ReaderManager* readerm)
{
int reader_num = -1;
printf("请输入你要删除读者的读者号>> ");
int res = scanf("%d", &reader_num);
if (res <= 0) {
return;
}
// 查询
Reader* reader = list_find(&readerm->bookManageList, cmp_reader_num, reader_num);
if (!reader) {
printf("不存在该读者的信息\n");
} // 这一部分可以封装,多次使用,这里为了初学的人好理解,就不封装了
// 移除
list_removeOne(&readerm->bookManageList, reader);
}
结果检测:
读者信息修改
这个需要根据实际业务,这里修改联系方式和可借书的数量。
代码比较冗余,本章小结中会提供一些改进方案,大家可以尝试区优化代码,也不难。
void readerManage_remove(ReaderManager* readerm)
{
int reader_num = -1;
printf("请输入你要删除读者的读者号>> ");
int res = scanf("%d", &reader_num);
if (res <= 0) {
return;
}
// 查询
Reader* reader = list_find(&readerm->bookManageList, cmp_reader_num, reader_num);
if (!reader) {
printf("不存在该读者的信息\n");
} // 这一部分可以封装,多次使用,这里为了初学的人好理解,就不封装了
// 移除
list_removeOne(&readerm->bookManageList, reader);
}
测试结果:
小结
- 这一部分其实和Part1恶的用户管理很相似,功能很多一样。
- 很多部分代码重复,比较冗余,比如说:
scanf
返回值处理,list_find
查找与数据修改,都可以用函数封装,当然有一部分也可以用宏去简化,难度不大,但是效果会好很多,请大家在完成上面部分的时候尝试简化代码。
Part3
0、图书管理需求
本模块至少应包括图书信息输入、图书信息修改、图书信息查询、汇总统计等功能。其它功能(如图书订阅、图书编目、新书通报等功能)可根据自身情况酌情实现。“图书管理员”可以使用本模块中的全部功能,“普通读者”和“系统管理员”只能使用其中的图书信息查询和图书数据统计功能(功能3和功能4),当普通用户选择其他功能时应该告知不能使用。
图书管理菜单至少要求包括如下选项:
**********************************
1.图书信息输入
2.图书信息修改
3.图书信息查询
4.汇总统计
5.返回主菜单
**********************************
在“2.图书信息修改”中,只要求提供对“藏书量”和“借出数"的修改功能。
如果在“图书管理”菜单中选择了"3.图书信息管理",系统应提示如下子菜单。
图书信息查询菜单应包含如下选项:
**********************************
1.按书号查询
2.按书名查询
3.按作者查询
4.按出版社查询
5.返回主菜单
**********************************
“图书管理子系统”设计“图书主文件”和书名索引表、作者索引表、出版社索引表三个次关键字索引表,分别如下面一系列图所示。系统开始运行时要将上述文件打开,将其内容读进来,分别存入四个一维数组中,在系统运行结束时再分别将四个一维数组中的内容写回相应文件。
- 图书主文件
记录号 | 书号 | 书名 | 作者 | 出版社 | 藏书量 | 借出数 | 指针1 | 指针2 | 指针3 |
---|---|---|---|---|---|---|---|---|---|
1 | 1021 | 数据库 | 杨艳 | 人民邮电 | 10 | 8 | 0 | 0 | 0 |
2 | 1014 | 数据结构 | 赵鹏 | 高等教育 | 9 | 7 | 0 | 0 | 0 |
3 | 1106 | 操作系统 | 金虎 | 人民邮电 | 8 | 6 | 0 | 0 | 1 |
4 | 1108 | 数据结构 | 高扬 | 清华大学 | 7 | 5 | 2 | 0 | 0 |
5 | 1203 | 程序设计 | 杨艳 | 高等教育 | 9 | 4 | 0 | 1 | 2 |
6 | 2105 | 数据库 | 金虎 | 清华大学 | 7 | 3 | 1 | 3 | 4 |
7 | 1012 | 数据结构 | 杨艳 | 人民邮电 | 8 | 2 | 4 | 5 | 3 |
8 | 0109 | 程序设计 | 赵鹏 | 清华大学 | 9 | 1 | 5 | 2 | 6 |
- 书名次关键字索引表
书名 | 链头指针 | 长度 |
---|---|---|
数据库 | 6 | 2 |
数据结构 | 7 | 3 |
操作系统 | 3 | 1 |
程序设计 | 8 | 2 |
- 作者次关键字索引表
作者 | 链头指针 | 长度 |
---|---|---|
杨艳 | 7 | 3 |
赵鹏 | 8 | 2 |
金虎 | 6 | 2 |
高扬 | 4 | 1 |
- 出版社次关键字索引表
出版社 | 链头指针 | 长度 |
---|---|---|
人民邮电 | 7 | 3 |
高等教育 | 5 | 2 |
清华大学 | 8 | 3 |
1、图书模块搭建
图书信息
- 首先创建“Book.h”和“Book.c”文件
- “Book.h”定义
注意:p1、p2、p3是索引,属于后面进阶的内容,先不用管,先写上。
#ifndef BOOK_H_
#define BOOK_H_
typedef struct Book {
int reacod_num; // 记录号
int book_num; // 书号码
char book_name[20]; // 书名
char book_author[20]; // 作者
char book_publish[20]; // 出版社
int book_count; // 藏书量
int book_borrow_cnt; // 借出数
int p1;
int p2;
int p3;
}Book;
// 创建一个空书
Book* createEmptyBook();
// 通过字符串创建书籍
Book* createBookFromString(const char* str);
// 打印书籍信息
void printBookMess(Book* book);
#endif // !BOOK_H_
- “Book.h”的实现
#include "Book.h"
#include <stdio.h>
#include <stdlib.h>
Book* createEmptyBook()
{
Book* new_book = calloc(1, sizeof(Book));
if (!new_book) {
printf("function is %s, calloc failed\n", __FUNCTION__);
return NULL;
}
return new_book;
}
Book* createBookFromString(const char* str)
{
Book* book = calloc(1, sizeof(Book));
if (!book) {
printf("function is %s, calloc failed\n", __FUNCTION__);
return NULL;
} // 可以封装成宏,建议尝试简化代码
int res = sscanf(str, "%d %d %s %s %s %d %d %d %d %d", &book->reacod_num, &book->book_num, book->book_name, book->book_author, book->book_publish, &book->book_count, &book->book_borrow_cnt, &book->p1, &book->p2, &book->p3);
if (res <= 0) {
printf("function is %s failed\n", __FUNCTION__);
free(book);
return NULL;
}
return book;
}
void printBookMess(Book* book)
{
printf("%-8d %-5d %-10s %-10s %-10s %-6d %-3d", book->reacod_num, book->book_num, book->book_name, book->book_author, book->book_publish, book->book_count, book->book_borrow_cnt);
}
框架的搭建
- 首先创建管理文件,“BookManager.c”和“BookManager.h”
- 初始化API定义与实现
**“BookManage.h”**定义
#ifndef BOOK_MANAGER_H_
#define BOOK_MANAGER_H_
#include "SeqList.h"
typedef struct BookManage
{
SeqList bookManages; // 图书信息存储
}BookManage;
// 图书信息初始化
void bookManage_init(BookManage* bookm, const char* filename);
// 图书信息加载,从文件
void bookManage_load_from_file(BookManage* bookm, const char* filename);
#endif // !BOOK_MANAGER_H_
**“BookManage.c”**实现
void bookManage_init(BookManage* bookm, const char* filename)
{
// 顺序表初始化
SeqList_init(&bookm->bookManages);
// 加载数据
bookManage_load_from_file(&bookm->bookManages, filename);
}
void bookManage_load_from_file(BookManage* bookm, const char* filename)
{
FILE* fp = fopen(filename, "r");
if (!fp) {
printf("function is %s, file open failed\n", __FUNCTION__);
return;
}
// 定义缓冲区
char buff[BUFSIZ] = { 0 };
// 读取文件头
fgets(buff, BUFSIZ, fp);
while (!feof(fp)) {
memset(buff, 0, sizeof(buff));
fgets(buff, BUFSIZ, fp);
// 插入
SeqList_pushBack(&bookm->bookManages, createBookFromString(buff));
}
fclose(fp);
}
- 由于第三点,图书信息的查询,是另外一个表,故这里内容比较多,可以细看。
**“BookManage.h”**定义
// 继续添加
// 图书信息操作
void bookManage_operator(BookManage* bookm);
// 图书信息输入
void bookManage_input(BookManage* bookm);
// 图书信息修改
void bookManage_modify(BookManage* bookm);
// 图书信息查询
void bookManage_search(BookManage* bookm);
// 图书信息汇总
void bookManage_collect(BookManage* bookm);
//----------- 图书查询部分API
// 按照书号查询
void bookManage_search_bookNo(BookManage* bookm);
// 按照书名查询
void bookManage_search_bookNmae(BookManage* bookm);
// 按照作者查询
void bookManage_search_authorName(BookManage* bookm);
// 按照出版社查询
void bookManage_search_publish(BookManage* bookm);
**“BookManage.cpp”**定义
void bookManage_operator(BookManage* bookm)
{
bool isQuit = false;
while (!isQuit) {
int op = managerMenu();
switch (op)
{
case 1: // 图书信息输入
bookManage_input(bookm);
break;
case 2: // 图书信息修改
bookManage_modify(bookm);
break;
case 3: // 图书信息查询
bookManage_search(bookm);
break;
case 4: // 图书信息汇总
bookManage_collect(bookm);
break;
case 0: // 退出
printf("欢迎下次使用\n");
isQuit = true;
break;
}
if (isQuit) {
break;
}
}
}
void bookManage_input(BookManage* bookm)
{
}
void bookManage_modify(BookManage* bookm)
{
}
void bookManage_search(BookManage* bookm)
{
bool isStop = false;
while (true) {
int op = searchMenu();
switch (op)
{
case 1: // 按书号查询
bookManage_search_bookNo(bookm);
break;
case 2: // 按书名查询
bookManage_search_bookNmae(bookm);
break;
case 3: // 按作者查询
bookManage_search_authorName(bookm);
break;
case 4: // 按出版社查询
bookManage_search_publish(bookm);
break;
case 0: // 退出
printf("欢迎下次使用\n");
isStop = true;
break;
}
if (isStop) {
break;
}
}
}
void bookManage_collect(BookManage* bookm)
{
}
//---------------------------------------
void bookManage_search_bookNo(BookManage* bookm)
{
}
void bookManage_search_bookNmae(BookManage* bookm)
{
}
void bookManage_search_authorName(BookManage* bookm)
{
}
void bookManage_search_publish(BookManage* bookm)
{
}
- 测试,看是否能实现菜单的切换
在“main.c"中添加图书功能,实现逻辑很简单,就是在初始化,run中修改添加,看代码肯容易看懂修改位置,代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdbool.h>
#include <Windows.h>
#include "UserManager.h"
#include "ReaderManager.h"
#include "BookManager.h"
#include "Menu.h"
typedef struct ManageMent
{
UserManage userManage; // 用户管理
ReaderManager readerManage; // 读者管理
BookManage bookManage; // 图书管理
}ManageMent;
// 初始化
void management_init(ManageMent* m);
// 登录
void management_login(ManageMent* m);
// 操作
void management_run(ManageMent* m);
int main()
{
ManageMent manage;
management_init(&manage);
management_run(&manage);
return 0;
}
void management_init(ManageMent* m)
{
userManage_init(&m->userManage, "./data/user.txt");
readerManage_init(&m->readerManage, "./data/reader.txt");
bookManage_init(&m->bookManage, "./data/book/books.txt");
}
// 登录
void management_login(ManageMent* m)
{
int countMax = 3;
int curCount = 0;
while (true)
{
unsigned long long user_id = -1;
char passward[10] = { 0 };
printf("请输入登录用户ID>> ");
int res = scanf("%llu", &user_id);
if (res <= 0) exit(0);
printf("请输入登录的密码>> ");
res = scanf("%s", passward);
// 验证是否登录成功
if (check_user_login(m, user_id, passward)) { // 成功
printf("login success~~\n");
break;
}
else { // 失败
curCount++;
if (curCount >= countMax) {
printf("超过三次输入错误\n");
exit(0);
}
printf("登录失败, 您还剩下%d次机会\n", countMax - curCount);
}
}
}
// 操作
void management_run(ManageMent* m)
{
int op = welcomeMenu();
if (op == 1)
{
management_login(m);
}
else
{
exit(0);
}
bool isQuit = false;
while (!isQuit) {
int op = mainMenu();
switch (op)
{
case 1: // 用户管理
userManage_operator(&m->userManage.listManager);
break;
case 2: // 读者管理
readerManage_operate(&m->readerManage.bookManageList);
break;
case 3: // 图书管理
bookManage_operator(&m->bookManage.bookManages);
break;
case 4: // 图书流通管理
circulateMenu();
break;
case 5: // 退出
isQuit = true;
break;
default:
printf("输入有误\n");
break;
}
if (isQuit) {
break;
}
}
}
- 测试效果
2、图书功能实现
这一部分功能的实现,和上面逻辑上是一样的,但是使用的数据结构不同,这里使用顺序表进行存储,并且,本人给初学者提供了优化地方,这次代码实现比较冗余,请大家在做的时候进行代码优化,减少代码的冗余。
图书信息输入实现
void bookManage_input(BookManage* bookm)
{
Book* book = createEmptyBook();
printf("请输入(书号、书名、作者、出版社、藏书量)>>");
int res = scanf("%d %s %s %s %d", &book->book_num, book->book_name, book->book_author, book->book_publish, &book->book_count);
if (res != 5) {
return;
}
// 插入
SeqList_pushBack(&bookm->bookManages, book);
printf("添加图书成功~~\n");
}
这个不好检测,需要等写完查询信息的时候一起测试。
图书信息修改实现
void bookManage_modify(BookManage* bookm)
{
int book_n = -1;
printf("请输入你要修改图书的图书号>>");
int res = scanf("%d", &book_n);
if (res <= 0) {
return;
}
// 查找
for (int i = 0; i < bookm->bookManages.size; i++) {
Book* tmp = (Book*)bookm->bookManages.data[i];
if (book_n == tmp->book_num) {
printf("请输入(藏书量、借出数)>");
res = scanf("%d %d", &tmp->book_count, &tmp->book_borrow_cnt);
if (res != 2) {
return;
}
printf("修改图书成功~~\n");
}
}
}
这个不好检测,需要等写完查询信息的时候一起测试。
图书查询功能实现
按书号查询
void bookManage_search_bookNo(BookManage* bookm)
{
int book_n = -1;
printf("请输入你要查询的图书号>>");
int res = scanf("%d", &book_n);
if (res <= 0) {
return;
}
// 查找, 可以进一步封装,大家可以思考,尝试,我这里仅仅实现功能
for (int i = 0; i < bookm->bookManages.size; i++) {
Book* tmp = (Book*)bookm->bookManages.data[i];
if (book_n == tmp->book_num) {
printf("%-8s %-5s %-10s %-10s %-10s %-6s %s\n", "记录号", "书号", "书名", "作者", "出版社", "藏书量", "借出数");
printBookMess(tmp); // 打印
break;
}
}
}
测试,结合输入、修改一起测试。
按书名查询
查询方式逻辑很相似,大家可以进一步封装。
void bookManage_search_bookNmae(BookManage* bookm)
{
// 很多相同,可以进行封装
char bookName[20] = { 0 };
printf("请输入你要查询的作者>>");
int res = scanf("%s", bookName);
if (res <= 0) {
return;
}
printf("%-8s %-5s %-10s %-10s %-10s %-6s %s\n", "记录号", "书号", "书名", "作者", "出版社", "藏书量", "借出数"); // 可以封装
bool isFind = false;
// 查找, 可以进一步封装,大家可以思考,尝试,我这里仅仅实现功能
for (int i = 0; i < bookm->bookManages.size; i++) {
Book* tmp = (Book*)bookm->bookManages.data[i];
if (strcmp(tmp->book_name, bookName) == 0) {
printBookMess(tmp);
isFind = true;
}
}
if (!isFind)
printf("不存在该图书信息~~\n");
}
按作者名查询
查询方式逻辑很相似,大家可以进一步封装。
void bookManage_search_authorName(BookManage* bookm)
{
// 很多相同,可以进行封装
char author[20] = { 0 };
printf("请输入你要查询的作者>>");
int res = scanf("%s", author);
if (res <= 0) {
return;
}
printf("%-8s %-5s %-10s %-10s %-10s %-6s %s\n", "记录号", "书号", "书名", "作者", "出版社", "藏书量", "借出数");
bool isFind = false;
// 查找, 可以进一步封装,大家可以思考,尝试,我这里仅仅实现功能
for (int i = 0; i < bookm->bookManages.size; i++) {
Book* tmp = (Book*)bookm->bookManages.data[i];
if (strcmp(tmp->book_author, author) == 0) {
printBookMess(tmp);
isFind = true;
}
}
if (!isFind)
printf("不存在该图书信息~~\n");
}
按出版社查询
查询方式逻辑很相似,大家可以进一步封装。
void bookManage_search_publish(BookManage* bookm)
{
// 很多相同,可以进行封装
char publish[20] = { 0 };
printf("请输入你要查询的作者>>");
int res = scanf("%s", publish);
if (res <= 0) {
return;
}
printf("%-8s %-5s %-10s %-10s %-10s %-6s %s\n", "记录号", "书号", "书名", "作者", "出版社", "藏书量", "借出数"); // 可以封装
bool isFind = false;
// 查找, 可以进一步封装,大家可以思考,尝试,我这里仅仅实现功能
for (int i = 0; i < bookm->bookManages.size; i++) {
Book* tmp = (Book*)bookm->bookManages.data[i];
if (strcmp(tmp->book_publish, publish) == 0) {
printBookMess(tmp);
isFind = true;
}
}
if (!isFind)
printf("不存在该图书信息~~\n");
}
汇总功能实现
void bookManage_collect(BookManage* bookm)
{
// 这里只统计可借书的数量
int sum = 0;
for (int i = 0; i < bookm->bookManages.size; i++) {
Book* tmp = (Book*)bookm->bookManages.data[i];
sum += tmp->book_count;
}
printf("还剩下【%d】本书可借\n", sum);
}
测试结果:
小结
- 这部分逻辑上和Part1和Part2功能很相似,跟到这里的朋友应该很容易做出来了;
- 强烈建议:思考代码优化,简化代码,对初学者来说能学到不少东西(提示:函数、宏)。
Part4
0、图书流通子系统需求
至少应该包括借书处理和还书处理功能。其他功能(如预约处理、语气处理等功能)可根据自身情况酌情实现。本模块由“图书管理员”使用,普通用户只能使用预约处理功能(如果有预约功能)。
“图书流通管理”菜单至少要求包括如下几项:
**********************************
1.借书处理
2.还书处理
5.返回主菜单
**********************************
“图书流通管理子系统”涉及“还书信息文件“,如下表所示,系统开始运行时要将文件打开,将其内容读进来,建立单链表,在系统运行结束时再将该单链表中的内容写回响应文件。本模块运行时还涉及”读者信息文件“和"图书主文件"。
- 借还书信息文件
读者号 | 书号 | 借书日期 | 还书日 | 备注 |
---|---|---|---|---|
1998017 | 1021 | 2022/03/21 | 2022/04/15 | |
2001021 | 1014 | 2022/03/25 | 过期未还 | |
1988003 | 1106 | 2022/03/28 | 2022/04/16 | |
20172568 | 1108 | 2022/06/09 | 2022/06/22 |
1、图书流通信息
- 首先创建**.h和.c文件**,如图:
- “circulate.h”
#ifndef CIRCULATE_H_
#define CIRCULATE_H_
typedef struct Circulate
{
unsigned long long reader_num; // 读者号
int book_num; // 书号
char borrow_date[20]; // 借书日期
char return_data[20]; // 还书日
char comment[20]; // 备注
}Circulate;
// 创建空对象
Circulate* createEmptyCirulate();
// 通过字符串创建
Circulate* createCircuateFromString(const char* str);
// 打印
void printCirulateMess(Circulate* data);
#endif // !CIRCULATE_H_
- “circulate.c”
#include "Circulate.h"
#include <stdio.h>
#include <stdlib.h>
Circulate* createEmptyCirulate()
{
Circulate* _new = calloc(1, sizeof(Circulate));
if (!_new) {
printf("function is %s, calloc failed\n", __FUNCTION__);
return NULL;
}
return _new;
}
Circulate* createCircuateFromString(const char* str)
{
Circulate* _new = calloc(1, sizeof(Circulate));
if (!_new) {
printf("function is %s, calloc failed\n", __FUNCTION__);
return NULL;
}
int res = sscanf(str, "%llu %d %s %s %s", &_new->reader_num, &_new->book_num, _new->borrow_date, _new->return_data, _new->comment);
if (res <= 0) {
free(_new);
return NULL;
}
return _new;
}
void printCirulateMess(Circulate* data)
{
printf("%-8llu %-8d %-10s %-10s %s\n", data->reader_num, data->book_num, data->borrow_date, data->return_data, data->comment);
}
2、框架搭建
这个部分有一个难点,就是借书和还书的时候需要更新图书状态,图书管理在Part3部分,故需要存储图书管理的指针,具体如下:
- 首先创建文件,如下:
- "CirculateManage.h"搭建
#ifndef CIRCULATEMANAGE_H_
#define CIRCULATEMANAGE_H_
#include "List.h"
#include "BookManager.h"
typedef struct CirculateManage {
List cirManage; //借书还书列表
BookManage* bookManage; //图书管理,需要联通借书、还书信息
}CirculateManage;
// 初始化
void circulateManage_init(CirculateManage* cirm, const char* filename, BookManage* bookm);
// 加载数据
void circulateManage_load(CirculateManage* cirm, const char* filename);
// 流通操作
void circulateManage_operator(CirculateManage* cirm);
// 借书处理
void circulateManage_borrow(CirculateManage* cirm);
// 还书处理
void circulateManage_return(CirculateManage* cirm);
#endif // !CIRCULATEMANAGE_H_
- "CirculateManage.c"搭建
#include "CirculateManage.h"
#include "Circulate.h"
#include "Menu.h"
#include "Book.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
void circulateManage_init(CirculateManage* cirm, const char* filename, BookManage* bookm)
{
}
void circulateManage_load(CirculateManage* cirm, const char* filename)
{
}
void circulateManage_operator(CirculateManage* cirm)
{
}
void circulateManage_borrow(CirculateManage* cirm)
{
}
void circulateManage_return(CirculateManage* cirm)
{
}
3、功能实现
这个功能比较少,但是难点在于如何实现与图书馆里的互通,这里就不再分点了,代码不多。
#include "CirculateManage.h"
#include "Circulate.h"
#include "Menu.h"
#include "Book.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
void circulateManage_init(CirculateManage* cirm, const char* filename, BookManage* bookm)
{
list_init(&cirm->cirManage); // 链表初始化
cirm->bookManage = bookm;
// 加载
circulateManage_load(cirm, filename);
}
void circulateManage_load(CirculateManage* cirm, const char* filename)
{
FILE* fp = fopen(filename, "r");
if (!fp) {
perror("file open failed\n");
return;
}
char buff[BUFSIZ] = { 0 };
// 读取头
fgets(buff, BUFSIZ, fp);
// 读取内容,添加进链表
while (!feof(fp)) {
memset(buff, 0, sizeof(buff));
fgets(buff, BUFSIZ, fp);
list_pushBack(&cirm->cirManage, createCircuateFromString(buff));
}
fclose(fp);
}
void circulateManage_operator(CirculateManage* cirm)
{
bool isDone = false;
while (!isDone) {
int op = circulateMenu();
switch (op)
{
case 1: // 借书处理
circulateManage_borrow(cirm);
break;
case 2: // 还书处理
circulateManage_return(cirm);
break;
case 0: // 返回
printf("欢迎下次使用~~\n");
isDone = true;
break;
}
if (isDone) {
break;
}
}
}
void circulateManage_borrow(CirculateManage* cirm)
{
// 创建
Circulate* _new = createEmptyCirulate();
printf("请输入借书的(读者号、书号、借书日期)>>");
int res = scanf("%llu %d %s", &_new->reader_num, &_new->book_num, _new->borrow_date);
if (res != 3) {
return;
}
// 插入信息
list_pushBack(&cirm->cirManage, _new);
// 图书借书数量+1
for (int i = 0; i < cirm->bookManage->bookManages.size; i++) {
Book* book = (Book*)cirm->bookManage->bookManages.data[i];
if (book->book_num == _new->book_num) {
book->book_borrow_cnt++;
break;
}
}
printf("插入借书信息成功~~\n");
}
bool cmp_cir(Circulate* v1, Circulate* v2)
{
return (v1->reader_num == v2->reader_num) && (v1->book_num == v2->book_num);
}
void circulateManage_return(CirculateManage* cirm)
{
Circulate cir;
printf("请输入还书的(读者号、书号)>");
int res = scanf("%llu %d", &cir.reader_num, &cir.book_num);
if (res <= 0) {
return;
}
// 查找是否有该书记录
Circulate* pList = list_find(&cirm->cirManage, cmp_cir, &cir);
if (!pList) {
printf("不存在该书的的借书消息\n");
return;
}
// 还书,移除链表
list_removeOne(&cirm->cirManage, pList);
// 还书,借书量-1
for (int i = 0; i < cirm->bookManage->bookManages.size; i++) {
Book* book = (Book*)cirm->bookManage->bookManages.data[i];
if (book->book_num == cir.book_num) {
book->book_borrow_cnt--;
break;
}
}
printf("还书信息成功~~\n");
}
4、测试
- 借书:
- 还书:
Part5
0、权限需求
“普通读者”只能选“用户管理”功能中“用户密码修改”子功能和“图书管理”子系统中“图书信息查询”子功能;
"图书管理员"可以选择“读者管理”、“图书管理”、“图书流通管理”功能;
“系统管理员”只能选择“用户管理”和"图书管理"子系统中"图书信息查询子功能"
需要根据用户类型显示相应的菜单,请用户选择功能,或者用同一的主菜单,但不允许用户使用没有相应权限的功能。
当退出系统是,需要将各个链表和数组中的内容写回相应的文件。
1、权限分析与准备
- 在“User.h”和“User.c”中定义判断身份函数,如下:
// User.h
// 判断你普通用户
bool user_isCommon(User* user);
// 判断系统管理用户
bool user_isSysAdmin(User* user);
// 判断图书管理员
bool user_isBookAdmin(User* user);
// User.c
bool user_isCommon(User* user)
{
return user->type == Comon;
}
bool user_isSysAdmin(User* user)
{
return user->type == SYS_MANAGER;
}
bool user_isBookAdmin(User* user)
{
return user->type == BOOK_MANAGER;
}
这里又有一个难点,就是怎么在不同的管理功能模块中,获取用户类型呢?
可以在初始化中传递相应的参数,但是这里为了简单,只实现一个简单的方法,采用全局变量的形势,在UserManage.c中前面定义:
User* get_user = NULL; // 全局变量,指针,在用户登录上使用
// 在登录模块,登录成功后赋值类型
get_user = res;
get_user->type == res->type; // 保存用户类型
那该如何在不同文件中使用呢?
// 在不同模块(文件)使用只需要在使用的地方前加上
extern User* get_user;
2、功能实现
图书流通
图书流通模块最简单,只允许图书管理员使用,故先实现这个功能。
- 首先在“CirculateManage.c”中定义静态函数
static bool canOp()
{
extern User* get_user;
if (!user_isBookAdmin(get_user)) {
printf("您没有此权限\n");
return false;
}
return true;
}
- 分别在借书和还书前判断有没有权限
void circulateManage_borrow(CirculateManage* cirm)
{
// 权限判断
if (!canOp()) {
return;
}
……………………………………………………………………
}
void circulateManage_return(CirculateManage* cirm)
{
// 权限判断
if (canOp()) {
return;
}
………………………………
}
- 测试
图书管理
- 这部分除了查询外,其他只有图书管理员有权限
// 定义判断函数,
static bool canOp()
{
extern User* get_user;
if (!user_isBookAdmin(get_user)) {
printf("您没有权限~~\n");
return false;
}
return true;
}
- 后面分别在各个功能前加上判断,如下:
// 判断
if (!canOp()) {
return;
}
- 测试:
读者模块
这个模块只有图书管理员有这个权限,故原理一样,代码如下:
// 判断函数,ReaderManage.c头上实现
static bool canOp()
{
extern User* get_user;
if (!user_isBookAdmin(get_user)) {
printf("您没有此权限,只有图书管理员有这个权限~~\n");
return false;
}
return true;
}
// 在每一个功能之前加上判断
if (canOp()) {
return;
}
用户模块
只有系统管理员有这个权限,除修改用户密码外,但是代码还是一样:
// 判断, 因为就是usermange.c中,故不用extern获取了,所以也不用封装,在相应的功能前加上即可。
if (!user_isSysAdmin(get_user)) {
printf("您没有权限,只有系统管理员有~~\n");
return;
}
3、保存完善
这一步,就是去掉我们之前注释菜单前的清空页面处理,system("cls")
。
接下来就是保存到文件中
- 首先创建退出模块,在每一个模块都定义,最后在main函数中调用退出函数。
// 封装退出模块,其中每一个内容
void management_quit(ManageMent* m)
{
// 每个文件定义退出, 这里为了简单,直接传递文件名的方式
userManage_quit(&m->userManage, "./data/user.txt");
readerManage_quit(&m->readerManage, "./data/reader.txt");
bookManage_quit(&m->bookManage, "./data/book/books.txt");
circulateManage_quit(&m->circulateManage, "./data/circulate.txt");
exit(0);
}
- 实现
// usermanage.c
void userManage_quit(UserManage* umage)
{
//保存文件
FILE* fp = fopen(umage->filename, "w");
if (!fp)
{
perror("file open failed");
return;
}
fputs("账户名\t密码\t权限类型\n", fp);
Node* curNode = umage->userlist.front->next;
while (curNode)
{
user_save(curNode->data, fp);
curNode = curNode->next;
}
list_transfrom(&umage->userlist, user_print);
fclose(fp);
}
// readermanage.c
void readerManage_quit(ReaderManager* readerm, const char* filename)
{
// 打开文件
FILE* fp = fopen(filename, "w");
if (!fp) {
printf("funcation is %s, 打开文件失败\n", __FUNCTION__);
return;
}
// 定义缓冲区
char buff[BUFSIZ] = { 0 };
// 输出表头
fputs("readerNo\treaderName\tdept\ttel\tcnt\tcnt\n", fp);
// 输出内容
Node* cur = readerm->bookManageList.front->next;
while (cur) {
memset(buff, 0, sizeof(buff));
Reader* tmp = cur->data;
// 存储
sprintf(buff, "%d\t%s\t%s\t%s\t%d\t%d\n", tmp->reader_number, tmp->reader_name, tmp->reader_company, tmp->reader_community, tmp->reader_collection_count, tmp->reader_borrow_count);
fputs(buff, fp);
cur = cur->next;
}
fclose(fp);
}
// circulatemanage.c
void circulateManage_quit(CirculateManage* cirm, const char* filename)
{
// 打开文件
FILE* fp = fopen(filename, "w");
if (!fp) {
printf("funcation is %s, 打开文件失败\n", __FUNCTION__);
return;
}
// 定义缓冲区
char buff[BUFSIZ] = { 0 };
// 输出表头
fputs("读者号\t书号\t结束日期\t还书日期\t备注\n", fp);
// 输出内容
Node* cur = cirm->cirManage.front->next;
while (cur) {
memset(buff, 0, sizeof(buff));
Circulate* tmp = cur->data;
// 存储
sprintf(buff, "%llu\t%d\t%s\t%s\t%s\n", tmp->reader_num, tmp->book_num, tmp->borrow_date, tmp->return_data, tmp->comment);
fputs(buff, fp);
cur = cur->next;
}
fclose(fp);
}
// bookmanage.c
void bookManage_quit(BookManage* bookm, const char* filename)
{
// 打开文件
FILE* fp = fopen(filename, "w");
if (!fp) {
printf("funcation is %s, 打开文件失败\n", __FUNCTION__);
return;
}
// 定义缓冲区
char buff[BUFSIZ] = { 0 };
// 输出表头
fputs("记录号\t书号\t书名\t作者\t出版社\t藏书量\t借出数\t指针1\t指针2\t指针3", fp);
// 输出内容
for (int i = 0; i < bookm->bookManages.size; i++) {
memset(buff, 0, sizeof(buff));
Book* book = bookm->bookManages.data[i];
sprintf(buff, "%d\t%d\t%s\t%s\t%s\t%d\t%d\t%d\t%d\t%d\n", book->reacod_num, book->book_num, book->book_name, book->book_author, book->book_publish, book->book_count, book->book_borrow_cnt, book->p1, book->p2, book->p3);
fputs(buff, fp);
}
fclose(fp);
}
- 简单测试,登录1990817账号,添加用户,结果如图:
4、进阶
这一部分主要是在第三部分图书搜索的时候,为不同的搜索方式分别建立索引(这里用顺序表),这一部分后面实现 🤠🤠🤠🤠🤠🤠🤠🤠🤠🤠🤠🤠🤠🤠,大家也可以思考一下如何实现,要求如下:
-
在输入图书信息时建立图书主文件,在图书主文件中记录号从1开始,根据设计要求,在建立图书文件的同时,需要建立一个主关键字(书号)索引表。索引表按书号升序排列(用插入排序法),索引表可以先在内存中用一推数组实现,最后再将相应内容一并写入(外存)文件。
-
根据设计要求,图书文件除了主关键字(书号)索引表外,还需要建立书名、作者、出版社三个次关键字索引表。次关键字索引表可采用头插法建立,具体做法是:根据一个主文件的记录,将要建立索引的次关键字与对应的次关键字索引表中的次关键字(如书名、作者、出版社)进行比较,若有相等的,就将主文件中的相应指针修改为索引表中的当前链头指针,并修改相应索引表中的链头指针为当前主文件的记录指针(即记录号),同时将长度加1;若没有相等的,就将主文件中的相应指针置为0,并在相应次关键字索引表中增加与该次关键字相关的一条记录,该记录的链头指针置为当前主文件的记录号,而将长度置为1。
代码网盘
通过网盘分享的文件:C-图书管理系统.zip
链接: https://pan.baidu.com/s/1hv6y4xiVdOLkj8GPr0OFIQ?pwd=nvpv 提取码: nvpv