Захватывать символы со стандартного ввода, не дожидаясь нажатия клавиши ввода


174

Я никогда не могу вспомнить, как я это делаю, потому что это случается так редко для меня. Но в C или C ++, как лучше всего читать символ из стандартного ввода, не дожидаясь перевода строки (нажмите ввод).

Также в идеале это не будет отображать вводимый символ на экране. Я просто хочу захватить нажатия клавиш без влияния на экран консоли.


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

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

Ответы:


99

В чистом C ++ это невозможно сделать переносимым способом, потому что он слишком сильно зависит от используемого терминала, с которым можно соединиться stdin(обычно это буферизованная линия). Однако вы можете использовать библиотеку для этого:

  1. Conio доступен с компиляторами Windows. Используйте _getch()функцию, чтобы дать вам символ, не дожидаясь клавиши Enter. Я не частый разработчик Windows, но я видел, как мои одноклассники просто включают <conio.h>и используют его. Смотрите conio.hв Википедии. В нем перечислены списки getch(), которые объявлены устаревшими в Visual C ++.

  2. проклятия доступны для Linux. Совместимые реализации curses доступны и для Windows. У него также есть getch()функция. (попробуйте man getchпросмотреть его справочную страницу). Смотрите Проклятия в Википедии.

Я бы порекомендовал вам использовать curses, если вы стремитесь к кроссплатформенности. Тем не менее, я уверен, что есть функции, которые вы можете использовать для отключения буферизации линии (я полагаю, что это называется «сырой режим», а не «приготовленный режим» - посмотрите man stty). Проклятия справились бы с тобой портативно, если я не ошибаюсь.


7
Обратите внимание, что в настоящее время ncursesэто рекомендуемый вариант curses.
Фонд Моники Иск

4
если вам нужна библиотека, то как они это сделали? чтобы сделать библиотеку, вам обязательно нужно было ее запрограммировать, а это значит, что любой может ее запрограммировать, так почему мы не можем просто запрограммировать код библиотеки, который нам нужен сам? это также сделало бы это не всегда переносимым в чистом с ++ неправильным
OverloadedCore

@ kid8, я не понимаю,
Йоханнес Шауб, -

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

@ АбхинавГауниял ах, понятно. Однако я не сказал, что разработчик библиотеки не создал переносимых методов (т. Е. Для использования достаточно переносимыми программами). Я подразумевал, что они не использовали переносимый 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, и это должно быть хорошо.
Сокол Момот

1
Я не знаю, как я оказался здесь, пока искал ввод с клавиатуры на терминале ROS в Raspberry Pi. Этот фрагмент кода работает для меня.
aknay

2
@FalconMomot В моей среде IDE NetBeans 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
Я нуждался в этом для случайного доказательства концепции понятия и возни. Как раз то, что мне было нужно. Спасибо. Следует отметить: я определенно поместил бы stty cooked в конец программы, иначе ваша оболочка останется в режиме stty raw, что в основном сломало мою оболочку lol после остановки программы.
Бенджамин

Я думаю, что вы забыли#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 con ++ реализация 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библиотеку для перевода терминала в «режим cbreak». API гигантский; процедуры, которые вы хотите больше всего,

  • initscr и endwin
  • cbreak и nocbreak
  • getch

Удачи!


3

Ниже приведено решение, извлеченное из Expert C Programming: 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.
Сету Гупта

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 );
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.