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

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; 
}
cwhiii
источник
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

chrissidtmeier
источник
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

если честно, это из какого-то старого кода, который у меня есть, так что вам придется немного поиграться с ним.

Крутая вещь, хотя это то, что он читает ввод без запроса чего-либо, поэтому символы не отображаются вообще.

Hasen
источник
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;
}
ssinfod
источник
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");
}
PolyThinker
источник
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;
}
AngryDane
источник
2

у меня работает на windows:

#include <conio.h>
char c = _getch();
user1438233
источник
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 );
}
Джефф Шухай
источник