Знімайте символи зі стандартного вводу, не чекаючи натискання клавіші Enter


174

Я ніколи не можу згадати, як я це роблю, тому що це так рідко приходить до мене. Але в C або C ++, який найкращий спосіб прочитати символ зі стандартного вводу, не чекаючи нового рядка (натисніть клавішу Enter).

Також в ідеалі це не повторюватиме вхідний символ на екран. Я просто хочу фіксувати натискання клавіш на ефект консолі.


1
@adam - Чи можете ви уточнити: чи хочете ви функцію, яка повернеться негайно, якщо немає символу, або функцію, яка завжди буде чекати одного натискання клавіші?
Родді

2
@Roddy - Я хочу функцію, яка завжди буде чекати одного натискання клавіші.
Адам

Відповіді:


99

Це неможливо портативним способом у чистому C ++, оскільки це занадто багато залежить від використовуваного терміналу, який може бути пов'язаний stdin(вони зазвичай буферні). Однак ви можете використовувати для цього бібліотеку:

  1. conio доступний з компіляторами Windows. Використовуйте _getch()функцію, щоб надати вам характер, не чекаючи клавіші Enter. Я не є частим розробником Windows, але я бачив, як мої однокласники просто включають його <conio.h>та використовують. Дивіться conio.hу Вікіпедії. У ньому перераховано список getch(), який оголошено застарілим у Visual C ++.

  2. прокльони, доступні для Linux. Сумісні реалізації прокльонів доступні і для Windows. Він також має getch()функцію. (спробуйте man getchпереглянути його сторінку). Дивіться прокльони у Вікіпедії.

Я б рекомендував вам використовувати прокльони, якщо ви прагнете сумісності між платформами. З цього приводу я впевнений, що є функції, які можна використовувати для вимкнення буферизації ліній (я вважаю, що це називається "сирої режим", на відміну від "приготованого режиму" - перегляньте man stty). Прокляття вирішували б це для вас портативно, якщо я не помиляюся.


7
Зауважимо, що сьогодні ncursesє рекомендованим варіантом curses.
Фонд позову Моніки

4
якщо вам потрібна бібліотека, то як вони її створили? щоб зробити бібліотеку вам, безумовно, довелося її програмувати, а це означає, що хтось міг би її запрограмувати, то чому ми не можемо просто програмувати код бібліотеки, який нам потрібен? це також зробить це, можливо, не переносимо чистим c ++ неправильним
OverloadedCore

@ kid8 Я не розумію
Йоханнес Шауб - запалений

1
@ JohannesSchaub-litb він, мабуть, намагається сказати, яким чином реалізатор бібліотеки зробив цей портативний метод у чистому c / c ++, коли ти кажеш, що це неможливо.
Абхінав Гауніял

@AbhinavGauniyal ах, я бачу. Однак я не говорив, що реалізатор бібліотеки не зробив переносних методів (тобто використовувався досить портативними програмами). Я мав на увазі, що вони не використовували портативний C ++. Як явно вони цього не зробили, я не розумію, чому його коментар говорить, що ця частина моєї відповіді неправильна.
Йоханнес Шауб - запалений

81

У Linux (та інших системах, схожих на unix) це можна зробити наступним чином:

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

В основному ви повинні вимкнути канонічний режим (і ехо-режим, щоб придушити лунання).


Я спробував реалізувати цей код, але отримав помилку під час виклику до read. Я включив обидва заголовки.
Майкл Дорст

6
Можливо тому, що цей код неправильний, оскільки read () - це системний виклик POSIX, визначений у unistd.h. stdio.h може включати його за збігом обставин, але вам фактично не потрібен stdio.h для цього коду; замініть його на unistd.h, і це повинно бути добре.
Falcon Momot

1
Я не знаю, як я опинився тут, поки шукав введення клавіатури на ROS-терміналі в Raspberry Pi. Цей фрагмент коду працює для мене.
aknay

2
@FalconMomot У моєму NetBeans IDE 8.1 (у Kali Linux) написано: error: ‘perror’ was not declared in this scopeколи компілюється, але працює добре, коли включений stdio.hразом із unistd.h.
Абхішек Кашяп

3
Чи є простий спосіб розширити це, щоб не блокувати?
Джо Строут

18

Я знайшов це на іншому форумі, шукаючи вирішити ту саму проблему. Я трохи змінив його з того, що знайшов. Це чудово працює. Я запускаю OS X, тому якщо ви працюєте з Microsoft, вам потрібно буде знайти правильну команду system () для переходу на сирої та приготовлений режим.

#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.
Кріс Лутц

1
Мені це було потрібно для випадкового доказування поняття та непорозуміння. Тільки те, що мені було потрібно. Дякую. Слід зазначити: я б точно поставив приготований штит в кінці програми, інакше ваша оболонка залишиться в режимі стирого режиму, що в основному зламало мою оболонку, хай після припинення програми.
Бенджамін

Я думаю, ти забув#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

або

Linux c ++ реалізація 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 для створення текстових інтерфейсів користувача." Здається дещо застарілим.
Кей - SE


5

C і C ++ мають дуже абстрактний погляд на введення / виведення, і немає стандартного способу робити те, що ви хочете. Існують стандартні способи отримання символів зі стандартного вхідного потоку, якщо є якісь, і нічого іншого не визначається жодною мовою. Тому будь-яка відповідь повинна бути орієнтована на платформу, можливо, залежно не тільки від операційної системи, але і програмного забезпечення.

Тут є кілька розумних здогадок, але немає можливості відповісти на ваше запитання, не знаючи, яке ваше цільове середовище.


5

Я використовую kbhit (), щоб побачити, чи є char, а потім 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бібліотеку, щоб перевести термінал у "режим виходу з ладу". API є гігантським; процедури, які ви хочете найбільше, є

  • initscr і endwin
  • cbreak і nocbreak
  • getch

Удачі!


3

Далі йде рішення, витягнене з програмування Expert 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 MODE, важко вбити процес. тому зберігайте крапку, щоб вбити процес, як я це робив "при натисканні" ~ "". ................ інші розумні ви можете вбити процес з іншого терміналу за допомогою 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), хоча я підозрюю, що вам може не сподобатися його поведінка. Коли я спробував це, мені довелося створити 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 );
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.