Вопрос

Я пытаюсь написать программу на C (в Linux), которая выполняет цикл до тех пор, пока пользователь не нажмет клавишу, но не должна требовать нажатия клавиши для продолжения каждого цикла.

Есть ли простой способ сделать это?Я полагаю, что, возможно, смог бы сделать это с помощью select() но, похоже, это большая работа.

В качестве альтернативы, есть ли способ поймать ctrl-c нажать клавишу, чтобы выполнить очистку перед закрытием программы вместо неблокирующего ввода-вывода?

Это было полезно?

Решение

Как уже говорилось, вы можете использовать sigaction чтобы перехватить ctrl-c, или select чтобы перехватить любой стандартный ввод.

Однако обратите внимание, что с помощью последнего метода вам также необходимо установить TTY так, чтобы он находился в режиме "по символам за раз", а не в режиме "по строкам за раз".Последнее используется по умолчанию - если вы вводите строку текста, она не отправляется в стандартный идентификатор запущенной программы, пока вы не нажмете enter.

Вам нужно было бы использовать tcsetattr() функция для отключения режима ICANON и, вероятно, также отключения ECHO.По памяти вы также должны вернуть терминал в режим ICANON при выходе из программы!

Просто для полноты картины, вот некоторый код, который я только что набросал (nb:нет проверки на ошибки!) который устанавливает TTY Unix и эмулирует DOS <conio.h> функции kbhit() и getch():

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>

struct termios orig_termios;

void reset_terminal_mode()
{
    tcsetattr(0, TCSANOW, &orig_termios);
}

void set_conio_terminal_mode()
{
    struct termios new_termios;

    /* take two copies - one for now, one for later */
    tcgetattr(0, &orig_termios);
    memcpy(&new_termios, &orig_termios, sizeof(new_termios));

    /* register cleanup handler, and set the new terminal mode */
    atexit(reset_terminal_mode);
    cfmakeraw(&new_termios);
    tcsetattr(0, TCSANOW, &new_termios);
}

int kbhit()
{
    struct timeval tv = { 0L, 0L };
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(0, &fds);
    return select(1, &fds, NULL, NULL, &tv);
}

int getch()
{
    int r;
    unsigned char c;
    if ((r = read(0, &c, sizeof(c))) < 0) {
        return r;
    } else {
        return c;
    }
}

int main(int argc, char *argv[])
{
    set_conio_terminal_mode();

    while (!kbhit()) {
        /* do some work */
    }
    (void)getch(); /* consume the character */
}

Другие советы

select() это слишком низкий уровень для удобства. Я предлагаю вам использовать библиотеку ncurses, чтобы перевести терминал в режим cbreak и delay, затем вызовите getch(), который вернет ERR, если ни один символ не готов:

WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);

В этот момент вы можете звонить getch без блокировки.

В системах UNIX вы можете использовать вызов sigaction для регистрации обработчика сигнала для сигнала SIGINT, который представляет последовательность клавиш Control + C. Обработчик сигнала может установить флаг, который будет проверяться в цикле, чтобы он соответствующим образом прерывался.

Вы, вероятно, хотите kbhit();

//Example will loop until a key is pressed
#include <conio.h>
#include <iostream>

using namespace std;

int main()
{
    while(1)
    {
        if(kbhit())
        {
            break;
        }
    }
}

это может работать не во всех средах. Переносимым способом было бы создать поток мониторинга и установить некоторый флаг на getch();

Еще один способ получить неблокирующий ввод с клавиатуры - открыть файл устройства и прочитать его!

Вы должны знать файл искомого устройства, один из / dev / input / event *. Вы можете запустить cat / proc / bus / input / devices, чтобы найти нужное устройство.

Этот код работает для меня (запускается от имени администратора).

  #include <stdlib.h>
  #include <stdio.h>
  #include <unistd.h>
  #include <fcntl.h>
  #include <errno.h>
  #include <linux/input.h>

  int main(int argc, char** argv)
  {
      int fd, bytes;
      struct input_event data;

      const char *pDevice = "/dev/input/event2";

      // Open Keyboard
      fd = open(pDevice, O_RDONLY | O_NONBLOCK);
      if(fd == -1)
      {
          printf("ERROR Opening %s\n", pDevice);
          return -1;
      }

      while(1)
      {
          // Read Keyboard Data
          bytes = read(fd, &data, sizeof(data));
          if(bytes > 0)
          {
              printf("Keypress value=%x, type=%x, code=%x\n", data.value, data.type, data.code);
          }
          else
          {
              // Nothing read
              sleep(1);
          }
      }

      return 0;
   }

Для этого можно использовать библиотеку curses. Конечно, select() и обработчики сигналов могут использоваться в определенной степени.

Если вы счастливы, просто поймав Control-C, это уже готово. Если вам действительно нужен неблокирующий ввод / вывод, но вам не нужна библиотека curses, другой альтернативой является перемещение блокировки, запаса и ствола в AT & amp; T sfio библиотека . Это хорошая библиотека, созданная по образцу C stdio, но более гибкая, поточно-ориентированная и работающая лучше. (sfio означает безопасный, быстрый ввод / вывод.)

Нет переносимого способа сделать это, но select () может быть хорошим способом. См. http://c-faq.com/osdep/readavail.html для получения дополнительной информации. возможные решения.

Вот функция, которая сделает это за вас. Вам нужно termios.h, которое поставляется с системами POSIX.

#include <termios.h>
void stdin_set(int cmd)
{
    struct termios t;
    tcgetattr(1,&t);
    switch (cmd) {
    case 1:
            t.c_lflag &= ~ICANON;
            break;
    default:
            t.c_lflag |= ICANON;
            break;
    }
    tcsetattr(1,0,&t);
}

Разбивка: tcgetattr получает информацию о текущем терминале и сохраняет ее в t. Если cmd равно 1, флаг локального ввода в tcsetattr устанавливается на неблокирующий ввод. В противном случае он сбрасывается. Затем <=> меняет стандартный ввод на <=>.

Если вы не сбросите стандартный ввод в конце вашей программы, у вас будут проблемы в вашей оболочке.

Вы можете сделать это, используя команду select следующим образом:

  int nfds = 0;
  fd_set readfds;
  FD_ZERO(&readfds);
  FD_SET(0, &readfds); /* set the stdin in the set of file descriptors to be selected */
  while(1)
  {
     /* Do what you want */
     int count = select(nfds, &readfds, NULL, NULL, NULL);
     if (count > 0) {
      if (FD_ISSET(0, &readfds)) {
          /* If a character was pressed then we get it and exit */
          getchar();
          break;
      }
     }
  }

Не слишком много работы: D

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top