Przechwytuj znaki ze standardowego wejścia bez czekania na naciśnięcie klawisza enter


174

Nigdy nie pamiętam, jak to robię, ponieważ tak rzadko mi się to zdarza. Ale w C lub C ++, jaki jest najlepszy sposób na odczytanie znaku ze standardowego wejścia bez czekania na znak nowej linii (naciśnij enter).

Idealnie byłoby również, gdyby nie powodował echa wprowadzanego znaku na ekranie. Chcę tylko przechwytywać naciśnięcia klawiszy bez wpływu na ekran konsoli.


1
@adam - Czy możesz wyjaśnić: Czy chcesz, aby funkcja powróciła natychmiast, jeśli żaden znak nie jest dostępna, czy też taka, która zawsze będzie czekać na jedno naciśnięcie klawisza?
Roddy,

2
@Roddy - Chcę funkcji, która zawsze będzie czekała na pojedyncze naciśnięcie klawisza.
Adam

Odpowiedzi:


99

Nie jest to możliwe w sposób przenośny w czystym C ++, ponieważ zbytnio zależy to od używanego terminala, z którym może być połączony stdin(są one zwykle buforowane liniowo). Możesz jednak użyć do tego biblioteki:

  1. conio dostępny z kompilatorami Windows. Użyj _getch()funkcji, aby nadać znak bez czekania na klawisz Enter. Nie jestem częstym programistą systemu Windows, ale widziałem, jak moi koledzy z klasy po prostu go dołączają <conio.h>i używają. Zobacz conio.hw Wikipedii. Zawiera listę getch(), która jest uznana za przestarzałą w programie Visual C ++.

  2. curses dostępne dla systemu Linux. Zgodne implementacje curses są również dostępne dla systemu Windows. Pełni też getch()funkcję. (spróbuj man getchwyświetlić jego stronę podręcznika). Zobacz Curses w Wikipedii.

Zalecałbym użycie curses, jeśli dążysz do kompatybilności między platformami. To powiedziawszy, jestem pewien, że istnieją funkcje, których można użyć do wyłączenia buforowania w trybie offline (uważam, że nazywa się to „trybem surowym”, w przeciwieństwie do „trybu gotowanego” - spójrz na man stty). Przekleństwa załatwiłyby to dla ciebie w przenośny sposób, jeśli się nie mylę.


7
Zauważ, że obecnie ncursesjest zalecanym wariantem curses.
Załóż pozew Moniki

4
jeśli potrzebujesz biblioteki, jak oni to zrobili? aby stworzyć bibliotekę, z pewnością musiałeś ją zaprogramować, a to oznacza, że ​​każdy mógł ją zaprogramować, więc dlaczego nie możemy po prostu zaprogramować kodu biblioteki, którego sami potrzebujemy? to również spowodowałoby, że to nie jest prawdopodobnie przenośne w czystym języku c ++ było złe
OverloadedCore

@ kid8 Nie rozumiem
Johannes Schaub - litb

1
@ JohannesSchaub-litb prawdopodobnie próbuje powiedzieć, w jaki sposób osoba wdrażająca bibliotekę stworzyła tę przenośną metodę w czystym c / c ++, skoro mówisz, że nie jest to możliwe.
Abhinav Gauniyal

@AbhinavGauniyal ah, rozumiem. Jednak nie powiedziałem, że implementujący bibliotekę nie stworzył metod przenośnych (tj. Do użycia przez rozsądnie przenośne programy). Zasugerowałem, że nie używali przenośnego C ++. Ponieważ najwyraźniej nie, nie rozumiem, dlaczego jego komentarz mówi, że ta część mojej odpowiedzi jest błędna.
Johannes Schaub - litb

81

W Linuksie (i innych systemach typu unix) można to zrobić w następujący sposób:

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

Zasadniczo musisz wyłączyć tryb kanoniczny (i tryb echa, aby stłumić echo).


Próbowałem zaimplementować ten kod, ale podczas połączenia z numerem wystąpił błąd read . Zawarłem oba nagłówki.
Michael Dorst,

6
Prawdopodobnie dlatego, że ten kod jest nieprawidłowy, ponieważ read () jest wywołaniem systemowym POSIX zdefiniowanym w unistd.h. stdio.h może zawierać to przez przypadek, ale w rzeczywistości nie potrzebujesz stdio.h dla tego kodu; zamień go na unistd.h i powinien być dobry.
Falcon Momot

1
Nie wiem, jak się tutaj skończyłem, gdy szukałem wejścia z klawiatury na terminalu ROS w Raspberry Pi. Ten fragment kodu działa dla mnie.
aknay

2
@FalconMomot W moim NetBeans IDE 8.1 (w Kali Linux) jest napisane: error: ‘perror’ was not declared in this scopepodczas kompilacji, ale działa dobrze, gdy jest dołączony stdio.hwraz z unistd.h.
Abhishek Kashyap

3
Czy jest jakiś łatwy sposób na przedłużenie tego, aby nie blokować?
Joe Strout

18

Znalazłem to na innym forum, szukając rozwiązania tego samego problemu. Trochę go zmodyfikowałem z tego, co znalazłem. Działa świetnie. Używam OS X, więc jeśli używasz Microsoft, musisz znaleźć prawidłowe polecenie system (), aby przełączyć się na tryb surowy i gotowany.

#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
Chociaż to działa, bez względu na to, co jest warte, ostrzeliwanie systemu rzadko jest moim zdaniem „najlepszym” sposobem na zrobienie tego. Program stty jest napisany w C, więc możesz dołączyć <termios.h> lub <sgtty.h> i wywołać ten sam kod, którego używa stty, bez uzależnienia od zewnętrznego programu / fork / whatnot.
Chris Lutz

1
Potrzebowałem tego do losowego sprawdzania koncepcji i grzebania w nim. Dokładnie to, czego potrzebowałem. Dziękuję Ci. Należy zauważyć: zdecydowanie umieściłbym stty gotowane na końcu programu, w przeciwnym razie twoja powłoka pozostanie w trybie surowym stty, co w zasadzie zepsuło moją powłokę lol po zatrzymaniu programu.
Benjamin

Myślę, że zapomniałeś#include <stdlib.h>
NerdOfCode

14

CONIO.H

potrzebne funkcje to:

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

lub

Implementacja conio.h w Linux C ++ http://sourceforge.net/projects/linux-conioh


9

Jeśli korzystasz z systemu Windows, możesz użyć PeekConsoleInput, aby wykryć, czy jest jakieś wejście,

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

następnie użyj ReadConsoleInput, aby „wykorzystać” znak wejściowy.

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

szczerze mówiąc, pochodzi to ze starego kodu, który mam, więc musisz trochę przy nim bawić.

Fajne jest jednak to, że czyta dane wejściowe bez pytania o nic, więc znaki nie są w ogóle wyświetlane.


9
#include <conio.h>

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

Służy kbhit()do sprawdzania, czy klawiatura jest naciskana i getch()do uzyskania naciśniętego znaku.


5
conio.h ? „conio.h to plik nagłówkowy C używany w starych kompilatorach MS-DOS do tworzenia tekstowych interfejsów użytkownika”. Wydaje się nieco przestarzały.
kay - SE is evil


5

C i C ++ mają bardzo abstrakcyjny pogląd na operacje we / wy i nie ma standardowego sposobu robienia tego, co chcesz. Istnieją standardowe sposoby pobierania znaków ze standardowego strumienia wejściowego, jeśli są jakieś do pobrania, i nic innego nie jest zdefiniowane przez żaden z języków. Dlatego każda odpowiedź będzie musiała być specyficzna dla platformy, być może zależna nie tylko od systemu operacyjnego, ale także od struktury oprogramowania.

Są tutaj rozsądne domysły, ale nie ma sposobu, aby odpowiedzieć na twoje pytanie, nie wiedząc, jakie jest twoje docelowe środowisko.


5

Używam kbhit (), aby sprawdzić, czy znak jest obecny, a następnie getchar (), aby odczytać dane. W systemie Windows możesz użyć „conio.h”. W Linuksie będziesz musiał zaimplementować własną kbhit ().

Zobacz kod poniżej:

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

Najbliższą rzeczą do przenośnych jest użycie ncursesbiblioteki do wprowadzenia terminala w „tryb cbreak”. API jest gigantyczne; najbardziej pożądane są procedury

  • initscr i endwin
  • cbreak i nocbreak
  • getch

Powodzenia!


3

Poniżej znajduje się rozwiązanie wyodrębnione z Expert C Programming: Deep Secrets , które ma działać na SVr4. Używa stty i 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

Zawsze chciałem, aby pętla odczytywała moje dane wejściowe bez naciskania klawisza Return. to działało dla mnie.

#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
gdy będziesz w TRYBIE RAW, trudno jest zabić proces. więc zachowaj punkt, aby zabić proces, tak jak zrobiłem to "po naciśnięciu" ~ "". ................ inaczej możesz zabić proces z innego terminala używając KILL.
Setu Gupta

3

ncurses zapewnia dobry sposób na zrobienie tego! Jest to również mój pierwszy post (który pamiętam), więc wszelkie komentarze są mile widziane. Docenię przydatne, ale zapraszamy wszystkich!

do kompilacji: 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

Możesz to zrobić przenośnie za pomocą SDL (Simple DirectMedia Library), chociaż podejrzewam, że możesz nie lubić jego zachowania. Kiedy próbowałem, musiałem poprosić SDL o utworzenie nowego okna wideo (mimo że nie potrzebowałem go w moim programie) i sprawić, by to okno „przechwytywało” prawie wszystkie dane z klawiatury i myszy (co było w porządku w moim przypadku, ale być irytujące lub niewykonalne w innych sytuacjach). Podejrzewam, że to przesada i nie warto, chyba że całkowita przenośność jest koniecznością - w przeciwnym razie wypróbuj jedno z innych sugerowanych rozwiązań.

Nawiasem mówiąc, to da ci osobne naciśnięcie i zwolnienie klawiszy, jeśli jesteś w tym.


1

Oto wersja, która nie trafia do systemu (napisana i przetestowana na 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 );
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.