赞
踩
来自C++专业课的第三次作业:
实验要求:
模拟一个办事机构(如银行)的叫号程序。
在一个显示区域内从上到下按顺序显示5个号码,最开始是1-5。四个方向键控制显示区域的移动。空格键产生一个新号码,将最前面的号码挤出显示区。ESC键退出系统。
使用键盘交互与计时器实现该程序,使用容器装载号码。
最开始想抄一下某学长的题解,但是有非常严重的闪烁现象,且代码弱注释,十分难以理解,不过还是看完了,在此感谢那位不知名学长,并附上参考链接:
源代码运行的效果:(两帧不同的画面)
可以看到,通过截图软件对不同帧进行截图,可以比较明显的捕捉到输出的频闪现象,从用户交互的方面讲,是不友好的。
分析实验要求,需要使用一个容器,容器保存序列信息,并且使用类封装,具有打印,更新队列的功能。
首先选择合适的容器是很重要的,由于实验数据强度很小,只有五行,因此不用考虑容器的性能问题,只需要找一个便于操作的容器即可。而容器有很多种,常见的有vector、deque、list、queue等,而我希望可以这个容器具有从尾部插入数据,从头部弹出数据的功能,则选择deque较为合适,且deque可以使用下标访问而无需迭代器,更加方便(其实是不太会用iterator)
编写的类如下:
- class list
- {
- private:
- deque<int>info; //新建一个deque info
-
- public:
- list() //构造函数,负责向info中压入从1-5的序号
- {
- for(int i=1;i<6;i++)
- {
- this->info.push_back(i);
- }
- }
- void update() //更新序列信息
- {
- this->info.pop_front(); //弹出第一个序号
- this->info.push_back(this->info.back()+1); //压入后一个序号
- }
- void show(int x,int y) //打印序列信息函数,这里传入的x,y是输出位置,因为实验要求通过键盘控制打印区域的位置
- {
- SetOutputPosition(x,y); //移动光标到指定位置
- printf("┏━━━━━━━┓"); //打印外部框架
- for(int i=1;i<6;i++)
- {
- SetOutputPosition(x,y+i); //每打印一行,光标下移一行
- printf("┃%-7d┃",this->info[i-1]); //printf自定义输出,左对齐,固定占用7个空格
- }
- SetOutputPosition(x,y+6); //再下移一行
- printf("┗━━━━━━━┛"); //打印外部框架
- }
- };
这里要注意,deque需要引用deque头文件。
下面编写接收键盘的类,负责接收键盘信息,并且记录光标的移动位置。
控制台光标坐标系示意图:
首先要明确,计算机记录键盘信息有多种方式,如果要使用计时器实现键盘触发记录,那么就是通过记录键盘状态,并通过计时器计时,当按下某个键的时间达到某一个阈值,则识别为一次触发,否则不触发,也就是本实验的要求。(其实还有另一种方式,就是通过检测键盘状态切换识别为触发,这种方式又分为按下触发和弹起触发,即键盘按下识别为一次触发或键盘弹起识别为一次触发,如果键盘状态没有改变,那么不记录触发)。
键盘类如下:
- const int FPS=100;
- const int edge_x=100;
- const int edge_y=30;
- class display{ //键盘类(显示类)
- private:
- list l; //类内定义一个list对象,即存储序列信息的对象
- clock_t clk_1; //定义一个计时器变量,用于记录时间信息(ms单位)
- int x,y,pre_x,pre_y;
- bool flag; //标记位
- public:
- display(int X,int Y) //构造函数
- {
- this->x=X;
- this->y=Y;
- clk_1=clock(); //clk_1记录初始时间,并启动计时器
- }
- void erase(int x,int y) //擦除函数
- {
- for(int i=0;i<7;i++) //通过在指定位置输出空格覆盖之前的信息,而不使用cls,可以达到更高的性能
- {
- SetOutputPosition(x,y+i); //光标移动至该行初始位置
- printf(" "); //打印空格覆盖
- } //这里不使用\n的原因:x方向也有偏移,如果使用回车,会导致下一行光标移动至x=0位置
- }
- void refresh(int x,int y) //刷新函数,重新打印新的信息
- {
- SetOutputPosition(x,y); //光标移动
- l.show(x,y); //通过l成员函数打印信息
- }
- void show() //键盘检测函数
- {
- if(clock()-this->clk_1>1000/FPS) //通过FPS计算触发时长,如果计时器当前的时间和clk_1记录的时间超过每一帧的时间间隔,则检测键盘状态
- {
- if(GetAsyncKeyState(VK_LEFT)&&x>0) //按下左键,并且x未到左边缘
- {
- pre_x=x; //记录下当前坐标信息
- pre_y=y;
- x--; //更新坐标
-
- erase(pre_x,pre_y); //擦除更新前的打印
- refresh(x,y); //在更新后的坐标处重新打印
- }
- if(GetAsyncKeyState(VK_RIGHT)&&x+5<edge_x) //按下右键,并且x未到右边缘
- {
- pre_x=x;
- pre_y=y;
- x++;
-
- erase(pre_x,pre_y);
- refresh(x,y);
- }
- if(GetAsyncKeyState(VK_UP)&&y>0)
- {
- pre_x=x;
- pre_y=y;
- y--;
-
- erase(pre_x,pre_y);
- refresh(x,y);
- }
- if(GetAsyncKeyState(VK_DOWN)&&y+5<edge_y)
- {
- pre_x=x;
- pre_y=y;
- y++;
-
- erase(pre_x,pre_y);
- refresh(x,y);
- }
- if(GetAsyncKeyState(VK_ESCAPE)) //按下ESC?
- {
- exit(0); //退出程序,代码0
- }
- this->clk_1=clock(); //该轮键盘检测结束后更新clk_1的值为当前时间
- }
- if(GetAsyncKeyState(VK_SPACE)&&flag==0) //按下空格,并且标志位为0,即之前是没有按下的
- {
- flag=1; //更新状态为已按下
- this->l.update(); //更新序列信息
- erase(x,y);
- refresh(x,y); //重新打印
- }
- if(!GetAsyncKeyState(VK_SPACE)) //如果没有按下空格
- {
- flag=0; //更新信息为未按下
- }
- }
- void start() //启动打印函数
- {
- flag=0; //空格键状态未按下
- refresh(x,y); //先打印出初始列表
- while(1)
- {
- show(); //持续键盘检测
- }
- }
- };
这样就可以实现方向键的控制,且基本上是实时响应的,可以自定义键盘检测刷新率和输出边界, 在没有键盘操作的情况下不进行画面刷新,避免了画面持续频闪,并且提高了程序响应速度。
但是对于空格键来说,有一点小小的问题,虽然方向键按帧率相应,但是空格键控制列表的刷新,也就是说我们希望列表在我们按下空格键时只更新一次,而不是按下一次空格,空格按帧率识别了多次,列表也更新多次。
对于空格键,需要使用上述的第二种检测方式,也就是按帧率不断记录空格键的状态,只有检测到当前帧的空格按下并且上一帧的空格没有按下时,才进行一次列表更新,其他情况均忽略,这样不管按下键盘的持续时间有多长,最终列表只更新一次,要再更新一次的话,就松开空格重新按下。
两种检测方案区别:
最终效果:
完整代码:
- /*
- PurityDreemurr
- 2024-04-19
- HITWH
- */
- #include<iostream>
- #include<cstdio>
- #include<deque>
- #include<cstdlib>
- #include<ctime>
- #include<conio.h>
- #include<windows.h>
- using namespace std;
- const int FPS=100;
- const int edge_x=100;
- const int edge_y=30;
- void SetOutputPosition(int x, int y)//设置输出坐标
- {
- HANDLE h;//接收控制台输出设备
- h = GetStdHandle(STD_OUTPUT_HANDLE);
- COORD pos;//获取控制台坐标
- pos.X = x;
- pos.Y = y;
- SetConsoleCursorPosition(h, pos);//设置控制台光标位置
- }
-
- class list
- {
- private:
- deque<int>info; //新建一个deque info
-
- public:
- list() //构造函数,负责向info中压入从1-5的序号
- {
- for(int i=1;i<6;i++)
- {
- this->info.push_back(i);
- }
- }
- void update() //更新序列信息
- {
- this->info.pop_front(); //弹出第一个序号
- this->info.push_back(this->info.back()+1); //压入后一个序号
- }
- void show(int x,int y) //打印序列信息函数,这里传入的x,y是输出位置,因为实验要求通过键盘控制打印区域的位置
- {
- SetOutputPosition(x,y); //移动光标到指定位置
- printf("┏━━━━━━━┓"); //打印外部框架
- for(int i=1;i<6;i++)
- {
- SetOutputPosition(x,y+i); //每打印一行,光标下移一行
- printf("┃%-7d┃",this->info[i-1]); //printf自定义输出,左对齐,固定占用7个空格
- }
- SetOutputPosition(x,y+6); //再下移一行
- printf("┗━━━━━━━┛"); //打印外部框架
- }
- };
- class display{ //键盘类(显示类)
- private:
- list l; //类内定义一个list对象,即存储序列信息的对象
- clock_t clk_1; //定义一个计时器变量,用于记录时间信息(ms单位)
- int x,y,pre_x,pre_y;
- bool flag; //标记位
- public:
- display(int X,int Y) //构造函数
- {
- this->x=X;
- this->y=Y;
- clk_1=clock(); //clk_1记录初始时间,并启动计时器
- }
- void erase(int x,int y) //擦除函数
- {
- for(int i=0;i<7;i++) //通过在指定位置输出空格覆盖之前的信息,而不使用cls,可以达到更高的性能
- {
- SetOutputPosition(x,y+i); //光标移动至该行初始位置
- printf(" "); //打印空格覆盖
- } //这里不使用\n的原因:x方向也有偏移,如果使用回车,会导致下一行光标移动至x=0位置
- }
- void refresh(int x,int y) //刷新函数,重新打印新的信息
- {
- SetOutputPosition(x,y); //光标移动
- l.show(x,y); //通过l成员函数打印信息
- }
- void show() //键盘检测函数
- {
- if(clock()-this->clk_1>1000/FPS) //通过FPS计算触发时长,如果计时器当前的时间和clk_1记录的时间超过每一帧的时间间隔,则检测键盘状态
- {
- if(GetAsyncKeyState(VK_LEFT)&&x>0) //按下左键,并且x未到左边缘
- {
- pre_x=x; //记录下当前坐标信息
- pre_y=y;
- x--; //更新坐标
-
- erase(pre_x,pre_y); //擦除更新前的打印
- refresh(x,y); //在更新后的坐标处重新打印
- }
- if(GetAsyncKeyState(VK_RIGHT)&&x+5<edge_x) //按下右键,并且x未到右边缘
- {
- pre_x=x;
- pre_y=y;
- x++;
-
- erase(pre_x,pre_y);
- refresh(x,y);
- }
- if(GetAsyncKeyState(VK_UP)&&y>0)
- {
- pre_x=x;
- pre_y=y;
- y--;
-
- erase(pre_x,pre_y);
- refresh(x,y);
- }
- if(GetAsyncKeyState(VK_DOWN)&&y+5<edge_y)
- {
- pre_x=x;
- pre_y=y;
- y++;
-
- erase(pre_x,pre_y);
- refresh(x,y);
- }
- if(GetAsyncKeyState(VK_ESCAPE)) //按下ESC?
- {
- exit(0); //退出程序,代码0
- }
- this->clk_1=clock(); //该轮键盘检测结束后更新clk_1的值为当前时间
- }
- if(GetAsyncKeyState(VK_SPACE)&&flag==0) //按下空格,并且标志位为0,即之前是没有按下的
- {
- flag=1; //更新状态为已按下
- this->l.update(); //更新序列信息
- erase(x,y);
- refresh(x,y); //重新打印
- }
- if(!GetAsyncKeyState(VK_SPACE)) //如果没有按下空格
- {
- flag=0; //更新信息为未按下
- }
- }
- void start() //启动打印函数
- {
- flag=0; //空格键状态未按下
- refresh(x,y); //先打印出初始列表
- while(1)
- {
- show(); //持续键盘检测
- }
- }
- };
- int main()
- {
- display dsp1(0,0); //新建键盘类
- dsp1.start(); //启动
- return 0;
- }
以上就是本人的解决方案,不过有一个缺陷,就是在持续左移或右移输出区域的时候会有画面撕裂现象,有解决该问题的同学可以评论,谢谢大家,本人大一学生,技术较低,大佬勿喷。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。