91

ユーザーがキーを押すまでループするプログラムをC(Linux上)で作成しようとしていますが、各ループを続行するためにキーを押す必要はありません。

これを行う簡単な方法はありますか?おそらくそれでできると思いますがselect()、それは大変な作業のようです。

または、ノンブロッキング io の代わりにプログラムが終了する前にctrl-キーを押してクリーンアップを行う方法はありますか?c

4

11 に答える 11

74

すでに述べたように、 を使用sigactionして ctrl-cselectをトラップしたり、標準入力をトラップしたりできます。

ただし、後者の方法では、一度に行単位モードではなく、一度に文字単位モードになるように TTY を設定する必要があることに注意してください。後者がデフォルトです。テキスト行を入力すると、Enter キーを押すまで実行中のプログラムの stdin に送信されません。

関数を使用tcsetattr()してICANONモードをオフにし、おそらくECHOも無効にする必要があります。また、メモリから、プログラムの終了時に端末を ICANON モードに戻す必要があります。

<conio.h>完全を期すために、Unix TTY を設定し、DOS機能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) > 0;
}

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 */
}
于 2009-01-15T23:37:48.147 に答える
16

select()便宜上、レベルが低すぎます。ncursesライブラリを使用して端末を cbreak モードと遅延モードにしてから、 を呼び出すことをお勧めします。これは、準備ができている文字がない場合getch()に返されます。ERR

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

その時点で、getchブロックせずに呼び出すことができます。

于 2009-01-16T01:03:13.747 に答える
11

UNIX システムでは、call を使用して、Control+C キー シーケンスを表すシグナルsigactionのシグナル ハンドラを登録できます。SIGINTシグナルハンドラーは、ループでチェックされるフラグを設定して、適切にブレークすることができます。

于 2009-01-15T23:31:33.260 に答える
6

あなたはおそらくしたいです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();

于 2009-01-15T23:37:35.387 に答える
6

ノンブロッキング キーボード入力を取得するもう 1 つの方法は、デバイス ファイルを開いて読み取ることです。

探しているデバイス ファイル (/dev/input/event* の 1 つ) を知っている必要があります。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;
   }
于 2014-04-23T19:55:36.813 に答える
3

curses ライブラリは、この目的に使用できます。もちろん、select()シグナルハンドラもある程度使用できます。

于 2009-01-15T23:36:32.813 に答える
2

Control-C をキャッチするだけで満足している場合は、それで完了です。ノンブロッキング I/O が本当に必要だが、curses ライブラリは必要ない場合は、lock、stock、barrel をAT&Tsfioライブラリに移動するという別の方法があります。C でパターン化された優れたライブラリですstdioが、より柔軟で、スレッドセーフで、パフォーマンスが向上しています。(sfio は、安全で高速な I/O の略です。)

于 2009-01-17T03:08:31.120 に答える
1

次のように 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

于 2016-02-04T08:10:43.780 に答える
1

これを行う移植可能な方法はありませんが、select() は良い方法かもしれません。可能な解決策については、 http://c-faq.com/osdep/readavail.htmlを参照してください。

于 2009-01-15T23:33:07.453 に答える
1

これを行う関数を次に示します。termios.hPOSIX システムに付属するものが必要です。

#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ます。が 1 の場合cmd、ローカル入力フラグ intは非ブロッキング入力に設定されます。それ以外の場合はリセットされます。次に、tcsetattr標準入力を に変更しtます。

プログラムの最後で標準入力をリセットしないと、シェルで問題が発生します。

于 2016-06-17T02:12:36.820 に答える