Как вы пишете программу на C, чтобы увеличить число нажатием клавиши и автоматически уменьшать ее в секунду?
Я пытаюсь написать программу, в которой число начинается с 0, но когда вы нажимаете любую клавишу, она увеличивается на 1. Если ничего не нажимается, оно продолжает уменьшаться на 1 секунду, пока не достигнет 0. Каждое приращение или декремент отображается в окне консоли.
Проблема с моим подходом заключается в том, что ничего не происходит, пока я не getch()
клавишу (то есть, она проверяет, нажата ли что-либо с помощью getch()
). Как проверить, что ничего не нажата? И, конечно же, !getch()
не работает, потому что для этого все равно нужно будет проверить keypress, который сводит на нет цель.
ОС: Windows 10 Enterprise, IDE: Код :: Блоки
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);
}
}
}
}
Ответы
Ответ 1
Следующая короткая программа не требует ни 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
(кажется необходимым на некоторых платформах)
Ответ 2
Это можно сделать с помощью многопоточности, как уже было предложено, но есть и другие возможности.
Например, ncurses имеет возможность ждать ввода с тайм-аутом.
Пример для ncurses (написанный Константином):
initscr();
timeout(1000);
char c = getch();
endwin();
printf("Char: %c\n", c);
Я думаю, что poll
также может быть использован на stdin
чтобы проверить, доступен ли вход.
И чтобы ваша программа была более отзывчивой, вы могли бы снизить свой сон или задержку до, например, 100 мс, и только уменьшить, если десять итераций сна прошли без ввода. Это уменьшит задержку ввода.
Ответ 3
Вот пример 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
.
Ответ 4
Вам нужно использовать поток, и вам нужно использовать __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;
}
Ответ 5
Другой пример использования 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();
}
Ответ 6
Вот еще один способ, который использует 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
будет использовать 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);
Ответ 7
Если вас не беспокоит мобильность, и вы всегда будете использовать Windows, вы можете использовать PeekConsoleInput
, который сообщает вам, какие события ввода в консоль ждут.
Вы не можете (легко) использовать ReadConsoleInput
, потому что он блокируется до тех пор, пока не будет хотя бы одно ожидающее событие ввода.
Ответ 8
У вашего кода две проблемы; один серьезный, другой нет.
Первая проблема, как вы выяснили, заключается в том, что 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.