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

我是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’,程序首先打印出“Hi”(应该如此),然后继续打印提示“请选择一个选项”两次。 如果我输入N个字符并按Enter键,提示将被打印N次。

当我使用限制为2的fgets()时会发生同样的行为。

如何让这个循环正常工作? 它应该只接受输入的第一个字符,然后根据输入的内容执行一次。

编辑

因此,使用具有较大缓冲区的fgets()可以正常工作,并停止重复的提示问题:

 fgets(choice, 80, stdin); 

这种帮助: 如何清除C中的输入缓冲区?

当你获得输入时,重要的是要注意用户输入了多个字符:至少, 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(stdin); ,在某些系统上,这将有效。 但是: 此行为未定义:标准明确指出fflush旨在用于更新/输出缓冲区,而不是输入缓冲区

C11 7.21.5.2 fflushfunction,fflush仅适用于输出/更新流,不适用于输入流

但是,某些实现(例如Microsoft )确实支持fflush(stdin); 作为延伸。 然而,依赖于它背后的理念却违背了C. C本来是便于携带的,并且坚持标准,你可以放心,你的代码是可移植的。 依靠特定的扩展会消除这种优势。

看起来非常简单的问题实际上非常复杂。 问题的根源在于终端以两种不同的模式运行:原始和熟食。 煮熟模式是默认设置,意味着终端不读取字符,而是读取行。 因此,除非输入整行(或接收到文件末尾字符),否则您的程序根本不会接收任何输入。 终端识别线路末端的方式是接收换行符所引起的换行符(0x0A)。 为了使其更加混乱,在Windows机器上按Enter键会生成两个字符(0x0D和0x0A)。

因此,您的基本问题是您需要单字符接口,但您的终端正在以线路(煮熟)模式运行。

正确的解决方案是将终端切换到原始模式,以便程序可以在用户键入时接收字符。 另外,我建议在此用法中使用getchar()而不是getc() 。 不同之处在于getc()将文件描述符作为参数,因此它可以从任何流中读取。 getchar()函数只从标准输入读取,这是你想要的。 因此,这是一个更具体的选择。 程序完成后,它应该将终端切换回原来的状态,因此需要在修改之前保存当前的终端状态。

此外,您应该处理终端接收EOF(0x04)的情况,用户可以通过按CTRL-D来完成。

这是完成这些事情的完整程序:

 #include  #include  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章详细解释了上述所有内容。

发生这种情况的原因是因为stdin被缓冲了。

当你到达代码行* choice = getc(stdin); 无论你键入多少个字符,getc(stdin)都只会检索第一个字符。 因此,如果您键入“foo”,它将检索“f”并将* choice设置为“f”。 字符“oo”仍在输入缓冲区中。 此外,您敲击返回键所导致的回车符也在输入缓冲区中。 因此,由于缓冲区不是空的,下次循环执行时,而不是等待你输入内容,getc(stdin); 将立即返回缓冲区中的下一个字符。 函数getc(stdin)将继续立即返回缓冲区中的下一个字符,直到缓冲区为空。 因此,一般来说,当您输入长度为N的字符串时,它会提示您N次。

您可以通过fflush(stdin)刷新缓冲区来解决这个问题。 紧跟在行* choice = getc(stdin)之后;

编辑:显然其他人说不要使用fflush(stdin); 跟他说的一样去吧。