目录
一、 概要设计
二、详细设计
模块一:管理员模块
一、概要设计
二 、详细设计
模块二:借阅者模块
一、概要设计
二、详细设计
模块三 初始界面模块
三、总结
本程序为图书馆管理系统。按需求,本程序大致分为2个部分:其一是管理员部分,其二是借阅者部分。模块的切换可以在main函数中让用户输入选项数字来确定。
总共有6个功能:
1.显示图书信息。包括图书的书名、价格、作者、总库存、现库存、借阅记录等。
2.添加图书。添加新的图书。
3.修改图书信息。可以直接修改图书的各项信息。
4.删除图书。
5.清除图书借阅记录。
6.显示借阅者信息。
模块二:借阅者模块
总共有2个功能
1.借书。
2.还书。
1.结构体:
struct LendBook{ //被借出去的书
string time; //借阅时间
string name; //书名
};
struct LendList{ //借阅的书的链表
string stuNum; //学号
LendList *next; //指针
vectorv; //向量,存储借阅的书
};
struct Book{ //图书文件
string name; //书名
double price; //价格
string author; //作者
double allStock; //所有库存
double nowStock; //现有库存
bool isLend; //借阅与否
vectorrecord; //存储借阅记录的向量
};
struct List{ //图书链表
Book *book; //Book类结构体
List *next; //指针
};
其实用类更好。不过我上数据结构课用的就是结构体,已经习惯了。
LendBook和Book是基本类型。LendList和List是这两种基本类型的链表。由于本程序要进行文件操作,涉及到一些文件的删改,如果直接对文件进行改动的话很不方便。所以这里采用的思路是先由文件生成链表,在链表中进行信息的改动,之后再把链表中的信息一起写入文件(所以这里是直接覆盖文件)。
2、函数
管理员模块:
string timeNow(){ //得到现在的时间
time_t rawtime;
time(&rawtime);
struct tm * timeinfo;
timeinfo = localtime(&rawtime);
timeinfo = gmtime(&rawtime);
char pblgtime[20];
strftime(pblgtime, 20, "%Y-%m-%d-%X", localtime(&rawtime));
int i=0;
string str="";
while(pblgtime[i]!='\0'){
str+=pblgtime[i];
i++;
}
return str;
}
这个函数是得到现在的时间。参照这位大神的博客:
(75条消息)
List * fileToList(){ //将图书的信息转换为链表
List *list = new List;
list->next=NULL;
List *r = list;
ifstream myfile;
myfile.open("LibraryDate.txt",ios::in); //读文件
string temp;
if (!myfile.is_open())
{
cout << "未成功打开文件" << endl;
}
while(getline(myfile,temp))
{
// cout<v;
while (is >> s)
{
switch(i){
case 0:{
book->name = s;
break;
}
case 1:{
double nums;
stringstream ss;
ss<>nums;
book->price = nums;
break;
}
case 2:{
book->author = s;
break;
}
case 3:{
double nums;
stringstream ss;
ss<>nums;
book->allStock = nums;
break;
}
case 4:{
double nums;
stringstream ss;
ss<>nums;
book->nowStock = nums;
break;
}
case 5:{
int ret = s.compare("true");
switch(ret){
case 0:{
book->isLend = true;
break;
}
default:{
book->isLend = false;
break;
}
}
break;
}
default:{
v.push_back(s);
break;
}
}
i++;
}
List *p = new List;
book->record=v;
p->book = book;
r->next = p;
p->next = NULL;
r = p;
}
myfile.close();
return list;
}
ifstream是文件输出流,头文件是fstream
getline(myfile,temp)函数是把文件的内容按行读取,每行的数据存储在temp中。
我设置的文件格式是这样的:
每本书的信息占一行,书的不同信息之间按空格分开。而要怎么才能按照空格分割string字符串呢?我这里用的是stringstream,包含在sstream头文件中。而程序中的is >>s则是以空格分割标记,并分别赋值给字符串s。我在这里用了一个辅助变量i,按照i的取值来确定s的信息类型(比如第一次分割得到的是书名,此时i=0)。一次类推。向量存储的是这本书的借阅记录。这些信息都存储在一个Book对象中,尾插到链表后。
void reviseFile(List *&m){ //修改图书文件信息
if(m==NULL){
cout<<"修改文件失败!!!"<next;
while(r!=NULL){
string yes;
if(r->book->isLend){
yes = "true";
}
else{
yes = "false";
}
ostringstream ost;
vectorv;
v = r->book->record;
for (vector::iterator it = v.begin(); it != v.end(); it++) {
ost<<*it;
}
ost<<" "<<"\n";
ostringstream oss;
oss<book->name<<" "<book->price<<" "<book->author<<" "<book->allStock<<" "<book->nowStock<<" "<next;
}
ofs.close();
}
这个函数是把链表中的信息存储到文件中。ostringstream变量是为了方便多个字符串的合成(用<<操作字符)。我发现在ostringstream变量添加空格,但是文件中经常不会显示有空格。所以我单独用文件操作符添加了空格。
void changeElement(List *&m){ //修改图书个别信息
if(m==NULL){
cout<<"修改失败!"<>choice))
{
cin.clear();
while (cin.get() != '\n')
{
continue;
}//跳过错误输入
cout << "请输入一个数字:"<next;
List *pre=m;
if(choice==999999){
cout<<"退出成功!"<book->name<<" ";
i++;
r=r->next;
}
}
string str;
cout<<"请输入你需要修改的书的书名:";
cin>>str;
r = m->next;
while(r!=NULL){
if(r->book->name==str){
break;
}
r=r->next;
}
if(r==NULL){
cout<<"您想修改的书不存在!此项即将退出,试一试重新输入吧!"<>choice)){
cin.clear();
while (cin.get() != '\n')
{
continue;
}//跳过错误输入
cout << "请输入一个数字:"<6||choice<0){
cout<<"检查一下是不是序号输错了,再输一遍吧"<>choice)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<>tarStr;
r->book->name=tarStr;
break;
}
case 2:{
cout<<"请输入您想修改的价格:";
while (!(cin >>tarNum)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<book->price=tarNum;
break;
}
case 3:{
cout<<"请输入您想修改的作者:";
cin>>tarStr;
r->book->author = tarStr;
break;
}
case 4:{
cout<<"请输入您想修改的总库存量:";
while (!(cin >>tarNum)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<book->allStock=tarNum;
break;
}
case 5:{
cout<<"请输入您想修改的现库存量:";
while (!(cin >>tarNum)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<book->nowStock=tarNum;
break;
}
case 6:{
cout<<"输入”1“代表借出,输入“0”代表未借出:";
while (!(cin >>tarNum)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<book->isLend=is;
break;
}
case 0:{
cout<<"请输入您想修改的名字:";
cin>>tarStr;
r->book->name=tarStr;
cout<<"请输入您想修改的价格:";
while (!(cin >>tarNum)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<book->price=tarNum;
cout<<"请输入您想修改的作者:";
cin>>tarStr;
r->book->author = tarStr;
cout<<"请输入您想修改的总库存量:";
while (!(cin >>tarNum)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<book->allStock=tarNum;
cout<<"请输入您想修改的现库存量:";
while (!(cin >>tarNum)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<book->nowStock=tarNum;
cout<<"输入”1“代表借出,输入“0”代表未借出:";
while (!(cin >>tarNum)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<book->isLend=is;
break;
}
default:{
break;
}
}
reviseFile(m);
cout<<"修改成功!!"<
这段修改书籍信息的代码如果可以通过可视化窗口会简单很多。
值得注意的是:检测用户输入的字符是不是数字字符,如果不是提醒,并让其重新输入。这个功能用到的主要知识点主要是cin.clear()这个函数。这段代码在后面用了很多次:
void addElement(List *&m){ //添加图书
if(m!=NULL){
List *r = m;
while(r->next!=NULL){
r=r->next;
}
cout<>str01;
if(str01==pause){
cout<<"-----退出成功!-----"<>price))
{
cin.clear();
while (cin.get() != '\n')
{
continue;
}//跳过错误输入
cout << "请输入一个数字:"<>str02;
if(str02==pause){
cout<<"-----退出成功!-----"<>nums01))
{
cin.clear();
while (cin.get() != '\n')
{
continue;
}//跳过错误输入
cout << "请输入一个数字:"<>nums02))
{
cin.clear();
while (cin.get() != '\n')
{
continue;
}//跳过错误输入
cout << "请输入一个数字:"<name = str01;
book->price = price;
book->author = str02;
book->isLend = false;
book->allStock = nums01;
book->nowStock = nums02;
p->book = book;
r->next = p;
p->next = NULL;
cout<<"添加新书成功!"<
这是添加图书的函数。没什么难的。简单在链表后添加信息。
void moveElement(List *&m){ //删除图书
cout<<"默认是根据书名来进行删除!"<>choice))
{
cin.clear();
while (cin.get() != '\n')
{
continue;
}//跳过错误输入
cout << "请输入一个数字:"<next;
List *pre=m;
if(choice==999999){
cout<<"退出成功!"<book->name<<" ";
i++;
r=r->next;
}
}
string str;
cout<>str;
if(str=="999999"){
cout<<"退出成功!"<next;
while(r!=NULL){
if(r->book->name==str){
pre->next=r->next;
delete r;
break;
}
pre=r;
r=pre->next;
}
cout<<"-----删除成功!-----"<
这是删除书籍的函数。直接删除链表中是的数字即可。凡是设计到链表的删除的应该都会用到一前一后两个指针。记得在最后面要对文件进行修改
void clearRecord(List *&m){ //清除图书借阅记录
cout<<"您确定清除所有借阅记录吗?此项操作不可恢复! 输入“1”确认,输入“0”退出:"<>choice)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<next;
while(r!=NULL){
r->book->record.clear();
r=r->next;
}
reviseFile(m);
cout<<"-----清除完毕!-----"<
这是清除所有的借阅记录。之所以会有这样的函数是因为我觉得很多借阅记录在文件里实在是太密了。注意这里的向量的clear()方法是不会把向量的存储空间都释放的,只会删除空间存储的数据。
void dispReader(LendList *&m){ //展示读者文件信息
if(m==NULL){
cout<<"无数据!输出失败!!"<next;
cout<v;
LendBook lendbook;
v = r->v;
cout<<"学号为"<stuNum<<"的同学的借阅记录如下:"<::iterator it = v.begin(); it != v.end(); it++) {
lendbook = *it;
cout<<" 于"<next;
cout<
这是展示借阅者信息的函数。主要也就是向量的遍历
总共有2个功能
1.借书。
2.还书。
借阅者通过学号登录
LendList * LendDataToList(){ //将读者的借阅信息转换为链表
LendList *allList = new LendList;
allList->next = NULL;
LendList *r = allList;
ifstream myfile;
myfile.open("ReaderDate.txt",ios::in);
string temp;
if(!myfile.is_open()){
cout<<"文件打开失败!"<>ch;
if(judge.eof()){
return allList;
}
//判断完毕
while(getline(myfile,temp)){
LendList *list = new LendList;
vector v;
list->next =NULL;
string s;
stringstream is(temp);
int i=0;
LendBook lendbook;
while(is>>s){
if(i==0){
list->stuNum = s;
}
else{
lendbook.time = s.substr(0,19);
lendbook.name = s.substr(19,s.length());
v.push_back(lendbook);
}
i++;
}
list->v=v;
r->next = list;
list->next = NULL;
r = list;
}
myfile.close();
return allList;
}
创建读者数据链表的过程与创建图书链表的过程相似。不同的是这里用到了string的substr()函数。因为我设计的读者文件是这样的:
时间与借阅书籍放在同一个string变量中,而时间的字数是固定的,所以用substr函数较为方便。在进行文件转换的前面顺便进行文件判空,即如果没有数据就提醒使用者。
void reviceReaderDate(LendList *&m){ //修改读者文件信息
if(m==NULL){
cout<<"存储失败!!"<next;
while(r!=NULL){
ostringstream oss;
oss<stuNum<<" ";
vectorv;
v = r->v;
LendBook lendbook;
for (vector::iterator it = v.begin(); it != v.end(); it++) {
lendbook = *it;
oss<next;
}
}
这是在对读者链表的数据进行修改后,把链表数据存储到文件中的函数,与上文修改图书文件的操作基本一致。
void Lend(List *&m,LendList *&s,string stuNum){ //读者借书
cout<<"-----您正在借书界面-----"<>choice)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<next;
while(r!=NULL){
cout<book->name<<" 现存量为:"<book->nowStock<next;
}
cout<>str;
if(str=="999999"){
cout<<"退出成功!!!"<next;
while(r!=NULL){
if(r->book->name==str){
break;
}
r=r->next;
}
if(r==NULL){
cout<<"你想借出的书籍不存在,此项即将退出,试一试重新输入。"<book->nowStock<=0){
cout<<"很遗憾!您想借出的书籍目前暂时没有库存了,请过几天再来试试看吧。"<>choice)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<>choice)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<book->nowStock--;
r->book->isLend=true;
r->book->record.push_back(record01);
}
else{
return ;
}
cout<<"借出成功!借书最大归还时限为一个月,记得及时归还哦。"<next;
LendBook lendbook;
while(rr!=NULL){
if(rr->stuNum==stuNum){
break;
}
rr=rr->next;
}
lendbook.name = r->book->name;
lendbook.time = time;
if(rr!=NULL){
rr->v.push_back(lendbook);
}
else{
pp->stuNum = stuNum;
vectorvec;
vec.push_back(lendbook);
pp->v = vec;
//遍历至最后一位插入
rr = s->next;
while(rr->next!=NULL){
rr=rr->next;
}
rr->next = pp;
pp->next = NULL;
}
reviceReaderDate(s);
}
}
读者借书的函数。这个函数会修改两个文件的信息。在修改读者文件之前,先遍历链表查找有没有学号对应的读者信息。如果有,则直接在后面插入,如果没有则另外创建一个结点。
void Back(List *&m1,LendList *&m2,string stuNum){ //读者还书
cout<<"-----您现在在还书界面-----"<next;
while(r!=NULL){
if(r->stuNum==stuNum){
break;
}
r=r->next;
}
if(r==NULL||r->v.size()==0){
cout<<"您没有需要归还的书籍!或许你想在此借书?输入”1“可以进入借书界面,输入”0“此项即将退出。"<>choice)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<v=r->v;
LendBook lendbook;
for (vector::iterator it = v.begin(); it != v.end(); it++) {
lendbook = *it;
cout<v1 = r->v;
vectorv2;
LendBook book;
for (vector::iterator it = v.begin(); it != v.end(); it++) {
book = *it;
if(book.name!=name){
v2.push_back(book);
}
}
r->v = v2;
List *r2 = m1->next;
while(r2!=NULL){
if(r2->book->name==name){
r2->book->nowStock++;
if(r2->book->nowStock>r2->book->allStock){
r2->book->allStock=r2->book->nowStock;
}
ostringstream oss;
string time = timeNow();
oss<<" 于"<
这是读者借书的函数。isEqual()函数是判断读者输入的书名是不是存在。而还书操作,意味着要把读者文件中相应的借阅信息删掉一次,借阅信息存储在向量中,如何做到删掉?我的方法是重新声明一个向量,把原向量除了归还书籍以外的所有书籍信息都写进新建向量中。
void inputPassword(string &str, int size) { //在登录界面输入密码且让密码不可见
char c;
int count = 0;
char *password = new char[size]; // 动态申请空间
while ((c = getch()) != '\r') {
if (c == 8) { // 退格
if (count == 0) {
continue;
}
putchar('\b'); // 回退一格
putchar(' '); // 输出一个空格将原来的*隐藏
putchar('\b'); // 再回退一格等待输入
count--;
}
if (count == size - 1) { // 最大长度为size-1
continue;
}
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { // 密码只可包含数字和字母
putchar('*'); // 接收到一个字符后, 打印一个*
password[count] = c;
count++;
}
}
password[count] = '\0';
str = password;
delete[] password; // 释放空间
cout << endl;
}
这个函数是使管理员在登录输入密码时,控制台上的密码显示为”*“,且超过设定长度时不会再继续输入。参见:
(76条消息) c++输入隐藏密码的实现_Waydrow的博客-CSDN博客_c++密码隐藏
void inputInfo(){ //管理员登录界面
cout<<"请输入用户名:"<>name;
if(name=="999999"){
cout<<"退出成功!" <> s){
string str = s;
if(i==1){
if(password==str){
passOK=true;
}
break;
}
else if(i==0){
if(name==str){
nameOK=true;
}
}
i++;
}
if(passOK&&nameOK){
cout<<"登录成功!"<
这是管理员登录时的界面。”ManagerDate.txt“文件是这样的:
前面是用户名,后面是密码。当用户输入一个用户名和密码后,遍历文件,如果两者都符合,则判定输入争取,否则递归调用,直至输入正确
void inputNum(bool &ok,string &stuNum){ //学生登录界面
cout<<"请输入您的学号:"<>stuNumLong)){
cin.clear();
while (cin.get() != '\n'){
continue;
}//跳过错误输入
cout << "请输入一个数字:"<>stuNum;
string regex[] = {"2021","2020","2019","2018"};
if(stuNum.length()>8){
cout<<"学号最长为8位数!"<
学生登录的界面。学号设定为现在的第一到大四的学生之间,即首四位是2018-2021.且学号长为8;
#include
#include
#include
#include
#include
#include
#include
这是需要导入的头文件。
另外要注意本程序的文件编码格式设定为:ANSI。
.cpp和.exe以及文件下载:
链接: https://pan.baidu.com/s/1Z1FnENcA-ZDCfYnWxyAfcQ 提取码: 99nw 复制这段内容后打开百度网盘手机App,操作更方便哦