จับตัวละครจากอินพุตมาตรฐานโดยไม่ต้องรอให้กด


174

ฉันไม่สามารถจำได้ว่าฉันทำเช่นนี้เพราะมันเกิดขึ้นไม่บ่อยนักสำหรับฉัน แต่ใน C หรือ C ++ วิธีที่ดีที่สุดในการอ่านอักขระจากอินพุตมาตรฐานคืออะไรโดยไม่ต้องรอขึ้นบรรทัดใหม่ (กด Enter)

อีกทั้งยังเป็นการดีที่มันจะไม่สะท้อนตัวอักษรที่ป้อนไปยังหน้าจอ ฉันแค่อยากจะกดแป้นพิมพ์โดยไม่ส่งผลต่อหน้าจอคอนโซล


1
@adam - คุณช่วยชี้แจงได้ไหม: คุณต้องการฟังก์ชั่นที่จะกลับมาทันทีหรือไม่ถ้าไม่มีตัวอักษรหรือว่าจะรอการกดแป้นเดียวเสมอ
Roddy

2
@Roddy - ฉันต้องการฟังก์ชั่นซึ่งมักจะรอการกดแป้นเดียว
Adam

คำตอบ:


99

นั่นเป็นไปไม่ได้ในลักษณะแบบพกพาใน C ++ บริสุทธิ์เพราะมันขึ้นอยู่มากเกินไปในขั้วที่ใช้ที่อาจจะเชื่อมต่อกับstdin(พวกเขามักจะบัฟเฟอร์บรรทัด) อย่างไรก็ตามคุณสามารถใช้ห้องสมุดเพื่อ:

  1. conio พร้อมใช้งานกับคอมไพเลอร์ Windows ใช้_getch()ฟังก์ชั่นเพื่อให้ตัวละครแก่คุณโดยไม่ต้องรอคีย์ Enter ฉันไม่ใช่นักพัฒนา Windows ที่ใช้บ่อย แต่ฉันเห็นเพื่อนร่วมชั้นของฉันใส่<conio.h>และใช้งาน ดูconio.hที่ Wikipedia มันแสดงรายการgetch()ซึ่งถูกประกาศเลิกใช้ใน Visual C ++

  2. curses พร้อมใช้งานสำหรับ Linux การใช้งาน curses ที่เข้ากันได้นั้นมีให้สำหรับ Windows เช่นกัน มันยังมีgetch()ฟังก์ชั่น (ลองman getchดู manpage ของมัน) ดูคำสาปที่วิกิพีเดีย

ฉันอยากจะแนะนำให้คุณใช้คำสาปถ้าคุณมุ่งมั่นเพื่อความเข้ากันได้ข้ามแพลตฟอร์ม ที่กล่าวว่าฉันแน่ใจว่ามีฟังก์ชั่นที่คุณสามารถใช้เพื่อปิดการบัฟเฟอร์บรรทัด (ฉันเชื่อว่าเรียกว่า "โหมดดิบ" ซึ่งตรงข้ามกับ "โหมดปรุงสุก" - มองเข้าไปman stty) สาปจะจัดการกับคุณในลักษณะพกพาถ้าฉันไม่ผิด


7
โปรดทราบว่าทุกวันนี้ncursesเป็นตัวแปรที่แนะนำให้cursesใช้
คดีกองทุนของโมนิกา

4
หากคุณต้องการห้องสมุดแล้วพวกเขาสร้างมันได้อย่างไร? เพื่อให้ห้องสมุดคุณต้องเขียนโปรแกรมอย่างแน่นอนและนั่นหมายความว่าทุกคนสามารถตั้งโปรแกรมได้ดังนั้นทำไมเราไม่สามารถเขียนโปรแกรมรหัสห้องสมุดที่เราต้องการได้? ที่จะทำให้นั่นไม่อาจพกพาได้ใน c + + บริสุทธิ์ผิด
OverloadedCore

@ kid8 ฉันไม่เข้าใจ
Johannes Schaub - litb

1
@ JohannesSchaub-litb เขาอาจจะพยายามที่จะพูดว่าผู้ดำเนินการห้องสมุดทำวิธีการพกพาในบริสุทธิ์ c / c ++ เมื่อคุณบอกว่ามันเป็นไปไม่ได้
Abhinav Gauniyal

@AbhinavGauniyal อ่าเข้าใจแล้ว อย่างไรก็ตามฉันไม่ได้บอกว่าตัวติดตั้งไลบรารียังไม่ได้ทำวิธีการพกพา (เช่นจะใช้โดยโปรแกรมแบบพกพาที่สมเหตุสมผล) ฉันได้บอกเป็นนัยว่าพวกเขาไม่ได้ใช้ C ++ แบบพกพา เห็นได้ชัดว่าพวกเขายังไม่ได้ฉันไม่เข้าใจว่าทำไมความคิดเห็นของเขาบอกว่าส่วนหนึ่งของคำตอบของฉันผิด
Johannes Schaub - litb

81

บน Linux (และระบบอื่นที่คล้ายยูนิกซ์) สามารถทำได้ดังนี้:

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

โดยทั่วไปคุณต้องปิดโหมด canonical (และโหมด echo เพื่อไม่ให้มีเสียงก้อง)


ฉันพยายามใช้โค้ดนี้ readแต่ฉันมีข้อผิดพลาดในการเรียกร้องให้ ฉันรวมส่วนหัวทั้งสองแล้ว
Michael Dorst

6
อาจเป็นเพราะรหัสนี้ผิดเนื่องจาก read () เป็นการเรียกระบบ POSIX ที่กำหนดใน unistd.h stdio.h อาจรวมไว้โดยบังเอิญ แต่จริงๆแล้วคุณไม่จำเป็นต้องใช้ stdio.h สำหรับรหัสนี้เลย แทนที่ด้วย unistd.h และควรจะดี
Falcon Momot

1
ฉันไม่รู้ว่าฉันจะอยู่ที่นี่ได้อย่างไรในขณะที่ฉันกำลังมองหาคีย์บอร์ดที่หน้าจอ ROS ใน Raspberry Pi ตัวอย่างโค้ดนี้ใช้ได้สำหรับฉัน
aknay

2
@FalconMomot ใน NetBeans IDE 8.1 ของฉัน (ที่กาลี Linux) มันพูดว่า: error: ‘perror’ was not declared in this scopeเมื่อรวบรวม แต่ทำงานได้ดีเมื่อรวมพร้อมกับstdio.h unistd.h
Abhishek Kashyap

3
มีวิธีง่าย ๆ ในการขยายสิ่งนี้เพื่อไม่ให้บล็อกหรือไม่
Joe Strout

18

ฉันพบสิ่งนี้ในฟอรัมอื่นในขณะที่ต้องการแก้ไขปัญหาเดียวกัน ฉันแก้ไขมันเล็กน้อยจากสิ่งที่ฉันพบ มันใช้งานได้ดี ฉันใช้ OS X ดังนั้นหากคุณใช้ Microsoft คุณจะต้องค้นหาคำสั่ง system () ที่ถูกต้องเพื่อเปลี่ยนเป็นโหมด raw และสุก

#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
ในขณะที่ใช้งานได้สำหรับสิ่งที่มีค่าการปอกเปลือกออกไปยังระบบไม่ค่อยเป็นวิธีที่ "ดีที่สุด" ที่จะทำในความคิดของฉัน โปรแกรม stty นั้นเขียนด้วยภาษา C ดังนั้นคุณสามารถรวม <termios.h> หรือ <sgtty.h> และเรียกใช้รหัสเดียวกันกับที่ stty ใช้โดยไม่ขึ้นอยู่กับโปรแกรมภายนอก / fork / whatnot
คริส Lutz

1
ฉันต้องการสิ่งนี้เพื่อพิสูจน์สิ่งที่เป็นแนวคิดแบบสุ่มและสับสน สิ่งที่ฉันต้องการ ขอบคุณ. ควรสังเกตว่า: ฉันจะใส่ stty ที่ทำให้สุกในตอนท้ายของโปรแกรมไม่เช่นนั้นเชลล์ของคุณจะยังคงอยู่ในโหมด stty raw ซึ่งโดยทั่วไปแล้วเปลือกของฉันจะแตก lol หลังจากที่โปรแกรมหยุดทำงาน
Benjamin

ฉันคิดว่าคุณลืม#include <stdlib.h>
NerdOfCode

14

conio.h

ฟังก์ชั่นที่คุณต้องการคือ:

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

หรือ

การใช้ c ++ Linux ของ conio.h http://sourceforge.net/projects/linux-conioh


9

หากคุณใช้ Windows คุณสามารถใช้PeekConsoleInputเพื่อตรวจสอบว่ามีอินพุตหรือไม่

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

จากนั้นใช้ ReadConsoleInput เพื่อ "กิน" อักขระอินพุต ..

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

ความซื่อสัตย์นี่คือจากรหัสเก่าที่ฉันมีดังนั้นคุณต้องทำตัวเล็กน้อยกับมัน

สิ่งที่เจ๋งคือมันอ่านอินพุตโดยไม่ต้องแจ้งอะไรเลยดังนั้นตัวละครจึงไม่แสดงเลย


9
#include <conio.h>

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

สิ่งนี้ใช้kbhit()เพื่อตรวจสอบว่าคีย์บอร์ดถูกกดอยู่หรือไม่และใช้getch()เพื่อให้ได้อักขระที่ถูกกด


5
conio.h ? "conio.h เป็นไฟล์ส่วนหัว C ที่ใช้ในคอมไพเลอร์ MS-DOS รุ่นเก่าเพื่อสร้างอินเทอร์เฟซผู้ใช้ข้อความ" ดูเหมือนจะค่อนข้างล้าสมัย
kay - SE ชั่ว


5

C และ C ++ ใช้มุมมองที่เป็นนามธรรมของ I / O และไม่มีวิธีมาตรฐานในการทำสิ่งที่คุณต้องการ มีวิธีมาตรฐานในการรับอักขระจากอินพุตสตรีมมาตรฐานหากมีวิธีการรับและไม่มีการกำหนดภาษาอื่นด้วยภาษาใดภาษาหนึ่ง คำตอบใด ๆ จะต้องมีเฉพาะแพลตฟอร์มอาจขึ้นอยู่กับระบบปฏิบัติการ แต่ยังกรอบซอฟต์แวร์

มีการคาดเดาที่สมเหตุสมผลที่นี่ แต่ไม่มีวิธีตอบคำถามของคุณโดยไม่ทราบว่าสภาพแวดล้อมเป้าหมายของคุณคืออะไร


5

ฉันใช้ kbhit () เพื่อดูว่ามีถ่านอยู่หรือไม่จากนั้น getchar () เพื่ออ่านข้อมูล บน windows คุณสามารถใช้ "conio.h" บน linux คุณจะต้องติดตั้ง kbhit () ของคุณเอง

ดูรหัสด้านล่าง:

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

สิ่งที่ใกล้เคียงกับอุปกรณ์พกพามากที่สุดคือการใช้ncursesห้องสมุดเพื่อทำให้เทอร์มินัลเข้าสู่ "โหมด cbreak" API นั้นมีขนาดมหึมา กิจวัตรที่คุณต้องการมากที่สุดคือ

  • initscr และ endwin
  • cbreak และ nocbreak
  • getch

โชคดี!


3

ต่อไปนี้เป็นโซลูชันที่แยกมาจากการเขียนโปรแกรมผู้เชี่ยวชาญ C: Deep Secretsซึ่งควรใช้กับ SVr4 มันใช้sttyและ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

ฉันต้องการวนรอบเพื่ออ่านอินพุตของฉันโดยไม่ต้องกดปุ่มย้อนกลับ สิ่งนี้ใช้ได้สำหรับฉัน

#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
เมื่อคุณอยู่ในโหมด RAW มันเป็นการยากที่จะฆ่ากระบวนการ ดังนั้นจงฆ่ากระบวนการให้เหมือนกับที่ฉันทำ "เมื่อกด" ~ "" คุณอาจฆ่ากระบวนการนี้จากเทอร์มินัลอื่นโดยใช้ KILL
Setu Gupta

3

ncurses ให้วิธีที่ดีในการทำเช่นนี้! นี่เป็นโพสต์แรกของฉัน (ที่ฉันจำได้) ดังนั้นความคิดเห็นใด ๆ ยินดีต้อนรับ ฉันจะขอบคุณคนที่มีประโยชน์ แต่ยินดีต้อนรับทุกคน!

เพื่อคอมไพล์: 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

คุณสามารถทำได้อย่างสะดวกโดยใช้ SDL (Simple DirectMedia Library) แต่ฉันคิดว่าคุณอาจไม่ชอบพฤติกรรมของมัน เมื่อฉันลองฉันต้องให้ SDL สร้างหน้าต่างวิดีโอใหม่ (แม้ว่าฉันไม่ต้องการมันสำหรับโปรแกรมของฉัน) และให้หน้าต่างนี้ "คว้า" เกือบทั้งคีย์บอร์ดและเมาส์อินพุต (ซึ่งก็โอเคสำหรับการใช้งานของฉัน แต่สามารถทำได้ น่ารำคาญหรือใช้การไม่ได้ในสถานการณ์อื่น) ฉันสงสัยว่ามันเกินขนาดและไม่คุ้มค่าเว้นแต่จะต้องพกพาได้อย่างสมบูรณ์ - ไม่เช่นนั้นลองวิธีการแก้ปัญหาที่แนะนำอย่างใดอย่างหนึ่ง

โดยวิธีการนี้จะให้กดปุ่มและปล่อยกิจกรรมแยกต่างหากถ้าคุณเป็นอย่างนั้น


1

นี่เป็นเวอร์ชั่นที่ไม่ได้ใช้ระบบ (เขียนและทดสอบบน 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 );
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.