一切代码都在本人的远程仓库中:github.com/1alve/CSDN.…
一.目的与要求
定义图书类,属性有:书名、出版社、ISBN号、作者、库存量、价格等信息和相关的对属性做操作的行为。
主要完成对图书的销售、统计和图书的简单管理。
功能要求: (1)销售功能。购买书籍时,输入相应的ISBN号,并在书库中查找该书的相关信息。如果有库存量,输入购买的册数,进行相应计算。如果库存量不够,给出提示信息,结束购买。 (2)图书简单管理功能。
- 添加功能:主要完成图书信息的添加,要求ISBN号唯一。当添加了重复的编号时,则提示数据添加重复并取消添加。
- 查询功能:可按书名、ISBN号、作者、出版社进行查询。若存在相应信息,输出所查询的信息,若不存在该记录,则提示“该标题不存在!”。
- 修改功能:可根据查询结果对相应的记录进行修改,修改时注意ISBN号的唯一性。
- 删除功能:主要完成图书信息的删除。输入要删除的ISBN号,根据编号删除该物品的记录,如果该编号不在物品库中,则提示“该编号不存在”。
- 统计功能: 输出当前书库中所有图书的总数及详细信息;可按书的价格、库存量、作者、出版社进行统计,输出统计信息时,要按从大到小进行排序。
(3)图书存盘:将当前程序中的图书信息存入文件中。
(4)读出信息:从文件中将图书信息读入程序。
(5)其他功能:
- 统计功能中,提供两种排序方式
- 按库存量降序排序
- 按价格降序排序
- 统计书库中的图书总
- 管理员需登陆账号、密码,正确时才可使用管理功能
- 提供CLEA 清屏函数,提升用户体验感 「使用vscode OK,如果使用Clion无效果」
- 提供显示书库中的图书总量
- 修改功能提供菜单和多种修改接口
- 统计功能中,提供两种排序方式
==注:这里统计作者 和 出版社的图书数 显示的是图书总数(非种类),若要显示种类,可继续优化,但代码大体不变。==
二.工具/准备工作
通过vscode进行开发
三.分析
根据题目所给要求,本实验需要一个 Book类 和 一个BookManager类来实现管理系统的各种功能。
至于代码和核心的算法,本人都将详细的放入 四.实现步骤 中。在此的分析过程,我将一个流程图来提供本人思路。
四.实现步骤
根据项目要求,要设计两个类来实现该项目Book类 BookManager类 Statistics类
void SignIn()
- 先验证管理员登陆的账号和密码 -- 输入错误,重新输入。
- 成功后进入管理员系统中 -- run函数。
// 管理员验证
void BookManager::SignIn()
{
while (true)
{
string account, password;
cout << "请输入账号:";
cin >> account;
cout << "请输入密码:";
cin >> password;
if (account == this->BM_account && password == this->BM_password)
{
CLEAR();
this->run(); // 进入管理员系统
CLEAR();
return;
}
else
{
cout << "登陆账号或密码错误,请重新登陆!!" << endl;
CLEAR(); // 清屏
}
}
}
void run()
- 显示管理员总功能 -- 增、删、改、查、退出。
- 输入选择不合法时重新输入。
- 根据选择进入各种接口。
// 运行管理员系统
void BookManager::run()
{
while (true)
{
int select;
BookManagerMenu();
cout << "请输入您的选择:";
cin >> select;
switch (select)
{
case 1: // 添加图书
addBook();
break;
case 2: // 查询图书
findBook();
break;
case 3: // 修改图书
modifyBook();
break;
case 4: // 删除图书
deleteBook();
break;
case 5: // 统计图书
showAllBooks();
break;
case 0: // 退出
return;
default:
{
cout << "输入错误,请重新输入" << endl;
CLEAR();
}
break;
}
}
}
void addBook()
- 依次输入要添加的图书信息.
- ==判断输入的ISBN号是否重复 -- ISBN号重复时==,重新输入
- 创建Book类对象,并将其保存至书库
books中 - 添加成功后保存
saveBooks至文件中
// 添加图书
void BookManager::addBook()
{
while (true)
{
bool result = true;
string name, author, publisher, ISBN;
int inventory;
double price;
cout << "请输入ISBN号:";
cin >> ISBN;
cout << "请输入书名:";
cin >> name;
cout << "请输入作者名:";
cin >> author;
cout << "请输入出版单位:";
cin >> publisher;
cout << "请输入价格:";
cin >> price;
cout << "请输入库存量:";
cin >> inventory;
for (int i = 0; i < books.size(); i++) // 保证ISBN号的唯一性
{
if (ISBN == books[i].getISBN())
{
result = false;
}
}
if (result)
{
Book book(name, publisher, ISBN, author, inventory, price);
books.push_back(book); // 将图书加入图书信息数组中
saveBooks(); // 将信息保存至文件中
cout << "添加成功!" << endl;
CLEAR();
break;
}
else
{
cout << "你输入的ISBN号已经存在,请重新输入!!" << endl;
CLEAR();
}
}
}
void findBook()
- 显示查询方式
- 根据用户选择提供各种接口
- 输入不合法时重新选择
// 查找图书信息
void BookManager::findBook()
{
while (true)
{
int select;
findBookMenu();
cout << "请输入您的选择:";
cin >> select;
switch (select)
{
case 1: // 按书名查询
findBookByName();
break;
case 2: // 按ISBN号查询
findBookByISBN();
break;
case 3: // 按作者查询
findBookByAuthor();
break;
case 4: // 按出版社查询
findBookByPublisher();
break;
case 0: // 退出
{
CLEAR();
return;
}
break;
default:
{
cout << "输入有误请重新输入!!" << endl;
CLEAR();
}
break;
}
}
}
void findBookoByName()
- 输入图书名
- 创建
vector<Book>results用于储存查询的图书(书名有可能重复) - 成功显示图书所有信息,失败则回复用户
// 查找图书信息 -- 书名查找
void BookManager::findBookByName()
{
string name;
cout << "请输入要查找的书名:";
cin >> name;
vector<Book> results; // 因为图书可能重名但作者不一样,所以用数组来存
for (int i = 0; i < books.size(); i++)
{
if (books[i].getname() == name)
{
results.push_back(books[i]); // 将图书推入result数组中
}
}
if (results.size() != 0)
{
for (int i = 0; i < results.size(); i++)
{
// 其实这里可以调用print函数
cout << "ISBN号:" << results[i].getISBN() << "\t"
<< "图书名:" << results[i].getname() << "\t"
<< "作者:" << results[i].getauthor() << "\t"
<< "出版社:" << results[i].getpublisher() << "\t"
<< "价格:" << results[i].getprice() << "\t"
<< "库存量:" << results[i].getinventory() << endl;
}
CLEAR();
}
else
{
cout << "未找到该图书!" << endl;
CLEAR();
}
}
void findBookByISBN()
- 输入要查找的ISBN号.
- 创建
vector<Book>results用于储存查询的图书(相当于复制一条数据,防止出意外) - 成功显示图书所有信息,失败则回复用户
// 查找图书信息 -- 通过ISBN号
void BookManager::findBookByISBN()
{
string ISBN;
cout << "请输入要查找的ISBN号:";
cin >> ISBN;
vector<Book> results;
for (int i = 0; i < books.size(); i++)
{
if (books[i].getISBN() == ISBN)
{
results.push_back(books[i]);
break; // 因为ISBN号唯一,所以找到后直接退出 节约时间
}
}
if (results.size() != 0)
{
cout << "ISBN号:" << results[0].getISBN() << "\t"
<< "图书名:" << results[0].getname() << "\t"
<< "作者:" << results[0].getauthor() << "\t"
<< "出版社:" << results[0].getpublisher() << "\t"
<< "价格:" << results[0].getprice() << "\t"
<< "库存量:" << results[0].getinventory() << endl;
CLEAR();
}
else
{
cout << "未找到该图书!" << endl;
CLEAR();
}
}
void findBookByAuthor()
- 输入要查找的作者名
- 创建
vector<Book>results用于储存查询的图书(同作者的书比较多) - 成功显示图书所有信息,失败则回复用户
// 查找图书信息 -- 作者名查找
void BookManager::findBookByAuthor()
{
string author;
cout << "请输入要查找的作者名:";
cin >> author;
vector<Book> results;
for (int i = 0; i < books.size(); i++)
{
if (books[i].getauthor() == author)
{
results.push_back(books[i]);
}
}
if (results.size() != 0)
{
for (int i = 0; i < results.size(); i++)
{
cout << "ISBN号:" << results[i].getISBN() << "\t"
<< "图书名:" << results[i].getname() << "\t"
<< "作者:" << results[i].getauthor() << "\t"
<< "出版社:" << results[i].getpublisher() << "\t"
<< "价格:" << results[i].getprice() << "\t"
<< "库存量:" << results[i].getinventory() << endl;
}
CLEAR();
}
else
{
cout << "未找到该图书!" << endl;
CLEAR();
}
}
void findBookByPublisher()
- 输入要查找的出版社名
- 创建
vector<Book>results用于储存查询的图书(同出版社的书比较多) - 成功显示图书所有信息,失败则回复用户
// 查找图书信息 -- 通过出版社查找
void BookManager::findBookByPublisher()
{
string publisher;
cout << "请输入要查找的出版社名:";
cin >> publisher;
vector<Book> results;
for (int i = 0; i < books.size(); i++)
{
if (books[i].getpublisher() == publisher)
{
results.push_back(books[i]);
}
}
if (results.size() != 0)
{
for (int i = 0; i < results.size(); i++)
{
cout << "ISBN号:" << results[i].getISBN() << "\t"
<< "图书名:" << results[i].getname() << "\t"
<< "作者:" << results[i].getauthor() << "\t"
<< "出版社:" << results[i].getpublisher() << "\t"
<< "价格:" << results[i].getprice() << "\t"
<< "库存量:" << results[i].getinventory() << endl;
}
CLEAR();
}
else
{
cout << "未找到该图书!" << endl;
CLEAR();
}
}
void modifyBook()
- 先输入要修改的图书的ISBN号
- 显示选择菜单 -- 修改属性
- 提供各种修改接口
- 输入不合法时,重新输入
- 修改成功与否,提示用户
注:此时需要记录要修改的 图书在数组中的下表,便于直接修改
// 修改图书信息
void BookManager::modifyBook()
{
string ISBN;
cout << "请输入要修改的ISBN号:";
cin >> ISBN;
for (int i = 0; i < books.size(); i++)
{
if (books[i].getISBN() == ISBN)
{
while (true)
{
int select;
modifyBookMenu(); // 显示修改菜单
cout << endl;
cout << "请输入您的选择:";
cin >> select;
switch (select)
{
case 1: // 修改书名
modifyBookName(i);
break;
case 2: // 修改出版社
modifyBookPublisher(i);
break;
case 3: // 修改ISBN号
modifyBookISBN(i);
break;
case 4: // 修改库存量
modifyBookInventory(i);
break;
case 5: // 修改价格
modifyBookPrice(i);
break;
case 0:
{
CLEAR();
return;
}
break;
default:
{
cout << "输入有误,请重新输入!!" << endl;
// 清屏 按任意键继续
CLEAR();
}
break;
}
}
}
}
cout << "未找到该图书!" << endl;
CLEAR();
}
void modifyBookName(int num) -- num为要修改数据的下标
- 输入新书名
- 调用Book类中的修改函数
modifyBookName进行修改 - 修改后需保存至文件中
saveBooks
// 修改图书名
void BookManager::modifyBookName(int num)
{
string newname;
cout << "请输入要修改的新书名:";
cin >> newname;
books[num].modifyBookName(newname);
saveBooks(); // 保存文件
cout << "修改成功!" << endl;
CLEAR();
}
void modifyBookPublisher(int num) -- num为要修改数据的下标
- 输入新的出版社名
- 调用Book类中的修改函数
modifyBookPublisher进行修改 - 修改后需保存至文件中
saveBooks
// 修改图书出版社
void BookManager::modifyBookPublisher(int num)
{
string newpublisher;
cout << "请输入新的出版社:";
cin >> newpublisher;
books[num].modifyBookPublisher(newpublisher);
saveBooks();
cout << "修改成功!!" << endl;
CLEAR();
}
void modifyBookISBN(int num) -- num 为要修改数据的下标
- 输入新的ISBN号
- ==判断修改后的ISBN号是否与书库中重复== -- 重复则重新输入
- 调用Book类中的修改函数modifyBookISBN进行修改
- 修改后需保存至文件中
saveBooks
// 修改图书ISBN号 「注:修改后的ISBN号不能与现有图书重复」
void BookManager::modifyBookISBN(int num)
{
while (true)
{
string newISBN;
cout << "请输入新的ISBN号:";
cin >> newISBN;
bool result = true;
// 判断输入的ISBN号是否与库存中重复
for (int i = 0; i < books.size(); i++)
{
if (newISBN == books[i].getISBN())
{
result = false;
}
}
if (result) // 不重复
{
books[num].modifyBookISBN(newISBN);
saveBooks();
cout << "修改成功!!" << endl;
CLEAR();
break;
}
else
{
cout << "输入的ISBN号与库存中重复,请重新输入!!" << endl;
CLEAR();
}
}
}
void modifyBookInventory(int num) -- num为要修改数据的下标
- 输入新的库存
- 调用Book类中的修改函数
modifyBookInventory进行修改 - 修改后需保存至文件中
saveBooks
// 修改图书库存量
void BookManager::modifyBookInventory(int num)
{
int newinventory;
cout << "请输入新的库存量:";
cin >> newinventory;
books[num].modifyBookInventory(newinventory);
saveBooks();
cout << "修改成功!!" << endl;
CLEAR();
}
void modifyBookPrice(int num) -- num为修改数据的下标
- 输入新的图书价格
- 调用Book类中的修改函数
modifyBookPrice进行修改 - 修改后需保存至文件中
saveBooks
// 修改图书价格
void BookManager::modifyBookPrice(int num)
{
double newprice;
cout << "请输入新的价格:";
cin >> newprice;
books[num].modifyBookPrice(newprice);
saveBooks();
cout << "修改成功!!" << endl;
CLEAR();
}
void deleteBook()
- 输入要删除的ISBN号
- 从数组中抹去数据
- 删除后需保存至文件中
saveBooks
// 删除图书信息
void BookManager::deleteBook()
{
string ISBN;
cout << "请输入要删除的ISBN号:";
cin >> ISBN;
for (int i = 0; i < books.size(); i++)
{
if (books[i].getISBN() == ISBN)
{
books.erase(books.begin() + i);
saveBooks(); // 更新文件
cout << "删除成功!" << endl;
CLEAR();
return;
}
}
cout << "未找到该图书信息!" << endl;
CLEAR();
}
void showAllBooks()
- 显示统计菜单,接收用户选择
- 输入非法则提示用户 -- 重新输入
- 提供各种功能接口
// 显示所有图书信息
void BookManager::showAllBooks()
{
while (true)
{
int select;
showAllBookMenu();
cout << "请输入您的选择:";
cin >> select;
switch (select)
{
case 1:
sortByPrice();
break;
case 2:
sortByInventory();
break;
case 0:
{
CLEAR();
return;
}
break;
default:
{
cout << "输入错误,请您重新输入!!" << endl;
CLEAR();
}
break;
}
}
}
void sortByPrice()
- 按价格降序排列
- 利用到了sort算法,但要注意的是Book类属性较多,程序并不知道比较什么属性
- ==排序时,不要将books书库直接排序,创建一个新的书库,并复制books中的数据,将其排序显示==
注 :此时
cmppric函数起重大作用,提供了比较条件。
// 按图书价格排序
bool cmpprice(Book a, Book b) { return a.getprice() > b.getprice(); }
void BookManager::sortByPrice()
{
vector<Book> result;
result = books;// 创建新的书库
sort(result.begin(), result.end(), cmpprice); // 按价格升序排列
// 输出显示
for (int i = 0; i < result.size(); i++)
{
result[i].print();
}
CLEAR();
}
void sortByInventory()
- 按库存量降序排列
- 利用到了sort算法,但要注意的是Book类属性较多,程序并不知道比较什么属性
- 排序时,不要将
books书库直接排序,创建一个新的书库,并复制books中的数据,将其排序显示
注 :此时
cmpinventory函数起重大作用,提供了比较条件。
// 按图书库存量排序
bool cmpinventory(Book a, Book b) { return a.getinventory() > b.getinventory(); }
void BookManager::sortByInventory()
{
vector<Book> result;
result = books;
sort(result.begin(), result.end(), cmpinventory); // 按库存量升序排列
// 输出显示
for (int i = 0; i < result.size(); i++)
{
result[i].print();
}
CLEAR();
}
void showAllBooksByAuhtor()
- 按作者名进行统计图书数量
- 利用到了
Statistics类 - 创建
results数组进行存储每个作者的图书总数 - 输出时需要按图书量降序输出
- 排序用到了
cmpnum函数,这里不过多赘述
// 排序
bool cmpnum(Statistics a, Statistics b) { return a.getnum() > b.getnum(); }
// 按作者进行统计
void BookManager::showAllBooksByAuthor()
{
vector<Statistics> results;// 用于存储
for (int i = 0; i < books.size(); i++)
{
bool re = true;
string name = books[i].getauthor();
int num = 0;
for (int j = 0; j < results.size(); j++)
{
if (name == results[j].getname())// 判断作者是否存入
{
re = false;
break;
}
}
if (re)
{
for (int g = 0; g < books.size(); g++)
{
if (name == books[g].getauthor())
{
num += books[g].getinventory();// 累计数量
}
}
Statistics p(name, num);
results.push_back(p);// 进入数组记录
}
}
sort(results.begin(), results.end(), cmpnum);// 排序
for (int i = 0; i < results.size(); i++)
{
results[i].PrintAuthor();
}
CLEAR();
}
void showAllBooksByPublisher()
- 与按作者统计相同,不同点在于此时的name为publisher
// 按出版社进行统计
void BookManager::showAllBooksByPublisher()
{
vector<Statistics> results;
for (int i = 0; i < books.size(); i++)
{
bool re = true;
string name = books[i].getpublisher();
int num = 0;
for (int j = 0; j < results.size(); j++)
{
if (name == results[j].getname())
{
re = false;
break;
}
}
if (re)
{
for (int g = 0; g < books.size(); g++)
{
if (name == books[g].getpublisher())
{
num += books[g].getinventory();
}
}
Statistics p(name, num);
results.push_back(p);
}
}
sort(results.begin(), results.end(), cmpnum);
for (int i = 0; i < results.size(); i++)
{
results[i].PrintPublisher();
}
CLEAR();
}
void BuyBook()
购买函数实则就是修改图书库存量,所以实现过程比较简单,但本人这代码写的有些冗余,可以尝试自己优化一下。
// 购买函数
void BookManager::BuyBoook()
{
while (true)
{
bool result = false;
int num; // 记录购买书在books中的下标
string ISBN;
cout << "请输入要够买的书的ISBN号:" << endl;
cin >> ISBN;
for (int i = 0; i < books.size(); i++)
{
if (ISBN == books[i].getISBN())
{
result = true; // 书库中存在该书
num = i;
break; // 节约时间,因为ISBN号唯一
}
}
if (result) // 如果书库中存在该书
{
int amount;
cout << "这是您要购买书的信息,请确认:" << endl;
books[num].print(); // 显示书籍信息
cout << endl;
cout << "请输入购买数量:";
cin >> amount;
if (books[num].getinventory() >= amount) // 库存足够
{
books[num].modifyBookInventory(books[num].getinventory() - amount); // 更新库存量
saveBooks(); // 更新文件数据
cout << "购买成功!!" << endl;
CLEAR();
return;
}
else
{
cout << "购买失败,库存不足!" << endl;
CLEAR();
break;
}
}
else
{
cout << "该书不存在!!" << endl;
CLEAR();
break;
}
}
}
void saveBooks() -- *重点
- 其实在文件中,针对一条数据进行增删改是比较麻烦的,尤其是修改操作,因为还涉及到针对某一行的某一列进行修改。所以本人找到了一个很简单的解决方法,但是在真实的项目中应该是不可取的,因为数据量较大时,就会浪费较多的时间,也有可能浪费很多的空间。
- 保存数据至文件操作—— 直接将文件中的所有数据清空,然后重新将数据全部写入文件中。
- 样就解决了很多不必要的麻烦「在增、删、改 中」。
// 将图书信息保存至文件
void BookManager::saveBooks()
{
ofstream file(filename);
if (file.is_open())
{
// 先清空文件,然后在重新写入文件
file.open(filename, ofstream::out | ofstream::trunc);
file.close();
file.open(filename);
for (int i = 0; i < books.size(); i++)
{
file << books[i].toString() << endl;
}
file.close();
}
}
==void loadBooks() -- *重点
- 从文件中读入数据也是一个比较麻烦的过程,要找到数据与数据之间存在的共性。
- 基于Book类中的toString函数,在文件中,每本书的各个数据都以一个字符串在文本中体现
- 例:(瞎写的数据,请见谅!!!)
- 思路:
- 每个数据前都有
:冒号,后都有一个\t - 通过定义两个指针
startend来截取字符串中的数据- start指向
:, end指向\t - 则截取字段为 : line.substr(start + 1, end - start-1)
- 更新字符串为 : line = line.substr(end + 1)
- start指向
- 别忘记 价格 、库存量 需要类型转换
- 每个数据前都有
其实下面方法都一样,但是下面代码重复较多,比较暴力了一点。
// 从文件中加载图书信息
void BookManager::loadBooks()
{
ifstream inFile(filename);
if (inFile.is_open())
{
string line; // 用来存储从文件中读取的每一行。
while (getline(inFile, line)) // getline() 函数从 inFile 中读取一行,并将其存储在 line 变量中。
{ // getline() 函数会返回一个输入流,如果成功读取一行,那么这个输入流在进行布尔检查时会被解析为true
int start = line.find(":");
int end = line.find("\t");
string ISBN = line.substr(start + 1, end - start-1); // 截取ISBN号
line = line.substr(end + 1);
start = line.find(":");
end = line.find("\t");
string name = line.substr(start + 1, end - start-1); // 截取书名
line = line.substr(end + 1);
start = line.find(":");
end = line.find("\t");
string author = line.substr(start + 1, end - start-1); // 截取作者名
line = line.substr(end + 1);
start = line.find(":");
end = line.find("\t");
string publisher = line.substr(start + 1, end - start-1); // 截取出版社名
line = line.substr(end + 1);
start = line.find(":");
end = line.find("\t");
double price = stod(line.substr(start + 1, end - start-1)); // 截取价格 stod将string转换为double类型
line = line.substr(end + 1);
start = line.find(":");
end = line.find("\t");
int inventory = stoi(line.substr(start + 1, end - start-1)); // 截取库存量 stoi将string转换为int类型
Book book(name,publisher,ISBN,author,inventory,price); // 创建book类对象
books.push_back(book); // 插入书库中
}
inFile.close(); // 关闭文件
}
}
Statistics类
主要用于统计中的对作者 出版社 进行统计输出图书数量的类这里不过多赘述。
class Statistics
{
public:
string name;
int num;
Statistics();
Statistics(string a,int b);
string getname();
int getnum();
void PrintAuthor();
void PrintPublisher();
};
Statistics::Statistics(string a, int b)
{
this->name = a;
this->num = b;
}
string Statistics::getname()
{
return this->name;
}
int Statistics::getnum()
{
return this->num;
}
void Statistics::PrintAuthor()
{
cout << "作者: " << this->name << "\t"
<< "数量: " << this->num << endl;
}
void Statistics::PrintPublisher()
{
cout << "出版社: " << this->name << "\t"
<< "数量: " << this->num << endl;
}
五.总结
基于几个"同理"的排序操作方法
void sortByPrice()
- *重点在于
cmpprice函数 - 按价格降序排列
- 利用到了
sort算法,但要注意的是Book类属性较多,程序并不知道比较什么属性 - 排序时,不要将
books书库直接排序,创建一个新的书库,并复制books中的数据,将其排序显示
注 :此时
cmpprice函数起重大作用,提供了比较条件。
// 按图书价格排序
bool cmpprice(Book a, Book b) { return a.getprice() > b.getprice(); }
void BookManager::sortByPrice()
{
vector<Book> result;
result = books;// 创建新的书库
sort(result.begin(), result.end(), cmpprice); // 按价格升序排列
// 输出显示
for (int i = 0; i < result.size(); i++)
{
result[i].print();
}
CLEAR();
}
void CLEAR()
此函数,在上述各种功能中多次出现,作用为提升用户体验感,防止操作结果冗余,造成输出结果不清晰。
cin.get()只有收到用户任意输入时,才进行清屏操作(直接清屏的话会让用户体验更差,所以要有中断过程)。
// 按任意键继续 清屏
void CLEAR()
{
cout << "请按任意键继续...." << endl;
cin.get();
cin.get();
system("clear");
}