_getch()是Windows平台下从控制台无回显、不等待回车读取单字符的非标准函数,需包含<conio.h>,返回int值;读功能键需调用两次,首次返回0或0xE0,二次返回实际键码。

Windows下用 _getch() 获取单字符按键(不回显)
在 Windows 平台,_getch() 是最直接的方式:它从控制台读取一个字符,不等待回车,也不显示在屏幕上。注意这不是标准 C++ 函数,而是 MSVC 和 MinGW 提供的 CRT 扩展,需包含 <conio.h>。
常见错误是误用 getchar() 或 std::cin.get() —— 它们会缓冲输入、等待回车、并回显字符,完全不符合“按键即得”需求。
- 必须链接
libcmt.lib(默认已连),无需额外编译选项 - 返回值是
int,不是char;若读到扩展键(如方向键),第一次调用返回0或0xE0,需立刻再调一次_getch()才能得到实际扫描码 - 对 Ctrl+C 等信号,
_getch()会照常返回'\x03',不会自动触发中断;如需捕获 Ctrl+C,得另设信号处理器
Linux/macOS 下用 termios 关闭回显和行缓冲
POSIX 系统没有 _getch(),但可通过 termios 控制终端属性实现等效效果:禁用 ICANON(关闭行缓冲)、禁用 ECHO(关闭回显),再用 read() 读单字节。
关键点在于不能只改一次终端设置——程序退出前必须恢复原始配置,否则终端会卡住(比如输命令不显示、回车没反应)。
立即学习“C++免费学习笔记(深入)”;
- 用
tcgetattr(STDIN_FILENO, &old)先保存原始termios结构体 - 清除
icflag中的ICANON | ECHO,并设c_cc[VMIN] = 1; c_cc[VTIME] = 0(即读到 1 字节就返回) - 调用
tcsetattr(STDIN_FILENO, TCSANOW, &new)生效;退出前务必用old恢复 - 注意:如果输入的是多字节序列(如方向键、ESC 组合),
read()每次只吐一个字节,需自己缓存+解析
跨平台封装时别硬套 std::cin
试图用 std::cin 配合 std::cin.sync() 或 std::cin.ignore() 实现无回显按键读取,基本无效。标准流设计就是面向行缓冲的,底层依赖 stdio,无法绕过终端驱动的回显和缓冲策略。
真正跨平台的做法只有两种:要么条件编译分别调用 _getch() 和 termios,要么引入轻量库如 linenoise 或 readline(但它们目标是行编辑,非单键响应)。
- 不要尝试用
system("stty -icanon -echo")—— 可移植性差,子进程改完终端后父进程未必立即生效,且难以安全还原 - Qt、SFML 等 GUI 框架自带事件循环和键盘钩子,但它们不适用于纯控制台程序
- 若项目已用 CMake,可用
check_symbol_exists(_getch conio.h HAVE_GETCH)做编译期探测,比运行时isatty()判断更可靠
处理功能键和特殊组合要分两次读
方向键、F1–F12、Insert/Delete 等在终端里不是单字节,而是以 ESC 序列形式发送(如 ← 是 "\033[D")。这意味着即使你关了回显和行缓冲,一次 _getch() 或 read() 也只拿到第一个字节。
必须做状态机式解析:先读一字节,如果是 0x1B(ESC)或 0x00/0xE0(Windows 扩展前缀),就紧接着再读 1–2 字节,拼出完整序列再查表映射为语义键(如 KEY_LEFT)。
- Linux 下常用序列以
"\033["开头,后面跟数字+字母(如"\033[A"是上箭头) - Windows 下方向键首次返回
0xE0,第二次返回0x48(上)、0x50(下)等;F1 返回0x00后接0x3B - 别用
std::string直接拼接——避免隐式 null 截断;用std::vector<uint8_t>或固定大小数组更稳妥
termios 设置残留,让后续 shell 彻底不可用。


















