我是C的新手,我有一个简单的程序,可以在while循环中输入一些用户输入,如果用户按下“q'
,则退出while(1)
{
printf("Please enter a choice: \n1)quit\n2)Something");
*choice = getc(stdin);
// Actions.
if (*choice == 'q') break;
if (*choice == '2') printf("Hi\n");
}
当我运行此命令并点击' q'时,程序确实正确退出。但是,如果我按下' 2'该程序首先打印出来"嗨" (应该如此)然后继续打印提示"请选择一个选项"两次。如果我输入N个字符并按Enter键,提示将被打印N次。
当我使用限制为2的fgets()时会发生同样的行为。
如何让此循环正常工作?它应该只取输入的第一个字符,然后根据输入的内容做一次。
修改
因此,使用具有较大缓冲区的fgets()可以正常工作,并停止重复的提示问题:
fgets(choice, 80, stdin);
答案 0 :(得分:3)
当你getc
输入时,重要的是要注意用户输入了多个字符:至少,stdin
包含2个字符:
2\n
当getc
获得用户输入的“2”时,尾随的\n
字符仍在缓冲区中,因此您必须清除它。这样做的最简单方法是添加:
if (*choice == '2')
puts("Hi");
while (*choice != '\n' && *choice != EOF)//EOF just in case
*choice = getc(stdin);
那应该解决它
为了完整性:
请注意,getc
返回一个int,而不是char
。确保使用-Wall -pedantic
标志进行编译,并始终检查所使用函数的返回类型。
使用fflush(stdin);
清除输入缓冲区很有诱惑力,在某些系统上,这样可行。但是: 此行为未定义:标准明确指出fflush
旨在用于更新/输出缓冲区,而不是输入缓冲区 :
C11 7.21.5.2 fflush功能,fflush仅适用于输出/更新流,不适用于输入流
但是,某些实现(例如Microsoft)支持fflush(stdin);
作为扩展。然而,依赖于它背后的理念却违背了C. C本来是便于携带的,并且坚持标准,你可以放心,你的代码是可移植的。依靠特定的扩展会带来这种优势。
答案 1 :(得分:2)
看起来非常简单的问题实际上非常复杂。问题的根源在于终端以两种不同的模式运行:生和熟。煮熟模式是默认值,表示终端不读取字符,它读取行。因此,除非输入整行(或接收到文件末尾字符),否则您的程序根本不会接收任何输入。终端识别线路末端的方式是接收换行符所引起的换行符(0x0A)。为了使其更加混乱,在Windows机器上按Enter键会生成两个字符(0x0D和0x0A)。
所以,你的基本问题是你需要一个单字符界面,但你的终端是在面向行(熟)模式下运行。
正确的解决方案是将终端切换到原始模式,以便程序可以在用户输入时接收字符。另外,我建议在此用法中使用getchar()
而不是getc()
。不同之处在于getc()将文件描述符作为参数,因此它可以从任何流中读取。 getchar()
函数仅从标准输入读取,这是您想要的。因此,这是一个更具体的选择。程序完成后,它应该将终端切换回原来的状态,因此需要在修改之前保存当前的终端状态。
此外,你应该处理终端收到EOF(0x04)的情况,用户可以通过按CTRL-D来完成。
以下是执行这些操作的完整程序:
#include <stdio.h>
#include <termios.h>
main(){
tty_mode(0); /* save current terminal mode */
set_terminal_raw(); /* set -icanon, -echo */
interact(); /* interact with user */
tty_mode(1); /* restore terminal to the way it was */
return 0; /* 0 means the program exited normally */
}
void interact(){
while(1){
printf( "\nPlease enter a choice: \n1)quit\n2)Something\n" );
switch( getchar() ){
case 'q': return;
case '2': {
printf( "Hi\n" );
break;
}
case EOF: return;
}
}
}
/* put file descriptor 0 into chr-by-chr mode and noecho mode */
set_terminal_raw(){
struct termios ttystate;
tcgetattr( 0, &ttystate); /* read current setting */
ttystate.c_lflag &= ~ICANON; /* no buffering */
ttystate.c_lflag &= ~ECHO; /* no echo either */
ttystate.c_cc[VMIN] = 1; /* get 1 char at a time */
tcsetattr( 0 , TCSANOW, &ttystate); /* install settings */
}
/* 0 => save current mode 1 => restore mode */
tty_mode( int operation ){
static struct termios original_mode;
if ( operation == 0 )
tcgetattr( 0, &original_mode );
else
return tcsetattr( 0, TCSANOW, &original_mode );
}
正如你所看到的,正确做法似乎是一个非常简单的问题。
我强烈建议用来浏览这些问题的书是Bruce Molay的“理解Unix / Linux编程”。第6章详细解释了上述所有内容。
答案 2 :(得分:1)
之所以发生这种情况,是因为stdin被缓冲了。
当你到达代码行* choice = getc(stdin);无论你键入多少个字符,getc(stdin)都只会检索第一个字符。因此,如果您键入“foo”,它将检索“f”并将* choice设置为“f”。字符“oo”仍在输入缓冲区中。此外,您敲击返回键所导致的回车符也在输入缓冲区中。因此,由于缓冲区不是空的,下次循环执行时,而不是等待你输入内容,getc(stdin);将立即返回缓冲区中的下一个字符。函数getc(stdin)将继续立即返回缓冲区中的下一个字符,直到缓冲区为空。因此,一般来说,当您输入长度为N的字符串时,它会提示您N次。
你可以通过用fflush(stdin)刷新缓冲区来解决这个问题。紧跟在行* choice = getc(stdin)之后;
编辑:显然其他人说不要使用fflush(stdin);跟他说的一样去吧。