craters 发表于 2022-6-12 00:42:18

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

不说了,直接上代码,下面解释:
#include <stdio.h>
#include <Windows.h>
int main()
{
        HANDLE in_handle = GetStdHandle(STD_INPUT_HANDLE);
        HANDLE out_handle = GetStdHandle(STD_OUTPUT_HANDLE);
        INPUT_RECORD what_key;
        DWORD save_key;
        bool have_press_esc = false;
        while (true)
        {
                ReadConsoleInput(in_handle, &what_key, 1, &save_key);
                if (what_key.EventType == KEY_EVENT && what_key.Event.KeyEvent.bKeyDown == TRUE)
                {
                        if (what_key.Event.KeyEvent.wVirtualKeyCode == 0x41) //0x41 is A's virtual key code. It's not a ASCII code.
                        {
                                printf("You pressed the A key.\n");
                        }
                        else if (what_key.Event.KeyEvent.wVirtualKeyCode == 0x1B) //0x1B is ESC's virtual key code. VK_ESCAPE == 0x1B
                        {
                                if (have_press_esc == false)
                                {
                                        have_press_esc = true;
                                        printf("Press the ESC key again to exit.");
                                }
                                else if (have_press_esc == true)
                                {
                                        break;
                                }
                        }
                }
        }
        CloseHandle(in_handle);
        CloseHandle(out_handle);
        return 0;
}

craters 发表于 2022-6-12 00:45:40

当然,比起_kbhit()+_getch()的方法或者GetAsyncKeyState(),这个方法未免太麻烦了点 ,但它使用起来比前两者好
因为_kbhit()+_getch()读取的是ASCII的值,无法使用虚拟键码,而GetAsyncKeyState()是通过直接读取键盘中断实现的,但这也使得它无法中断和停顿,会在一些地方出现各种各样的错误
更何况,上述的两者都无法判断出键盘按下与松开两种状态

craters 发表于 2022-6-12 00:48:00

ReadConsoleInput()是个在c/c++控制台编程下很有用的函数,这里我们用它来读取键盘事件

craters 发表于 2022-6-12 00:49:41

首先我们先定义两个HANDLE结构体,从GetStdHandle()来获得他们的初始值

craters 发表于 2022-6-12 00:51:41

然后再定义一个INPUT_HANDLE结构体,目的是将从ReadConsoleInput()中读取到的数据记录下来

craters 发表于 2022-6-12 00:57:18

得到记录下来的数据后,我们通过访问结构体的成员(在文中是what_key.EventType和what_key.Event.KeyEvent.bKeyDown)来判断事件类型和具体的事件
其中what_key.EventType是判断读取到内容是否为键盘事件,如果为键盘事件,则值为KEY_EVENT
而what_key.Event.KeyEvent则用于判断是按下键位还是松开键位,文中的bKeyDown指的是按下键位

craters 发表于 2022-6-12 00:58:46

what_key.Event.KeyEvent.wVirtualKeyCode则指的是按下的键的虚拟键码值,可以找一张虚拟键码表来对比一下键位

0xAA55 发表于 2022-6-12 20:09:02

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

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

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

craters 发表于 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来转发。

0xAA55 发表于 2022-6-13 20:14:21

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

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

craters 发表于 2022-6-13 21:09:17

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

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

craters 发表于 2022-6-13 21:11:33

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

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

0xAA55 发表于 2022-6-13 22:01:29

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

你一定是记错函数了。

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

craters 发表于 2022-6-13 22:36:37

0xAA55 发表于 2022-6-13 22:01
你一定是记错函数了。

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

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

craters 发表于 2022-6-13 22:38:12

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

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

0xAA55 发表于 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表示按住不放的过程。
页: [1]
查看完整版本: Windows控制台下C_C++更好的按键检测