如何避免使用getchar()按下Enter键仅读取单个字符?


80

在下面的代码中:

#include <stdio.h>

int main(void) {   
  int c;   
  while ((c=getchar())!= EOF)      
    putchar(c); 
  return 0;
}

我必须按Enter来打印输入的所有字母getchar,但是我不想这样做,我要做的是按字母,然后立即看到我介绍的字母重复,而无需按Enter。例如,如果我按字母“ a”,我想在其旁边看到另一个“ a”,依此类推:

aabbccddeeff.....

但是,当我按“ a”时,什么也没发生,我可以写其他字母,并且只有在按Enter以下命令时,副本才会出现:

abcdef
abcdef

我怎样才能做到这一点?

cc -o example example.c正在Ubuntu下使用命令进行编译。


5
这在很大程度上取决于您的操作系统和终端如何处理输入。如果指定操作环境,则可能会得到更好的答案。
goldPseudo

cplusplus.com/forum/articles/19975提供的答案在使用WinAPI的Windows上有效。

Answers:


71

在Linux系统上,您可以使用以下stty命令修改终端行为。默认情况下,终端将缓冲所有信息,直到Enter被按下为止,甚至将其发送到C程序。

一个快速,肮脏且不怎么便携的示例,可以从程序本身内部更改行为:

#include<stdio.h>
#include<stdlib.h>

int main(void){
  int c;
  /* use system call to make terminal send all keystrokes directly to stdin */
  system ("/bin/stty raw");
  while((c=getchar())!= '.') {
    /* type a period to break out of the loop, since CTRL-D won't work raw */
    putchar(c);
  }
  /* use system call to set terminal behaviour to more normal behaviour */
  system ("/bin/stty cooked");
  return 0;
}

请注意,这并不是真正的最佳选择,因为它只是假设这stty cooked是程序退出时想要的行为,而不是检查原始终端设置是什么。另外,由于在原始模式下会跳过所有特殊处理,因此许多键序列(例如CTRL-CCTRL-D)在没有在程序中显式处理它们的情况下实际上不会按您期望的那样工作。

您可以man stty完全根据您要实现的目的来控制终端行为。


使用system是一个坏主意。
Sapphire_Brick

太原始了....我正在寻找一个使我不必等待ENTER的解决方案。该解决方案制动了所有输入端子功能。
美国广播公司

92

这取决于您的操作系统,如果您在类似UNIX的环境中,则默认情况下会启用ICANON标志,因此将缓冲输入,直到下一个'\n'EOF。通过禁用规范模式,您将立即获得字符。在其他平台上也可以这样做,但是没有直接的跨平台解决方案。

编辑:我看到您指定您使用Ubuntu。我昨天刚刚发布了类似的内容,但是请注意,这将禁用终端的许多默认行为。

#include<stdio.h>
#include <termios.h>            //termios, TCSANOW, ECHO, ICANON
#include <unistd.h>     //STDIN_FILENO


int main(void){   
    int c;   
    static struct termios oldt, newt;

    /*tcgetattr gets the parameters of the current terminal
    STDIN_FILENO will tell tcgetattr that it should write the settings
    of stdin to oldt*/
    tcgetattr( STDIN_FILENO, &oldt);
    /*now the settings will be copied*/
    newt = oldt;

    /*ICANON normally takes care that one line at a time will be processed
    that means it will return if it sees a "\n" or an EOF or an EOL*/
    newt.c_lflag &= ~(ICANON);          

    /*Those new settings will be set to STDIN
    TCSANOW tells tcsetattr to change attributes immediately. */
    tcsetattr( STDIN_FILENO, TCSANOW, &newt);

    /*This is your part:
    I choose 'e' to end input. Notice that EOF is also turned off
    in the non-canonical mode*/
    while((c=getchar())!= 'e')      
        putchar(c);                 

    /*restore the old settings*/
    tcsetattr( STDIN_FILENO, TCSANOW, &oldt);


    return 0;
}

您会注意到,每个字符出现两次。这是因为输入会立即回显到终端,然后您的程序也将其放回去putchar()。如果要取消输入与输出的关联,还必须关闭ECHO标志。您只需将相应的行更改为:

newt.c_lflag &= ~(ICANON | ECHO); 

9
+1尽管是tbh,但这可能不是原始海报
所能接受

2
大!那就是我想要的!
DenilsonSáMaia '04 -4-28

感谢您的帮助以及详细的评论,它解决了我的问题。
kaminsknator '16

令人难以置信的是,如此基本的东西很难实现且不可移植。
Pedro77

12

getchar()是一个标准函数,在许多平台上,您都需要按Enter来获取输入,因为平台会缓冲输入,直到按下该键为止。许多编译器/平台都支持不关心ENTER的非标准getch()(绕过平台缓冲,将ENTER视为另一个键)。


1
+1正确,getchar()仅在按Enter键后才开始逐个读取字符
Andomar

32
getchar()不在乎ENTER,它只处理通过stdin进行的操作。行缓冲往往是OS /终端定义的行为。
goldPseudo

1
@goldPseudo:是的,但是几乎所有平台都会缓冲输入,因此这是在大多数实际情况下都会看到的行为。getch()(在受支持的地方)将管理或忽略缓冲。我知道一些旧的DOS实现绕过OS缓冲来直接与硬件交互。其他实现可能会刷新标准输入以获得相同的行为。
Eric J.

10
但是,不是getchar()“需要您按Enter”,而是环境。
Benji XVI

1
非常简明易懂。其他评分最高的答案只会带来大量不必要的技术细节。
mzoz

6

I / O是操作系统功能。在许多情况下,只有按Enter键,操作系统才会将键入的字符传递给程序。这允许用户在将输入发送到程序之前修改输入(例如,退格和重新键入)。对于大多数目的来说,这很好用,为用户提供了一个一致的界面,使程序无需处理。在某些情况下,希望程序在按下按键时从按键中获取字符。

C库本身处理文件,而不关心数据如何进入输入文件。因此,语言本身无法在按下按键时获取按键。相反,这是特定于平台的。由于您尚未指定操作系统或编译器,因此我们无法为您查找。

此外,通常会缓冲标准输出以提高效率。这是由C库完成的,因此有一个C解决方案,该解决方案是fflush(stdout);在每个字符写入之后进行的。之后,是否立即显示字符取决于操作系统,但是我熟悉的所有OS都会立即显示输出,因此通常不存在问题。


6

我喜欢卢卡斯的回答,但我想对其进行详细说明。在termios.hnamed中有一个内置函数,cfmakeraw()其中man描述为:

cfmakeraw() sets the terminal to something like the "raw" mode of the
old Version 7 terminal driver: input is available character by
character, echoing is disabled, and all special processing of
terminal input and output characters is disabled. [...]

这基本上与Lucas建议的功能相同,并且更多,您可以在手册页中看到它设置的确切标志:termios(3)

用例

int c = 0;
static struct termios oldTermios, newTermios;

tcgetattr(STDIN_FILENO, &oldTermios);
newTermios = oldTermios;

cfmakeraw(&newTermios);

tcsetattr(STDIN_FILENO, TCSANOW, &newTermios);
c = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldTermios);

switch (c) {
    case 113: // q
        printf("\n\n");
        exit(0);
        break;
    case 105: // i
        printf("insert\n");
        break;
    default:
        break;

请注意,这cfmakeraw()POSIXtermios.h的非便携式,非标准扩展,不一定可用。
安德鲁·亨利

4

由于您正在使用Unix派生词(Ubuntu),因此这是一种实现方法-不推荐使用,但是它可以工作(只要您可以正确键入命令即可):

echo "stty -g $(stty -g)" > restore-sanity
stty cbreak
./your_program

当您对程序感到无聊时,可以使用中断来停止该程序。

sh restore-sanity
  • “ echo”行将当前的终端设置保存为将其还原的shell脚本。
  • “ stty”行关闭了大多数特殊处理(因此 Control-D例如,无效),并在字符可用时立即将其发送到程序。这意味着您无法再编辑输入内容。
  • “ sh”行将恢复您的原始终端设置。

如果“ stty sane”能够足够准确地恢复您的设置,那么您可以节省开支。'-g'的格式不能在'stty'的各个版本之间移植(因此Solaris 10上生成的内容不能在Linux上运行,反之亦然),但是该概念可在所有地方使用。'stty sane'选项不是通用的,AFAIK(但是在Linux上)。


2

您可以包括'ncurses'库,并使用getch()代替getchar()


1

是的,您也可以在Windows上执行此操作,这是下面的代码,使用conio.h库

#include <iostream> //basic input/output
#include <conio.h>  //provides non standard getch() function
using namespace std;

int main()
{  
  cout << "Password: ";  
  string pass;
  while(true)
  {
             char ch = getch();    

             if(ch=='\r'){  //when a carriage return is found [enter] key
             cout << endl << "Your password is: " << pass <<endl; 
             break;
             }

             pass+=ch;             
             cout << "*";
             }
  getch();
  return 0;
}

6
这个问题是用c而不是c ++
Spikatrix

@CoolGuy那又如何呢?如果有人打开完全相同的问题,但将标签从c更改为c ++,为什么对任何人有帮助?
安德烈亚斯·哈弗堡

@AndreasHaferburg因为这里没有C ++的答案,尤其是写得不好的答案,所以wink wink
Sapphire_Brick

1

我在目前正在处理的作业中遇到了这个问题。它还取决于您从哪个输入中获取。我在用

/dev/tty

在程序运行时获取输入,因此必须是与命令关联的文件流。

在我必须测试/定位的ubuntu机器上,它不仅需要

system( "stty -raw" );

要么

system( "stty -icanon" );

我必须添加--file标志以及命令的路径,如下所示:

system( "/bin/stty --file=/dev/tty -icanon" );

现在一切都已经崩溃了。


2
请注意,此解决方案取决于操作系统。
奥塔维奥·坎帕纳

0

这段代码对我有用。注意:即使大多数编译器(我使用GCC)都支持,它也不是标准库的一部分。

#include <stdio.h>
#include <conio.h>
int main(int argc, char const *argv[]) {
    char a = getch();
    printf("You typed a char with an ASCII value of %d, printable as '%c'\n", a, a);
    return 0;
}

此代码检测第一次按键。


0

如何避免按下Entergetchar()

首先,终端输入通常是线路输入或完全缓冲的。这意味着操作系统会将来自终端的实际输入存储到缓冲区中。通常,当fe\n在/中提供信号时,此缓冲区将刷新到程序stdin。这是新闻界对Enter

getchar()只是在链的尽头。它没有能力真正影响缓冲过程。


我该怎么办?

沟渠 getchar()摆在首位,如果你不想将使用特定的系统调用来改变终端的明确像其他的答案很好地解释的行为。

不幸的是,没有标准的库函数,也没有可移植的方式在单字符输入时刷新缓冲区。但是,存在基于实现的非便携式解决方案。


在Windows / MS-DOS,还有getch()getche()在功能conio.h头文件中,它们确实可以完成您想要的事情-读取单个字符而无需等待换行符刷新缓冲区。

之间的主要区别getch()getche()getch()不会立即输出在控制台的实际输入字符,而getche()做。附加"e"代表回声

例:

#include <stdio.h>
#include <conio.h>   

int main (void) 
{
    int c;

    while ((c = getche()) != EOF)
    {
        if (c == '\n')
        {
            break;
        }

        printf("\n");
    }

    return 0;
}

在Linux中,获取直接字符处理和输出的一种方法是使用cbreak()echo()选项以及getch()和和refresh()ncurses-library中例程。

请注意,您需要使用来初始化所谓的标准屏幕initscr()并使用来关闭该屏幕endwin()例程。

例:

#include <stdio.h>
#include <ncurses.h>   

int main (void) 
{
    int c;

    cbreak(); 
    echo();

    initscr();

    while ((c = getch()) != ERR)
    {
        if (c == '\n')
        {
            break;
        }

        printf("\n");
        refresh();
    }

    endwin();

    return 0;
}

注意:您需要使用该-lncurses选项来调用编译器,以便链接器可以搜索并找到ncurses-library。


-1

默认情况下,C库将缓冲输出,直到看到返回为止。要立即打印出结果,请使用fflush

while((c=getchar())!= EOF)      
{
    putchar(c);
    fflush(stdout);
}

2
但是,输出是问题的一部分。发问者想要让程序在按下按键时读取按键并立即在屏幕上打印。这既是输入问题,也是输出问题。
David Thornley,2009年

例如,fflush仅刷新输出,但是在按下键后,getchar仍不会返回(键将被缓冲)。
NickSoft
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.