Capture caracteres da entrada padrão sem esperar que a tecla Enter seja pressionada


174

Nunca me lembro de como faço isso, porque isso aparece com pouca frequência para mim. Mas em C ou C ++, qual é a melhor maneira de ler um caractere da entrada padrão sem esperar por uma nova linha (pressione enter).

Idealmente, também não ecoaria o caractere de entrada na tela. Eu só quero capturar pressionamentos de tecla sem afetar a tela do console.


1
@adam - Você pode esclarecer: deseja uma função que retorne imediatamente se nenhum caractere estiver disponível, ou que sempre espere por um único pressionamento de tecla?
Roddy

2
@ Roddy - Eu quero uma função que sempre espera por um único pressionamento de tecla.
284 Adam

Respostas:


99

Isso não é possível de maneira portátil em C ++ puro, porque depende muito do terminal usado que pode ser conectado stdin(eles geralmente são armazenados em buffer de linha). No entanto, você pode usar uma biblioteca para isso:

  1. disponível com compiladores do Windows. Use a _getch()função para fornecer um caractere sem aguardar a tecla Enter. Não sou desenvolvedor de Windows frequente, mas já vi meus colegas de classe incluí <conio.h>-lo e usá-lo. Veja conio.hna Wikipedia. Ele lista getch(), que é declarado obsoleto no Visual C ++.

  2. maldições disponíveis para Linux. Implementações de maldições compatíveis também estão disponíveis para Windows. Também tem uma getch()função. (tente man getchvisualizar sua página de manual). Veja Maldições na Wikipedia.

Eu recomendaria que você usasse maldições se pretender compatibilidade entre plataformas. Dito isso, tenho certeza de que existem funções que você pode usar para desativar o buffer de linha (acredito que isso seja chamado de "modo bruto", em oposição ao "modo cozido" - veja man stty). Maldições lidariam com isso de maneira portátil, se não me engano.


7
Observe que hoje em dia, ncursesé a variante recomendada de curses.
Fund Monica's Lawsuit

4
se você precisa de uma biblioteca, como eles conseguiram? para criar a biblioteca, você certamente teve que programá-lo e isso significa que alguém poderia programá-lo. Por que não podemos simplesmente programar o código da biblioteca de que precisamos? que também faria o que não é, possivelmente, portably em puro c ++ errado
OverloadedCore

@ kid8 eu não entendo
Johannes Schaub - litb

1
@ JohannesSchaub-litb, ele provavelmente está tentando dizer como o implementador da biblioteca criou esse método portátil em c / c ++ puro quando você diz que não é possível.
Abhinav Gauniyal

@AbhinavGauniyal ah, entendo. No entanto, eu não disse que o implementador da biblioteca não criou métodos portáteis (isto é, para serem usados ​​por programas razoavelmente portáteis). Eu impliquei que eles não usaram C ++ portátil. Como claramente eles não entenderam, não entendo por que o comentário dele diz que essa parte da minha resposta está errada.
Johannes Schaub - litb 18/01

81

No Linux (e em outros sistemas unix), isso pode ser feito da seguinte maneira:

#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);
}

Basicamente, você precisa desativar o modo canônico (e o modo de eco para suprimir o eco).


Tentei implementar esse código, mas recebi um erro na chamada para read. Eu incluí os dois cabeçalhos.
Michael Dorst

6
Possivelmente porque esse código está errado, pois read () é uma chamada de sistema POSIX definida em unistd.h. stdio.h pode incluí-lo por coincidência, mas você realmente não precisa de stdio.h para esse código; substitua-o por unistd.he deve ser bom.
Falcon Momot

1
Não sei como acabo aqui enquanto procurava obter entrada do teclado no terminal ROS no Raspberry Pi. Este trecho de código funciona para mim.
aknay

2
@FalconMomot No meu NetBeans IDE 8.1 (no Kali Linux), ele diz: error: ‘perror’ was not declared in this scopequando compila, mas funciona bem quando incluído stdio.hjunto unistd.h.
Abhishek Kashyap

3
Existe alguma maneira fácil de estender isso para não bloquear?
21817 Joe Strout

18

Encontrei isso em outro fórum enquanto procurava resolver o mesmo problema. Eu o modifiquei um pouco do que encontrei. Isso funciona muito bem. Estou executando o OS X, portanto, se você estiver executando o Microsoft, precisará encontrar o comando system () correto para alternar para os modos cru e cozido.

#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
Enquanto isso funciona, pelo que vale a pena, descascar com o sistema raramente é a "melhor" maneira de fazer isso na minha opinião. O programa stty é escrito em C, então você pode incluir <termios.h> ou <sgtty.h> e chamar o mesmo código que o stty usa, sem depender de um programa externo / fork / outros enfeites.
31720 Chris Lutz

1
Eu precisava disso para provas aleatórias de coisas conceituais e para brincar. Apenas o que eu precisava. Obrigado. Deve-se notar: eu definitivamente colocaria o stty cozido no final do programa, caso contrário, seu shell permanecerá no modo bruto stty, que basicamente quebrou meu shell lol depois que o programa parou.
Benjamin

Eu acho que você esqueceu#include <stdlib.h>
NerdOfCode

14

CONIO.H

as funções que você precisa são:

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

ou

Implementação do Linux c ++ de conio.h http://sourceforge.net/projects/linux-conioh


9

Se você estiver no Windows, poderá usar o PeekConsoleInput para detectar se há alguma entrada,

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

use ReadConsoleInput para "consumir" o caractere de entrada.

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

para ser honesto, isso é de algum código antigo que eu tenho, então você precisa mexer um pouco nele.

O legal é que ele lê as entradas sem solicitar nada, para que os caracteres não sejam exibidos.


9
#include <conio.h>

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

Isso serve kbhit()para verificar se o teclado está sendo pressionado e getch()para obter o caractere que está sendo pressionado.


5
conio.h ? "conio.h é um arquivo de cabeçalho C usado em compiladores antigos do MS-DOS para criar interfaces de usuário de texto." Parece um pouco desatualizado.
Kay - SE is evil


5

C e C ++ adotam uma visão muito abstrata da E / S e não há uma maneira padrão de fazer o que você deseja. Existem maneiras padrão de obter caracteres do fluxo de entrada padrão, se houver algum, e nada mais é definido por nenhum idioma. Qualquer resposta precisará, portanto, ser específica da plataforma, talvez dependendo não apenas do sistema operacional, mas também da estrutura do software.

Há algumas suposições razoáveis ​​aqui, mas não há como responder à sua pergunta sem saber qual é o seu ambiente de destino.


5

Eu uso kbhit () para ver se um char está presente e, em seguida, getchar () para ler os dados. No Windows, você pode usar "conio.h". No linux, você terá que implementar seu próprio kbhit ().

Veja o código abaixo:

// 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;
}

4

O mais próximo do portátil é usar a ncursesbiblioteca para colocar o terminal no "modo de interrupção". A API é gigantesca; as rotinas que você mais deseja são

  • initscr e endwin
  • cbreak e nocbreak
  • getch

Boa sorte!


3

A seguir, uma solução extraída da programação Expert C: Deep Secrets , que deve funcionar no SVr4. Ele usa stty e ioctl .

#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

Eu sempre quis um loop para ler minha entrada sem pressionar a tecla Enter. isso funcionou para mim.

#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
Uma vez que você esteja no modo RAW, é difícil interromper o processo. então mantenha um ponto para matar o processo como eu fiz "ao pressionar" ~ "". ................ caso contrário, você pode interromper o processo de outro terminal usando KILL.
Setu Gupta

3

O ncurses fornece uma boa maneira de fazer isso! Além disso, este é o meu primeiro post (que me lembro), então quaisquer comentários são bem-vindos. Eu aprecio os úteis, mas todos são bem-vindos!

compilar: g ++ -std = c ++ 11 -pthread -lncurses .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;
}

2

funciona para mim no windows:

#include <conio.h>
char c = _getch();

1

Você pode fazer isso de maneira portável usando SDL (a Simple DirectMedia Library), embora eu suspeite que você não goste do comportamento dele. Quando o experimentei, tive que fazer com que o SDL criasse uma nova janela de vídeo (mesmo que não precisasse dela para o meu programa) e ter essa janela "agarrada" quase todas as entradas de teclado e mouse (o que foi bom para o meu uso, mas poderia irritante ou impraticável em outras situações). Eu suspeito que é um exagero e não vale a pena, a menos que a portabilidade completa seja uma obrigação - caso contrário, tente uma das outras soluções sugeridas.

A propósito, isso fornecerá pressionamentos de tecla e liberará eventos separadamente, se você gostar.


1

Aqui está uma versão que não é concedida ao sistema (escrita e testada no 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 );
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.