Как вы пишете программу на C для увеличения числа нажатием клавиши и его автоматического уменьшения в секунду? - PullRequest
0 голосов
/ 09 октября 2018

Я пытаюсь написать программу, в которой число начинается с 0, но когда вы нажимаете любую клавишу, оно увеличивается на 1. Если ничего не нажимается, оно продолжает уменьшаться на 1 в секунду, пока не достигнет 0.Каждое увеличение или уменьшение отображается в окне консоли.

Проблема с моим подходом заключается в том, что ничего не происходит, пока я не нажму клавишу (то есть он проверяет, нажата ли какая-либо кнопка с помощью getch()).Как мне проверить, что ничего не нажимается?И, конечно, !getch() не работает, потому что для того, чтобы это работало, ему все равно нужно будет проверить, не нажата ли клавиша, что сводит на нет само назначение.

ОС: 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);
            }
        }
    }
}

Ответы [ 8 ]

0 голосов
/ 09 октября 2018

Ваш код имеет две проблемы;одна серьезная, другая нет.

Первая проблема, как вы узнали, заключается в том, что getch () является функцией блокировки.Другими словами, вызов функции не вернется, пока не будет нажата клавиша.

Вторая проблема, хотя и незначительная, заключается в том, что программа реагирует только на ввод каждую секунду.

Я имеюслегка изменил ваши требования, начальный счетчик начался с 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.

0 голосов
/ 09 октября 2018

Вот еще один способ, который использует 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);
0 голосов
/ 09 октября 2018

Если вас не беспокоит переносимость, и вы всегда будете использовать Windows, вы можете использовать PeekConsoleInput, который сообщает вам, какие события ввода с консоли ожидают.

Вы не можете (легко) использовать ReadConsoleInput, потому что он блокируется, пока не будет хотя бы одного ожидающего события ввода.

0 голосов
/ 09 октября 2018

Другой пример использования таймеров и сигналов 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();
}
0 голосов
/ 09 октября 2018

Вам нужно использовать поток и использовать __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;
}
0 голосов
/ 09 октября 2018

Следующая короткая программа не требует ни ncurses, ни потоков.Однако для этого необходимо изменить атрибуты терминала - используя tcsetattr().Это будет работать в Linux и Unix-подобных системах, но не в Windows, которая не включает заголовочный файл termios.h.(Возможно, смотрите этот пост , если вы заинтересованы в этом предмете.)

#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);

выводит терминал из канонического режима(и отключает символ 'echo'),

new_attr.c_cc[VMIN] = 0;

переводит его в режим опроса (а не в 'блокирование'), а

new_attr.c_cc[VTIME] = 10;

дает программе команду ждать до 10 децисекунддля ввода.

Обновление (2019-01-13)

  • добавление clearerr(stdin) для очистки EOF в stdin (кажется необходимымна некоторых платформах)
0 голосов
/ 09 октября 2018

Это может быть сделано с многопоточностью, как уже предлагалось, но есть и другие возможности.

ncurses например, имеет возможность ждать ввода с таймаутом .

Пример для ncurses (написанный Константином) :

initscr();
timeout(1000);
char c = getch();
endwin();
printf("Char: %c\n", c);

Я думаю, poll также можно использовать на stdin чтобы проверить, доступен ли ввод.

И чтобы сделать вашу программу более отзывчивой, вы можете снизить сон или задержку, например, до 100 мс, и уменьшить его, только если десять итераций сна прошли без ввода.Это уменьшит задержку ввода.

0 голосов
/ 09 октября 2018

Вот пример pthread, который работает на Linux.С концепцией все в порядке, но, вероятно, для этого существуют петли / библиотеки.

#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;
}

Это запускает второй поток с обратным отсчетом.Это уменьшает счетчик каждую секунду.В основном потоке есть цикл с getchar.Они оба модифицируют i.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...