本文主要是介绍C++图书馆管理系统——基于jsoncpp与windows.h,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
0. 演示视频
先直接上演示视频看看具体的实现效果
C++图书馆管理系统——基于jsoncpp和windows.h
系统的代码打包为百度云
链接:https://pan.baidu.com/s/1Lm40gKgYdHdnyz35GsxMWA
提取码:qu72
1. 系统概述
1.1 系统功能
编写一个图书管理系统对学生信息和图书信息进行管理。其中学生信息包括姓名,学号,所属学院,最大借阅量等属性,图书信息包括索取号,书名,作者,出版社,数量等属性,要求能输入、输出、修改、查询学生借阅图书的信息。 在该系统中,分为学生使用端和管理员端,学生端的主要功能有两种:查询书籍以及查看个人信息;管理员端的功能有:查询学生信息、查找书籍、查询书籍具体信息、修改书籍信息、借/还书、修改密码。
在学生信息中,包含学生姓名、学号、学院、最大借阅量、当前借阅量、借阅信息;在书籍信息中,包含书籍名称、ISBN号、作者、出版社、索引号、馆藏数、现有数、借阅信息;由于我们这个是图书馆管理系统,所以设置只能修改书籍信息以及学生的借阅信息,至于学生的个人信息,应该认为在学校学生管理系统中管理,在本系统中不可修改。关于登录密码,学生端的密码也认为存储在学校的学生信息数据库中,本系统只可查询这个数据库,不可修改,而管理员端的密码是可以修改的。
1.2 系统数据库
图书馆管理系统应该有独立存储书籍学生信息的数据库,理想情况下可以用mySQL等数据库进行数据存储,但考虑到实际情况,本系统采用的数据存放方式为json文件存储,json格式是良好的数据交换格式,其键值对的数据结构能够很好的表示图书信息以及学生信息。在本系统中,采用jsoncpp库进行json格式文件的读写
2.系统结构
2.1 代码结构
系统采用多文件来组织代码,代码文件分别为
- LibararyManSys.cpp – 主文件
- User.h User.cpp – 存放学生类以及管理员类
- DataBase.h DataBase.cpp – 存放数据库类
- CreatePage.cpp – 存放绘制系统的不同功能对应不同页面的函数
2.2 类结构
系统一共有4个类,其结构为
2.3 类及数据结构体数据成员及功能
2.3.1 DataBase.h DataBase.cpp 存放的类与数据结构
1) 存放书籍信息的结构体
struct bookInfo
{string ISBN;string name;string author;string press;string index;int totalAmounts;int amounts;vector<vector<string>> borrowers{ {},{} };
};
结构体存放着书籍的信息,其中借阅信息borrowers为二维的stirng数组,由于借阅人数位置,因此用vector容器来定义,方便动态存储,其中,borrowers的第一行存放借阅者的学号,如”292521”,第二行存放着借阅者的借阅日期,如2020年12月22号,则以”20201222”的形式存放,两行的索引一一对应为一个学生。
2) 存放学生信息的结构体
struct stuInfo
{string id;string name;string college;int maxLoan;int loan;vector<vector<string>> books{ {},{} };
};
同样,结构体存放着学生的各种信息,其中books的第一行存放书籍的ISBN号,第二行存放借阅日期,索引号也是一一对应为一本书。
3) 数据库类DataBase
class DataBase {string studentPswPath = "./Data/student_psw.json";string adminPswPath = "./Data/admin_psw.json";string studentInfoPath = "./Data/student_info.json";string bookInfoPath = "./Data/book_info.json";int maxMonth = 2;
public:int pswConfirm(string, string, int); string getData();string getReturnData(string);void getBooks(vector<vector<string>> &);bookInfo getBookInfo(string);stuInfo getStuInfo(string);int returnBook(string, string);int borrowBook(string, string);int addBook(bookInfo);int deleteBook(string);int appendBook(string, int);int modifyBookInfo(bookInfo);int pswChange(string, string, string);
};
DataBase类的私有成员为各个数据库json文件的相对路径,以及定义的最大借阅日期,下面来看各个函数的输入输出及功能:
/*
获取当前日期
params:
None
return:
@data -- 当前的日期,如2020年12月19日,返回"20201219"
*/
string DataBase::getData()/*
获取还书日期
params:
@date -- 借书日期
return:
@rdata -- 还书日期,为字符串形式
*/
string DataBase::getReturnData(string date)/*
确认密码是否正确
params:
@account -- 账号
@psw -- 密码
@people -- 用户类型 0:学生 1:管理员
return:
@-2 -- 数据库文件丢失或解析错误
@-1 -- 没用该用户
@0 -- 密码错误
@1 -- 密码正确
*/
int DataBase::pswConfirm(string account, string psw, int people)/*
获取所有书籍的ISBN号以及名字
params:
@&books -- 二维string vector变量的引用,在函数中,将会在第一维存放ISBN号,在第二维存放书籍名字,其索引号一一对应
return:
None
*/
void DataBase::getBooks(vector<vector<string>> &books)/*
获取某本书籍的所有信息
params:
@tISBN -- 目标书籍的ISBN号
return:
@book -- 存放书籍所有信息的结构体
*/
bookInfo DataBase::getBookInfo(string tISBN)/*
获取某个学生的所有信息
params:
@tID -- 目标学生的校园卡号
return:
@stu -- 存放目标学生所有信息的结构体
*/
stuInfo DataBase::getStuInfo(string tID)/*
进行还书操作的函数
params:
@stuID -- 要还书的学生的校园卡号
@bookISBN -- 学生要还书籍的ISBN号
return:
@-1 -- 该学生不存在
@-2 -- 该书籍不存在
@0 -- 学生没有借这本书
@1 -- 还书成功
*/
int DataBase::returnBook(string stuID, string bookISBN)/*
进行借书操作
params:
@stuID -- 借书学生校园卡号
@bookISBN -- 学生要借的书的ISBN号
return:
@-1 -- 学生不存在
@-2 -- 书籍不存在
@2 -- 书籍没有了
@1 -- 学生借书前已经达到最大借阅量,不可以再借
@0 -- 借书成功
*/
int DataBase::borrowBook(string stuID, string bookISBN)/*
在书籍数据库中增加一本书
params:
@book -- 要增加书籍的书籍信息结构体
return:
@0 -- 这本书已经存在,增加书籍失败
@1 -- 增加书籍成功
*/
int DataBase::addBook(bookInfo book)/*
删除一本书
params:
@bookISBN -- 要删除的书籍的ISBN号
return:
@-1 -- 书籍不存在,删除失败
@0 -- 书籍的馆藏数量(total_amounts)和现有数量(amounts)不相等,即有人把书借走了还没换,不允许删除书籍
@1 -- 删除书籍成功
*/
int DataBase::deleteBook(string bookISBN)/*
调整书籍的数量(函数名用了append,因为设置增量可以为负数)
params:
@bookISBN -- 要调整数量的书籍的ISBN号
@num -- 增加的数量
return:
@-1 -- 书籍不存在
@0 -- 书籍现有数量加上增加数量后小于0,不允许数量调整操作
@1 -- 调整数量成功
*/
int DataBase::appendBook(string bookISBN, int num)/*
修改书籍的信息
params:
@book -- 要修改信息的书籍的结构体
return:
@0 -- 书籍不存在
@1 -- 修改信息成功
*/
int DataBase::modifyBookInfo(bookInfo book)/*
管理员密码修改
params:
@account -- 管理员账号
@rawPsw -- 管理员原密码
@newPSw -- 管理员新密码
return:
@0 -- 旧密码不正确,修改失败
@1 -- 密码修改成功
*/
int DataBase::pswChange(string account, string rawPsw, string newPsw)
在这些数据读写函数中,均返回状态码,而且每个函数都是相互独立的,因此开发者可以根据状态码判断数据的读写是否正常,若不正常,便可进行相应的异常响应。在获取学生信息与书籍信息的函数中,返回的不是状态码而是相应数据的结构体,在这两个函数中,我们可以通过判断返回学生结构体(书籍结构体)中的string类型的成员s是否为空(利用成员函数s.empty()或直接s==””),因为函数中,如果搜索不到对应的学生(书籍),将返回一个未赋值的结构体,而结构体中string类型数据初始值为空串,通过这个可以判断是否成功读到数据。
2.3.2 User.h User.cpp 存放的类及类函数
1) 基类baseUser
class baseUser {
protected:DataBase DB;
public:virtual void Login() {};virtual void Search() {};virtual void Home() {};string *_Search(string);int LCS(string, string);
};
在基类中,有唯一的保护成员,数据库类的实例DB,因为无论是学生还是管理员都需要对数据库进行读或写,设置称保护成员是为了派生类可以访问,在函数中,设置了3个虚函数,分别是登录函数,搜索函数和主页函数,因为这几个都是学生端和管理员端都应该有的页面,但是它们的形式都不一样,这里先给出一个概念,后面第四节会详细介绍。基类还有两个函数,_Search()和LCS(),这两个函数是配合使用的,设置这两个函数的目的是为了可以对书名进行模糊搜索,在本个系统中,由于书籍数受限,因此只根据用户输入的书名,给出最符合的3本书,开发者只需要调用前者,便可获得存放这3本书ISBN号的数组。
2) 派生类Student
class Student :public baseUser {
protected:string name;string id;string college;int max_loan;int loan;vector<vector<string>> books{ {},{} };
public:void Login(HANDLE);void Search(HANDLE);void Home(HANDLE);void getInfo(HANDLE);
};
派生类学生类的保护成员为学生的各种信息,在学生登录成功后便会进行初始化,成员函数为学生端的各个界面,后面会介绍
3) 派生类Admin
class Admin :public baseUser {
protected:string account;
public:void Login(HANDLE);void Search(HANDLE);void Home(HANDLE);void getBookInfo(HANDLE, string);void getStuInfo(HANDLE);void returnBook(HANDLE);void borrowBook(HANDLE);void addBook(HANDLE);void deleteBook(HANDLE, string);void appendBook(HANDLE, string);void modifyBookInfo(HANDLE, bookInfo);void pswChange(HANDLE);
};
管理员类也是本系统的核心类之一,因为图书馆管理系统的大部分功能都继承在管理员类中,可以看到,管理员类的数据成员只有管理员的账号,这是登陆成功后初始化的,类的成员函数便是各个功能的操作界面。
4) 函数的输入输出及功能
/*
获得最长公共子序列的长度(因为在系统中这个函数主要为了模糊搜索函数提供一个score,即LCS的长度
因此不需要找出最长公共子序列的具体内容)
params:
@sStr -- searchStr 搜索时的字符
@tStr -- targetStr 目标字符
return:
@lenLCS -- 搜索时字符与目标字符的LCS长度
*/
int baseUser::LCS(string sStr, string tStr)/*
对用户输入的书名进行模糊搜索,获得最匹配的3本书
params:
@sStr -- 用户搜索书名时输入的字符串
return:
@topBooks -- string数组 ,存放top3的书籍ISBN号,由匹配程度由高到低排列
*/
string *baseUser::_Search(string sStr)/*
学生端登录功能部分
params:
@hOut -- console窗口句柄
*/
void Student::Login(HANDLE hOut)/*
学生端主页
params:
@hOut -- console窗口句柄
*/
void Student::Home(HANDLE hOut)/*
学生端查看个人信息页面
params:
@hOut -- console窗口句柄
*/
void Student::getInfo(HANDLE hOut)/*
学生端查找书籍页面
params:
@hOut -- console窗口句柄
*/
void Student::Search(HANDLE hOut)/*
管理员端查找书籍页面
params:
@hOut -- console窗口句柄
*/
void Admin::Search(HANDLE hOut)/*
管理员端主页
params:
@hOut -- console窗口句柄
*/
void Admin::Home(HANDLE hOut)/*
管理员登录页面
params:
@hOut -- console窗口句柄
*/
void Admin::Login(HANDLE hOut)/*
管理员端查看书籍具体信息页面
params:
@hOut -- console窗口句柄
@ISBN -- 被查书籍的ISBN号
*/
void Admin::getBookInfo(HANDLE hOut, string ISBN)/*
管理员端查看学生信息页面
params:
@hOut -- console窗口句柄
*/
void Admin::getStuInfo(HANDLE hOut)/*
管理员端还书界面
params:
@hOut -- console窗口句柄
*/
void Admin::returnBook(HANDLE hOut)/*
管理员端借书界面
params:
@hOut -- console窗口句柄
*/
void Admin::borrowBook(HANDLE hOut)/*
管理员端增加书籍界面
params:
@hOut -- console窗口句柄
*/
void Admin::addBook(HANDLE hOut)/*
管理员端删除书籍界面
params:
@hOut -- console窗口句柄
*/
void Admin::deleteBook(HANDLE hOut, string ISBN)/*
管理员端调整书籍数量界面
params:
@hOut -- console窗口句柄
*/
void Admin::appendBook(HANDLE hOut, string ISBN)/*
管理员端修改书籍信息界面
params:
@hOut -- console窗口句柄
*/
void Admin::modifyBookInfo(HANDLE hOut, bookInfo book)/*
管理员端修改密码界面
params:
@hOut -- console窗口句柄
*/
void Admin::pswChange(HANDLE hOut)
5) LCS最长公共子序列算法简介
详见这篇博客https://blog.csdn.net/weixin_40673608/article/details/84262695
一般求解LCS使用动态规划法
问题描述:设X=<x1, x2, …, xm>,Y=<y1,y2,…,yn>,Z=<z1,z2,…,zk>是X和Y的公共子序列,那么:
1、如果xm = yn,则zk = xm = yn 且 Zk-1是Xm-1和Yn-1的一个LCS
2、如果xm != yn 且 zk != xm,则Z是Xm-1和Y的一个LCS
3、如果xm != yn 且 zk != yn,则Z是X和Yn-1的一个LCS
使用二维数组c[i,j]来表示字符串X,Y的前i,j个字符的LCS长度,可以得到:
因此,我们只需将c从第一项到最后一项填满,便可得到LCS的长度,算法的伪代码为:
由于在该系统中,我们希望得到某本书籍名字相对于输入内容的分数,因此我们只需要得到LCS的长度,而不需要获得其内容。在实际操作中,由于输入字符长度未知,因此需要对数组c进行动态内存分配:
初始化与销毁:
int **c;
int i, j;
c = new int *[m];for (i = 0;i < m;i++)c[i] = new int[n];for (i = 0;i < m;i++)c[i][0] = 0;
for (i = 0;i < n;i++)c[0][i] = 0;for (i = 0;i < m;i++)delete [] c[i];
delete[]c;
刚刚说到,我们是根据搜索书籍相对于搜索串的分数来给出搜索结果的,而这个分数在系统中定义为:
假设搜索时输入的串为a,书籍名的串为b,他们LCS的长度为L,那么我们定义分数为:
在这种朴素LCS算法中,对英文字符串的搜索效果是很好的,但对于中文字符串有一定的局限性,因为一个汉字占两个字符位,所以这会对搜索结果产生影响。
3. 数据库读写及绘制界面用到的库文件说明
3.1 Jsoncpp
jsoncpp的入门教程可以参考这篇博客https://blog.csdn.net/shuiyixin/article/details/89330529
在VS包含目录设置中,最好设置为绝对路径
前面谈到,系统是用json格式来存储数据的,由于自己写一个json读写的程序很耗费精力而且没有必要,因此选用了开发者比较常用的对json进行读写的库jsoncpp作为系统开放的辅助。在github上下载这个库后,将必要的文件放入工程中,配置好include项,便可以使用这个库
我们先来看看系统需要用到的各种数据在json中以怎样一种格式存放:
学生密码库:
{"292521":"123abc","291234":"246efg"
}
键为学生的学号,值为学生的密码
管理员密码库:
{"admin1" : "123456","admin2" : "456edc"
}
键为管理员账号,值位管理员密码
学生信息库:
{"291234" : {"books" : [[ "9787115461476", "20201111" ]],"college" : "计算机与软件工程学院","loan" : 1,"max_loan" : 3,"name" : "张三"},"292521" : {"books" : [[ "9787111632887", "20201006" ],[ "9787115461476", "20201218" ]],"college" : "电子与信息工程学院","loan" : 2,"max_loan" : 3,"name" : "李四"}
}
其中,键位学生学号,该键下的子节点是学生的各种信息,其中借阅信息以列表格式存放,列表中的一个子列表对应借阅的一本书,第一项位ISBN号,第二项位借阅日期
图书信息库:
{"9787111632887" : {"amounts" : 2,"author" : "理查德哈特利","borrowers" : [[ "292521", "20201006" ]],"index" : "c147","name" : "计算机视觉中的多视图几何","press" : "机械工业出版社","total_amounts" : 3},"9787115461476" : {"amounts" : 1,"author" : "Yoshua Bengio","borrowers" : [[ "291234", "20201111" ],[ "292521", "20201218" ]],"index" : "b246","name" : "深度学习","press" : "人民邮电出版社","total_amounts" : 3}
}
由于书籍的ISBN号是唯一的,因此最适合作为进行索引书籍的键名,书籍ISBN键下子节点下是书籍的各种信息,其中借阅者列表第一项位借阅学生的学号,第二项位借阅日期。
了解完数据存放的格式,再来看看在本系统的代码中,如何使用jsoncpp:
由于jsoncpp是作用于已经读入系统内存中字符串,对其进行解析,因此我们需要C++文件的读写来配合使用
我们需要包含两个头文件
#include <fstream>
#include <json.h>
由于jsoncpp有两套API,我下载的jsoncpp库默认使用新API,但由于旧的API可读性更好,也更容易操作,因此需要在文件开始加入几行代码说明使用旧API
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#elif defined(_MSC_VER)
#pragma warning(disable : 4996)
#endif
一般从json文件中解析json数据的步骤为:
- 实例化两个对象,reader是解析器,root是jsoncpp中的值对象,可以将它看作json文件中一个节点
Json::Reader reader;
Json::Value root;
- 读取文件流
ifstream in(pswPath, ios::binary);
第一个参数为已经赋值的json文件路径,在第二个参数中,由于json文件是二进制文件,因此需要用读二进制
- 将流解析到节点中
reader.parse(in, root)
经过这三步,便可以处理解析后的json文件,要对节点下的键进行索引,操作也很方便,比如要索引图书数据库中深度学习的第一个借阅者学号,便使用
root[ISBN(假设已经赋值)][“borrowers”][0][0]
在实际应用中,会用到的jsoncpp的用法有:
- 判断节点的某个键是否存在
root[account].isString()
如果键存在,那么它会返回true
- 将某个解析得到的值转换为C++可处理的数据类型
root[account].asString()
root[account].asInt()
等等
- 生成可迭代对象,在系统中可用于遍历得到数据库中所有书籍的ISBN及名字
Json::Value::Members mem = root.getMemberNames();for (auto iter = mem.begin(); iter != mem.end(); iter++)
{books[0].push_back(*iter);books[1].push_back(root[*iter]["name"].asString());
}
- 创建一个节点,在系统中可用于管理员在数据库中添加书籍
在这个操作中,我们先创建节点对象:
Json::Value bookNode;
再往节点中喂想要的键值对,比如
bookNode["name"] = Json::Value(book.name);
如果想要某个键的值为空列表,则
bookNode["borrowers"].resize(0);
将创建的节点挂载到已有的节点上
root[book.ISBN] = bookNode;
- 刷新数据库
由于我们对数据的修改是操作在读入到内存中的数据上的,因此我们修改完数据后,需要将数据重新写入文件中,以实现数据库的刷新,在此操作中,我们需要
建立jsoncpp的writer对象以及文件输出流
Json::StyledWriter sw;
ofstream os;
jsoncpp常用的写对象有Writer和StyledWriter,前者对数据直接进行写入,后者在写入时会遵循一定的格式,会让文件可读性更高,因此采用后者
用输出流打开文件
os.open(bookInfoPath, ios::out | ios::trunc);
由于我们要刷新数据库,因此我们采用trunc模式,对文件内容进行清除
将数据输出,关闭文件
os << sw.write(root);
os.close();
到这里jsoncpp最常用的用法介绍完毕
3.2 Windows.h conio.h
由于这是一个图书馆管理系统,因此希望用户与应用之间的交互更简便,界面更直观,而我们这个系统是控制台应用,因此可以采用Windows.h来创建多界面,可刷新,交互性更好的系统。至于conio.h,它里面的函数可以检测键盘事件,可以配合前者进行更多可编程的操作。
在Windows.h中,是根据句柄来标识窗体的,因此我们需要获得一个句柄,后续的所有操作均建立在此句柄上
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
我们可以设置窗口名称
SetConsoleTitleA("图书馆管理系统");
在Windows.h中,有几个常用的结构体,这些结构体是几个常用函数中的参数
CONSOLE_CURSOR_INFO
这个结构体存储着光标的信息,可以设置它使得光标可见(不可见)
COORD
SHORT
COORD结构体是存放坐标信息的,它里面的数据是SHORT类型的,在窗体中,左上角为原点(0,0),x轴往右,y轴往下
SMALL_RECT
此结构体代表一个矩形,它的四个值依次为矩形左上角的x,y坐标以及右下角的x,y坐标,可以用于调整窗体大小
WORD
这个数据类型的数据大小为1个字节,通常用来表示颜色,字节的高4位表示背景色,低4位表示前景色
在本系统中,用到的Windows.h的操作有
1) 清空控制台内容
system("cls");
2) 设置光标是否可见 true为可见 false为不可见
CONSOLE_CURSOR_INFO cursorInfo = { 1, false };
SetConsoleCursorInfo(hOut, &cursorInfo);
3) 设置颜色
WORD color = 0x02;
SetConsoleTextAttribute(hOut, color);
在颜色中,前景色和背景色均有15种(4位),颜色与值的对应关系为:
(前者是背景色,值乘以了16,后者是字体颜色)
在本系统中采用的背景色均为黑,用的前景色有
7 —— 白色,用于正常字体打印
1、2 —— 蓝色和绿色,用于打印页面的标题和有关名称的栏
6 —— 黄色,用于打印警告信息
4 —— 红色,用于打印错误信息
4) 设置光标位置
pos = { x, y };
SetConsoleCursorPosition(hOut, pos);
可以将光标移动到(x,y)处
该操作是系统用得最多的操作,因为我们要想打印一个理想的画面,就要在指定的位置输出指定的内容,我们要用户在指定的位置输入,就要先将光标移到那里。
5) 检测键盘活动(conio.h的函数)
while (1)
{if (_kbhit()){i = _getch();if (i == 72 || i == 80){}else if (i == 13){}}
}
这是监测键盘事件的代码结构,我们设置死循环一直等待键盘敲下,如果有键盘被敲下kbhit()函数(在VS2017中,要求使用_kbhit())返回真,便可用整形变量获得输入(getch(),_getch() in VS2017),键盘的每个键都对应着一个数,通过判断便可进入相应的操作循环中。对于获得键盘每个键对应的数,我编写了一个小程序以获取想要的键的对应
while(1)
{if (kbhit()){i = getch();cout << i << " ";}
}
用得最多的键为:上箭头——72;下箭头——80;回车——13
4. 系统操作逻辑
该系统由多个页面组成,每个页面操作的是系统中不同的功能,页面的进入退出关系由下图所示
对于界面的构造,我们将区分为静态界面及动态界面,在以上的界面中,静态界面有:学生查看个人信息界面、管理员修改书籍信息界面、管理员删除书籍界面;
在静态界面中,用户不需要与系统进行互动,或者只与系统发生一次互动(一次的意思是不会产生循环互动行为),这些静态页面会由Student类和Admin类相应的函数进行构造
相对于提及的三个静态界面,其余所有界面都是动态界面,动态画面的意思是,交互界面会与用户循环互动,直至用户给出退出界面的命令,在系统中,每个动态页面的交互程序都是Student类和Admin类中的函数,因为这些页面需要不断地刷新,因此我们需要一个基本画面,这个画面是静态的(与上面提到的静态有所不同,这里的静态是顾名思义的静态),动态画面建立在静态画面的基础上,因此在代码文件中,建立了 createPage.cpp createPage.h 来存放创造静态画面的函数,Student端和Admin端在与用户的动态互动中可以调用相应的函数来刷新页面,因此Student和Admin里的类函数就可以专注与实现图书馆管理系统的本质功能:根据用户的行为来对数据库进行增删查改。
createPage文件里面的函数的函数名是与User文件里面学生类、管理员类下的函数的函数名有直接关系的,可以很快地找到哪个操作功能对应着哪个静态页面构造函数
比如:
void Student::Login(HANDLE hOut) void createStuLoginPage(HANDLE hOut)
void Admin::Login(HANDLE hOut) void createAdminLoginPage(HANDLE hOut)
在页面的嵌套转换中,我们使用while(1)循环嵌套来实现,比如,页面2是页面1的子页面,我们可以用这种结构来实现页面间的转换:
页面1动态交互函数(功能函数){创造页面1静态页面;操作…;while(1){if (获得进入页面2的命令){页面2功能函数();创造页面1静态页面;//在每次页面2返回后都要刷新回父页面}}
}
关于如何进入子页面和返回父页面,系统根据不同的场景提供了两种不同的方式,一种是光标选择式,一种是输入命令式,
在光标选择式中,可以上下移动光标,然后回车选择需要进入(或返回)页面:
在这种方式中,每个光标(=>)的x坐标都是固定的,而每个不同的y坐标都对应着不同的子页面,在用户按下上下箭头时,会根据键位修改y坐标,刷新光标位置,当用户按下回车键后,根据当时的y坐标来判断进入哪个子页面。
在命令输入式下,系统设置为:只要用户能输入字符,则只要用户输入exit,就回返回上一页面,在输入命令进入子页面的过程中,可以输入特殊的命令来进入子界面
如,我们在搜索书籍得到结果后,会有如下界面
如果我们想返回,则在搜索栏输入exit,就会返回前图(管理员主页)的页面,如果我们要查看书籍的具体信息(管理员查找书籍页面只有一个子页面——书籍具体信息页面),我们可以输入more命令来得到,比如我想看第1本书的信息,则输入”more1”,如果想看第3本,则”more3”,而在按ISBN检索时,检索成功只会出现一本书,此时输入任意more命令都可以进入更多信息
我们在输入more1命令后,进入深度学习这本书的具体信息页面:
5. 系统不足
1.模糊搜索的算法可以优化,理想的情况是,算法能区别中英文字符,将一个汉字看作串的一个成员,而不是以两个连续字节来表示,其次,英文可能需要将一个单词看作一个个体,这就牵扯到更复杂的字符串相似度算法了。
2.由于是初次接触使用Windows.h的编程,因此对这个库的更高级的用法还不是很熟悉,在项目中只使用了较简单的几个用法,这可能是导致编写交互画面的代码量巨大的原因(因为在一个画面中需要对放置在特定位置的字进行逐一的光标坐标转换,打印,改变颜色),相信如果能使用更高级的用法,编写人性化控制台交互程序的代码会变得更清爽;还有一点是,交互画面中,每个字符串的位置都是绝对位置(直接给定坐标),不是相对位置(比如用比例来表示),这就导致用户不能对控制台进行缩放,缩放后字符串的位置会混乱
3.用户输入时的交互需要优化,在系统的代码中,用户输入是直接使用cin输入的,这导致如果用户什么都没有输入,按下了回车,光标会跳到下一行,虽然用户可以继续输入,但是这不符合交互逻辑。
这篇关于C++图书馆管理系统——基于jsoncpp与windows.h的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!