Capture caracteres de la entrada estándar sin esperar a que se presione enter


174

Nunca puedo recordar cómo hago esto porque me sale con poca frecuencia. Pero en C o C ++, cuál es la mejor manera de leer un carácter desde la entrada estándar sin esperar una nueva línea (presione Intro).

Además, idealmente no reflejaría el carácter de entrada en la pantalla. Solo quiero capturar las pulsaciones del teclado sin afectar la pantalla de la consola.


1
@adam - ¿Puede aclarar: desea una función que regrese inmediatamente si no hay caracteres disponibles, o una que siempre esperará una sola pulsación de tecla?
Roddy

2
@Roddy: quiero una función que siempre espere una sola pulsación de tecla.
Adam

Respuestas:


99

Eso no es posible de manera portátil en C ++ puro, porque depende demasiado del terminal utilizado con el que pueda estar conectado stdin(generalmente están almacenados en línea). Sin embargo, puede usar una biblioteca para eso:

  1. conio disponible con compiladores de Windows. Use la _getch()función para darle un carácter sin esperar la tecla Intro. No soy un desarrollador frecuente de Windows, pero he visto a mis compañeros de clase simplemente incluirlo <conio.h>y usarlo. Ver conio.hen Wikipedia. Enumera getch(), que se declara obsoleto en Visual C ++.

  2. maldiciones disponibles para Linux. Las implementaciones de maldiciones compatibles también están disponibles para Windows. También tiene una getch()función. (intente man getchver su página de manual). Ver Maldiciones en Wikipedia.

Te recomendaría usar maldiciones si buscas compatibilidad multiplataforma. Dicho esto, estoy seguro de que hay funciones que puede usar para desactivar el almacenamiento en línea (creo que eso se llama "modo sin procesar", en lugar de "modo cocido" - ver man stty). Las maldiciones se encargarían de eso de manera portátil, si no me equivoco.


77
Tenga en cuenta que hoy en día, ncurseses la variante recomendada de curses.
Financia la demanda de Mónica

44
si necesitas una biblioteca, ¿cómo la hicieron? para crear la biblioteca, seguramente tuvo que programarla y eso significa que cualquiera podría programarla, así que ¿por qué no podemos programar el código de la biblioteca que necesitamos? eso también haría que el que no sea posiblemente portátil en puro C ++ sea incorrecto
OverloadedCore

@ kid8 no entiendo
Johannes Schaub - litb

1
@ JohannesSchaub-litb probablemente esté tratando de decir cómo hizo el implementador de la biblioteca ese método portátil en puro c / c ++ cuando dice que no es posible.
Abhinav Gauniyal

@AbhinavGauniyal ah, ya veo. Sin embargo, no he dicho que el implementador de la biblioteca no haya hecho métodos portátiles (es decir, para ser utilizados por programas razonablemente portátiles). He implicado que no han usado C ++ portátil. Como claramente no lo han hecho, no entiendo por qué su comentario dice que esta parte de mi respuesta es incorrecta.
Johannes Schaub - litb

81

En Linux (y otros sistemas similares a Unix) esto se puede hacer de la siguiente manera:

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

Básicamente, debe desactivar el modo canónico (y el modo de eco para suprimir el eco).


Intenté implementar este código, pero recibí un error en la llamada read. He incluido ambos encabezados.
Michael Dorst

66
Posiblemente porque este código es incorrecto, ya que read () es una llamada al sistema POSIX definida en unistd.h. stdio.h podría incluirlo por coincidencia, pero en realidad no necesita stdio.h para este código; reemplácelo con unistd.h y debería ser bueno.
Falcon Momot

1
No sé cómo termino aquí mientras buscaba obtener entrada de teclado en la terminal ROS en Raspberry Pi. Este fragmento de código funciona para mí.
aknay

2
@FalconMomot En mi NetBeans IDE 8.1 (en Kali Linux) dice: error: ‘perror’ was not declared in this scopecuando se compila, pero funciona bien cuando se incluye stdio.hjunto con unistd.h.
Abhishek Kashyap

3
¿Hay alguna manera fácil de extender esto para no bloquear?
Joe Strout

18

Encontré esto en otro foro mientras buscaba resolver el mismo problema. Lo modifiqué un poco de lo que encontré. Funciona muy bien Estoy ejecutando OS X, por lo que si está ejecutando Microsoft, deberá encontrar el comando system () correcto para cambiar a los modos crudo y cocido.

#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
Si bien esto funciona, por lo que vale, el bombardeo al sistema rara vez es la "mejor" forma de hacerlo en mi opinión. El programa stty está escrito en C, por lo que puede incluir <termios.h> o <sgtty.h> y llamar al mismo código que stty utiliza, sin depender de un programa externo / fork / whatnot.
Chris Lutz

1
Necesitaba esto para pruebas de concepto al azar y perder el tiempo. Justo lo que necesitaba. Gracias. Cabe señalar: definitivamente pondría el stty cocinado al final del programa; de lo contrario, su shell permanecerá en modo stty raw, que básicamente rompió mi shell lol después de que el programa se detuvo.
Benjamin

Creo que te olvidaste#include <stdlib.h>
NerdOfCode

14

CONIO.H

Las funciones que necesita son:

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

o

Implementación Linux c ++ de conio.h http://sourceforge.net/projects/linux-conioh


9

Si está en Windows, puede usar PeekConsoleInput para detectar si hay alguna entrada,

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

luego use ReadConsoleInput para "consumir" el carácter 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, esto es de algún código antiguo que tengo, así que tienes que jugar un poco con él.

Sin embargo, lo bueno es que lee la entrada sin solicitar nada, por lo que los caracteres no se muestran en absoluto.


9
#include <conio.h>

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

Esto se usa kbhit()para verificar si se está presionando el teclado y se usa getch()para obtener el carácter que se está presionando.


55
conio.h ? "conio.h es un archivo de encabezado C utilizado en compiladores antiguos de MS-DOS para crear interfaces de usuario de texto". Parece algo anticuado.
kay - SE es malo


5

C y C ++ tienen una visión muy abstracta de E / S, y no hay una forma estándar de hacer lo que desea. Hay formas estándar de obtener caracteres de la secuencia de entrada estándar, si hay alguna que obtener, y ninguno de los dos idiomas define nada más. Por lo tanto, cualquier respuesta tendrá que ser específica de la plataforma, tal vez dependiendo no solo del sistema operativo sino también del marco del software.

Aquí hay algunas conjeturas razonables, pero no hay forma de responder a su pregunta sin saber cuál es su entorno objetivo.


5

Utilizo kbhit () para ver si hay un char y luego getchar () para leer los datos. En Windows, puede usar "conio.h". En Linux, tendrá que implementar su propio kbhit ().

Ver código a continuación:

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

Lo más parecido a portable es usar la ncursesbiblioteca para poner el terminal en "modo cbreak". La API es gigantesca; las rutinas que más querrás son

  • initscr y endwin
  • cbreak y nocbreak
  • getch

¡Buena suerte!


3

La siguiente es una solución extraída de la Programación Expert C: Deep Secrets , que se supone que funciona en SVr4. Utiliza stty y 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

Siempre quise un bucle para leer mi entrada sin presionar la tecla de retorno. Esto funcionó para mí.

#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
una vez que esté en MODO SIN PROCESAR, es difícil matar el proceso. así que mantén un punto para matar el proceso como lo hice "al presionar" ~ "". ................ de lo contrario, puede matar el proceso desde otra terminal usando KILL.
Setu Gupta

3

¡ncurses proporciona una buena manera de hacer esto! Además, esta es mi primera publicación (que puedo recordar), por lo que cualquier comentario es bienvenido. Apreciaré los útiles, ¡pero todos son bienvenidos!

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


1

Puede hacerlo de forma portátil utilizando SDL (la Biblioteca Simple DirectMedia), aunque sospecho que es posible que no le guste su comportamiento. Cuando lo probé, tuve que hacer que SDL creara una nueva ventana de video (aunque no la necesitaba para mi programa) y que esta ventana "tomara" casi todas las entradas del teclado y el mouse (lo cual estaba bien para mi uso pero podría ser molesto o inviable en otras situaciones). Sospecho que es excesivo y no vale la pena a menos que sea imprescindible la portabilidad completa; de lo contrario, pruebe una de las otras soluciones sugeridas.

Por cierto, esto te dará pulsación de teclas y eventos de lanzamiento por separado, si te gusta eso.


1

Aquí hay una versión que no se aplica al sistema (escrita y probada en 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 );
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.