web-dev-qa-db-fra.com

Comment écrivez-vous un programme en C pour incrémenter un nombre en le pressant et le décrémenter automatiquement par seconde?

J'essaie d'écrire un programme dans lequel un nombre commence à 0, mais lorsque vous appuyez sur une touche, il est incrémenté de 1. Si vous n'appuyez sur aucune touche, il continue de diminuer de 1 par seconde jusqu'à atteindre 0. Chaque incrément ou décrément est affiché dans la fenêtre de la console.

Le problème avec mon approche est que rien ne se passe tant que je n’ai pas appuyé sur une touche (c’est-à-dire qu’elle vérifie si quelque chose est enfoncé avec getch()). Comment puis-je vérifier que rien n'est pressé? Et bien sûr, !getch() ne fonctionne pas car, pour que cela fonctionne, il faut toujours vérifier si vous appuyez sur une touche, ce qui annule le but lui-même.

Système d'exploitation: Windows 10 Entreprise, 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);
            }
        }
    }
}
13
Ritika

Le programme court suivant ne nécessite ni ncurses ni threads. Cependant, il faut modifier les attributs du terminal - en utilisant tcsetattr(). Cela fonctionnera sur les systèmes Linux et Unix, mais pas sur Windows, qui n'inclut pas le fichier d'en-tête termios.h. (Peut-être voir cet article si vous êtes intéressé par ce sujet.)

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

Les points essentiels sont que

new_attr.c_lflag &= ~(ICANON | ECHO);

désactive le terminal en mode canonique (et désactive le caractère 'echo'),

new_attr.c_cc[VMIN] = 0;

le place en mode interrogation (plutôt qu'en mode "bloquant"), et

new_attr.c_cc[VTIME] = 10;

indique au programme d'attendre jusqu'à 10 décisecondes pour la saisie.

Mise à jour (2019-01-13)

  • ajoutez clearerr(stdin) pour effacer EOF sur stdin (semble être nécessaire sur certaines plates-formes)
11
David Collins

Cela pourrait être fait avec le multithreading comme déjà suggéré, mais il y a d'autres possibilités.

ncurses a par exemple la possibilité d’attendre une entrée avec un timeout .

Un exemple pour ncurses (écrit par Constantin) :

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

Je pense que poll pourrait également être utilisé sur stdin pour vérifier si une entrée est disponible.

Et pour rendre votre programme plus réactif, vous pouvez réduire votre sommeil ou le retarder à par exemple 100 ms et ne le décrémenter que si dix itérations de sommeil se sont écoulées sans entrée. Cela réduira le délai d’entrée.

4
Kami Kaze

Vous devez utiliser thread et utiliser __sync_add_and_fetch et __sync_sub_and_fetch pour éviter les problèmes de concurrence.

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

Voici un autre moyen qui utilise select pour vérifier si une entrée existe et aussi pour attendre. Pas une jolie solution mais ça marche. Linux seulement si.

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

Un meilleur moyen d'éviter system("stty raw") et ces \rs serait d'utiliser tcgetattr et 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);
2
Spikatrix

Voici un exemple pthread qui fonctionne sur linux. Le concept est ok, mais il existe probablement des boucles/bibliothèques pour cela.

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

Cela commence un deuxième thread, qui a le compte à rebours. Cela diminue le compteur toutes les secondes. Sur le thread principal, il a une boucle avec getchar. Ils modifient tous les deux i.

2
matt

Un autre exemple utilisant ncurses et les timers et signaux POSIX (et les variables globales).

#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();
}
2
pmg

Si la portabilité ne vous dérange pas et que vous utiliserez toujours Windows, vous pouvez utiliser PeekConsoleInput , qui vous indique les événements d'entrée de la console en attente.

Vous ne pouvez pas (facilement) utiliser ReadConsoleInput, car il bloque jusqu'à ce qu'il y ait au moins un événement d'entrée en attente.

1
Roger Lipscombe

Votre code a deux problèmes; un sérieux, un pas.

Le premier problème, comme vous l'avez découvert, est que getch () est une fonction de blocage. En d’autres termes, l’appel de fonction ne reviendra qu’une fois que vous aurez appuyé sur une touche.

Le deuxième problème, bien que mineur, est que le programme ne répond à l'entrée que toutes les secondes.

J'ai légèrement modifié vos besoins en démarrant le compteur initial à 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);
}

Compilé à l'aide de Microsoft Visual C++ 2015 (ligne de commande: "cl main.c").
Testé sous Windows 7 et 10.

0
A.Wise