Abadikan karakter dari input standar tanpa menunggu masuk untuk ditekan

174

Saya tidak pernah bisa mengingat bagaimana saya melakukan ini karena ini jarang terjadi pada saya. Tetapi dalam C atau C ++, apa cara terbaik untuk membaca karakter dari input standar tanpa menunggu baris baru (tekan enter).

Idealnya, itu tidak akan menggemakan karakter input ke layar. Saya hanya ingin menangkap penekanan tombol tanpa mempengaruhi layar konsol.

Adam
sumber
1
@adam - Bisakah Anda mengklarifikasi: Apakah Anda ingin fungsi yang akan segera kembali jika tidak ada karakter yang tersedia, atau yang selalu menunggu satu penekanan tombol?
Roddy
2
@ Raddy - Saya ingin fungsi yang akan selalu menunggu keystroke tunggal.
Adam

Jawaban:

99

Itu tidak mungkin secara portabel dalam C + + murni, karena itu tergantung terlalu banyak pada terminal yang digunakan yang dapat dihubungkan dengan stdin(mereka biasanya line buffered). Namun, Anda dapat menggunakan perpustakaan untuk itu:

  1. conio tersedia dengan kompiler Windows. Gunakan _getch()fungsi ini untuk memberi Anda karakter tanpa menunggu tombol Enter. Saya bukan pengembang Windows yang sering, tetapi saya telah melihat teman sekelas saya hanya memasukkan <conio.h>dan menggunakannya. Lihat conio.hdi Wikipedia. Ini daftar getch(), yang dinyatakan usang dalam Visual C ++.

  2. kutukan tersedia untuk Linux. Implementasi kutukan yang kompatibel juga tersedia untuk Windows. Ini juga memiliki getch()fungsi. (coba man getchlihat manualnya). Lihat Kutukan di Wikipedia.

Saya akan merekomendasikan Anda untuk menggunakan kutukan jika Anda bertujuan untuk kompatibilitas lintas platform. Yang mengatakan, saya yakin ada fungsi yang dapat Anda gunakan untuk mematikan penyangga baris (saya percaya itu disebut "mode mentah", sebagai lawan dari "mode yang dimasak" - lihat ke dalam man stty). Kutukan akan mengatasinya untuk Anda secara portabel, jika saya tidak salah.

Johannes Schaub - litb
sumber
7
Perhatikan bahwa saat ini, ncursesadalah varian yang direkomendasikan curses.
Dana Gugatan Monica
4
jika Anda membutuhkan perpustakaan lalu bagaimana mereka membuatnya? untuk membuat perpustakaan Anda pasti harus memprogramnya dan itu berarti siapa pun bisa memprogramnya jadi mengapa kita tidak bisa memprogram kode perpustakaan yang kita butuhkan sendiri? itu juga akan membuat Itu tidak mungkin portable di c ++ murni salah
OverloadedCore
@ kid8 saya tidak mengerti
Johannes Schaub - litb
1
@ JohannesSchaub-litb dia mungkin mencoba mengatakan bagaimana pelaksana perpustakaan membuat metode portabel dalam c / c ++ murni ketika Anda mengatakan itu tidak mungkin.
Abhinav Gauniyal
@AbhinavGauniyal ah, begitu. Namun saya belum mengatakan bahwa pelaksana perpustakaan belum membuat metode portabel (yaitu untuk digunakan oleh program yang cukup portabel). Saya telah menyiratkan bahwa mereka belum pernah menggunakan portable C ++. Karena jelas belum, saya tidak mengerti mengapa komentarnya mengatakan bahwa bagian dari jawaban saya salah.
Johannes Schaub - litb
81

Di Linux (dan sistem mirip-unix lainnya) ini dapat dilakukan dengan cara sebagai berikut:

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

Pada dasarnya Anda harus mematikan mode kanonik (dan mode gema untuk menekan gema).

Falcon Momot
sumber
Saya mencoba menerapkan kode ini, tetapi saya mendapat kesalahan saat menelepon ke read. Saya telah memasukkan kedua header.
Michael Dorst
6
Mungkin karena kode ini salah, karena read () adalah panggilan sistem POSIX yang didefinisikan dalam unistd.h. stdio.h mungkin memasukkannya secara kebetulan, tetapi Anda sebenarnya tidak perlu stdio.h untuk kode ini sama sekali; ganti dengan unistd.h dan itu pasti bagus.
Falcon Momot
1
Saya tidak tahu bagaimana saya bisa sampai di sini ketika saya sedang mencari input keyboard di terminal ROS di Raspberry Pi. Cuplikan kode ini berfungsi untuk saya.
aknay
2
@ FalconMomot Dalam NetBeans IDE 8.1 saya (Pada Kali Linux) dikatakan: error: ‘perror’ was not declared in this scopesaat dikompilasi, tetapi berfungsi dengan baik ketika disertakan stdio.hbersama unistd.h.
Abhishek Kashyap
3
Apakah ada cara mudah untuk memperpanjang ini untuk tidak memblokir?
Joe Strout
18

Saya menemukan ini di forum lain sambil mencari untuk menyelesaikan masalah yang sama. Saya telah memodifikasinya sedikit dari apa yang saya temukan. Ini bekerja dengan baik. Saya menjalankan OS X, jadi jika Anda menjalankan Microsoft, Anda harus menemukan perintah system () yang benar untuk beralih ke mode mentah dan matang.

#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
sumber
13
Meskipun ini berfungsi, untuk apa nilainya, keluar dari sistem jarang merupakan cara "terbaik" untuk melakukannya menurut saya. Program stty ditulis dalam C, jadi Anda dapat memasukkan <termios.h> atau <sgtty.h> dan memanggil kode yang sama yang digunakan stty, tanpa bergantung pada program eksternal / fork / yang lainnya.
Chris Lutz
1
Saya membutuhkan ini untuk bukti acak tentang hal konsep dan bermain-main. Apa yang saya butuhkan. Terima kasih. Perlu dicatat: Saya pasti akan meletakkan stty dimasak di akhir program jika tidak shell Anda akan tetap dalam mode baku stty, yang pada dasarnya memecahkan shell saya lol setelah program berhenti.
Benjamin
Saya pikir Anda lupa#include <stdlib.h>
NerdOfCode
14

CONIO.H

fungsi yang Anda butuhkan adalah:

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

atau

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

chrissidtmeier
sumber
9

Jika Anda berada di windows, Anda dapat menggunakan PeekConsoleInput untuk mendeteksi jika ada input,

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

kemudian gunakan ReadConsoleInput untuk "mengkonsumsi" karakter input ..

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

sejujurnya ini dari beberapa kode lama yang saya miliki, jadi Anda harus bermain-main sedikit dengannya.

Yang keren adalah bahwa ia membaca input tanpa meminta apa pun, sehingga karakter tidak ditampilkan sama sekali.

Hasen
sumber
9
#include <conio.h>

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

Ini digunakan kbhit()untuk memeriksa apakah keyboard sedang ditekan dan digunakan getch()untuk mendapatkan karakter yang sedang ditekan.

Joseph Dykstra
sumber
5
conio.h ? "conio.h adalah file header C yang digunakan dalam kompiler MS-DOS lama untuk membuat antarmuka pengguna teks." Sepertinya sudah ketinggalan zaman.
kay - SE itu jahat
5

C dan C ++ mengambil pandangan yang sangat abstrak tentang I / O, dan tidak ada cara standar untuk melakukan apa yang Anda inginkan. Ada cara standar untuk mendapatkan karakter dari aliran input standar, jika ada untuk mendapatkan, dan tidak ada lagi yang didefinisikan oleh kedua bahasa tersebut. Karena itu jawaban apa pun harus spesifik platform, mungkin tergantung tidak hanya pada sistem operasi tetapi juga kerangka kerja perangkat lunak.

Ada beberapa dugaan yang masuk akal di sini, tetapi tidak ada cara untuk menjawab pertanyaan Anda tanpa mengetahui apa lingkungan target Anda.

David Thornley
sumber
5

Saya menggunakan kbhit () untuk melihat apakah ada char dan kemudian getchar () untuk membaca data. Di windows, Anda dapat menggunakan "conio.h". Di linux, Anda harus mengimplementasikan kbhit () Anda sendiri.

Lihat kode di bawah ini:

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

Hal yang paling dekat dengan portable adalah menggunakan ncursesperpustakaan untuk meletakkan terminal ke "mode cbreak". API itu sangat besar; rutinitas yang paling Anda inginkan

  • initscr dan endwin
  • cbreak dan nocbreak
  • getch

Semoga berhasil!

Norman Ramsey
sumber
3

Berikut ini adalah solusi yang diekstrak dari Pemrograman C Ahli: Rahasia Dalam , yang seharusnya bekerja pada SVr4. Menggunakan stty dan 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
sumber
3

Saya selalu ingin loop membaca input saya tanpa menekan tombol kembali. ini bekerja untuk saya.

#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
sumber
1
setelah Anda berada dalam MODE RAW sulit untuk mematikan prosesnya. jadi pertahankan poin untuk menghentikan proses seperti yang saya lakukan "saat menekan" ~ "". ................ jika tidak, Anda dapat mematikan proses dari terminal lain menggunakan KILL.
Setu Gupta
3

ncurses menyediakan cara yang bagus untuk melakukan ini! Juga ini adalah posting pertama saya (yang dapat saya ingat), jadi semua komentar sama sekali diterima. Saya akan menghargai yang bermanfaat, tetapi semuanya selamat datang!

untuk mengkompilasi: 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
sumber
2

bekerja untuk saya di windows:

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

Anda dapat melakukannya dengan mudah menggunakan SDL (Simple DirectMedia Library), meskipun saya curiga Anda mungkin tidak menyukai perilakunya. Ketika saya mencobanya, saya harus membuat SDL membuat jendela video baru (walaupun saya tidak membutuhkannya untuk program saya) dan memiliki jendela ini "ambil" hampir semua input keyboard dan mouse (yang oke untuk penggunaan saya tetapi bisa menjengkelkan atau tidak bisa digunakan dalam situasi lain). Saya menduga itu berlebihan dan tidak layak kecuali portabilitas lengkap adalah suatu keharusan - jika tidak coba salah satu solusi yang disarankan.

Ngomong-ngomong, ini akan memberi Anda tombol tekan dan lepaskan acara secara terpisah, jika Anda menyukainya.

Ruchira
sumber
1

Berikut adalah versi yang tidak keluar ke sistem (ditulis dan diuji pada 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
sumber