【问题标题】:How to avoid pressing Enter with getchar() for reading a single character only?如何避免使用 getchar() 按 Enter 键仅读取单个字符?
【发布时间】:2010-12-20 09:20:14
【问题描述】:

在下一个代码中:

#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

我该怎么做?

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

【问题讨论】:

标签: c input character getchar unbuffered


【解决方案1】:

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

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

【讨论】:

  • 但是,输出是问题的一部分。提问者希望程序在按键被按下时读取按键并立即在屏幕上打印。这既是输入问题,也是输出问题。
  • 即fflush 只刷新输出,但 getchar 在按键后仍然不会返回(按键将被缓冲)。
【解决方案2】:

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

【讨论】:

  • +1 正确,getchar() 只有在您按下回车后才会开始一个一个地读取字符
  • getchar() 不关心 ENTER,它只处理来自标准输入的任何内容。行缓冲往往是操作系统/终端定义的行为。
  • @goldPseudo:是的,但几乎所有平台都会缓冲输入,因此这是您在大多数实际情况下会看到的行为。在支持的情况下,getch() 将管理或忽略缓冲。我知道一些旧的 DOS 实现绕过操作系统缓冲直接与硬件交互。其他实现可能会刷新标准输入以获得相同的行为。
  • 不过,“需要你按回车”的不是getchar(),而是环境。
  • 非常简洁易懂。其他评分最高的答案只是抛出了压倒性不必要的技术细节。
【解决方案3】:

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

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

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

【讨论】:

    【解决方案4】:

    这取决于您的操作系统,如果您处于类似 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); 
    

    【讨论】:

    • +1 虽然 tbh,但这可能不是原始发布者所熟悉的代码级别 ;)
    • 太棒了!这就是我要找的!
    • 感谢您的帮助以及详细的 cmets,它解决了我的问题。
    • 令人难以置信的是,如此基本的东西如此难以实现且不可移植。
    【解决方案5】:

    由于您正在使用 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 上)。

    【讨论】:

      【解决方案6】:

      在 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 是个坏主意。
      • Too much raw .... 我正在寻找一种无需等待 ENTER 的解决方案。此解决方案会中断所有输入终端功能。
      【解决方案7】:

      您可以包含“ncurses”库,并使用getch() 而不是getchar()

      【讨论】:

        【解决方案8】:

        是的,您也可以在 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;
        }
        

        【讨论】:

        • 问题标记为c,而不是c++
        • @CoolGuy 那又怎样?如果有人打开完全相同的问题,但将标签从 c 更改为 c++,为什么它会对任何人有所帮助?
        • @AndreasHaferburg 因为我们这里没有 C++ 答案,尤其是写得不好的答案,wink wink
        【解决方案9】:

        我在当前正在处理的任务中遇到了这个问题/问题。 它还取决于您从哪个输入中获取。 我正在使用

        /dev/tty
        

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

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

        system( "stty -raw" );
        

        system( "stty -icanon" );
        

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

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

        现在一切都好起来了。

        【讨论】:

        • 请注意,此解决方案取决于操作系统。
        【解决方案10】:

        我喜欢卢卡斯的回答,但我想详细说明一下。 termios.h 中有一个名为 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()POSIX termios.h 的非可移植、非标准扩展,不一定可用。
        【解决方案11】:

        这段代码对我有用。注意:这不是标准库的一部分,即使大多数编译器(我使用 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;
        }
        

        此代码检测第一次按键。

        【讨论】:

          【解决方案12】:

          如何避免使用getchar()Enter

          首先,终端输入通常是行或完全缓冲的。这意味着操作系统将来自终端的实际输入存储到缓冲区中。通常,当 f.e. \nstdin 中被通知/提供。这是 f.e.按下 Enter

          getchar() 就在链的末尾。它无法实际影响缓冲过程。


          我该怎么做?

          首先放弃getchar(),如果您不想使用特定的系统调用来明确更改终端的行为,就像其他答案中很好解释的那样。

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


          在 Windows/MS-DOS 中,conio.h 头文件中有 getch()getche() 函数,它们完全符合您的要求 - 读取单个字符而无需等待换行符刷新缓冲区。

          getch()getche() 的主要区别在于getch() 不会立即在控制台中输出实际输入的字符,而getche() 会。额外的"e" 代表echo

          例子:

          #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() 选项以及 ncurses 库中的 getch()refresh() 例程。

          注意,您需要使用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。

          【讨论】:

            【解决方案13】:

            可以创建一个检查 Enter 的新函数:

            #include <stdio.h>
            
            char getChar()
            {
                printf("Please enter a char:\n");
            
                char c = getchar();
                if (c == '\n')
                {
                    c = getchar();
                }
            
                return c;
            }
            
            int main(int argc, char *argv[])
            {
                char ch;
                while ((ch = getChar()) != '.')
                {
                    printf("Your char: %c\n", ch);
                }
            
                return 0;
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2019-09-14
              • 1970-01-01
              • 1970-01-01
              • 2017-01-08
              • 2015-11-06
              相关资源
              最近更新 更多