文章目录
写在前面
正如标题所示,本文是写给C++的初学者的一个书店管理系统实例。为了贴近目标读者的需求,本篇博客中的代码仅采用了初级的内容。比如:对于书的名字,采取了最简单粗暴的多维字符串存储而不是Trie树甚或AC自动机;对于所有排序操作,均采用简单粗暴的冒泡排序,而不是重载运算符后调用STL中的sort函数。力求简单易懂,为C++初学者提供良好的阅读体验。
此外,与我惯于对付的传统竞赛题不同的是,此类题目需要更多考虑到使用者的层次和需求。需要提供一定的使用指导和人机交互界面,还需要考虑到一些不合法的输入。因为本篇博客中的代码仅作抛砖引玉之用(以及本人的懒惰),代码中仅加入了有限的不合法输入判定,仍然存在非常多的不合法输入可以导致程序不能正常运行。同时,如果仅仅满足下方图片所示的要求,作为一个书店管理系统还是有所欠缺,可以加入诸如图形界面、撤销操作、修改操作等更多功能。
学海无涯,本人也不过先行几步。以上所述,就权作对后续学习的一点浅见。
题目要求
(鉴于图片足够清晰,就不手打了)
主体思路
对于图书信息的储存,显然单独开若干个数组将书名、作者、出版社等信息分别储存并不是一种优美的做法(当然,从这句话可以看出这种解决方案依旧可行)。所以,我们应该将这些信息“绑定”在一起,相当于一种新的数据类型 b o o k book book。这种方法叫做定义结构体,将在下一个章节讲到。
当我们想要查找某一本书的时候,直接用for循环遍历已有的所有书并进行for循环暴力字符串匹配即可。
对于一本书的销售额、销售数量,同样可以定义两个变量一起“绑定”在 b o o k book book里,在排序时直接按照给定的关键字来排序。
至于进出货记录,可以直接开多维数组记录 进/出货、数量、交易额等信息,也可以定义结构体。
代码实现
定义结构体book
这一步很简单,掌握了相应的语法之后,只需要把你想要“绑定”在一起的各个变量、数组加进去即可。语法如下:
struct 定义的新数据类型名字
{
变量1、变量2、……
数组1、数组2、……
};
在本题中,按照题目要求,我们定义如下结构体:
struct book
{
int number,publishDate[5],salesVolume;
char name[50],publisher[50],author[50];
double price,sales;
bool discount;
}books[M];
注:右花括号与分号之间的books[M]
是声明数组一个
b
o
o
k
book
book类型的数组book books[M];
的简化写法。
读入带空格字符串的函数
显然,书名、作者、出版社等信息都是带空格的字符串,不能简单地用scanf("%s",str)
来代替。因此,我们需要单独编写对应函数。
在下方所示的函数中,传入参数时,传入了字符串数组的地址,这样就可以直接修改对应字符串数组。
第一个for循环筛除了字符串开头所有非数字且非字母的字符,其中isdigit()
判断一个字符是否为数字,isalpha()
判断一个字符是否为字母。
第二个for循环开始读入,直到读到换行符时停止读入。其中,(为了省事)第0个位置str[0]
被我用来记录字符串的长度,str[1]
~str[str[0]]
为字符串内容。
void readString(char *str)
{
char c=getchar();
for(;!isdigit(c)&&!isalpha(c);c=getchar());
for(str[0]=0;c!='\n';c=getchar())str[++str[0]]=c;
}
通过书名查找书的编号
这个就很暴力了,for循环即可。
值得注意的是,我们这里传入的数组是形参形式,函数内对数组的更改并不会影响原本的数组。
int enquireByName(char goal[])
{
for(int i=1;i<=totalNumber;++i)
{
int flag=1;
for(int j=1;j<=books[i].name[0];++j)
if(books[i].name[j]!=goal[j]){flag=0;break;}
if(flag)return i;
}
return 0;
}
进货函数
有了之前写的若干函数,就很简单了,先输入要进货的书的书名,在已有书中检索,如果书已存在,直接对数目进行修改;如果之前没有过这本书,就用读入带空格字符串函数读入各种信息储存到books
数组里,并为新入库的书编号。在函数结尾,将进货记录保存到records
数组里。
void purchase()
{
int id=0,tmp=0;
puts("-Please input the title of the book you want to stock (Press \"Enter\" to complete your input)");
readString(tmpBook);
if(id=enquireByName(tmpBook))
{
puts("-The information of this book has already been recorded in system");
puts("-So you only need to input the number of newly purchased books");
scanf("%d",&tmp),books[id].number+=tmp;
}
else
{
id=++totalNumber;
for(int i=0;i<=tmpBook[0];++i)books[id].name[i]=tmpBook[i];
puts("-This is a book that we never purchased before");
puts("-Please input its detailed information");
puts("-Please input the author of the book you want to stock (Press \"Enter\" to complete your input)");
readString(books[totalNumber].author);
puts("-Please input the publishing house of the book you want to stock (Press \"Enter\" to complete your input)");
readString(books[totalNumber].publisher);
puts("-Please input the price of the book you want to stock (Press \"Enter\" to complete your input)");
scanf("%lf",&books[totalNumber].price);
puts("-Is there a discount for this book (Input 0 for No or 1 for Yes)");
scanf("%d",&books[totalNumber].discount);
puts("-Please input the number of newly purchased books (Press \"Enter\" to complete your input)");
scanf("%d",&books[totalNumber].number),tmp=books[totalNumber].number;
puts("-Please input the publishing date of the book you want to stock (Press \"Enter\" to complete your input)");
puts("-Please input as follow format: Year Month Day (3 numbers divided by space)");
for(int i=1;i<=3;++i)
scanf("%d",&books[totalNumber].publishDate[i]);
}
records[++cot][0]=0,records[cot][1]=id,records[cot][2]=tmp;
puts("-Opration ends");
}
出货函数
出货函数和进货函数逻辑类似,都是在库存中查找,检查是否有足够的库存,再检查有无折扣,最后生成一条新的出货记录。
void sell()
{
puts("-Please input the title of the book you want to sell (Press \"Enter\" to complete your input)");
readString(tmpBook);
int id=enquireByName(tmpBook),tmp,lev=0;
if(!id){puts("-There is no book called this!");return;}
if(books[id].discount)
{
puts("-This book can be discounted.");
puts("-Please input the buyer's VIP level (Press \"Enter\" to complete your input)");
scanf("%d",&lev);
if(lev<0||lev>3){puts("-Invalid level!");return;}
}
else puts("-This book can't be discounted.");
puts("-Please input the number of the book you want to sell (Press \"Enter\" to complete your input)");
scanf("%d",&tmp);
if(tmp>books[id].number){puts("-No enough books here");return;}
books[id].number-=tmp;
books[id].salesVolume+=tmp;
books[id].sales+=tmp*VIP[lev]*books[id].price;
records[++cot][0]=1,records[cot][1]=id,records[cot][2]=tmp,salesRecords[cot]=tmp*VIP[lev]*books[id].price;
puts("-Opration ends");
}
查询某书的详细信息、销售情况、库存函数
这三个函数比较雷同,都是先通过书名查找,然后直接将结构体内记录的内容输出即可。如果是通过编号查找,则直接输出即可。
查询详细信息函数
void enquireBy(int p)//0-name 1-id
{
int id;
if(p)
{
puts("-Please input the title of the book you want to enquire (Press \"Enter\" to complete your input)");
scanf("%d",&id);
if(id<=0||id>totalNumber){puts("-There is no book numbered this!");return;}
printBook(id);
return;
}
puts("-Please input the title of the book you want to enquire (Press \"Enter\" to complete your input)");
readString(tmpBook);
id=enquireByName(tmpBook);
if(!id){puts("-There is no book called this!");return;}
printBook(id);
puts("-Opration ends");
}
查询销售情况函数
void enquireSalesBy(int p)//0-name 1-id
{
int id;
if(p)
{
puts("-Please input the title of the book you want to enquire (Press \"Enter\" to complete your input)");
scanf("%d",&id);
if(id<=0||id>totalNumber){puts("-There is no book numbered this!");return;}
printf("-Sell %d books for $%.2lf.\n",books[id].salesVolume,books[id].sales);
return;
}
puts("-Please input the title of the book you want to enquire (Press \"Enter\" to complete your input)");
readString(tmpBook);
id=enquireByName(tmpBook);
if(!id){puts("-There is no book called this!");return;}
printf("-Sell %d books for $%.2lf.\n",books[id].salesVolume,books[id].sales);
puts("-Opration ends");
}
查询库存函数
void enquireInventoryBy(int p)//0-name 1-id
{
int id;
if(p)
{
puts("-Please input the title of the book you want to enquire (Press \"Enter\" to complete your input)");
scanf("%d",&id);
if(id<=0||id>totalNumber){puts("-There is no book numbered this!");return;}
printf("-The number of this book in inventory is %d.\n",books[id].number);
return;
}
puts("-Please input the title of the book you want to enquire (Press \"Enter\" to complete your input)");
readString(tmpBook);
id=enquireByName(tmpBook);
if(!id){puts("-There is no book called this!");return;}
printf("-The number of this book in inventory is %d.\n",books[id].number);
puts("-Opration ends");
}
查询进/出货记录函数
根据查找的类型,把records
数组里的信息输出即可,这里出货额度我单独开了一个double
类型的salesRecords
数组。
void enquireRecords(int p)//0-purchase 1-sell
{
if(p)puts("-Sell records:");
else puts("-Purchase records:");
for(int i=1;i<=cot;++i)
if(records[i][0]==p)
{
if(p)printf("-Sell");
else printf("-Purchase");
printf(" %d ",records[i][2]);
printString(books[records[i][1]].name);
if(p)printf("for $%.2lf\n\n",salesRecords[i]);
}
puts("-Opration ends");
}
排序函数
因为这里有两种关键字排序方式,且book
类型中有很多与排序操作无关的其他信息,最重要的是我直接把数组下标作为书的编号,所以books
数组里的数据位置不能随意变动。
分析一下,实际上与排序有关的变量只有两个:排序所用关键字以及关键字对应的书的编号。只要有这两个信息,我们就可以实现排序功能。
因此,我们开一个临时二位数组tool
来储存这两个信息,再使用冒泡排序以给定的关键字排序。
具体实现是,先把编号和所需关键字复制到tool
数组中:
for(int i=1;i<=totalNumber;++i)
{
tool[i][0]=i;
tool[i][1]=p?books[i].sales:books[i].salesVolume;
}
这里第二行使用了三目运算,相当于一个if+else:
if(p)tool[i][1]=books[i].sales;
else tool[i][1]=books[i].salesVolume;
之后就是双重for循环实现的冒泡排序,注意在交换元素的时候,要将tool[i][0]
和tool[i][1]
中的数据一起交换。
for(int i=1;i<=totalNumber;++i)for(int j=i+1;j<=totalNumber;++j)
if(tool[i][1]<tool[j][1])swap(tool[i][1],tool[j][1]),swap(tool[i][0],tool[j][0]);
这样我们把tool
数组按照tool[i][1]
排好了序,tool[i][0]
里就是对应的书的编号,按照该顺序输出编号对应书名即可。
for(int i=1;i<=totalNumber;++i)
printString(books[(int)tool[i][0]].name);
注:因为销售额为小数类型,所以二位数组不能开int类型,但是double类型又不能作为数组下标,所以在代入books数组是要用(int)tool[i][0]
转换数据类型(不偷懒的做法是定义一个结构体)。
完整代码:
double tool[M][2];
void sortAs(int p)//0-salesVolume 1-sales
{
for(int i=1;i<=totalNumber;++i)
{
tool[i][0]=i;
tool[i][1]=p?books[i].sales:books[i].salesVolume;
}
for(int i=1;i<=totalNumber;++i)for(int j=i+1;j<=totalNumber;++j)
if(tool[i][1]<tool[j][1])swap(tool[i][1],tool[j][1]),swap(tool[i][0],tool[j][0]);
for(int i=1;i<=totalNumber;++i)
printString(books[(int)tool[i][0]].name);
puts("-Opration ends");
}
其他函数
这些函数都比较浅显,不多做说明。
输出带空格字符串函数
这个函数是建立在读入函数的规则之上的。
void printString(char str[])
{
for(int i=1;i<=str[0];++i)printf("%c",str[i]);
putchar(10);
}
输出一本书的详细信息函数
void printBook(int id)
{
puts("-The title of the book is:");
printString(books[id].name);
puts("-The author of the book is:");
printString(books[id].author);
puts("-The publishing house of the book is");
printString(books[id].publisher);
puts("-The price of the book is:");
printf("$%.2lf\n",books[id].price);
if(books[id].discount)puts("-There is a discount for this book");
else puts("-There isn't a discount for this book");
puts("-The publishing date of the book is:");
for(int i=1;i<=3;++i)printf("%d ",books[id].publishDate[i]);putchar(10);
}
输出操作列表函数
void operationList()
{
puts("-Operate by inputting the corresponding number as follow:");
puts(" 1 Enquire a book's detailed information through its name.");
puts(" 2 Enquire a book's sales.");
puts(" 3 Enquire a book's inventory.");
puts(" 4 Sort by sales.");
puts(" 5 Sort by sales Volume.");
puts(" 6 Enquire the records of stocking.");
puts(" 7 Enquire the records of exporting.");
puts(" 8 Purchase one kind of book.");
puts(" 9 Sell one kind of book.");
puts(" 0 Operation list.");
puts("-Input a digit less than 0 to exit the system." );
}
完整代码
W e l c o m e t o u s e P i B o o k b o o k m a n a g e m e n t s y s t e m ! Welcome\ to\ use\ PiBook\ book\ management\ system! Welcome to use PiBook book management system!
#include<bits/stdc++.h>
using namespace std;
const int M=1005;
int totalNumber;
char tmpBook[50];
struct book
{
int number,publishDate[5],salesVolume;
char name[50],publisher[50],author[50];
double price,sales;
bool discount;
}books[M];
int records[M<<3][5],cot;//[0]:0-purchase 1-sell [1]:id [2]:number
double salesRecords[M<<3];
double VIP[]={1,0.9,0.8,0.6};
void readString(char *str)
{
char c=getchar();
for(;!isdigit(c)&&!isalpha(c);c=getchar());
for(str[0]=0;c!='\n';c=getchar())str[++str[0]]=c;
}
void printString(char str[])
{
for(int i=1;i<=str[0];++i)printf("%c",str[i]);
putchar(10);
}
void printBook(int id)
{
puts("-The title of the book is:");
printString(books[id].name);
puts("-The author of the book is:");
printString(books[id].author);
puts("-The publishing house of the book is");
printString(books[id].publisher);
puts("-The price of the book is:");
printf("$%.2lf\n",books[id].price);
if(books[id].discount)puts("-There is a discount for this book");
else puts("-There isn't a discount for this book");
puts("-The publishing date of the book is:");
for(int i=1;i<=3;++i)printf("%d ",books[id].publishDate[i]);putchar(10);
}
int enquireByName(char goal[])
{
for(int i=1;i<=totalNumber;++i)
{
int flag=1;
for(int j=1;j<=books[i].name[0];++j)
if(books[i].name[j]!=goal[j]){flag=0;break;}
if(flag)return i;
}
return 0;
}
void purchase()
{
int id=0,tmp=0;
puts("-Please input the title of the book you want to stock (Press \"Enter\" to complete your input)");
readString(tmpBook);
if(id=enquireByName(tmpBook))
{
puts("-The information of this book has already been recorded in system");
puts("-So you only need to input the number of newly purchased books");
scanf("%d",&tmp),books[id].number+=tmp;
}
else
{
id=++totalNumber;
for(int i=0;i<=tmpBook[0];++i)books[id].name[i]=tmpBook[i];
puts("-This is a book that we never purchased before");
puts("-Please input its detailed information");
puts("-Please input the author of the book you want to stock (Press \"Enter\" to complete your input)");
readString(books[totalNumber].author);
puts("-Please input the publishing house of the book you want to stock (Press \"Enter\" to complete your input)");
readString(books[totalNumber].publisher);
puts("-Please input the price of the book you want to stock (Press \"Enter\" to complete your input)");
scanf("%lf",&books[totalNumber].price);
puts("-Is there a discount for this book (Input 0 for No or 1 for Yes)");
scanf("%d",&books[totalNumber].discount);
puts("-Please input the number of newly purchased books (Press \"Enter\" to complete your input)");
scanf("%d",&books[totalNumber].number),tmp=books[totalNumber].number;
puts("-Please input the publishing date of the book you want to stock (Press \"Enter\" to complete your input)");
puts("-Please input as follow format: Year Month Day (3 numbers divided by space)");
for(int i=1;i<=3;++i)
scanf("%d",&books[totalNumber].publishDate[i]);
}
records[++cot][0]=0,records[cot][1]=id,records[cot][2]=tmp;
puts("-Opration ends");
}
void enquireBy(int p)//0-name 1-id
{
int id;
if(p)
{
puts("-Please input the title of the book you want to enquire (Press \"Enter\" to complete your input)");
scanf("%d",&id);
if(id<=0||id>totalNumber){puts("-There is no book numbered this!");return;}
printBook(id);
return;
}
puts("-Please input the title of the book you want to enquire (Press \"Enter\" to complete your input)");
readString(tmpBook);
id=enquireByName(tmpBook);
if(!id){puts("-There is no book called this!");return;}
printBook(id);
puts("-Opration ends");
}
void enquireSalesBy(int p)//0-name 1-id
{
int id;
if(p)
{
puts("-Please input the title of the book you want to enquire (Press \"Enter\" to complete your input)");
scanf("%d",&id);
if(id<=0||id>totalNumber){puts("-There is no book numbered this!");return;}
printf("-Sell %d books for $%.2lf.\n",books[id].salesVolume,books[id].sales);
return;
}
puts("-Please input the title of the book you want to enquire (Press \"Enter\" to complete your input)");
readString(tmpBook);
id=enquireByName(tmpBook);
if(!id){puts("-There is no book called this!");return;}
printf("-Sell %d books for $%.2lf.\n",books[id].salesVolume,books[id].sales);
puts("-Opration ends");
}
void enquireInventoryBy(int p)//0-name 1-id
{
int id;
if(p)
{
puts("-Please input the title of the book you want to enquire (Press \"Enter\" to complete your input)");
scanf("%d",&id);
if(id<=0||id>totalNumber){puts("-There is no book numbered this!");return;}
printf("-The number of this book in inventory is %d.\n",books[id].number);
return;
}
puts("-Please input the title of the book you want to enquire (Press \"Enter\" to complete your input)");
readString(tmpBook);
id=enquireByName(tmpBook);
if(!id){puts("-There is no book called this!");return;}
printf("-The number of this book in inventory is %d.\n",books[id].number);
puts("-Opration ends");
}
void sell()
{
puts("-Please input the title of the book you want to sell (Press \"Enter\" to complete your input)");
readString(tmpBook);
int id=enquireByName(tmpBook),tmp,lev=0;
if(!id){puts("-There is no book called this!");return;}
if(books[id].discount)
{
puts("-This book can be discounted.");
puts("-Please input the buyer's VIP level (Press \"Enter\" to complete your input)");
scanf("%d",&lev);
if(lev<0||lev>3){puts("-Invalid level!");return;}
}
else puts("-This book can't be discounted.");
puts("-Please input the number of the book you want to sell (Press \"Enter\" to complete your input)");
scanf("%d",&tmp);
if(tmp>books[id].number){puts("-No enough books here");return;}
books[id].number-=tmp;
books[id].salesVolume+=tmp;
books[id].sales+=tmp*VIP[lev]*books[id].price;
records[++cot][0]=1,records[cot][1]=id,records[cot][2]=tmp,salesRecords[cot]=tmp*VIP[lev]*books[id].price;
puts("-Opration ends");
}
void enquireRecords(int p)//0-purchase 1-sell
{
if(p)puts("-Sell records:");
else puts("-Purchase records:");
for(int i=1;i<=cot;++i)
if(records[i][0]==p)
{
if(p)printf("-Sell");
else printf("-Purchase");
printf(" %d ",records[i][2]);
printString(books[records[i][1]].name);
if(p)printf("for $%.2lf\n\n",salesRecords[i]);
}
puts("-Opration ends");
}
double tool[M][2];
void sortAs(int p)//0-salesVolume 1-sales
{
for(int i=1;i<=totalNumber;++i)
{
tool[i][0]=i;
tool[i][1]=p?books[i].sales:books[i].salesVolume;
}
for(int i=1;i<=totalNumber;++i)for(int j=i+1;j<=totalNumber;++j)
if(tool[i][1]<tool[j][1])swap(tool[i][1],tool[j][1]),swap(tool[i][0],tool[j][0]);
for(int i=1;i<=totalNumber;++i)
printString(books[(int)tool[i][0]].name);
puts("-Opration ends");
}
void operationList()
{
puts("-Operate by inputting the corresponding number as follow:");
puts(" 1 Enquire a book's detailed information through its name.");
puts(" 2 Enquire a book's sales.");
puts(" 3 Enquire a book's inventory.");
puts(" 4 Sort by sales.");
puts(" 5 Sort by sales Volume.");
puts(" 6 Enquire the records of stocking.");
puts(" 7 Enquire the records of exporting.");
puts(" 8 Purchase one kind of book.");
puts(" 9 Sell one kind of book.");
puts(" 0 Operation list.");
puts("-Input a digit less than 0 to exit the system." );
}
int main()
{
puts("-Welcome to use PiBook book management system!");
operationList();
for(int op,tmp;;)
{
scanf("%d",&op);
if(op<0)break;
if(op>9){puts("-Invalid input!");continue;}
if(op&&op<=3)
{
puts("-Input the way you want to enquire the book by (0-name 1-ID)");
scanf("%d",&tmp);
}
if(!op)operationList();
else if(op==1)enquireBy(tmp);
else if(op==2)enquireSalesBy(tmp);
else if(op==3)enquireInventoryBy(tmp);
else if(op==4)sortAs(1);
else if(op==5)sortAs(0);
else if(op==6)enquireRecords(0);
else if(op==7)enquireRecords(1);
else if(op==8)purchase();
else if(op==9)sell();
puts("================================");
}
puts("-Thanks for using PiBook book managemnet system!");
system("pause");
}
结语
正如前面所说,人机交互系统的难点就落在人机交互上,此次编程我也算深有感触。如果删去各种puts()
出来的提示信息和非法输入判断,我可以很快结束代码的编写,但除我以外也就没人会使用这个系统了。即便我花费了大量的时间在交互上,这份代码依然存在我前面所说的种种不足,种种bug。当用户完全按照给定指令操作时或许无伤大雅,但是面对刁钻的甲方,以及可能存在的恶意攻击,这份代码很容易就会陷入故障。
管中窥豹,我不得不感慨想要做出一个成熟、完备而安全,能够制止各种非法操作且具有高人性化交互的系统是多么的困难。在IT学习上,我还有很长的路要走……