技术宅的结界

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

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 112|回复: 17
收起左侧

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

[复制链接]

2

主题

15

帖子

58

积分

用户组: 小·技术宅

UID
7909
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2022-6-12
发表于 2022-6-12 00:42:18 | 显示全部楼层 |阅读模式

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

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

x
不说了,直接上代码,下面解释:
[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;
}
回复

使用道具 举报

2

主题

15

帖子

58

积分

用户组: 小·技术宅

UID
7909
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2022-6-12
 楼主| 发表于 2022-6-12 00:45:40 | 显示全部楼层
当然,比起_kbhit()+_getch()的方法或者GetAsyncKeyState(),这个方法未免太麻烦了点 ,但它使用起来比前两者好
因为_kbhit()+_getch()读取的是ASCII的值,无法使用虚拟键码,而GetAsyncKeyState()是通过直接读取键盘中断实现的,但这也使得它无法中断和停顿,会在一些地方出现各种各样的错误
更何况,上述的两者都无法判断出键盘按下与松开两种状态

2

主题

15

帖子

58

积分

用户组: 小·技术宅

UID
7909
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2022-6-12
 楼主| 发表于 2022-6-12 00:48:00 | 显示全部楼层
ReadConsoleInput()是个在c/c++控制台编程下很有用的函数,这里我们用它来读取键盘事件

2

主题

15

帖子

58

积分

用户组: 小·技术宅

UID
7909
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2022-6-12
 楼主| 发表于 2022-6-12 00:49:41 | 显示全部楼层
首先我们先定义两个HANDLE结构体,从GetStdHandle()来获得他们的初始值

2

主题

15

帖子

58

积分

用户组: 小·技术宅

UID
7909
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2022-6-12
 楼主| 发表于 2022-6-12 00:51:41 | 显示全部楼层
然后再定义一个INPUT_HANDLE结构体,目的是将从ReadConsoleInput()中读取到的数据记录下来

2

主题

15

帖子

58

积分

用户组: 小·技术宅

UID
7909
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2022-6-12
 楼主| 发表于 2022-6-12 00:57:18 | 显示全部楼层
得到记录下来的数据后,我们通过访问结构体的成员(在文中是what_key.EventType和what_key.Event.KeyEvent.bKeyDown)来判断事件类型和具体的事件
其中what_key.EventType是判断读取到内容是否为键盘事件,如果为键盘事件,则值为KEY_EVENT
而what_key.Event.KeyEvent则用于判断是按下键位还是松开键位,文中的bKeyDown指的是按下键位

2

主题

15

帖子

58

积分

用户组: 小·技术宅

UID
7909
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2022-6-12
 楼主| 发表于 2022-6-12 00:58:46 | 显示全部楼层
what_key.Event.KeyEvent.wVirtualKeyCode则指的是按下的键的虚拟键码值,可以找一张虚拟键码表来对比一下键位

1093

主题

2661

帖子

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
238
威望
606 点
宅币
22186 个
贡献
46077 次
宅之契约
0 份
在线时间
2128 小时
注册时间
2014-1-26
发表于 2022-6-12 20:09:02 | 显示全部楼层
craters 发表于 2022-6-12 00:45
当然,比起_kbhit()+_getch()的方法或者GetAsyncKeyState(),这个方法未免太麻烦了点 ,但它使用起来比前两 ...

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

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

2

主题

15

帖子

58

积分

用户组: 小·技术宅

UID
7909
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2022-6-12
 楼主| 发表于 2022-6-13 11:46:01 | 显示全部楼层
0xAA55 发表于 2022-6-12 20:09
GetAsyncKeyState() 判断的就是按键的按下与松开的两种状态,和键盘中断无关,并且其返回值最高 bit 可以 ...

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

7

主题

145

帖子

7313

积分

用户组: 真·技术宅

UID
4293
精华
5
威望
414 点
宅币
5516 个
贡献
799 次
宅之契约
0 份
在线时间
229 小时
注册时间
2018-9-19
发表于 2022-6-13 18:54:20 | 显示全部楼层
0xAA55 发表于 2022-6-12 20:09
GetAsyncKeyState() 判断的就是按键的按下与松开的两种状态,和键盘中断无关,并且其返回值最高 bit 可以 ...

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

1093

主题

2661

帖子

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
238
威望
606 点
宅币
22186 个
贡献
46077 次
宅之契约
0 份
在线时间
2128 小时
注册时间
2014-1-26
发表于 2022-6-13 20:14:21 | 显示全部楼层
craters 发表于 2022-6-13 11:46
问题是,GetAsyncKeyState()似乎是直接通过读取键盘的消息队列来实现的,即使你目前没有在执行GetAsyncKe ...

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

2

主题

15

帖子

58

积分

用户组: 小·技术宅

UID
7909
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2022-6-12
 楼主| 发表于 2022-6-13 21:09:17 | 显示全部楼层
0xAA55 发表于 2022-6-13 20:14
感觉不像是读取键盘的消息队列,因为就算我完全不处理消息队列里面的消息,也不影响我使用这个函数来读取 ...

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

2

主题

15

帖子

58

积分

用户组: 小·技术宅

UID
7909
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2022-6-12
 楼主| 发表于 2022-6-13 21:11:33 | 显示全部楼层
系统消息 发表于 2022-6-13 18:54
话说,GetAsyncKeyState走键盘中断这个说法是怎么来的哦?我以前上学的秦海玉老尸也说它是键盘中断实现, ...

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

1093

主题

2661

帖子

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
238
威望
606 点
宅币
22186 个
贡献
46077 次
宅之契约
0 份
在线时间
2128 小时
注册时间
2014-1-26
发表于 2022-6-13 22:01:29 | 显示全部楼层
craters 发表于 2022-6-13 21:09
我曾经写过一个控制台游戏,用的就是GetAsyncKeyState()来判断键位,然后我发现游戏暂停时按下按键恢复游 ...

你一定是记错函数了。

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

2

主题

15

帖子

58

积分

用户组: 小·技术宅

UID
7909
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2022-6-12
 楼主| 发表于 2022-6-13 22:36:37 | 显示全部楼层
0xAA55 发表于 2022-6-13 22:01
你一定是记错函数了。

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

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

2

主题

15

帖子

58

积分

用户组: 小·技术宅

UID
7909
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2022-6-12
 楼主| 发表于 2022-6-13 22:38:12 | 显示全部楼层
craters 发表于 2022-6-13 22:36
刚刚试了一下,确实是的,顺便一提如何在不使用Sleep()的情况下避免出现按一次反应几下的结果 ...

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

1093

主题

2661

帖子

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
238
威望
606 点
宅币
22186 个
贡献
46077 次
宅之契约
0 份
在线时间
2128 小时
注册时间
2014-1-26
发表于 2022-6-16 15:52:40 | 显示全部楼层
craters 发表于 2022-6-13 22:36
刚刚试了一下,确实是的,顺便一提如何在不使用Sleep()的情况下避免出现按一次反应几下的结果 ...

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

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

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

7

主题

145

帖子

7313

积分

用户组: 真·技术宅

UID
4293
精华
5
威望
414 点
宅币
5516 个
贡献
799 次
宅之契约
0 份
在线时间
229 小时
注册时间
2018-9-19
发表于 2022-6-16 20:02:40 | 显示全部楼层
craters 发表于 2022-6-13 22:36
刚刚试了一下,确实是的,顺便一提如何在不使用Sleep()的情况下避免出现按一次反应几下的结果 ...

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

本版积分规则

QQ|申请友链||Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2022-7-1 12:33 , Processed in 0.051972 second(s), 29 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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