Erfassen Sie Zeichen von der Standardeingabe, ohne darauf zu warten, dass die Eingabetaste gedrückt wird

174

Ich kann mich nie erinnern, wie ich das mache, weil es für mich so selten vorkommt. Aber was ist in C oder C ++ der beste Weg, um ein Zeichen aus der Standardeingabe zu lesen, ohne auf eine neue Zeile zu warten (drücken Sie die Eingabetaste).

Idealerweise wird das eingegebene Zeichen auch nicht auf dem Bildschirm angezeigt. Ich möchte nur Tastenanschläge erfassen, ohne den Konsolenbildschirm zu beeinträchtigen.

Adam
quelle
1
@adam - Können Sie klarstellen: Möchten Sie eine Funktion, die sofort zurückgegeben wird, wenn kein Zeichen verfügbar ist, oder eine, die immer auf einen einzelnen Tastendruck wartet?
Roddy
2
@Roddy - Ich möchte eine Funktion, die immer auf einen einzigen Tastendruck wartet.
Adam

Antworten:

99

Dies ist in reinem C ++ nicht portabel möglich, da es zu stark vom verwendeten Terminal abhängt, mit dem eine Verbindung hergestellt werden kann stdin(diese sind normalerweise zeilengepuffert). Sie können jedoch eine Bibliothek dafür verwenden:

  1. conio mit Windows-Compilern verfügbar. Verwenden Sie die _getch()Funktion, um Ihnen ein Zeichen zu geben, ohne auf die Eingabetaste zu warten. Ich bin kein häufiger Windows-Entwickler, aber ich habe gesehen, dass meine Klassenkameraden es einfach einschließen <conio.h>und verwenden. Siehe conio.hbei Wikipedia. Es werden Listen aufgelistet getch(), die in Visual C ++ als veraltet deklariert sind.

  2. Flüche für Linux verfügbar. Kompatible Curses-Implementierungen sind auch für Windows verfügbar. Es hat auch eine getch()Funktion. (Versuchen Sie man getch, die Manpage anzuzeigen). Siehe Flüche bei Wikipedia.

Ich würde Ihnen empfehlen, Flüche zu verwenden, wenn Sie plattformübergreifende Kompatibilität anstreben. Ich bin mir jedoch sicher, dass es Funktionen gibt, mit denen Sie die Zeilenpufferung ausschalten können (ich glaube, das wird als "Rohmodus" bezeichnet, im Gegensatz zum "Kochmodus" - schauen Sie hinein man stty). Flüche würden das für Sie auf tragbare Weise erledigen, wenn ich mich nicht irre.

Johannes Schaub - litb
quelle
7
Beachten Sie, dass heutzutage ncursesdie empfohlene Variante von ist curses.
Fund Monica Klage
4
Wenn Sie eine Bibliothek brauchen, wie haben sie es dann gemacht? Um die Bibliothek zu erstellen, mussten Sie sie sicherlich programmieren, und das bedeutet, dass jeder sie programmieren kann. Warum können wir nicht einfach den Bibliothekscode programmieren, den wir selbst benötigen? das würde auch das machen, was in reinem c ++ möglicherweise nicht portabel falsch ist
OverloadedCore
@ Kid8 Ich verstehe nicht
Johannes Schaub - Litb
1
@ JohannesSchaub-litb er versucht wahrscheinlich zu sagen, wie der Bibliotheksimplementierer diese portable Methode in reinem c / c ++ erstellt hat, wenn Sie sagen, dass dies nicht möglich ist.
Abhinav Gauniyal
@AbhinavGauniyal ah, ich verstehe. Ich habe jedoch nicht gesagt, dass der Bibliotheksimplementierer keine portablen Methoden erstellt hat (dh von einigermaßen portablen Programmen verwendet werden soll). Ich habe impliziert, dass sie kein portables C ++ verwendet haben. Ich verstehe nicht, warum sein Kommentar besagt, dass dieser Teil meiner Antwort falsch ist.
Johannes Schaub - litb
81

Unter Linux (und anderen Unix-ähnlichen Systemen) kann dies folgendermaßen erfolgen:

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

Grundsätzlich müssen Sie den kanonischen Modus deaktivieren (und den Echo-Modus, um das Echo zu unterdrücken).

Falcon Momot
quelle
Ich habe versucht, diesen Code zu implementieren, aber beim Aufruf von ist ein Fehler aufgetreten read. Ich habe beide Header eingefügt.
Michael Dorst
6
Möglicherweise, weil dieser Code falsch ist, da read () ein in unistd.h definierter POSIX-Systemaufruf ist. stdio.h kann es zufällig einschließen, aber Sie benötigen stdio.h für diesen Code überhaupt nicht. Ersetzen Sie es durch unistd.h und es sollte gut sein.
Falcon Momot
1
Ich weiß nicht, wie ich hier gelandet bin, als ich nach Tastatureingaben am ROS-Terminal in Raspberry Pi gesucht habe. Dieser Codeausschnitt funktioniert für mich.
Aknay
2
@FalconMomot In meiner NetBeans IDE 8.1 (unter Kali Linux) heißt es: error: ‘perror’ was not declared in this scopebeim Kompilieren, funktioniert aber gut, wenn es stdio.hzusammen mit enthalten ist unistd.h.
Abhishek Kashyap
3
Gibt es eine einfache Möglichkeit, dies so zu erweitern, dass es nicht blockiert?
Joe Strout
18

Ich habe dies in einem anderen Forum gefunden, als ich versucht habe, das gleiche Problem zu lösen. Ich habe es ein bisschen von dem geändert, was ich gefunden habe. Es funktioniert großartig. Ich verwende OS X. Wenn Sie also Microsoft ausführen, müssen Sie den richtigen Befehl system () finden, um in den Roh- und Kochmodus zu wechseln.

#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; 
}
cwhiii
quelle
13
Während dies funktioniert, ist es meiner Meinung nach selten der "beste" Weg, dies für das zu tun, was es wert ist. Das stty-Programm ist in C geschrieben, sodass Sie <termios.h> oder <sgtty.h> einschließen und denselben Code aufrufen können, den stty verwendet, ohne von einem externen Programm / fork / whatnot abhängig zu sein.
Chris Lutz
1
Ich brauchte das für zufällige Proof-of-Concept-Sachen und zum Herumspielen. Genau das, was ich brauchte. Danke dir. Es sollte beachtet werden: Ich würde definitiv den am Ende des Programms gekochten stty setzen, sonst bleibt Ihre Shell im stty raw-Modus, was im Grunde meine Shell lol brach, nachdem das Programm gestoppt wurde.
Benjamin
Ich denke du hast vergessen#include <stdlib.h>
NerdOfCode
14

CONIO.H

Die Funktionen, die Sie benötigen, sind:

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

oder

Linux c ++ Implementierung von conio.h http://sourceforge.net/projects/linux-conioh

chrissidtmeier
quelle
9

Wenn Sie unter Windows arbeiten, können Sie PeekConsoleInput verwenden, um festzustellen, ob Eingaben vorhanden sind.

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

Verwenden Sie dann ReadConsoleInput, um das Eingabezeichen zu "verbrauchen".

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

Um ehrlich zu sein, stammt dies aus einem alten Code, den ich habe, also muss man ein bisschen damit herumspielen.

Das Coole ist jedoch, dass Eingaben gelesen werden, ohne dass Sie dazu aufgefordert werden, sodass die Zeichen überhaupt nicht angezeigt werden.

hasen
quelle
9
#include <conio.h>

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

Dies wird verwendet kbhit(), um zu überprüfen, ob die Tastatur gedrückt wird, und getch()um das Zeichen zu erhalten, das gedrückt wird.

Joseph Dykstra
quelle
5
conio.h ? "conio.h ist eine C-Header-Datei, die in alten MS-DOS-Compilern zum Erstellen von Textbenutzeroberflächen verwendet wird." Scheint etwas veraltet zu sein.
Kay - SE ist böse
5

C und C ++ sehen E / A sehr abstrakt, und es gibt keine Standardmethode, um das zu tun, was Sie wollen. Es gibt Standardmethoden, um Zeichen aus dem Standardeingabestream abzurufen, falls vorhanden, und nichts anderes wird von beiden Sprachen definiert. Jede Antwort muss daher plattformspezifisch sein, möglicherweise nicht nur vom Betriebssystem, sondern auch vom Software-Framework.

Hier gibt es einige vernünftige Vermutungen, aber es gibt keine Möglichkeit, Ihre Frage zu beantworten, ohne zu wissen, was Ihre Zielumgebung ist.

David Thornley
quelle
5

Ich benutze kbhit (), um zu sehen, ob ein Zeichen vorhanden ist, und dann getchar (), um die Daten zu lesen. Unter Windows können Sie "conio.h" verwenden. Unter Linux müssen Sie Ihr eigenes kbhit () implementieren.

Siehe Code unten:

// 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;
}
ssinfod
quelle
4

Am nächsten an Portable ist es, die ncursesBibliothek zu verwenden, um das Terminal in den "cbreak-Modus" zu versetzen. Die API ist gigantisch; Die Routinen, die Sie am meisten wollen, sind

  • initscr und endwin
  • cbreak und nocbreak
  • getch

Viel Glück!

Norman Ramsey
quelle
3

Das Folgende ist eine Lösung, die aus der Expert C-Programmierung extrahiert wurde : Deep Secrets , die auf SVr4 funktionieren soll. Es verwendet stty und 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");
}
PolyThinker
quelle
3

Ich wollte immer, dass eine Schleife meine Eingabe liest, ohne die Eingabetaste zu drücken. das hat bei mir funktioniert.

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

    }
Setu Gupta
quelle
1
Sobald Sie sich im RAW-Modus befinden, ist es schwierig, den Prozess abzubrechen. Behalte also einen Punkt, um den Prozess zu beenden, wie ich es "beim Drücken von" ~ "" getan habe. ................ Andernfalls können Sie den Prozess mit KILL von einem anderen Terminal aus beenden.
Setu Gupta
3

ncurses bietet eine gute Möglichkeit, dies zu tun! Dies ist auch mein allererster Beitrag (an den ich mich erinnern kann), daher sind Kommentare überhaupt willkommen. Ich werde nützliche schätzen, aber alle sind willkommen!

zum Kompilieren: 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;
}
AngryDane
quelle
2

funktioniert für mich unter Windows:

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

Sie können dies portabel mit SDL (der Simple DirectMedia Library) tun, obwohl ich vermute, dass Ihnen das Verhalten möglicherweise nicht gefällt. Als ich es ausprobierte, musste SDL ein neues Videofenster erstellen (obwohl ich es für mein Programm nicht benötigte) und dieses Fenster fast alle Tastatur- und Mauseingaben "greifen" (was für meine Verwendung in Ordnung war, aber konnte) in anderen Situationen ärgerlich oder nicht praktikabel sein). Ich vermute, es ist übertrieben und es lohnt sich nicht, es sei denn, vollständige Portabilität ist ein Muss - ansonsten versuchen Sie es mit einer der anderen vorgeschlagenen Lösungen.

Übrigens, dies gibt Ihnen die Möglichkeit, Ereignisse einzeln zu drücken und freizugeben, wenn Sie sich dafür interessieren.

Ruchira
quelle
1

Hier ist eine Version, die nicht an das System weitergegeben wird (geschrieben und getestet unter 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 );
}
Jeff Szuhay
quelle