C - 如何在while循环中处理用户输入

时间:2014-10-01 14:53:12

标签: c

我是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);

这种帮助:How to clear input buffer in C?

3 个答案:

答案 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);跟他说的一样去吧。