webentwicklung-frage-antwort-db.com.de

Wie schreibt man ein C-Programm, um eine Zahl durch Tastendruck zu erhöhen und diese pro Sekunde automatisch zu dekrementieren?

Ich versuche, ein Programm zu schreiben, in dem eine Zahl mit 0 beginnt, aber wenn Sie eine beliebige Taste drücken, wird sie um 1 erhöht. Wenn nichts gedrückt wird, sinkt sie um 1 pro Sekunde, bis sie 0 erreicht. Jedes Inkrement oder Dekrement wird im Konsolenfenster angezeigt.

Das Problem bei meinem Ansatz ist, dass nichts passiert, bis ich eine Taste drücke (dh es wird überprüft, ob mit getch() etwas gedrückt wird). Wie überprüfe ich, dass nichts gedrückt ist? Und natürlich funktioniert !getch() nicht, weil dies für die Funktionsweise immer noch auf Tastendruck überprüft werden muss, wodurch der Zweck selbst aufgehoben wird.

Betriebssystem: 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);
            }
        }
    }
}
13
Ritika

Das folgende kurze Programm erfordert weder ncurses noch Threads. Es ist jedoch erforderlich, die Terminalattribute zu ändern - mit tcsetattr(). Dies funktioniert auf Linux- und Unix-artigen Systemen, nicht jedoch auf Windows - was die termios.h-Header-Datei nicht enthält. (Vielleicht sehen Sie diesen Beitrag wenn Sie sich für dieses Thema interessieren.)

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

Die entscheidenden Punkte sind das

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

nimmt das Terminal aus dem kanonischen Modus heraus (und deaktiviert das Zeichen 'Echo'),

new_attr.c_cc[VMIN] = 0;

versetzt es in den Abrufmodus (anstatt "zu blockieren"), und

new_attr.c_cc[VTIME] = 10;

weist das Programm an, bis zu 10 Sekunden auf die Eingabe zu warten.

Update (2019-01-13)

  • fügen Sie clearerr(stdin) hinzu, um EOF auf stdin zu löschen (scheint auf einigen Plattformen erforderlich zu sein)
11
David Collins

Dies könnte, wie bereits vorgeschlagen, mit Multithreading erfolgen, es gibt jedoch andere Möglichkeiten.

ncurses hat zum Beispiel die Möglichkeit, auf die Eingabe mit einem timeout zu warten.

Ein Beispiel für ncurses (geschrieben von Constantin) :

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

Ich denke, poll könnte auch bei stdin verwendet werden, um zu prüfen, ob eine Eingabe verfügbar ist.

Und um Ihr Programm ansprechender zu machen, können Sie Ihren Schlaf verringern oder sich auf beispielsweise 100 ms verzögern und nur dann dekrementieren, wenn zehn Iterationen des Schlafes ohne Eingabe vergangen sind. Dadurch wird die Eingangsverzögerung reduziert.

4
Kami Kaze

Sie benötigen einen Thread und müssen __sync_add_and_fetch und __sync_sub_and_fetch verwenden, um Parallelitätsprobleme zu vermeiden

#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

Hier ist eine weitere Möglichkeit, mit select zu überprüfen, ob Eingaben vorhanden sind, und auch zu warten. Keine schöne Lösung, aber es funktioniert. Linux allerdings nur.

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

Eine bessere Möglichkeit, system("stty raw") zu vermeiden, und diese \rs wäre tcgetattr und 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

Hier ist ein Beispiel für ein pthread, das unter Linux funktioniert. Das Konzept ist in Ordnung, aber es gibt wahrscheinlich bereits existierende Loops/Bibliotheken.

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

Dadurch wird ein zweiter Thread gestartet, der den Countdown enthält. Das verringert den Zähler jede Sekunde. Auf dem Haupt-Thread gibt es eine Schleife mit getchar. Sie ändern beide i.

2
matt

Ein anderes Beispiel, das ncurses und POSIX-Timer und -Signale (und globale Variablen) verwendet.

#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

Wenn Sie sich nicht mit der Portabilität beschäftigen und immer Windows verwenden, können Sie PeekConsoleInput verwenden. Hier erfahren Sie, welche Konsolen-Eingangsereignisse auf Sie warten.

Sie können ReadConsoleInput nicht (leicht) verwenden, da es blockiert, bis mindestens ein anstehendes Eingabeereignis vorliegt.

1
Roger Lipscombe

Ihr Code hat zwei Probleme. einer ernst, einer nicht.

Das erste Problem ist, wie Sie herausgefunden haben, dass getch () eine Sperrfunktion ist. Mit anderen Worten, der Funktionsaufruf kehrt erst zurück, wenn eine Taste gedrückt wurde.

Das zweite Problem, obwohl geringfügig, ist, dass das Programm nur jede Sekunde auf Eingaben reagiert.

Ich habe Ihre Anforderungen leicht geändert und den Anfangszähler bei 5 gestartet.

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

Kompiliert mit Microsoft Visual C++ 2015 (Befehlszeile: "cl main.c").
Getestet unter Windows 7 und 10.

0
A.Wise