是否有一种简单的方法这个?我认为我可以用
select()
来做到这一点,但这似乎需要很多工作。或者,有一种方法可以在程序关闭之前捕获ctrl-c按键进行清理,而不是非关闭。阻止io?
#1 楼
如前所述,您可以使用sigaction
捕获ctrl-c或select
捕获任何标准输入。但是请注意,对于后一种方法,您还需要设置TTY,以使其符合字符-一次而不是一次一行的模式。后者是默认设置-如果您输入一行文本,直到按Enter键,它才会发送到正在运行的程序的stdin。
您需要使用
tcsetattr()
函数关闭ICANON模式,也可能也禁用了ECHO。从内存中,您还必须在程序退出时将终端设置回ICANON模式!为了完整起见,这是我刚刚敲过的一些代码(nb:无错误检查!) Unix TTY并模拟DOS
<conio.h>
函数kbhit()
和getch()
:#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>
struct termios orig_termios;
void reset_terminal_mode()
{
tcsetattr(0, TCSANOW, &orig_termios);
}
void set_conio_terminal_mode()
{
struct termios new_termios;
/* take two copies - one for now, one for later */
tcgetattr(0, &orig_termios);
memcpy(&new_termios, &orig_termios, sizeof(new_termios));
/* register cleanup handler, and set the new terminal mode */
atexit(reset_terminal_mode);
cfmakeraw(&new_termios);
tcsetattr(0, TCSANOW, &new_termios);
}
int kbhit()
{
struct timeval tv = { 0L, 0L };
fd_set fds;
FD_ZERO(&fds);
FD_SET(0, &fds);
return select(1, &fds, NULL, NULL, &tv);
}
int getch()
{
int r;
unsigned char c;
if ((r = read(0, &c, sizeof(c))) < 0) {
return r;
} else {
return c;
}
}
int main(int argc, char *argv[])
{
set_conio_terminal_mode();
while (!kbhit()) {
/* do some work */
}
(void)getch(); /* consume the character */
}
评论
是getch()返回返回按下的键,还是只是消耗掉字符?
–敬而远之
2012年7月19日,11:56
@Salepate应该返回字符。是(void)位获取该字符然后将其丢弃。
– Alnitak
2012年7月19日在12:05
哦,我明白了,也许我做错了,但是当我在printf(“%c”)上使用getch()时;它在屏幕上显示奇怪的字符。
–敬而远之
2012年7月19日在12:07
轻微的编辑,您需要#include
– jernkuan
13年4月30日在0:42
是否有一种简单的方法来读取整行(例如,使用“ getline”)而不是单个字符?
– azmeuk
2014-2-26在17:02
#2 楼
为方便起见,select()
有点太低了。我建议您使用ncurses
库将终端置于cbreak模式和延迟模式,然后调用getch()
,如果没有可用的字符,它将返回ERR
:WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);
您可以无阻塞地拨打
getch
。评论
我也考虑过使用curses,但是潜在的问题是initscr()调用可以清除屏幕,并且也可能妨碍使用标准stdio进行屏幕输出。
– Alnitak
09年1月16日在8:09
是的,诅咒几乎全无。
–诺曼·拉姆齐(Norman Ramsey)
09年1月17日,下午3:01
#3 楼
在UNIX系统上,可以使用sigaction
调用为SIGINT
信号注册代表Control + C键序列的信号处理程序。信号处理程序可以设置一个标志,该标志将在循环中进行检查以使其适当中断。评论
我想我可能会很轻松地完成此操作,但是我将接受另一个答案,因为它更接近标题的要求。
– Zxaos
09年1月15日23:37
#4 楼
您可能想要kbhit();
//Example will loop until a key is pressed
#include <conio.h>
#include <iostream>
using namespace std;
int main()
{
while(1)
{
if(kbhit())
{
break;
}
}
}
这可能不适用于所有环境。一种可移植的方法是创建一个监视线程并在
getch();
上设置一些标志评论
绝对不是便携式的。 kbhit()是DOS / Windows控制台IO函数。
– Alnitak
09年1月15日23:38
仍然是许多用途的有效答案。 OP没有指定可移植性或任何特定平台。
– AShelly
09年1月15日在23:54
Alnitak:我不知道它暗示一个平台-Windows的等效术语是什么?
– Zxaos
09年1月16日下午5:15
@Alnitak为什么作为编程概念的“非阻塞”在Unix环境中会更加熟悉?
–MestreLion
2015年2月23日下午5:45
@Alnitak:我的意思是,在接触任何* nix之前,我很熟悉“无阻塞呼叫”一词。因此就像Zxaos一样,我永远也不会意识到这意味着平台。
–MestreLion
15年2月23日在10:52
#5 楼
获取非阻塞键盘输入的另一种方法是打开设备文件并读取它!您必须知道要查找的设备文件,它是/ dev / input / event *之一。您可以运行cat / proc / bus / input / devices来查找所需的设备。
此代码对我有用(以管理员身份运行)。
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/input.h>
int main(int argc, char** argv)
{
int fd, bytes;
struct input_event data;
const char *pDevice = "/dev/input/event2";
// Open Keyboard
fd = open(pDevice, O_RDONLY | O_NONBLOCK);
if(fd == -1)
{
printf("ERROR Opening %s\n", pDevice);
return -1;
}
while(1)
{
// Read Keyboard Data
bytes = read(fd, &data, sizeof(data));
if(bytes > 0)
{
printf("Keypress value=%x, type=%x, code=%x\n", data.value, data.type, data.code);
}
else
{
// Nothing read
sleep(1);
}
}
return 0;
}
评论
一个很棒的例子,但是我遇到了一个问题,即当按住六个以上的键时事件设备停止发出信号时,Xorg将继续接收键事件:\很奇怪,对吗?
–雷神召唤师
19/12/26在7:57
#6 楼
curses库可用于此目的。当然,在一定程度上也可以使用select()
和信号处理程序。 评论
curses既是库的名称,也是库的名称;)但是,由于我要向您提到+1。
–Wayne Werner
10年7月22日在15:46
#7 楼
如果您很高兴只是接住Control-C,那就已经做完了。如果您确实想要非阻塞I / O但又不想使用curses库,则另一种选择是将锁,库存和桶装设备移至AT&Tsfio
库。它是在C stdio
上模式化的不错的库,但更灵活,线程安全并且性能更好。 (sfio代表安全,快速的I / O。)#8 楼
没有可移植的方法来执行此操作,但是select()可能是一个好方法。有关更多可能的解决方案,请参见http://c-faq.com/osdep/readavail.html。评论
这个答案是不完整的。如果没有使用tcsetattr()进行终端操作,[请参阅我的答案]在按Enter键之前,您不会在fd 0上得到任何东西。
– Alnitak
09年1月15日23:40
#9 楼
您可以使用select进行以下操作: int nfds = 0;
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(0, &readfds); /* set the stdin in the set of file descriptors to be selected */
while(1)
{
/* Do what you want */
int count = select(nfds, &readfds, NULL, NULL, NULL);
if (count > 0) {
if (FD_ISSET(0, &readfds)) {
/* If a character was pressed then we get it and exit */
getchar();
break;
}
}
}
不需要太多的工作:D
评论
这个答案是不完整的。您需要将终端设置为非规范模式。另外,nfds应该设置为1。
–luser droog
16年2月18日在22:18
#10 楼
这是为您执行此操作的功能。您需要POSIX系统随附的termios.h
。#include <termios.h>
void stdin_set(int cmd)
{
struct termios t;
tcgetattr(1,&t);
switch (cmd) {
case 1:
t.c_lflag &= ~ICANON;
break;
default:
t.c_lflag |= ICANON;
break;
}
tcsetattr(1,0,&t);
}
简单说明一下:
tcgetattr
获取当前的终端信息并将其存储在t
中。如果cmd
为1,则将t
中的本地输入标志设置为非阻塞输入。否则将被重置。然后,tcsetattr
将标准输入更改为t
。如果不在程序结束时重置标准输入,则Shell中会出现问题。
评论
switch语句缺少一个括号:)
–étale-cohomology
16 Dec 10'在3:24
评论
打开线程怎么办?