当前位置:   article > 正文

2024-键盘交互与计时器-实验3-题解

2024-键盘交互与计时器-实验3-题解

 来自C++专业课的第三次作业:

实验要求:

        模拟一个办事机构(如银行)的叫号程序。

在一个显示区域内从上到下按顺序显示5个号码,最开始是1-5。四个方向键控制显示区域的移动。空格键产生一个新号码,将最前面的号码挤出显示区。ESC键退出系统。

使用键盘交互与计时器实现该程序,使用容器装载号码。

        最开始想抄一下某学长的题解,但是有非常严重的闪烁现象,且代码弱注释,十分难以理解,不过还是看完了,在此感谢那位不知名学长,并附上参考链接:

第三次实验:键盘交互与计时器-容器_辩之竹计时器键盘怎么用-CSDN博客

        源代码运行的效果:(两帧不同的画面)


        可以看到,通过截图软件对不同帧进行截图,可以比较明显的捕捉到输出的频闪现象,从用户交互的方面讲,是不友好的。

        分析实验要求,需要使用一个容器,容器保存序列信息,并且使用类封装,具有打印,更新队列的功能。

        首先选择合适的容器是很重要的,由于实验数据强度很小,只有五行,因此不用考虑容器的性能问题,只需要找一个便于操作的容器即可。而容器有很多种,常见的有vector、deque、list、queue等,而我希望可以这个容器具有从尾部插入数据,从头部弹出数据的功能,则选择deque较为合适,且deque可以使用下标访问而无需迭代器,更加方便(其实是不太会用iterator)

        编写的类如下:

  1. class list
  2. {
  3. private:
  4. deque<int>info; //新建一个deque info
  5. public:
  6. list() //构造函数,负责向info中压入从1-5的序号
  7. {
  8. for(int i=1;i<6;i++)
  9. {
  10. this->info.push_back(i);
  11. }
  12. }
  13. void update() //更新序列信息
  14. {
  15. this->info.pop_front(); //弹出第一个序号
  16. this->info.push_back(this->info.back()+1); //压入后一个序号
  17. }
  18. void show(int x,int y) //打印序列信息函数,这里传入的x,y是输出位置,因为实验要求通过键盘控制打印区域的位置
  19. {
  20. SetOutputPosition(x,y); //移动光标到指定位置
  21. printf("┏━━━━━━━┓"); //打印外部框架
  22. for(int i=1;i<6;i++)
  23. {
  24. SetOutputPosition(x,y+i); //每打印一行,光标下移一行
  25. printf("┃%-7d┃",this->info[i-1]); //printf自定义输出,左对齐,固定占用7个空格
  26. }
  27. SetOutputPosition(x,y+6); //再下移一行
  28. printf("┗━━━━━━━┛"); //打印外部框架
  29. }
  30. };

        这里要注意,deque需要引用deque头文件。

        下面编写接收键盘的类,负责接收键盘信息,并且记录光标的移动位置。

        控制台光标坐标系示意图:

        首先要明确,计算机记录键盘信息有多种方式,如果要使用计时器实现键盘触发记录,那么就是通过记录键盘状态,并通过计时器计时,当按下某个键的时间达到某一个阈值,则识别为一次触发,否则不触发,也就是本实验的要求。(其实还有另一种方式,就是通过检测键盘状态切换识别为触发,这种方式又分为按下触发和弹起触发,即键盘按下识别为一次触发或键盘弹起识别为一次触发,如果键盘状态没有改变,那么不记录触发)。

        键盘类如下:

  1. const int FPS=100;
  2. const int edge_x=100;
  3. const int edge_y=30;
  4. class display{ //键盘类(显示类)
  5. private:
  6. list l; //类内定义一个list对象,即存储序列信息的对象
  7. clock_t clk_1; //定义一个计时器变量,用于记录时间信息(ms单位)
  8. int x,y,pre_x,pre_y;
  9. bool flag; //标记位
  10. public:
  11. display(int X,int Y) //构造函数
  12. {
  13. this->x=X;
  14. this->y=Y;
  15. clk_1=clock(); //clk_1记录初始时间,并启动计时器
  16. }
  17. void erase(int x,int y) //擦除函数
  18. {
  19. for(int i=0;i<7;i++) //通过在指定位置输出空格覆盖之前的信息,而不使用cls,可以达到更高的性能
  20. {
  21. SetOutputPosition(x,y+i); //光标移动至该行初始位置
  22. printf(" "); //打印空格覆盖
  23. } //这里不使用\n的原因:x方向也有偏移,如果使用回车,会导致下一行光标移动至x=0位置
  24. }
  25. void refresh(int x,int y) //刷新函数,重新打印新的信息
  26. {
  27. SetOutputPosition(x,y); //光标移动
  28. l.show(x,y); //通过l成员函数打印信息
  29. }
  30. void show() //键盘检测函数
  31. {
  32. if(clock()-this->clk_1>1000/FPS) //通过FPS计算触发时长,如果计时器当前的时间和clk_1记录的时间超过每一帧的时间间隔,则检测键盘状态
  33. {
  34. if(GetAsyncKeyState(VK_LEFT)&&x>0) //按下左键,并且x未到左边缘
  35. {
  36. pre_x=x; //记录下当前坐标信息
  37. pre_y=y;
  38. x--; //更新坐标
  39. erase(pre_x,pre_y); //擦除更新前的打印
  40. refresh(x,y); //在更新后的坐标处重新打印
  41. }
  42. if(GetAsyncKeyState(VK_RIGHT)&&x+5<edge_x) //按下右键,并且x未到右边缘
  43. {
  44. pre_x=x;
  45. pre_y=y;
  46. x++;
  47. erase(pre_x,pre_y);
  48. refresh(x,y);
  49. }
  50. if(GetAsyncKeyState(VK_UP)&&y>0)
  51. {
  52. pre_x=x;
  53. pre_y=y;
  54. y--;
  55. erase(pre_x,pre_y);
  56. refresh(x,y);
  57. }
  58. if(GetAsyncKeyState(VK_DOWN)&&y+5<edge_y)
  59. {
  60. pre_x=x;
  61. pre_y=y;
  62. y++;
  63. erase(pre_x,pre_y);
  64. refresh(x,y);
  65. }
  66. if(GetAsyncKeyState(VK_ESCAPE)) //按下ESC?
  67. {
  68. exit(0); //退出程序,代码0
  69. }
  70. this->clk_1=clock(); //该轮键盘检测结束后更新clk_1的值为当前时间
  71. }
  72. if(GetAsyncKeyState(VK_SPACE)&&flag==0) //按下空格,并且标志位为0,即之前是没有按下的
  73. {
  74. flag=1; //更新状态为已按下
  75. this->l.update(); //更新序列信息
  76. erase(x,y);
  77. refresh(x,y); //重新打印
  78. }
  79. if(!GetAsyncKeyState(VK_SPACE)) //如果没有按下空格
  80. {
  81. flag=0; //更新信息为未按下
  82. }
  83. }
  84. void start() //启动打印函数
  85. {
  86. flag=0; //空格键状态未按下
  87. refresh(x,y); //先打印出初始列表
  88. while(1)
  89. {
  90. show(); //持续键盘检测
  91. }
  92. }
  93. };

        这样就可以实现方向键的控制,且基本上是实时响应的,可以自定义键盘检测刷新率和输出边界, 在没有键盘操作的情况下不进行画面刷新,避免了画面持续频闪,并且提高了程序响应速度。

        但是对于空格键来说,有一点小小的问题,虽然方向键按帧率相应,但是空格键控制列表的刷新,也就是说我们希望列表在我们按下空格键时只更新一次,而不是按下一次空格,空格按帧率识别了多次,列表也更新多次。

        对于空格键,需要使用上述的第二种检测方式,也就是按帧率不断记录空格键的状态,只有检测到当前帧的空格按下并且上一帧的空格没有按下时,才进行一次列表更新,其他情况均忽略,这样不管按下键盘的持续时间有多长,最终列表只更新一次,要再更新一次的话,就松开空格重新按下。

两种检测方案区别:

最终效果:

        

完整代码:

  1. /*
  2. PurityDreemurr
  3. 2024-04-19
  4. HITWH
  5. */
  6. #include<iostream>
  7. #include<cstdio>
  8. #include<deque>
  9. #include<cstdlib>
  10. #include<ctime>
  11. #include<conio.h>
  12. #include<windows.h>
  13. using namespace std;
  14. const int FPS=100;
  15. const int edge_x=100;
  16. const int edge_y=30;
  17. void SetOutputPosition(int x, int y)//设置输出坐标
  18. {
  19. HANDLE h;//接收控制台输出设备
  20. h = GetStdHandle(STD_OUTPUT_HANDLE);
  21. COORD pos;//获取控制台坐标
  22. pos.X = x;
  23. pos.Y = y;
  24. SetConsoleCursorPosition(h, pos);//设置控制台光标位置
  25. }
  26. class list
  27. {
  28. private:
  29. deque<int>info; //新建一个deque info
  30. public:
  31. list() //构造函数,负责向info中压入从1-5的序号
  32. {
  33. for(int i=1;i<6;i++)
  34. {
  35. this->info.push_back(i);
  36. }
  37. }
  38. void update() //更新序列信息
  39. {
  40. this->info.pop_front(); //弹出第一个序号
  41. this->info.push_back(this->info.back()+1); //压入后一个序号
  42. }
  43. void show(int x,int y) //打印序列信息函数,这里传入的x,y是输出位置,因为实验要求通过键盘控制打印区域的位置
  44. {
  45. SetOutputPosition(x,y); //移动光标到指定位置
  46. printf("┏━━━━━━━┓"); //打印外部框架
  47. for(int i=1;i<6;i++)
  48. {
  49. SetOutputPosition(x,y+i); //每打印一行,光标下移一行
  50. printf("┃%-7d┃",this->info[i-1]); //printf自定义输出,左对齐,固定占用7个空格
  51. }
  52. SetOutputPosition(x,y+6); //再下移一行
  53. printf("┗━━━━━━━┛"); //打印外部框架
  54. }
  55. };
  56. class display{ //键盘类(显示类)
  57. private:
  58. list l; //类内定义一个list对象,即存储序列信息的对象
  59. clock_t clk_1; //定义一个计时器变量,用于记录时间信息(ms单位)
  60. int x,y,pre_x,pre_y;
  61. bool flag; //标记位
  62. public:
  63. display(int X,int Y) //构造函数
  64. {
  65. this->x=X;
  66. this->y=Y;
  67. clk_1=clock(); //clk_1记录初始时间,并启动计时器
  68. }
  69. void erase(int x,int y) //擦除函数
  70. {
  71. for(int i=0;i<7;i++) //通过在指定位置输出空格覆盖之前的信息,而不使用cls,可以达到更高的性能
  72. {
  73. SetOutputPosition(x,y+i); //光标移动至该行初始位置
  74. printf(" "); //打印空格覆盖
  75. } //这里不使用\n的原因:x方向也有偏移,如果使用回车,会导致下一行光标移动至x=0位置
  76. }
  77. void refresh(int x,int y) //刷新函数,重新打印新的信息
  78. {
  79. SetOutputPosition(x,y); //光标移动
  80. l.show(x,y); //通过l成员函数打印信息
  81. }
  82. void show() //键盘检测函数
  83. {
  84. if(clock()-this->clk_1>1000/FPS) //通过FPS计算触发时长,如果计时器当前的时间和clk_1记录的时间超过每一帧的时间间隔,则检测键盘状态
  85. {
  86. if(GetAsyncKeyState(VK_LEFT)&&x>0) //按下左键,并且x未到左边缘
  87. {
  88. pre_x=x; //记录下当前坐标信息
  89. pre_y=y;
  90. x--; //更新坐标
  91. erase(pre_x,pre_y); //擦除更新前的打印
  92. refresh(x,y); //在更新后的坐标处重新打印
  93. }
  94. if(GetAsyncKeyState(VK_RIGHT)&&x+5<edge_x) //按下右键,并且x未到右边缘
  95. {
  96. pre_x=x;
  97. pre_y=y;
  98. x++;
  99. erase(pre_x,pre_y);
  100. refresh(x,y);
  101. }
  102. if(GetAsyncKeyState(VK_UP)&&y>0)
  103. {
  104. pre_x=x;
  105. pre_y=y;
  106. y--;
  107. erase(pre_x,pre_y);
  108. refresh(x,y);
  109. }
  110. if(GetAsyncKeyState(VK_DOWN)&&y+5<edge_y)
  111. {
  112. pre_x=x;
  113. pre_y=y;
  114. y++;
  115. erase(pre_x,pre_y);
  116. refresh(x,y);
  117. }
  118. if(GetAsyncKeyState(VK_ESCAPE)) //按下ESC?
  119. {
  120. exit(0); //退出程序,代码0
  121. }
  122. this->clk_1=clock(); //该轮键盘检测结束后更新clk_1的值为当前时间
  123. }
  124. if(GetAsyncKeyState(VK_SPACE)&&flag==0) //按下空格,并且标志位为0,即之前是没有按下的
  125. {
  126. flag=1; //更新状态为已按下
  127. this->l.update(); //更新序列信息
  128. erase(x,y);
  129. refresh(x,y); //重新打印
  130. }
  131. if(!GetAsyncKeyState(VK_SPACE)) //如果没有按下空格
  132. {
  133. flag=0; //更新信息为未按下
  134. }
  135. }
  136. void start() //启动打印函数
  137. {
  138. flag=0; //空格键状态未按下
  139. refresh(x,y); //先打印出初始列表
  140. while(1)
  141. {
  142. show(); //持续键盘检测
  143. }
  144. }
  145. };
  146. int main()
  147. {
  148. display dsp1(0,0); //新建键盘类
  149. dsp1.start(); //启动
  150. return 0;
  151. }

        以上就是本人的解决方案,不过有一个缺陷,就是在持续左移或右移输出区域的时候会有画面撕裂现象,有解决该问题的同学可以评论,谢谢大家,本人大一学生,技术较低,大佬勿喷。

       

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/557572
推荐阅读
相关标签
  

闽ICP备14008679号