本项目会搭建一个控制台操作的图书管理系统。
学习目标:链表操作、文件读写、分文件编写、
V1.0.0
– 程序安装包
documents
– 项目开发相关文档
projects/BMS
– 项目文件夹
工具 | 说明 | 版本 | 备注 |
---|---|---|---|
MindMaster | 思维导图设计工具 | 12.0.5 | https://www.edrawsoft.cn/mindmaster/ |
ProcessOn | 流程图绘 | NULL | https://www.processon.com/ |
MODAO | 原型图绘制工具 | v1.2.5 | https://modao.cc/brand |
工具 | 版本 | 备注 |
---|---|---|
Windows | 11 | 操作系统 |
Visual Studio | 2022 | https://visualstudio.microsoft.com/zh-hans/vs/ |
bms.cpp
:main.cpp
base.cpp/h
:结构体、初始化相关函数
BLL.cpp/h
:业务逻辑层
DAL.cpp/h
:数据访问层
UI.cpp/h:
表现层
startinterface.cpp/h
:开始界面动画
map.cpp/h
:操作界面外壳
tools.cpp/h
:控制台优化类函数
point.cpp/h
:操作界面外壳元素
bms.cpp
:main.cpp
包含内容:
#include ..
void init() {
//1.开始动画
SetWindowSize(41, 32);//tools设置窗口大小
SetColor(3);//tools设置开始动画颜色
StartInterface* start = new StartInterface();//动态分配一个StartInterface类start
start->Action();//开始动画
delete start;//释放内存空间
/*设置关标位置,并输出提示语,等待任意键输入结束*/
SetCursorPosition(13, 26);
std::cout << "Press any key to start... ";
SetCursorPosition(13, 27);
system("pause");
//2.初始化头节点
bHEAD = createbHead();
mHEAD = createmHead();
//3.读取文件操作
readInfoFromFile_b("book.txt", bHEAD);
readInfoFromFile_m("man.txt", mHEAD);
}
int main() {
//初始化
init();
//调用主界面函数:进行登录-注册页面
while (true) {
switch (loginface()) {
case 0:
//调用管理界面函数
SetCursorPosition(5,5);
guanL();
break;
case 1:
//调用用户界面函数
SetCursorPosition(5,5);
yongH();
break;
}
}
return 0;
}
base.h/cpp
:结构体、初始化链表相关函数
具体内容:
#include ..
typedef struct _man
{
char Account[MAX];
long key;//最多九位
struct _man* next;
int pw = 0;//0是管理员,1是用户
}*man;
extern man reader;//当前登录的人,不论是管理员还是用户
typedef struct _book
{
int storage_num ;//编号
char name[MAX];
char writer[MAX];
int num;//该书储存量
struct _book* next;
}*book;
//创建全局链表表头
extern book bHEAD;
extern man mHEAD;
//创建表头:
book createbHead();
man createmHead();
//创建节点
book createNode(char* name, char* writer, int storage_num, int num);
man createNode(char* Account, long key, int pw);
BLL.cpp
:业务逻辑层
主要内容:
//一、核心操作
//添加节点到链表头部(头节点后面的第一个节点)
void Add(book headNode, char* name, char* writer, int storage_num, int num) {
book newNode = createNode(name, writer, storage_num, num);
newNode->next = headNode->next;
headNode->next = newNode;
}
man Addm(man headNode, char* Account, long key, int pw = 1) {//默认注册只能注册用户,pw=1
man newNode = createNode(Account, key, pw);
newNode->next = headNode->next;
headNode->next = newNode;
return newNode;
}
//删除书籍节点
void Delete(char* bookName)
{
book posLeftNode = bHEAD;
book posNode = bHEAD->next;//创建临时变量posNode记录要删除的节点
while (posNode != NULL && strcmp(posNode->name, bookName)) //思路二:调用Find函数
{
posLeftNode = posNode;
posNode = posLeftNode->next;
}
if (posNode == NULL) {
SetCursorPosition(5, 15);
printf("Error!Try again.");
}
else
{
SetCursorPosition(5, 15);
printf("删除成功\n");
posLeftNode->next = posNode->next;
free(posNode);//释放临时变量指向的堆区数据
posNode = NULL;
}
}
//查找书籍:返回书籍结构体指针
book Find(char* fileName, book headNode = bHEAD) {
book posNode = headNode->next;
while (posNode != NULL && strcmp(posNode->name, fileName)) {
posNode = posNode->next;
}
return posNode;
}
//查找用户:返回用户节点
man Find_man(char* Account) {
man node;
for (node = mHEAD->next; node != NULL; node = node->next) {
if (!strcmp(node->Account, Account)) {
return node;
}
}
return NULL;
}
//递归查找并打印书籍
void findBook(book temp) {
if (temp->next != NULL) {
findBook(temp->next);
}
printf("\v\t%d 书名:%s\n\t\t数量:%d\t\n", temp->storage_num, temp->name, temp->num);
}
DAL.cpp
:数据访问层
主要内容:将本地文件中的数据读取到链表中,将链表中的数据存储到本地。
//二、数据处理部分
#include ..
//文件写操作
void saveInforToFile_b(const char* fileName, book headNode) {
FILE* fp = fopen(fileName, "w");
book pMove = headNode->next;
while (pMove != NULL) {
fprintf(fp, "%d %s %s %d\n", pMove->storage_num, pMove->name, pMove->writer, pMove->num);
pMove = pMove->next;
}
fclose(fp);
}
void saveInforToFile_m(const char* fileName, man headNode) {
FILE* fp = fopen(fileName, "w");
man pMove = headNode->next;
while (pMove != NULL) {
fprintf(fp, "%s %d %d\n", pMove->Account, pMove->key, pMove->pw);
pMove = pMove->next;
}
fclose(fp);
}
//文件读操作
void readInfoFromFile_b(const char* fileName, book headNode) {
FILE* fp = fopen(fileName, "r");
if (fp == NULL) {
//第一次打开程序文件肯定不存在,所以要创建一个文件
fp = fopen(fileName, "w+");
}
int storage_num = 0, num = 0; char name[MAX] = { 0 }, writer[MAX] = { 0 };
while (fscanf(fp, "%d %s %s %d\n", &storage_num, name, writer, &num) != EOF) {
Add(bHEAD, name, writer, storage_num, num);
}
fclose(fp);
}
void readInfoFromFile_m(const char* fileName, man headNode) {
FILE* fp = fopen(fileName, "r");
if (fp == NULL) {
fp = fopen(fileName, "w+");
}
char Account[MAX] = { 0 }; int key = 0, pw = 0;
while (fscanf(fp, "%s %d %d\n", Account, &key, &pw) != EOF) {
Addm(mHEAD, Account, key, pw);
}
fclose(fp);
}
UI.cpp/h:
表现层
主要内容:
//三、用户交互界面
#include ..
//主界面
int loginface();
//登陆成功后的初始化页面
void startface();
//开始菜单选择界面
void Select();
//登录页面
int login(long key);
//注册页面
man _register();
//1.浏览信息页面
void skimface();
void manface();
//2.查找书籍页面
void bookface();
//3.添加书籍界面
void addface();
//4.删除书籍
void DeleteBook();
//用户操作界面:1
int yongH();//1.浏览书目2.查询书籍3.个人信息4.退出
//管理员操作界面:0
int guanL();//1.浏览书目2.查询书籍3.添加书籍4.退出
//(非核心)优化界面:绘制界面边框
void DrawFace();
//结束界面
int Pause();
PS:选择效果的实现
int Select()
{
/*初始化界面选项*/
SetColor(3);
SetCursorPosition(13, 26);
std::cout << " ";
SetCursorPosition(13, 27);
std::cout << " ";
SetCursorPosition(6, 21);
std::cout << "请选择功能:";
SetCursorPosition(6, 22);
std::cout << "(上下键选择,回车确认)";
SetCursorPosition(27, 22);
SetBackColor();//第一个选项设置背景色以表示当前选中
std::cout << "用户登录";
SetCursorPosition(27, 24);
SetColor(3);
std::cout << "用户注册";
SetCursorPosition(27, 26);
std::cout << "开发信息";
SetCursorPosition(27, 28);
std::cout << "退出系统";
SetCursorPosition(0, 31);
/*上下方向键选择模块*/
int ch;//记录键入值
int key = 1;//记录选中项,初始选择第一个
bool flag = false;//记录是否键入Enter键标记,初始置为否
while ((ch = _getch()))//检测键盘按键输入
{
switch (ch)//检测输入键
{
case 72://UP:键盘上的上方向键按钮
if (key > 1)//当此时选中项为第一项时,UP上方向键无效
{
switch (key)
{
case 2:
SetCursorPosition(27, 22);//给待选中项设置背景色
SetBackColor();
std::cout << "用户登录";
SetCursorPosition(27, 24);//将已选中项取消我背景色
SetColor(3);
std::cout << "用户注册";
--key;
break;
case 3:
SetCursorPosition(27, 24);
SetBackColor();
std::cout << "用户注册";
SetCursorPosition(27, 26);
SetColor(3);
std::cout << "开发信息";
--key;
break;
case 4:
SetCursorPosition(27, 26);
SetBackColor();
std::cout << "开发信息";
SetCursorPosition(27, 28);
SetColor(3);
std::cout << "退出系统";
--key;
break;
}
}
break;
case 80://DOWN:键盘上的下方向键按钮
if (key < 4)
{
switch (key)
{
case 1:
SetCursorPosition(27, 24);
SetBackColor();
std::cout << "用户注册";
SetCursorPosition(27, 22);
SetColor(3);
std::cout << "用户登录";
++key;
break;
case 2:
SetCursorPosition(27, 26);
SetBackColor();
std::cout << "开发信息";
SetCursorPosition(27, 24);
SetColor(3);
std::cout << "用户注册";
++key;
break;
case 3:
SetCursorPosition(27, 28);
SetBackColor();
std::cout << "退出系统";
SetCursorPosition(27, 26);
SetColor(3);
std::cout << "开发信息";
++key;
break;
}
}
break;
case 13://Enter回车键
flag = true;
break;
default://无效按键
break;
}
if (flag) return key;//输入Enter回车键确认,退出检查输入循环
SetCursorPosition(0, 31);//将光标置于左下角,避免光标闪烁影响体验
}
}
startinterface.cpp/h
:开始界面动画
实现工具:C++ vector、deque容器
点坐标图:
通过坐标(0,14)观察队列输出特点:先进先出
9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|---|---|
14 | ■ | ■ | ■ | |||||||
15 | ■ | ■ | ||||||||
16 | ■ | ■ | ||||||||
17 | ■ | ■ | ||||||||
18 | ■ |
-31 | -30 | -29 | -23 | -22 | -21 | -20 | -19 | -18 | -17 | -16 | -15 | -14 | -13 | -6 | -5 | -4 | |||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
14 | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ||||||||||||
15 | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ||||||||||||
16 | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ||||||||||||
17 | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ||||||||||||
18 | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ |
实现流程:
实现方法:
这一部分的实现首先是建立一个deque双端队列,用于存储点的对象,这些点就是组成蛇身的元素,然后再用一个for循环将容器中的点依次打印出来,每打印一个点停顿一会,这样就达到了移动的效果。
void StartInterface::PrintFirst()//第一阶段:蛇从左边出现到完全出现的过程
{
for (auto& point : startsnake)//只读方式遍历容器
{
point.Print();
Sleep(speed);//停顿
}
}
全部打印完后就到了第二部分,这部分蛇的每次前进都是通过计算将要移动到的下一个点的坐标,然后将这个点打印出来,与此同时将蛇尾,亦即queue中的首端点去掉,并擦除屏幕上该点颜色。
void StartInterface::PrintSecond()//第二阶段:蛇从左向右移动的过程
{
for (int i = 10; i != 40; ++i) //蛇头需要从10移动到40
{
/*计算蛇头的下一个位置,并将其压入startsnake中,绘制出来,将蛇尾去掉*/
//计算从坐标j
int j = (((i - 2) % 8) < 4) ? (15 + (i - 2) % 8) : (21 - (i - 2) % 8);
startsnake.emplace_back(Point(i, j));
//queue中的首端点去掉,并擦除屏幕上该点颜色
startsnake.back().Print();
startsnake.front().Clear();
startsnake.pop_front();
//停顿
Sleep(speed);
}
}
第三部分就直接依次从蛇尾擦除即可。
void StartInterface::PrintThird()//第三阶段:蛇从接触右边到消失的过程
{
while (!startsnake.empty() || textsnake.back().GetX() < 33) //当蛇还没消失或文字没移动到指定位置
{
if (!startsnake.empty()) //如果蛇还没消失,继续移动
{
startsnake.front().Clear();
startsnake.pop_front();
}
ClearText();//清除已有文字
PrintText();//绘制更新位置后的文字
Sleep(speed);
}
}
同理,文字BMS的移动也基本类似,稍微改动即可,因为无需对首尾进行操作,而是要对所以点进行移动,因此容器选用vector。
注:为什么使用emplace_back() :
class StartInterface
{
public:
StartInterface() : speed(35) {
startsnake.emplace_back(0, 14);//Éß
startsnake.emplace_back(1, 14);
startsnake.emplace_back(2, 15);
startsnake.emplace_back(3, 16);
startsnake.emplace_back(4, 17);
startsnake.emplace_back(5, 18);
startsnake.emplace_back(6, 17);
startsnake.emplace_back(7, 16);
startsnake.emplace_back(8, 15);
startsnake.emplace_back(9, 14);
……
}//绘制动画BMS
void PrintFirst();//第一阶段
void PrintSecond();//第二阶段
void PrintThird();//第三阶段
void PrintText();//输出文字
void ClearText();//清除文字
void Action();//绘制
private:
std::deque<Point> startsnake;//开始动画
std::vector<Point> textsnake;//开始动画中的文字
int speed;//动画的速度
};
map.h
:操作界面外壳
实现原理:C++vector容器
class Map
{
public:
//默认构造函数,将圆形各点压入initmap
Map(){
initmap.emplace_back(1, 1);
initmap.emplace_back(2, 1);
initmap.emplace_back(3, 1);
……
initmap.emplace_back(28, 30);
initmap.emplace_back(29, 30);
initmap.emplace_back(30, 30);
}
void PrintInitmap();//绘制初始地图
private:
std::vector<Point> initmap;//保存初始地图
};
tools.cpp
:控制台优化类函数
#include "tools.h"
#include <windows.h>
void SetWindowSize(int cols, int lines)//设置窗口大小
{
system("title 图书管理系统->嘎嘎极客");//设置窗口标题
char cmd[30];
sprintf_s(cmd, "mode con cols=%d lines=%d", cols * 2, lines);//一个图形■占两个字符,故宽度乘以2
system(cmd);//system(mode con cols=88 lines=88)设置窗口宽度和高度
}
void SetCursorPosition(const int x, const int y)//设置光标位置
{
COORD position;
position.X = x * 2;
position.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);
}
void SetColor(int colorID)//设置文本颜色
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colorID);
}
void SetBackColor()//设置文本背景色
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
FOREGROUND_BLUE |
BACKGROUND_BLUE |
BACKGROUND_GREEN |
BACKGROUND_RED);
}
point.h
:操作界面外壳元素
实现原理:C++ vector
class Point
{
public:
Point() {}
Point(const int x, const int y) : x(x), y(y) {}
void Print();
void PrintCircular();
void Clear();
void ChangePosition(const int x, const int y);
bool operator== (const Point& point) { return (point.x == this->x) && (point.y == this->y); }
int GetX() { return this->x; }
int GetY() { return this->y; }
private:
int x, y;
};
全部整合后的第一版。
进一步优化了界面。
修复了注册界面Bug。
Bug描述:
long
而不是char[]
,导致了用户如果在输入字符与数字的混合密码时会出现问题,可能会导致密码为空或者导致密码只保存了数字,但是无法给用户提示,从而导致用户无法登录。//base.cpp
typedef struct _man
{
char Account[MAX];
long key;//最多九位
struct _man* next;
int pw = 0;//0是管理员,1是用户
}*man;
Bug1解决方案:
char*
**:更改难度极大,该结构体已经被应用于多个函数中,且函数之间的关系比较复杂,也需要同时对函数中的操作所涉及的一系列对字符串操作的修改。Bug2解决方案:
问题解决:TODO
//注册页面
man _register() {
char Account[MAX] = { 0 }; int key = 0;
SetCursorPosition(10, 10);
printf("\n\t\t\t注册!");
printf("\n\n\t\t\t\t\t ________________");
printf("\n\t\t请输入用户名:(20个字符) \b|________________|\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
SetColor(2);
//TODO2:原版scanf("%s",Account);
//如果到达文件末尾或者没有读取到任何字符,返回一个空指针。如果发生错误,返回一个空指针。
while ((fgets(Account, MAX, stdin)) != NULL && Account[0] != '\n')
{
while (getchar() != '\n') {
continue;
}
SetCursorPosition(8, 16);
std::cout << "账号:" << Account << std::endl;
break;
}
getchar();
SetColor(3);
printf("\n\t\t\t\t\t ________________");
printf("\n\t\t请输入你的密码:\t\b |________________|\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
SetColor(2);
//TODO1:原版:scanf("ld", &key);
while (1) {
scanf("ld", &key);//利用scanf将换行符留在缓存区的特点
if(getchar() == '\n')break;
else {
while (getchar() != '\n') //关键步骤,清空缓存区
{
continue;
}
SetCursorPosition(8, 20);
std::cout << "请输入数字!" << std::endl;
}
}
return Addm(mHEAD, Account, key);
}
参考:
https://blog.csdn.net/xiaoxiaoguailou/article/details/121733862
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有