如何在类 Unix 环境下无需等待回车即可获取来自键盘的输入?
◆ 问题
在类 Unix 操作系统中,C++ 程序使用 stdio 或 iostream 程序库接收键盘输入时,需等待回车键按下后才能接收到输入内容。如何能在无需等待回车即可获取来自键盘的输入?
◆ 解法
通过 termios 程序库中与终端关联的 termios 结构和 tcgetattr、tcsetattr 函数,改变终端编辑模式为非规范输入且不回显,即可实现“无需等待回车即可获取来自键盘的输入”的功能。
◆ 示例
以下代码片段展示了此实现方案,(how_to_get_input_without_enter.cpp)
#include
#include // #1
int hit_key() {
termios oldattr;
tcgetattr(0, &oldattr); // #2
termios newattr = oldattr; // #3
newattr.c_lflag &= ~(ICANON | ECHO); // #4
tcsetattr(0, TCSANOW, &newattr);
int code = getchar();
tcsetattr(0, TCSANOW, &oldattr); // #5
return code;
}
首先需要包含 termios.h 程序库(#1),该文件定义了与终端关联的 termios 结构和 tcgetattr、tcsetattr 函数。使用 tcgetattr 函数获取当前终端的属性,并保存在 termios 结构的变量中(#2)。为了能将终端属性恢复成原有值,先用原有终端属性复制出一份临时属性(#3)。改变终端编辑模式的关键,在于 termios.c_lflag 字段。将临时属性的 c_lflag 字段改变为非规范输入且不回显(#4),并设置终端属性为新值。应用结束后,将终端属性恢复成原有值(#5)。
◆ 验证
笔者分别在 macOS Mojave(版本 10.14.6)和 Raspbian Stretch(版本 9.4)两个操作系统的原生终端内,用 87 键键盘输入 ASCII 字符集进行了验证。
编译代码成功后运行可执行文件。对于十进制 ASCII 码为 32 ~ 127 的可打印字符可直接按键输入,而对于 ASCII 码为 0 ~ 31 的控制字符按如下组合键输入,
十进制 | 字符 | macOS Mojave 组合键 | Raspbian Stretch 组合键 |
---|---|---|---|
0 | ^@ | Ctrl-@ | Ctrl-@ |
1 | ^A | Ctrl-A | Ctrl-A |
2 | ^B | Ctrl-B | Ctrl-B |
3 | ^C | Ctrl-V Ctrl-C | (未验证) |
4 | ^D | Ctrl-D | Ctrl-D |
5 | ^E | Ctrl-E | Ctrl-E |
6 | ^F | Ctrl-F | Ctrl-F |
7 | ^G | Ctrl-G | Ctrl-G |
8 | ^H | Ctrl-H | Ctrl-H |
9 | Tab, ^I | Tab, Ctrl-I | Tab, Ctrl-I |
10 | ^J | Ctrl-J | Ctrl-J |
11 | ^K | Ctrl-K | Ctrl-K |
12 | ^L | Ctrl-L | Ctrl-L |
13 | ^M | Ctrl-V Ctrl-M | (未验证) |
14 | ^N | Ctrl-N | Ctrl-N |
15 | ^O | Ctrl-V Ctrl-O | Ctrl-O |
16 | ^P | Ctrl-P | Ctrl-P |
17 | ^Q | Ctrl-V Ctrl-Q | (未验证) |
18 | ^R | Ctrl-R | Ctrl-R |
19 | ^S | Ctrl-V Ctrl-S | (未验证) |
20 | ^T | Ctrl-T | Ctrl-T |
21 | ^U | Ctrl-U | Ctrl-U |
22 | ^V | Ctrl-V Ctrl-V | Ctrl-V |
23 | ^W | Ctrl-W | Ctrl-W |
24 | ^X | Ctrl-X | Ctrl-X |
25 | ^Y | Ctrl-V Ctrl-Y | Ctrl-Y |
26 | ^Z | Ctrl-V Ctrl-Z | (未验证) |
27 | ESC, ^[ | Ctrl-[ | ESC, Ctrl-[ |
28 | ^\ | Ctrl-V Ctrl-\ | (未验证) |
29 | ^] | Ctrl-] | Ctrl-] |
30 | ^^ | Ctrl-^ | Ctrl-^ |
31 | ^- | Ctrl-- | Ctrl-- |
在 Raspbian Stretch 上的测试中存在”(未验证)“的 ASCII 码,是因为该示例代码无法通过键盘输入对应的控制符。
以下是运行过程中的部分输出,
macos:15691156 green$ ./a.out
Please hit a key (Ctrl-C to quit).
......
Your input is A, its ASCII code is 65.
Your input is [, its ASCII code is 91.
Your input is B, its ASCII code is 66.
Your input is 5, its ASCII code is 53.
Your input is ~, its ASCII code is 126.
......
◆ 最后
完整示例代码请参考 [github] cnblogs/15691156 。
写作过程中,笔者参考了 Linux下C++/C终端获取键盘事件/termios结构详细解释 和 LINUX 使用tcgetattr与tcsetattr函数控制终端。致 Liuqz2009 和 凡人只做一事 两位作者。