kbhit() 是什么?
kbhit() 是一个非标准库函数,它的全称是 "keyboard hit"(键盘敲击),它的主要作用是检查当前是否有键盘按键被按下。
关键特性:
- 非阻塞式:它不会等待用户按键,如果调用时没有按键被按下,它会立即返回
0,如果有一个按键被按下,它会返回一个非零值(通常是按键的 ASCII 码),并且不会消耗这个按键(即按键仍然留在输入缓冲区中)。 - 主要用于控制台程序:在像 Windows 的命令提示符 或 Linux/macOS 的终端 这样的控制台环境中非常有用。
- 实现不统一:由于它不是 C 标准库(如
stdio.h或stdlib.h)的一部分,它的实现和头文件在不同的操作系统上是不同的。
为什么需要 kbhit()?
在标准的 C 程序中,当你使用 scanf() 或 getchar() 来读取输入时,程序会阻塞(暂停执行),直到用户真正按下回车键。
场景示例: 你想制作一个简单的游戏,比如一个左右移动的小方块,如果用 scanf,你的代码会像这样:
#include <stdio.h>
int main() {
int position = 0;
char input;
printf("Use 'a' and 'd' to move. Press 'q' to quit.\n");
while (1) {
// 程序会在这里停下来,等待你输入一个字符并按回车
scanf(" %c", &input);
if (input == 'q') {
break;
} else if (input == 'a') {
position--;
} else if (input == 'd') {
position++;
}
printf("Position: %d\n", position);
}
return 0;
}
这个程序的问题是,每移动一次,你都必须按一次回车,这非常不流畅。
kbhit() 的解决方案:
使用 kbhit(),你可以创建一个游戏循环,这个循环可以一直运行,不断地检查是否按下了某个键,如果按下了,就执行相应的动作;如果没有,就继续做其他事情(比如更新画面、计算逻辑等),从而实现实时响应。
如何在不同平台上使用 kbhit()?
因为 kbhit() 不是标准函数,所以你需要根据你的操作系统来选择正确的实现方式。
A. 在 Windows 平台上
在 Windows 中,kbhit() 函数是标准 C 运行时库的一部分。
- 头文件:
<conio.h> - 函数原型:
int kbhit(void); - 返回值:
- 如果有按键被按下,返回一个非零值(通常就是按键的 ASCII 码)。
- 如果没有按键被按下,返回
0。
- 辅助函数: 通常会和
getch()配合使用。getch()也是<conio.h>中的函数,它会从键盘读取一个字符,并且不显示在屏幕上,也不会等待回车。
示例代码 (Windows):
#include <stdio.h>
#include <conio.h> // Windows 专用头文件
int main() {
printf("Press any key to continue, or ESC to quit...\n");
while (1) {
// 检查是否有按键被按下
if (kbhit()) {
// 如果有,获取按键的 ASCII 码
int key = getch();
// 如果按下的是 ESC 键 (ASCII 27),则退出循环
if (key == 27) {
break;
}
// 打印按下的键
printf("You pressed: %c (ASCII: %d)\n", key, key);
}
// 如果没有按键,这里可以做其他事情,比如显示一个等待提示
// _sleep(100); // 暂停 100 毫秒,避免 CPU 占用过高
}
printf("Program exited.\n");
return 0;
}
B. 在 Linux / macOS 平台上
在 Linux 和 macOS 的标准 C 库中没有 kbhit() 函数,你需要自己手动实现它,通常的方法是使用 POSIX 标准中的 <termios.h> 和 <unistd.h> 来修改终端的属性,使其进入“原始模式”(raw mode),然后使用 select() 或 poll() 来检查文件描述符(标准输入 STDIN_FILENO)是否可读。
实现思路:
- 保存终端的当前设置。
- 将终端设置为“原始模式”,这样按键就不会被立即显示,也不会被缓冲(直到回车)。
- 使用
select()函数来检查stdin是否有数据可读(即是否有按键)。 select()返回有数据,则使用read()读取字符。- 程序退出前,恢复终端的原始设置。
示例代码 (Linux/macOS):
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <sys/select.h>
// 定义 kbhit() 函数
int kbhit() {
struct timeval tv = {0L, 0L};
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
return select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
}
// 定义 getch() 函数
int getch() {
struct termios oldt, newt;
int ch;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
return ch;
}
int main() {
printf("Press any key to continue, or ESC to quit...\n");
while (1) {
if (kbhit()) {
int key = getch();
if (key == 27) { // ESC
break;
}
printf("You pressed: %c (ASCII: %d)\n", key, key);
}
// usleep(10000); // 暂停 10 毫秒
}
printf("Program exited.\n");
return 0;
}
编译命令 (Linux/macOS):
gcc your_program.c -o your_program
跨平台解决方案
如果你想让你的代码同时在 Windows 和 Linux/macOS 上运行,你可以使用 #ifdef 预处理指令来根据操作系统选择不同的实现。
示例代码 (跨平台):
#include <stdio.h>
#ifdef _WIN32
// Windows 平台
#include <conio.h>
#else
// Linux / macOS 平台
#include <unistd.h>
#include <termios.h>
#include <sys/select.h>
int kbhit() {
struct timeval tv = {0L, 0L};
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
return select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
}
int getch() {
struct termios oldt, newt;
int ch;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
return ch;
}
#endif
int main() {
printf("Press any key to continue, or ESC to quit...\n");
while (1) {
if (kbhit()) {
int key = getch();
if (key == 27) { // ESC
break;
}
printf("You pressed: %c (ASCII: %d)\n", key, key);
}
}
printf("Program exited.\n");
return 0;
}
| 特性 | 描述 |
|---|---|
| 功能 | 检查键盘是否有按键被按下,但不等待。 |
| 类型 | 非标准库函数。 |
| Windows | 使用 <conio.h>,直接可用。 |
| Linux/macOS | 需要手动使用 <termios.h> 和 <unistd.h> 实现。 |
| 用途 | 创建实时交互的控制台程序、游戏、工具等。 |
| 搭档 | 通常与 getch() 配合使用,以获取按下的具体字符。 |
kbhit() 是一个非常实用的工具,但它也提醒我们 C 语言在不同平台上的差异性,编写可移植的代码需要对这些差异有所了解。
