找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 1422|回复: 17

Windows控制台下C_C++更好的按键检测

[复制链接]
发表于 2022-6-12 00:42:18 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
不说了,直接上代码,下面解释:
  1. #include <stdio.h>
  2. #include <Windows.h>
  3. int main()
  4. {
  5.         HANDLE in_handle = GetStdHandle(STD_INPUT_HANDLE);
  6.         HANDLE out_handle = GetStdHandle(STD_OUTPUT_HANDLE);
  7.         INPUT_RECORD what_key;
  8.         DWORD save_key;
  9.         bool have_press_esc = false;
  10.         while (true)
  11.         {
  12.                 ReadConsoleInput(in_handle, &what_key, 1, &save_key);
  13.                 if (what_key.EventType == KEY_EVENT && what_key.Event.KeyEvent.bKeyDown == TRUE)
  14.                 {
  15.                         if (what_key.Event.KeyEvent.wVirtualKeyCode == 0x41) //0x41 is A's virtual key code. It's not a ASCII code.
  16.                         {
  17.                                 printf("You pressed the A key.\n");
  18.                         }
  19.                         else if (what_key.Event.KeyEvent.wVirtualKeyCode == 0x1B) //0x1B is ESC's virtual key code. VK_ESCAPE == 0x1B
  20.                         {
  21.                                 if (have_press_esc == false)
  22.                                 {
  23.                                         have_press_esc = true;
  24.                                         printf("Press the ESC key again to exit.");
  25.                                 }
  26.                                 else if (have_press_esc == true)
  27.                                 {
  28.                                         break;
  29.                                 }
  30.                         }
  31.                 }
  32.         }
  33.         CloseHandle(in_handle);
  34.         CloseHandle(out_handle);
  35.         return 0;
  36. }
复制代码
回复

使用道具 举报

 楼主| 发表于 2022-6-12 00:45:40 | 显示全部楼层
当然,比起_kbhit()+_getch()的方法或者GetAsyncKeyState(),这个方法未免太麻烦了点 ,但它使用起来比前两者好
因为_kbhit()+_getch()读取的是ASCII的值,无法使用虚拟键码,而GetAsyncKeyState()是通过直接读取键盘中断实现的,但这也使得它无法中断和停顿,会在一些地方出现各种各样的错误
更何况,上述的两者都无法判断出键盘按下与松开两种状态
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2022-6-12 00:48:00 | 显示全部楼层
ReadConsoleInput()是个在c/c++控制台编程下很有用的函数,这里我们用它来读取键盘事件
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2022-6-12 00:49:41 | 显示全部楼层
首先我们先定义两个HANDLE结构体,从GetStdHandle()来获得他们的初始值
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2022-6-12 00:51:41 | 显示全部楼层
然后再定义一个INPUT_HANDLE结构体,目的是将从ReadConsoleInput()中读取到的数据记录下来
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2022-6-12 00:57:18 | 显示全部楼层
得到记录下来的数据后,我们通过访问结构体的成员(在文中是what_key.EventType和what_key.Event.KeyEvent.bKeyDown)来判断事件类型和具体的事件
其中what_key.EventType是判断读取到内容是否为键盘事件,如果为键盘事件,则值为KEY_EVENT
而what_key.Event.KeyEvent则用于判断是按下键位还是松开键位,文中的bKeyDown指的是按下键位
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2022-6-12 00:58:46 | 显示全部楼层
what_key.Event.KeyEvent.wVirtualKeyCode则指的是按下的键的虚拟键码值,可以找一张虚拟键码表来对比一下键位
回复 赞! 靠!

使用道具 举报

发表于 2022-6-12 20:09:02 | 显示全部楼层
craters 发表于 2022-6-12 00:45
当然,比起_kbhit()+_getch()的方法或者GetAsyncKeyState(),这个方法未免太麻烦了点 ,但它使用起来比前两 ...

GetAsyncKeyState() 判断的就是按键的按下与松开的两种状态,和键盘中断无关,并且其返回值最高 bit 可以用于判断按键状态的变化,按键自上次检测以来是否上升沿。

而你这个方式唯一的好处就是可以清空 STDIN 的读缓冲区,除此以外没有优势。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2022-6-13 11:46:01 | 显示全部楼层
0xAA55 发表于 2022-6-12 20:09
GetAsyncKeyState() 判断的就是按键的按下与松开的两种状态,和键盘中断无关,并且其返回值最高 bit 可以 ...

问题是,GetAsyncKeyState()似乎是直接通过读取键盘的消息队列来实现的,即使你目前没有在执行GetAsyncKeyState(),只要你按下了按键,等到之后执行GetAsyncKeyState()时会把之前你按过的键都一一判断一遍
回复 赞! 靠!

使用道具 举报

发表于 2022-6-13 18:54:20 | 显示全部楼层
0xAA55 发表于 2022-6-12 20:09
GetAsyncKeyState() 判断的就是按键的按下与松开的两种状态,和键盘中断无关,并且其返回值最高 bit 可以 ...

话说,GetAsyncKeyState走键盘中断这个说法是怎么来的哦?我以前上学的秦海玉老尸也说它是键盘中断实现,但随着我毕业多年以后的研究结果,发现它并非键盘中断(可能键盘驱动那一层会用到中断,但应用层的GetAsyncKeyState只是读取系统保存到内存上的256个按钮状态而已)。
另外ReadConsoleInput感觉比较GUI界面的键盘消息的用法,毕竟CUI程序的控制台窗口不是自己进程的,不能直接接收它的窗口消息,所以通过Console API来转发。
回复 赞! 靠!

使用道具 举报

发表于 2022-6-13 20:14:21 | 显示全部楼层
craters 发表于 2022-6-13 11:46
问题是,GetAsyncKeyState()似乎是直接通过读取键盘的消息队列来实现的,即使你目前没有在执行GetAsyncKe ...

感觉不像是读取键盘的消息队列,因为就算我完全不处理消息队列里面的消息,也不影响我使用这个函数来读取实时的按键信息;与此同时,我在处理消息队列的时候,我依然能在消息处理过程中处理之前没处理的按键消息。
即使你目前没有在执行GetAsyncKeyState(),只要你按下了按键,等到之后执行GetAsyncKeyState()时会把之前你按过的键都一一判断一遍
你说的后半句我没理解意思。它确实能做到在你两次调用之间如果发生按键的按下和弹出后,会返回信息看到有个按键被按下过的状态,但这个并不一定是你说的“一一判断一遍”。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2022-6-13 21:09:17 | 显示全部楼层
0xAA55 发表于 2022-6-13 20:14
感觉不像是读取键盘的消息队列,因为就算我完全不处理消息队列里面的消息,也不影响我使用这个函数来读取 ...

我曾经写过一个控制台游戏,用的就是GetAsyncKeyState()来判断键位,然后我发现游戏暂停时按下按键恢复游戏后GetAsyncKeyState()居然还会根据我按过的按键进行相应的运行
另外GetAsyncKeyState()无法获取按下的究竟是哪个键,_kbhit()+_getch()获取到的不但不是虚拟键值表,而且竟是ASCII码,所以我选择了上述的方法
何况GetAsyncKeyState()还是非阻塞函数,在我的程序里面老是乱套
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2022-6-13 21:11:33 | 显示全部楼层
系统消息 发表于 2022-6-13 18:54
话说,GetAsyncKeyState走键盘中断这个说法是怎么来的哦?我以前上学的秦海玉老尸也说它是键盘中断实现, ...

这个方法确实比较GUI了,所以我在网上查到的绝大部分资料都是GetAsyncKeyState()或_kbhit()+_getch(),不符合我的用途,直到某次我无意在知乎上看到了ReadConsoleInput()
回复 赞! 靠!

使用道具 举报

发表于 2022-6-13 22:01:29 | 显示全部楼层
craters 发表于 2022-6-13 21:09
我曾经写过一个控制台游戏,用的就是GetAsyncKeyState()来判断键位,然后我发现游戏暂停时按下按键恢复游 ...

你一定是记错函数了。

GetAsyncKeyState() 函数并不按照你描述的那样工作,并且它的参数就是你要判断状态的按键的虚拟键值。它并不会扫描你整个键盘或者鼠标,而是根据你要判断的键值来返回其是否按下的状态。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2022-6-13 22:36:37 | 显示全部楼层
0xAA55 发表于 2022-6-13 22:01
你一定是记错函数了。

GetAsyncKeyState() 函数并不按照你描述的那样工作,并且它的参数就是你要判断状 ...

刚刚试了一下,确实是的,顺便一提如何在不使用Sleep()的情况下避免出现按一次反应几下的结果
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2022-6-13 22:38:12 | 显示全部楼层
craters 发表于 2022-6-13 22:36
刚刚试了一下,确实是的,顺便一提如何在不使用Sleep()的情况下避免出现按一次反应几下的结果 ...

同时也要支持按住不动也行的效果,使用Sleep()的话有可能在Sleep()执行时按下了键位导致没有反应的结果
回复 赞! 靠!

使用道具 举报

发表于 2022-6-16 15:52:40 | 显示全部楼层
craters 发表于 2022-6-13 22:36
刚刚试了一下,确实是的,顺便一提如何在不使用Sleep()的情况下避免出现按一次反应几下的结果 ...

其实 GetAsyncKeyState() 不是给控制台用的,而是给游戏用的,用于读取游戏按键的状态。

你说的检测多次,那是你自己的程序逻辑的问题。这个函数是立即返回的,而非阻塞式。所以你循环的有多快,它检测的次数就越多。

使用 Sleep 仅仅是用于减慢你的循环的速度,你真正需要理解的是“边沿”的逻辑,即“上升沿检测”和“电平检测”概念的区别。你的代码是“电平检测”,但你想做的是“上升沿检测”。在软件上,你需要定义变量来存储旧的按键状态,用于比对。
回复 赞! 靠!

使用道具 举报

发表于 2022-6-16 20:02:40 | 显示全部楼层
craters 发表于 2022-6-13 22:36
刚刚试了一下,确实是的,顺便一提如何在不使用Sleep()的情况下避免出现按一次反应几下的结果 ...

GetAsyncKeyState会返回多种返回值,比较常用的是返回 -32767 和 -32768,-32767 表示按下键盘按键这个动作产生,-32768表示按住不放的过程。
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-12-26 23:00 , Processed in 0.033879 second(s), 21 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表