前言
写游戏需要检测按键,写控制台工具需要检测按键,写快捷键功能也需要检测按键。c++ 里获取键盘输入的方式五花八门,windows 和 linux 下还不一样,初学者很容易搞混。
我自己最早接触的是 getch(),后来发现 windows 下还有 getasynckeystate、_kbhit,linux 下又有 ncurses、termios 这些玩意。每种方法各有各的适用场景,用错了轻则功能不达预期,重则程序卡死。
这篇文章就把 c++ 里各种获取按键的方式捋一遍,windows 和 linux 分开讲,每种都带上能直接跑的代码。文章末尾有个表格对比,方便你按需选择。
一、windows 平台
windows 下获取键盘输入主要有这么几条路:控制台专用的 _kbhit + _getch、实时检测物理按键的 getasynckeystate、依赖消息队列的 getkeystate、窗口消息 wm_keydown,以及更底层的 readconsoleinput。
1._kbhit()+_getch()—— 最简单粗暴
这是 windows 控制台程序里最常用的组合。_kbhit() 检查键盘缓冲区有没有按键,有的话返回非零;_getch() 从缓冲区读取一个字符,不需要按回车。
#include <iostream>
#include <conio.h> // _kbhit 和 _getch 都在这个头文件里
int main() {
std::cout << "按任意键试试,按 esc 退出" << std::endl;
while (true) {
if (_kbhit()) { // 有按键按下了?
int key = _getch(); // 读取按键值
if (key == 27) { // esc 的 ascii 码是 27
std::cout << "esc 按下,退出" << std::endl;
break;
}
std::cout << "按下了键,码值: " << key << std::endl;
}
}
return 0;
}
特点:
- 非阻塞,可以在循环里一直跑,有按键才处理
- 只能用于控制台程序,gui 程序用不了
- 方向键、功能键会返回两个字节(先 0 或 224,再是实际码值),需要额外处理
2.getasynckeystate()—— 实时检测物理按键
这个函数直接查询键盘的硬件状态,不管你程序有没有焦点,不管消息队列里有没有消息,调用时按键是什么状态就返回什么状态。
返回值是 short 类型,最高位(第 15 位)为 1 表示按键当前被按下。
#include <iostream>
#include <windows.h>
int main() {
std::cout << "检测 w 键是否被按下,按 esc 退出" << std::endl;
while (true) {
// 检测 w 键 (虚拟键码 0x57)
if (getasynckeystate(0x57) & 0x8000) {
std::cout << "w 键正在被按下!" << std::endl;
}
// 检测 esc 键退出
if (getasynckeystate(vk_escape) & 0x8000) {
std::cout << "esc 按下,退出" << std::endl;
break;
}
sleep(50); // 别让 cpu 跑满
}
return 0;
}
虚拟键码:windows 定义了一堆 vk_xxx 常量,比如 vk_space(空格)、vk_up(上箭头)、vk_return(回车)。也可以直接用字符的 ascii 码,比如 'a' 就是检测 a 键。
特点:
- 真正的实时检测,适合游戏循环、热键监控
- 不依赖消息队列,程序没焦点也能检测(但需要程序在前台才能可靠响应)
- 频繁调用开销稍大
3.getkeystate()—— 消息队列里的按键状态
getkeystate() 和 getasynckeystate() 名字像,但工作机制完全不同。它不直接查硬件,而是查消息队列里最近一次键盘消息处理时的按键状态。
简单说:getasynckeystate() 问的是“现在这个键按着没”,getkeystate() 问的是“刚才处理键盘消息的时候这个键按着没”。
#include <iostream>
#include <windows.h>
int main() {
std::cout << "检测 shift 键状态(需要在消息处理中调用才准确)" << std::endl;
// 注意:在普通控制台程序的主循环里直接调用 getkeystate 是不准的!
// 这个函数应该在窗口消息处理函数(如 wm_keydown)里调用
while (true) {
// 这样直接调用的结果可能不准确
short state = getkeystate(vk_shift);
if (state & 0x8000) {
std::cout << "shift 被按下(可能不准)" << std::endl;
}
sleep(200);
}
return 0;
}
正确的用法是在 mfc 或者 win32 窗口程序的 wm_keydown 消息处理里调用:
// 在窗口过程或 mfc 的 onkeydown 中
void onkeydown(uint nchar, uint nrepcnt, uint nflags) {
short shiftstate = getkeystate(vk_shift);
if (shiftstate & 0x8000) {
// shift 键在按下这个字母时是处于按下状态的
// 比如检测 ctrl+s 快捷键
}
}
特点:
- 只能在消息处理函数里用才有意义
- 适合检测组合键(比如 ctrl+s 这种)
- 不能用在游戏主循环里,否则可能死循环
4.wm_keydown窗口消息 —— gui 程序的标准做法
如果你在写 win32 窗口程序或者 mfc 程序,最正统的方式就是处理 wm_keydown 消息。
// win32 窗口过程里的处理
lresult callback wndproc(hwnd hwnd, uint message, wparam wparam, lparam lparam) {
switch (message) {
case wm_keydown:
// wparam 就是虚拟键码
switch (wparam) {
case vk_left:
// 左箭头被按下
break;
case vk_right:
// 右箭头被按下
break;
case vk_escape:
// esc 被按下
break;
}
break;
}
return defwindowproc(hwnd, message, wparam, lparam);
}
wparam 存的是虚拟键码,lparam 里包含了重复次数、扫描码等额外信息。
特点:
- windows gui 程序的标准做法
- 依赖窗口焦点,只有当前获得焦点的窗口才能收到消息
- 配合 getkeystate() 可以检测组合键
5.readconsoleinput()—— 控制台底层事件
这个方法直接读取控制台的输入缓冲区,能拿到比 _getch() 更详细的信息,比如按键是按下还是释放、重复次数、alt/ctrl/shift 的状态等。
#include <iostream>
#include <windows.h>
int main() {
handle hinput = getstdhandle(std_input_handle);
dword mode;
getconsolemode(hinput, &mode);
setconsolemode(hinput, mode & ~enable_line_input & ~enable_echo_input);
input_record ir;
dword read;
std::cout << "监听键盘事件,按 esc 退出" << std::endl;
while (true) {
readconsoleinput(hinput, &ir, 1, &read);
if (ir.eventtype == key_event) {
key_event_record& ker = ir.event.keyevent;
if (ker.bkeydown) { // 按键按下
std::cout << "按键: " << ker.wvirtualkeycode
<< ", 字符: " << ker.uchar.asciichar << std::endl;
if (ker.wvirtualkeycode == vk_escape) {
break;
}
}
}
}
setconsolemode(hinput, mode);
return 0;
}
特点:
- 信息最丰富,能区分按下和释放
- 代码比较啰嗦,一般场景用不上
- 适合需要精细控制键盘输入的场景
二、linux 平台
linux 下没有 windows 那套 winapi,获取键盘输入的方式也不太一样。主要有三条路:ncurses 库、termios 修改终端属性、x11 库(图形界面)。
1.ncurses库 —— 终端程序的最佳选择
ncurses 是 linux 下做终端界面程序的标准库,提供了 getch() 函数,可以不等待回车直接读取按键。
先安装:
sudo apt-get install libncurses5-dev libncursesw5-dev # ubuntu/debian
代码示例:
#include <ncurses.h>
#include <iostream>
int main() {
initscr(); // 初始化 ncurses
cbreak(); // 禁用行缓冲,按键立即响应
noecho(); // 不显示输入的字符
keypad(stdscr, true); // 开启功能键支持(方向键、f1 等)
printw("按任意键查看码值,按 esc 退出\n");
refresh();
int ch;
while ((ch = getch()) != 27) { // 27 是 esc
printw("按键: %c, ascii: %d\n", ch, ch);
refresh();
}
endwin(); // 退出 ncurses 模式
return 0;
}
编译时需要链接 ncurses 库:
g++ your_program.cpp -lncurses -o your_program
特点:
- 功能强大,支持方向键、f1-f12 等特殊键
- 可以设置非阻塞模式(nodelay())
- 适合做终端游戏、tui 工具
- 需要额外安装库
2.termios—— 不依赖第三方库的方案
如果不想装 ncurses,可以用 termios 直接修改终端属性,关闭行缓冲和回显,然后用 read() 读取。
#include <iostream>
#include <unistd.h>
#include <termios.h>
int main() {
struct termios oldt, newt;
// 获取当前终端设置
tcgetattr(stdin_fileno, &oldt);
newt = oldt;
// 关闭行缓冲和回显
newt.c_lflag &= ~(icanon | echo);
newt.c_cc[vmin] = 1; // 读到 1 个字符就返回
newt.c_cc[vtime] = 0; // 不设超时
tcsetattr(stdin_fileno, tcsanow, &newt);
std::cout << "按任意键查看码值,按 esc 退出" << std::endl;
char ch;
while (true) {
int n = read(stdin_fileno, &ch, 1);
if (n == 1) {
int key = static_cast<unsigned char>(ch);
std::cout << "按键码值: " << key << std::endl;
if (key == 27) break;
}
}
// 恢复终端设置
tcsetattr(stdin_fileno, tcsanow, &oldt);
return 0;
}
特点:
- 不依赖第三方库
- 代码稍复杂,需要自己管理终端状态
- 方向键等特殊键会返回多个字节,需要额外处理
3. x11 —— 图形界面下的方案
如果你在 linux 下写的是图形界面程序(有 x server),可以用 x11 库来捕获键盘事件。
#include <x11/xlib.h>
#include <x11/keysym.h>
#include <iostream>
int main() {
display* dpy = xopendisplay(nullptr);
if (!dpy) {
std::cerr << "无法打开 x 显示" << std::endl;
return 1;
}
window root = defaultrootwindow(dpy);
xselectinput(dpy, root, keypressmask);
xevent ev;
while (true) {
xnextevent(dpy, &ev);
if (ev.type == keypress) {
keysym keysym = xlookupkeysym(&ev.xkey, 0);
std::cout << "按键: " << xkeysymtostring(keysym) << std::endl;
if (keysym == xk_escape) break;
}
}
xclosedisplay(dpy);
return 0;
}
编译时需要链接 x11:
g++ your_program.cpp -lx11 -o your_program
特点:
- 适用于 linux 图形界面程序
- 可以捕获全局键盘事件(用 xgrabkey)
- 依赖 x server,纯终端环境用不了
三、完整示例代码
我把 windows 和 linux 下最常用的几种方式整理成完整的可运行代码,方便你直接复制测试。
windows 完整示例(综合版)
#include <iostream>
#include <windows.h>
#include <conio.h>
// 方式1: _kbhit + _getch
void test_kbhit() {
std::cout << "=== _kbhit + _getch 方式 ===" << std::endl;
std::cout << "按任意键,按 esc 退出" << std::endl;
while (true) {
if (_kbhit()) {
int key = _getch();
if (key == 27) break;
std::cout << "按键码: " << key << std::endl;
}
sleep(50);
}
}
// 方式2: getasynckeystate
void test_async() {
std::cout << "=== getasynckeystate 方式 ===" << std::endl;
std::cout << "检测 w/a/s/d 按键,按 esc 退出" << std::endl;
while (true) {
if (getasynckeystate('w') & 0x8000)
std::cout << "w " << std::flush;
if (getasynckeystate('a') & 0x8000)
std::cout << "a " << std::flush;
if (getasynckeystate('s') & 0x8000)
std::cout << "s " << std::flush;
if (getasynckeystate('d') & 0x8000)
std::cout << "d " << std::flush;
if (getasynckeystate(vk_escape) & 0x8000)
break;
sleep(50);
}
std::cout << std::endl;
}
int main() {
test_kbhit();
// test_async(); // 取消注释可测试另一种方式
return 0;
}
linux 完整示例(termios 方式)
#include <iostream>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
int main() {
struct termios oldt, newt;
tcgetattr(stdin_fileno, &oldt);
newt = oldt;
newt.c_lflag &= ~(icanon | echo);
newt.c_cc[vmin] = 1;
newt.c_cc[vtime] = 0;
tcsetattr(stdin_fileno, tcsanow, &newt);
// 设置为非阻塞
int flags = fcntl(stdin_fileno, f_getfl, 0);
fcntl(stdin_fileno, f_setfl, flags | o_nonblock);
std::cout << "按任意键,按 esc 退出" << std::endl;
char ch;
while (true) {
int n = read(stdin_fileno, &ch, 1);
if (n == 1) {
int key = static_cast<unsigned char>(ch);
if (key == 27) break;
std::cout << "按键码: " << key << std::endl;
}
usleep(50000);
}
tcsetattr(stdin_fileno, tcsanow, &oldt);
return 0;
}
四、方法对比表格
| 方法 | 平台 | 阻塞/非阻塞 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|---|
| _kbhit + _getch | windows | 非阻塞 | 控制台游戏、简单按键检测 | 简单易用,头文件自带 | 只支持控制台,方向键需额外处理 |
| getasynckeystate | windows | 非阻塞 | 游戏循环、热键、全局监听 | 实时检测,不依赖消息队列 | 频繁调用有性能开销 |
| getkeystate | windows | 非阻塞 | 窗口消息处理、组合键检测 | 适合检测快捷键组合 | 只能在消息处理中用 |
| wm_keydown | windows | 阻塞(消息驱动) | win32/mfc gui 程序 | windows 标准做法,信息完整 | 依赖窗口焦点 |
| readconsoleinput | windows | 阻塞 | 需要精细控制按键事件的场景 | 信息最丰富,区分按下/释放 | 代码复杂 |
| ncurses | linux | 可配置 | 终端游戏、tui 工具 | 功能强大,支持特殊键 | 需要安装库 |
| termios | linux | 可配置 | 不想装第三方库的场景 | 不依赖外部库 | 代码稍复杂,特殊键需额外处理 |
| x11 | linux | 阻塞(事件驱动) | linux 图形界面程序 | 可捕获全局事件 | 依赖 x server |
五、怎么选?我的建议
- windows 控制台程序:直接用 _kbhit + _getch,简单够用。
- windows 游戏或实时交互:用 getasynckeystate,实时性好。
- windows gui 程序:走 wm_keydown 消息,这是正道。
- linux 终端程序:优先用 ncurses,省心省力;不想装库就用 termios。
- 跨平台需求:用条件编译分别实现,windows 用 getasynckeystate,linux 用 termios 或 ncurses。
到此这篇关于浅谈c++ 获取键盘按键的 n 种方式的文章就介绍到这了,更多相关c++ 获取键盘按键内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论