从标准输入中捕获字符,而无需等待按下回车键


174

我永远不记得我是怎么做的,因为它对我来说很少出现。但是在C或C ++中,什么是从标准输入中读取字符而不等待换行符(按Enter键)的最佳方法。

理想情况下,它也不会将输入字符回显到屏幕上。我只想捕获击键而不影响控制台屏幕。


1
@adam-您能否阐明:您想要一个在没有可用字符的情况下会立即返回的函数,还是总是等待一次击键的函数?
罗迪

2
@Roddy-我想要一个始终等待单个按键的函数。
亚当

Answers:


99

在纯C ++中这不可能以可移植的方式实现,因为它在很大程度上取决于所使用的连接终端stdin(它们通常是行缓冲的)。但是,您可以为此使用库:

  1. Windows编译器可用的conio。使用该_getch()功能可以为您提供一个字符,而无需等待Enter键。我不是Windows的常客,但是我看到我的同学只是在<conio.h>使用和使用它。参见conio.hWikipedia。它列出getch(),在Visual C ++中声明为已弃用。

  2. 适用于Linux的curses。兼容的curses实现也可用于Windows。它还具有getch()功能。(尝试man getch查看其联机帮助页)。参见维基百科的诅咒

如果您希望跨平台兼容,我建议您使用curses。就是说,我确定您可以使用一些功能来关闭行缓冲(我认为这称为“原始模式”,而不是“熟模式”-查看man stty)。如果我没记错的话,Curses会以一种可移植的方式为您解决这个问题。


7
请注意,现在ncurses是的推荐变体curses
Fund Monica的诉讼

4
如果您确实需要一个图书馆,那么他们是怎么做到的?要确定您必须对该库进行编程,那意味着任何人都可以对其进行编程,那么为什么我们不能对我们自己需要的库代码进行编程呢?这也将让这不可能可移植性纯C ++错误
OverloadedCore

@ kid8我听不懂
Johannes Schaub-litb

1
@ JohannesSchaub-litb,他可能试图说出当您说不可能的时候,库实现者是如何在纯c / c ++中制作出该可移植方法的。
Abhinav Gauniyal

@AbhinavGauniyal啊,我明白了。但是,我没有说过库实现者尚未开发出可移植的方法(即由合理的可移植程序使用)。我暗示他们没有使用可移植的C ++。显然他们没有,我不明白为什么他的评论说我回答的这一部分是错误的。
Johannes Schaub-litb

81

在Linux(和其他类似Unix的系统)上,可以通过以下方式完成此操作:

#include <unistd.h>
#include <termios.h>

char getch() {
        char buf = 0;
        struct termios old = {0};
        if (tcgetattr(0, &old) < 0)
                perror("tcsetattr()");
        old.c_lflag &= ~ICANON;
        old.c_lflag &= ~ECHO;
        old.c_cc[VMIN] = 1;
        old.c_cc[VTIME] = 0;
        if (tcsetattr(0, TCSANOW, &old) < 0)
                perror("tcsetattr ICANON");
        if (read(0, &buf, 1) < 0)
                perror ("read()");
        old.c_lflag |= ICANON;
        old.c_lflag |= ECHO;
        if (tcsetattr(0, TCSADRAIN, &old) < 0)
                perror ("tcsetattr ~ICANON");
        return (buf);
}

基本上,您必须关闭规范模式(和回声模式以抑制回声)。


我尝试实现此代码,但是在调用时出现错误read。我已经包括两个标题。
迈克尔·多斯特

6
可能是因为此代码错误,因为read()是unistd.h中定义的POSIX系统调用。stdio.h可能偶然包含了它,但是您实际上根本不需要stdio.h来编写此代码;用unistd.h替换它,应该很好。
Falcon Momot 2012年

1
当我想在Raspberry Pi的ROS终端上获取键盘输入时,我不知道如何结束。此代码段对我有用。
aknay 2015年

2
@FalconMomot在我的NetBeans IDE 8.1(在Kali Linux中)中说:error: ‘perror’ was not declared in this scope编译时,但stdio.h与一起使用时效果很好unistd.h
阿卜舍克·卡什雅普

3
有什么简单的方法可以将其扩展为不阻塞?
乔·斯特鲁

18

我在寻求解决相同问题的同时在另一个论坛上找到了这个。我已经对发现的内容进行了一些修改。效果很好。我正在运行OS X,因此,如果您正在运行Microsoft,则需要找到正确的system()命令才能切换到原始和熟化模式。

#include <iostream> 
#include <stdio.h>  
using namespace std;  

int main() { 
  // Output prompt 
  cout << "Press any key to continue..." << endl; 

  // Set terminal to raw mode 
  system("stty raw"); 

  // Wait for single character 
  char input = getchar(); 

  // Echo input:
  cout << "--" << input << "--";

  // Reset terminal to normal "cooked" mode 
  system("stty cooked"); 

  // And we're out of here 
  return 0; 
}

13
尽管这样做可行,但就其价值而言,炮轰系统并不是我认为的“最佳”方法。stty程序是用C编写的,因此您可以包含<termios.h>或<sgtty.h>并调用stty使用的相同代码,而无需依赖于外部程序/ fork / whatnot。
克里斯·卢兹

1
我需要用它来随机证明概念方面的东西和混乱。正是我所需要的。谢谢。应该注意的是:我一定会在程序结束时放置好stty,否则您的shell将保持stty raw模式,这在程序停止后基本上破坏了我的shell大声笑。
本杰明

我认为您忘记了#include <stdlib.h>
NerdOfCode

14

康耐

您需要的功能是:

int getch();
Prototype
    int _getch(void); 
Description
    _getch obtains a character  from stdin. Input is unbuffered, and this
    routine  will  return as  soon as  a character is  available  without 
    waiting for a carriage return. The character is not echoed to stdout.
    _getch bypasses the normal buffering done by getchar and getc. ungetc 
    cannot be used with _getch. 
Synonym
    Function: getch 


int kbhit();
Description
    Checks if a keyboard key has been pressed but not yet read. 
Return Value
    Returns a non-zero value if a key was pressed. Otherwise, returns 0.

libconio http://sourceforge.net/projects/libconio

要么

conio.h的Linux c ++实现 http://sourceforge.net/projects/linux-conioh


9

如果您在Windows上,则可以使用PeekConsoleInput来检测是否有任何输入,

HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
DWORD events;
INPUT_RECORD buffer;
PeekConsoleInput( handle, &buffer, 1, &events );

然后使用ReadConsoleInput来“消费”输入字符..

PeekConsoleInput(handle, &buffer, 1, &events);
if(events > 0)
{
    ReadConsoleInput(handle, &buffer, 1, &events);  
    return buffer.Event.KeyEvent.wVirtualKeyCode;
}
else return 0

老实说,这是从我的一些旧代码中摘录的,因此您必须对其进行一些调整。

不过,很酷的一点是,它读取输入时不会提示任何内容,因此根本不会显示字符。


9
#include <conio.h>

if (kbhit() != 0) {
    cout << getch() << endl;
}

这用于kbhit()检查是否按下了键盘,并用于getch()获取正在按下的字符。


5
conio.h?“ conio.h是旧的MS-DOS编译器中用于创建文本用户界面的C头文件。” 似乎有些过时了。
凯-SE很邪恶


5

C和C ++对I / O采取了非常抽象的观点,并且没有标准的方式来执行所需的操作。存在从标准输入流中获取字符的标准方法(如果有),并且任何一种语言都没有定义其他任何内容。因此,任何答案都必须是特定于平台的,可能不仅取决于操作系统,还取决于软件框架。

这里有一些合理的猜测,但是如果不知道目标环境是什么,就无法回答您的问题。


5

我使用kbhit()查看是否存在字符,然后使用getchar()读取数据。在Windows上,您可以使用“ conio.h”。在Linux上,您将必须实现自己的kbhit()。

参见下面的代码:

// kbhit
#include <stdio.h>
#include <sys/ioctl.h> // For FIONREAD
#include <termios.h>
#include <stdbool.h>

int kbhit(void) {
    static bool initflag = false;
    static const int STDIN = 0;

    if (!initflag) {
        // Use termios to turn off line buffering
        struct termios term;
        tcgetattr(STDIN, &term);
        term.c_lflag &= ~ICANON;
        tcsetattr(STDIN, TCSANOW, &term);
        setbuf(stdin, NULL);
        initflag = true;
    }

    int nbbytes;
    ioctl(STDIN, FIONREAD, &nbbytes);  // 0 is STDIN
    return nbbytes;
}

// main
#include <unistd.h>

int main(int argc, char** argv) {
    char c;
    //setbuf(stdout, NULL); // Optional: No buffering.
    //setbuf(stdin, NULL);  // Optional: No buffering.
    printf("Press key");
    while (!kbhit()) {
        printf(".");
        fflush(stdout);
        sleep(1);
    }
    c = getchar();
    printf("\nChar received:%c\n", c);
    printf("Done.\n");

    return 0;
}


3

以下是从Expert C编程中提取的解决方案:Deep Secrets,应该在SVr4上使用。它使用sttyioctl

#include <sys/filio.h>
int kbhit()
{
 int i;
 ioctl(0, FIONREAD, &i);
 return i; /* return a count of chars available to read */
}
main()
{
 int i = 0;
 intc='';
 system("stty raw -echo");
 printf("enter 'q' to quit \n");
 for (;c!='q';i++) {
    if (kbhit()) {
        c=getchar();
       printf("\n got %c, on iteration %d",c, i);
    }
}
 system("stty cooked echo");
}

3

我一直希望有一个循环来读取我的输入而无需按回车键。这对我有用。

#include<stdio.h>
 main()
 {
   char ch;
    system("stty raw");//seting the terminal in raw mode
    while(1)
     {
     ch=getchar();
      if(ch=='~'){          //terminate or come out of raw mode on "~" pressed
      system("stty cooked");
     //while(1);//you may still run the code 
     exit(0); //or terminate
     }
       printf("you pressed %c\n ",ch);  //write rest code here
      }

    }

1
一旦进入RAW模式,就很难终止该过程。因此,要像我“按”〜”“时那样终止进程。.......否则,您可以使用KILL从其他终端终止该进程。
Setu Gupta 2015年

3

ncurses提供了一种不错的方法!这也是我的第一篇文章(我记得),因此欢迎提出任何意见。我会感激有用的,但欢迎大家!

编译:g ++ -std = c ++ 11 -pthread -Incurses .cpp -o

#include <iostream>
#include <ncurses.h>
#include <future>

char get_keyboard_input();

int main(int argc, char *argv[])
{
    initscr();
    raw();
    noecho();
    keypad(stdscr,true);

    auto f = std::async(std::launch::async, get_keyboard_input);
    while (f.wait_for(std::chrono::milliseconds(20)) != std::future_status::ready)
    {
        // do some work
    }

    endwin();
    std::cout << "returned: " << f.get() << std::endl;
    return 0;
}

char get_keyboard_input()
{
    char input = '0';
    while(input != 'q')
    {
        input = getch();
    }
    return input;
}


1

您可以使用SDL(简单DirectMedia库)来进行移植,尽管我怀疑您可能不喜欢它的行为。当我尝试它时,我必须让SDL创建一个新的视频窗口(即使我的程序不需要它),并且让该窗口“抓住”几乎所有的键盘和鼠标输入(可以使用,但可以在其他情况下令人讨厌或无法工作)。我怀疑这是过大的,并且不值得,除非必须具有完全的可移植性,否则请尝试其他建议的解决方案之一。

顺便说一句,如果您愿意的话,这将分别为您提供按键和释放事件。


1

这是一个不包含在系统中的版本(在macOS 10.14上编写和测试)

#include <unistd.h>
#include <termios.h>
#include <stdio.h>
#include <string.h>

char* getStr( char* buffer , int maxRead ) {
  int  numRead  = 0;
  char ch;

  struct termios old = {0};
  struct termios new = {0};
  if( tcgetattr( 0 , &old ) < 0 )        perror( "tcgetattr() old settings" );
  if( tcgetattr( 0 , &new ) < 0 )        perror( "tcgetaart() new settings" );
  cfmakeraw( &new );
  if( tcsetattr( 0 , TCSADRAIN , &new ) < 0 ) perror( "tcssetattr makeraw new" );

  for( int i = 0 ; i < maxRead ; i++)  {
    ch = getchar();
    switch( ch )  {
      case EOF: 
      case '\n':
      case '\r':
        goto exit_getStr;
        break;

      default:
        printf( "%1c" , ch );
        buffer[ numRead++ ] = ch;
        if( numRead >= maxRead )  {
          goto exit_getStr;
        }
        break;
    }
  }

exit_getStr:
  if( tcsetattr( 0 , TCSADRAIN , &old) < 0)   perror ("tcsetattr reset to old" );
  printf( "\n" );   
  return buffer;
}


int main( void ) 
{
  const int maxChars = 20;
  char      stringBuffer[ maxChars+1 ];
  memset(   stringBuffer , 0 , maxChars+1 ); // initialize to 0

  printf( "enter a string: ");
  getStr( stringBuffer , maxChars );
  printf( "you entered: [%s]\n" , stringBuffer );
}
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.