Chụp các ký tự từ đầu vào tiêu chuẩn mà không cần đợi nhập


174

Tôi không bao giờ có thể nhớ làm thế nào tôi làm điều này bởi vì nó xuất hiện rất ít khi đối với tôi. Nhưng trong C hoặc C ++, cách tốt nhất để đọc một ký tự từ đầu vào tiêu chuẩn mà không cần chờ một dòng mới (nhấn enter).

Ngoài ra, lý tưởng là nó sẽ không lặp lại ký tự đầu vào màn hình. Tôi chỉ muốn chụp các tổ hợp phím mà không tạo hiệu ứng cho màn hình giao diện điều khiển.


1
@adam - Bạn có thể làm rõ: Bạn có muốn một chức năng sẽ trả về ngay lập tức nếu không có ký tự nào, hoặc một chức năng sẽ luôn chờ một lần nhấn phím?
Roddy

2
@Roddy - Tôi muốn một chức năng sẽ luôn chờ một lần nhấn phím.
Adam

Câu trả lời:


99

Điều đó là không thể theo cách di động trong C ++ thuần túy, bởi vì nó phụ thuộc quá nhiều vào thiết bị đầu cuối được sử dụng có thể được kết nối với stdin(chúng thường được đệm dòng). Tuy nhiên, bạn có thể sử dụng một thư viện cho điều đó:

  1. conio có sẵn với trình biên dịch Windows. Sử dụng _getch()chức năng để cung cấp cho bạn một ký tự mà không cần chờ phím Enter. Tôi không phải là nhà phát triển Windows thường xuyên, nhưng tôi đã thấy các bạn cùng lớp của mình chỉ bao gồm <conio.h>và sử dụng nó. Xem conio.htại Wikipedia. Nó liệt kê getch(), được tuyên bố không dùng nữa trong Visual C ++.

  2. những lời nguyền có sẵn cho Linux. Triển khai lời nguyền tương thích cũng có sẵn cho Windows. Nó cũng có một getch()chức năng. (cố gắng man getchxem trang chủ của nó). Xem những lời nguyền tại Wikipedia.

Tôi sẽ khuyên bạn nên sử dụng những lời nguyền nếu bạn nhắm đến khả năng tương thích đa nền tảng. Điều đó nói rằng, tôi chắc chắn có những chức năng mà bạn có thể sử dụng để tắt bộ đệm dòng (tôi tin rằng đó gọi là "chế độ thô", trái ngược với "chế độ nấu" - xem xét man stty). Những lời nguyền sẽ xử lý điều đó cho bạn theo cách di động, nếu tôi không nhầm.


7
Lưu ý rằng ngày nay, ncurseslà biến thể được đề nghị của curses.
Vụ kiện của Quỹ Monica

4
Nếu bạn cần một thư viện thì họ đã làm nó như thế nào? để làm cho thư viện bạn chắc chắn phải lập trình nó và điều đó có nghĩa là bất cứ ai cũng có thể lập trình nó, vậy tại sao chúng ta không thể lập trình mã thư viện mà chúng ta cần? đó cũng sẽ làm cho của đó không thể portably trong tinh khiết c ++ sai
OverloadedCore

@ kid8 tôi không hiểu
Johannes Schaub - litb

1
@ JohannesSchaub-litb có lẽ anh ta đang cố gắng nói làm thế nào mà người triển khai thư viện đã tạo ra phương thức di động đó trong c / c ++ thuần túy khi bạn nói rằng điều đó là không thể.
Abhinav Gauniyal

@AbhinavGauniyal ah, tôi hiểu rồi. Tuy nhiên tôi đã không nói rằng người triển khai thư viện đã không thực hiện các phương thức di động (nghĩa là được sử dụng bởi các chương trình di động hợp lý). Tôi đã ngụ ý rằng họ chưa sử dụng C ++ di động. Rõ ràng là họ không có, tôi không hiểu tại sao bình luận của anh ấy nói rằng phần này trong câu trả lời của tôi là sai.
Julian Schaub - litb

81

Trên Linux (và các hệ thống giống như unix khác), điều này có thể được thực hiện theo cách sau:

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

Về cơ bản, bạn phải tắt chế độ chính tắc (và chế độ echo để triệt tiêu tiếng vang).


Tôi đã thử thực hiện mã này, nhưng tôi gặp lỗi trong cuộc gọi đến read. Tôi đã bao gồm cả hai tiêu đề.
Michael Dorst

6
Có thể do mã này sai, vì read () là một cuộc gọi hệ thống POSIX được xác định trong unistd.h. stdio.h có thể bao gồm nó bởi sự trùng hợp, nhưng bạn thực sự không cần stdio.h cho mã này; thay thế nó bằng unistd.h và nó sẽ tốt
Falcon Momot

1
Tôi không biết làm thế nào tôi kết thúc ở đây trong khi tôi đang tìm kiếm đầu vào bàn phím tại thiết bị đầu cuối ROS trong Raspberry Pi. Đoạn mã này làm việc cho tôi.
aknay

2
@FalconMomot Trong NetBeans IDE 8.1 của tôi (Tại Kali Linux) có ghi: error: ‘perror’ was not declared in this scopekhi biên dịch, nhưng hoạt động tốt khi được bao gồm stdio.hcùng với unistd.h.
Abhishek Kashyap

3
Có cách nào dễ dàng để mở rộng điều này để không chặn?
Joe Strout

18

Tôi tìm thấy điều này trên một diễn đàn khác trong khi tìm cách giải quyết vấn đề tương tự. Tôi đã sửa đổi nó một chút từ những gì tôi tìm thấy. Nó hoạt động rất tốt. Tôi đang chạy OS X, vì vậy nếu bạn đang chạy Microsoft, bạn sẽ cần tìm lệnh system () chính xác để chuyển sang chế độ thô và nấu.

#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
Trong khi điều này hoạt động, đối với những gì nó có giá trị, tách ra khỏi hệ thống hiếm khi là cách "tốt nhất" để làm điều đó theo ý kiến ​​của tôi. Chương trình stty được viết bằng C, vì vậy bạn có thể bao gồm <termios.h> hoặc <sgtty.h> và gọi cùng một mã mà stty sử dụng, mà không phụ thuộc vào chương trình / fork / whatnot bên ngoài.
Chris Lutz

1
Tôi cần điều này cho bằng chứng ngẫu nhiên của các công cụ khái niệm và lộn xộn xung quanh. Đúng thứ tôi cần. Cảm ơn bạn. Cần lưu ý: Tôi chắc chắn sẽ đặt stty nấu vào cuối chương trình nếu không vỏ của bạn sẽ ở chế độ thô, điều này về cơ bản đã phá vỡ vỏ lol của tôi sau khi chương trình dừng lại.
Benjamin

Tôi nghĩ bạn đã quên#include <stdlib.h>
NerdOfCode

14

CONIO.H

các chức năng bạn cần là:

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

hoặc là

Linux c ++ triển khai conio.h http://sourceforge.net/projects/linux-conioh


9

Nếu bạn đang ở trên windows, bạn có thể sử dụng PeekConsoleInput để phát hiện nếu có bất kỳ đầu vào nào,

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

sau đó sử dụng ReadConsoleInput để "tiêu thụ" ký tự đầu vào ..

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

Thành thật mà nói đây là từ một số mã cũ tôi có, vì vậy bạn phải mân mê một chút với nó.

Mặc dù vậy, điều thú vị là nó đọc đầu vào mà không cần nhắc bất cứ điều gì, vì vậy các ký tự hoàn toàn không được hiển thị.


9
#include <conio.h>

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

Điều này sử dụng kbhit()để kiểm tra xem bàn phím có được nhấn hay không và sử dụng getch()để lấy ký tự đang được nhấn.


5
conio.h ? "conio.h là tệp tiêu đề C được sử dụng trong trình biên dịch MS-DOS cũ để tạo giao diện người dùng văn bản." Có vẻ hơi lỗi thời.
kay - SE là ác


5

C và C ++ có một cái nhìn rất trừu tượng về I / O, và không có cách tiêu chuẩn nào để làm những gì bạn muốn. Có các cách tiêu chuẩn để nhận các ký tự từ luồng đầu vào tiêu chuẩn, nếu có bất kỳ cách nào để nhận và không có gì khác được xác định bởi ngôn ngữ. Do đó, bất kỳ câu trả lời nào cũng sẽ phải dành riêng cho nền tảng, có lẽ không chỉ phụ thuộc vào hệ điều hành mà còn cả khung phần mềm.

Có một số dự đoán hợp lý ở đây, nhưng không có cách nào để trả lời câu hỏi của bạn mà không biết môi trường mục tiêu của bạn là gì.


5

Tôi sử dụng kbhit () để xem nếu một char có mặt và sau đó getchar () để đọc dữ liệu. Trên cửa sổ, bạn có thể sử dụng "conio.h". Trên linux, bạn sẽ phải thực hiện kbhit () của riêng bạn.

Xem mã dưới đây:

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

Cách gần nhất để di động là sử dụng ncursesthư viện để đặt thiết bị đầu cuối vào "chế độ bẻ khóa". API là khổng lồ; những thói quen bạn muốn nhất là

  • initscrendwin
  • cbreaknocbreak
  • getch

Chúc may mắn!


3

Sau đây là một giải pháp được trích xuất từ Lập trình Expert C: Deep Secrets , được cho là hoạt động trên SVr4. Nó sử dụng sttyioctl .

#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

Tôi luôn muốn một vòng lặp để đọc đầu vào của mình mà không cần nhấn phím quay lại. Điều này làm việc cho tôi.

#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
một khi bạn sẽ ở chế độ RAW, thật khó để giết quá trình. vì vậy hãy giữ một điểm để giết quá trình như tôi đã "nhấn" ~ "". ................ khôn ngoan khác bạn có thể giết quá trình từ thiết bị đầu cuối khác bằng cách sử dụng KILL.
Setu Gupta

3

ncurses cung cấp một cách tốt đẹp để làm điều này! Ngoài ra đây là bài viết đầu tiên của tôi (mà tôi có thể nhớ), vì vậy mọi bình luận đều được chào đón. Tôi sẽ đánh giá cao những người hữu ích, nhưng tất cả đều được chào đón!

để biên dịch: 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

Bạn có thể làm điều đó một cách hợp lý bằng cách sử dụng SDL (Thư viện DirectMedia đơn giản), mặc dù tôi nghi ngờ bạn có thể không thích hành vi của nó. Khi tôi thử nó, tôi phải có SDL tạo một cửa sổ video mới (mặc dù tôi không cần nó cho chương trình của mình) và cửa sổ này "lấy" gần như tất cả đầu vào bàn phím và chuột (có thể sử dụng được nhưng tôi có thể sử dụng gây phiền nhiễu hoặc không khả thi trong các tình huống khác). Tôi nghi ngờ đó là quá mức cần thiết và không có giá trị trừ khi tính di động hoàn toàn là bắt buộc - nếu không hãy thử một trong những giải pháp được đề xuất khác.

Nhân tiện, điều này sẽ cung cấp cho bạn các phím bấm và phát hành các sự kiện riêng biệt, nếu bạn tham gia vào đó.


1

Đây là phiên bản không trình bày ra hệ thống (được viết và thử nghiệm trên 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 );
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.