数字が0から始まるプログラムを作成しようとしていますが、いずれかのキーを押すと1ずつ増加します。何も押さないと、0に達するまで1秒ごとに1ずつ減少し続けます。コンソールウィンドウに減分が表示されます。
私のアプローチの問題は、キーを押すまで何も起こらないことです(つまり、getch()
で何かが押されたかどうかをチェックします)。何も押されていないことを確認するにはどうすればよいですか?そしてもちろん、!getch()
は機能しません。そのためには、目的自体を無効にするキー押下をチェックする必要があるからです。
OS:Windows 10 Enterprise、IDE:Code :: Blocks
void main()
{
int i, counter = 0;
for (i = 0; i < 1000; i++)
{
delay(1000);
// if a key is pressed, increment it
if (getch())
{
counter += 1;
printf("\n%d", counter);
}
while (counter >= 1)
{
if (getch())
{
break;
}
else
{
delay(1000);
counter--;
printf("\n%d", counter);
}
}
}
}
次の短いプログラムでは、ncursesもスレッドも必要ありません。ただし、tcsetattr()
を使用して端末属性を変更する必要があります。これはLinuxおよびUnixライクなシステムで動作しますが、_termios.h
_ヘッダーファイルを含まないWindowsでは動作しません。 (おそらく、このテーマに興味があるなら この投稿 を参照してください。)
_#include <stdio.h>
#include <string.h>
#include <termios.h>
int main(int argc, char *argv[]) {
struct termios orig_attr, new_attr;
int c = '\0';
// or int n = atoi(argv[1]);
int n = 5;
tcgetattr(fileno(stdin), &orig_attr);
memcpy(&new_attr, &orig_attr, sizeof(new_attr));
new_attr.c_lflag &= ~(ICANON | ECHO);
new_attr.c_cc[VMIN] = 0;
// Wait up to 10 deciseconds (i.e. 1 second)
new_attr.c_cc[VTIME] = 10;
tcsetattr(fileno(stdin), TCSANOW, &new_attr);
printf("Starting with n = %d\n", n);
do {
c = getchar();
if (c != EOF) {
n++;
printf("Key pressed!\n");
printf("n++ => %d\n", n);
} else {
n--;
printf("n-- => %d\n", n);
if (n == 0) {
printf("Exiting ...\n");
break;
}
if (feof(stdin)) {
//puts("\t(clearing terminal error)");
clearerr(stdin);
}
}
} while (c != 'q');
tcsetattr(fileno(stdin), TCSANOW, &orig_attr);
return 0;
}
_
重要なポイントは
_new_attr.c_lflag &= ~(ICANON | ECHO);
_
端末を標準モードから外します(文字「エコー」を無効にします)。
_new_attr.c_cc[VMIN] = 0;
_
ポーリング(「ブロッキング」モードではなく)モードにします。
_new_attr.c_cc[VTIME] = 10;
_
入力を10デシ秒まで待つようにプログラムに指示します。
更新(2019-01-13)
EOF
のstdin
をクリアするためにclearerr(stdin)
を追加します(一部のプラットフォームでは必要と思われます)これはすでに提案されているようにマルチスレッドで行うことができますが、他の可能性もあります。
ncurses たとえば、 timeout で入力を待つ可能性があります。
Ncursesの例 (Constantinによって書かれた) :
initscr();
timeout(1000);
char c = getch();
endwin();
printf("Char: %c\n", c);
poll
をstdin
でも使用して、入力が利用可能かどうかを確認できると思います。
また、プログラムの応答性を高めるために、睡眠または遅延をたとえば100ミリ秒に下げ、入力なしで10回の睡眠が経過した場合にのみ減少させることができます。これにより、入力ラグが減少します。
スレッドを使用する必要があり、__ sync_add_and_fetchと__sync_sub_and_fetchを使用して同時実行の問題を回避する必要があります
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <iostream>
static void* thread(void* p) {
int* counter = (int*)p;
while (1) {
if (*counter > 0) {
__sync_sub_and_fetch(counter, 1);
printf("sub => %d\n", *counter);
} else {
sleep(1);
}
}
return NULL;
}
int main() {
int counter = 0;
char ch;
struct termios orig_attr, new_attr;
tcgetattr(fileno(stdin), &orig_attr);
memcpy(&new_attr, &orig_attr, sizeof(new_attr));
new_attr.c_lflag &= ~(ICANON | ECHO);
tcsetattr(fileno(stdin), TCSANOW, &new_attr);
pthread_t pid;
if (pthread_create(&pid, NULL, thread, &counter)) {
fprintf(stderr, "Create thread failed");
exit(1);
}
while(1) {
char c = getchar();
__sync_add_and_fetch(&counter, 1);
printf("add: %d\n", counter);
}
return 0;
}
select
を使用して入力が存在するかどうかを確認し、待機する別の方法を次に示します。かなりの解決策ではありませんが、機能します。ただし、Linuxのみ。
_#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/select.h>
#define WAIT_TIME 1000 //Delay time in milliseconds
bool inputExists(void)
{
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(0, &readfds);
struct timeval tv;
tv.tv_sec = tv.tv_usec = 0;
if(select(1, &readfds, NULL, NULL, &tv))
return true;
else
return false;
}
void wait()
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = WAIT_TIME * 1000;
select(0, NULL, NULL, NULL, &tv);
}
int main(void)
{
system("stty raw"); /* Switch to terminal raw input mode */
unsigned int count = 0;
for(;;)
{
if(inputExists())
{
char input[256] = {0};
read(0, input, 255);
count += strlen(input);
printf("\rCount is now %d\n", count);
}
else if(count > 0)
{
count--;
printf("\rDecremented count to %d\n", count);
}
puts("\rWaiting...");
wait();
}
}
_
system("stty raw")
とそれらの_\r
_ sを避けるより良い方法は、tcgetattr
とtcsetattr
を使用することです:
_struct termios orig_attr, new_attr;
tcgetattr(STDIN_FILENO, &orig_attr);
new_attr = orig_attr;
new_attr.c_lflag &= ~(ICANON | ECHO); //Disables echoing and canonical mode
tcsetattr(STDIN_FILENO, TCSANOW, &new_attr);
//...
tcsetattr(STDIN_FILENO, TCSANOW, &old_attr);
_
Linuxで動作するpthreadの例を次に示します。コンセプトは大丈夫ですが、おそらくこのための既存のループ/ライブラリがあります。
#include <stdio.h>
#include<pthread.h>
void *timer(void* arg){
int* counter = (int*)arg;
while(*counter > 0){
int a = *counter;
printf("counter: %d \n", a);
*counter = a - 1;
sleep(1);
}
}
int main(int arg_c, char** args){
int i = 100;
pthread_t loop;
pthread_create(&loop, NULL, timer, &i);
while(i>0){
i++;
getchar();
printf("inc counter: %d \n", i);
}
printf("%d after\n", i);
pthread_join(loop, NULL);
return 0;
}
これにより、カウントダウンが設定された2番目のスレッドが開始されます。それは毎秒カウンターを減少させます。メインスレッドには、getcharを使用したループがあります。両方ともi
を変更します。
NcursesとPOSIXタイマーおよびシグナル(およびグローバル変数)を使用する別の例。
#include <ncurses.h>
#include <signal.h>
#include <time.h>
int changed, value;
void timer(union sigval t) {
(void)t; // suppress unused warning
changed = 1;
value--;
}
int main(void) {
int ch;
timer_t tid;
struct itimerspec its = {0};
struct sigevent se = {0};
se.sigev_notify = SIGEV_THREAD;
se.sigev_notify_function = timer;
its.it_value.tv_sec = its.it_interval.tv_sec = 1;
timer_create(CLOCK_REALTIME, &se, &tid);
timer_settime(tid, 0, &its, NULL);
initscr();
halfdelay(1); // hit Ctrl-C to exit
noecho();
curs_set(0);
for (;;) {
ch = getch();
if (ch != ERR) {
changed = 1;
value++;
}
if (changed) {
changed = 0;
mvprintw(0, 0, "%d ", value);
refresh();
}
}
endwin();
}
移植性に煩わされず、常にWindowsを使用している場合は、 PeekConsoleInput
を使用できます。これにより、どのコンソール入力イベントが待機しているかがわかります。
少なくとも1つの保留中の入力イベントが発生するまでブロックされるため、ReadConsoleInput
を(簡単に)使用することはできません。
コードには2つの問題があります。深刻なものとそうでないもの。
最初にわかった問題は、getch()がブロッキング関数であることです。つまり、関数呼び出しはキーが押されるまで戻りません。
2番目の問題は、マイナーではありますが、プログラムは1秒ごとにしか入力に応答しないことです。
最初のカウンターを5から始めて、要件を少し変更しました。
#include <windows.h>
int main(void)
{
int Counter;
time_t StartTime;
DWORD EventCount;
Counter=5;
do
{
StartTime=time(NULL);
do
{
Sleep(10); /* in ms. Don't hog the CPU(s). */
GetNumberOfConsoleInputEvents(GetStdHandle(STD_INPUT_HANDLE),&EventCount);
}
while( (StartTime==time(NULL)) && (EventCount==0) );
/* Wait for a timeout or a key press. */
if (EventCount!=0) /* Have a key press. */
{
FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE)); /* Clear the key press. */
Counter++;
}
else /* Timed out. */
Counter--;
printf("Counter = %d\n",Counter);
}
while(Counter>0);
return(0);
}
Microsoft Visual C++ 2015を使用してコンパイル(コマンドライン: "cl main.c")。
Windows 7および10でテスト済み。