C项目--带权限的图书管理系统(1000多行代码,代码数据可下载,极其适合初学练手)

本专栏目的

  • 更新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

创建**“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用户密码用户类型
19980171234567q1(系统管理员)
2001021S12345672(图书管理员)
1988003123W56783(普通读者)
20172568ZZ123qq93(普通读者)

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丁一网络中心13915082233105
2001021王二图书馆13145092236103
1998003张三计算机学院13745092237108
20172568李四软件学院1394509223953

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
11021数据库杨艳人民邮电108000
21014数据结构赵鹏高等教育97000
31106操作系统金虎人民邮电86001
41108数据结构高扬清华大学75200
51203程序设计杨艳高等教育94012
62105数据库金虎清华大学73134
71012数据结构杨艳人民邮电82453
80109程序设计赵鹏清华大学91526
  • 书名次关键字索引表
书名链头指针长度
数据库62
数据结构73
操作系统31
程序设计82
  • 作者次关键字索引表
作者链头指针长度
杨艳73
赵鹏82
金虎62
高扬41
  • 出版社次关键字索引表
出版社链头指针长度
人民邮电73
高等教育52
清华大学83

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.返回主菜单
**********************************

“图书流通管理子系统”涉及“还书信息文件“,如下表所示,系统开始运行时要将文件打开,将其内容读进来,建立单链表,在系统运行结束时再将该单链表中的内容写回响应文件。本模块运行时还涉及”读者信息文件“和"图书主文件"。

  • 借还书信息文件
读者号书号借书日期还书日备注
199801710212022/03/212022/04/15
200102110142022/03/25过期未还
198800311062022/03/282022/04/16
2017256811082022/06/092022/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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值