如何防止 scanf() 永远等待输入字符?

问题描述 投票:0回答:6

我想在控制台应用程序中完成以下任务:

  1. 如果用户输入一个字符,应用程序将执行以下操作 相应的任务。例如,如果用户输入
    1
    ,则程序 将执行任务1,如果用户输入
    q
    ,程序将退出;
  2. 如果用户什么都不输入,程序会每隔10秒执行一次默认任务(时间不必很严格)。

这是我的代码:

#include <stdio.h>
#include <time.h>

char buff[64];
char command;

while(command != 'q')
{
   begin:
   printf(">> ");
   scanf("%s", buff);
   command = buff[0];

   switch (command)
   {
       case '1':
       // task 1 code will be added here;
          break;
       case '2':
       // task 2 code will be added here;
          break;
       case 'q':
          printf("quit the loop.\n");
          break;
   }

   // wait for 10 seconds;
   Sleep(10000);
   // default task code will be added here;

  if(command != 'q')
  {
     goto begin;
  }

}

但问题是,如果没有输入字符,程序将永远陷入

scanf()
函数行等待输入字符。所以我想知道如何在一定时间后跳过
scanf()
行,我的意思是,例如,如果1秒后没有输入,程序可以继续,从而完成上面列出的第二件事。

如果重要的话,平台是 Windows。

我删除了

while()
后面的分号,这是一个明显的错误。

c scanf
6个回答
13
投票

尝试使用 select() 函数。然后你可以等待 10 秒,直到你可以从 stdin 读取而不阻塞。如果 select() 返回零,则执行默认操作。 我不知道这是否适用于 Windows,这是 POSIX 标准。如果你碰巧在unix/linux上开发,请尝试

man select

我刚刚使用 select 编写了一个工作示例:

#include <stdlib.h>
#include <stdio.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#define MAXBYTES 80

int main(int argc, char *argv[])
{
        fd_set readfds;
        int    num_readable;
        struct timeval tv;
        int    num_bytes;
        char   buf[MAXBYTES];
        int    fd_stdin;

        fd_stdin = fileno(stdin);

        while(1) {
                FD_ZERO(&readfds);
                FD_SET(fileno(stdin), &readfds);

                tv.tv_sec = 10;
                tv.tv_usec = 0;

                printf("Enter command: ");
                fflush(stdout);
                num_readable = select(fd_stdin + 1, &readfds, NULL, NULL, &tv);
                if (num_readable == -1) {
                        fprintf(stderr, "\nError in select : %s\n", strerror(errno));
                        exit(1);
                }
                if (num_readable == 0) {
                        printf("\nPerforming default action after 10 seconds\n");
                        break;  /* since I don't want to test forever */
                } else {
                        num_bytes = read(fd_stdin, buf, MAXBYTES);
                        if (num_bytes < 0) {
                                fprintf(stderr, "\nError on read : %s\n", strerror(errno));
                                exit(1);
                        }
                        /* process command, maybe by sscanf */
                        printf("\nRead %d bytes\n", num_bytes);
                        break; /* to terminate loop, since I don't process anything */
                }
        }

        return 0;
}

注意:下面的 poll() 示例也可以,没有问题。对于其余部分,我选择将可用字节读入缓冲区(最多 MAXBYTES)。之后可以对其进行扫描。 (scanf() 只是不太适合我的朋友,但这是个人品味问题)。


9
投票

这是一个如何使用

poll
执行此操作的示例(可能是 Linux 上最“正确”的方法):

#include <unistd.h>
#include <poll.h>
#include <stdio.h>

int main()
{
    struct pollfd mypoll = { STDIN_FILENO, POLLIN|POLLPRI };
    char string[10];

    if( poll(&mypoll, 1, 2000) )
    {
        scanf("%9s", string);
        printf("Read string - %s\n", string);
    }
    else
    {
        puts("Read nothing");
    }

    return 0;
}

超时是

poll
的第三个参数,以毫秒为单位 - 此示例将等待 2 秒以等待标准输入上的输入。 Windows 有
WSAPoll
,其工作原理应该类似。


3
投票

但问题是程序会永远陷入 scanf() 函数所在行等待输入字符,

删除

while
后面的分号。


2
投票

尝试闹钟(3)

#include <stdio.h>
#include <unistd.h>
int main(void)
{
   char buf [10];
   alarm(3);
   scanf("%s", buf);
   return 0;
}

2
投票

正如其他人所说,实现真正异步 IO 的最佳方法是使用

select(...)

但是做你想要的事情的一种快速而肮脏的方法是使用

getline(...)
它将返回每次读取的字节数(不挂在 IO 上)并在没有读取字节时返回 -1。

以下内容来自 getline(3) 手册页:

// The following code fragment reads lines from a file and writes them to standard output.  
// The fwrite() function is used in case the line contains embedded NUL characters.

char *line = NULL;
size_t linecap = 0;
ssize_t linelen;
while ((linelen = getline(&line, &linecap, fp)) > 0)
    fwrite(line, linelen, 1, stdout);

2
投票

不幸的是,您所要求的在普通的 ISO C 中是不可能的。但是,大多数平台都提供特定于平台的扩展,这些扩展可以提供您所需的功能。

例如,在 Linux 上,您可以使用

ncurses
库。

既然您说您的问题适用于 Microsoft Windows,那么您正在寻找的两个功能是

WaitForSingleObject
ReadConsoleInput
。为此,您将无法使用函数
scanf
或 C 风格流 (
FILE*
)。

函数

WaitForSingleObject
允许您等待,直到特定对象处于有信号状态。它还允许您设置超时(在您的情况下应该是 10 秒)。

WaitForSingleObject
可以等待的一种对象是控制台句柄。当有新的输入可供
ReadConsoleInput
读取时,它将处于有信号状态。根据我的测试,它不能与
ReadConsole
一起可靠地工作,因为即使
WaitForSingleObject
指示输入正在等待,函数
ReadConsole
有时仍然会阻塞。这是因为与
ReadConsoleInput
相比,函数
ReadConsole
会过滤一些事件。因此,尝试使用
ReadConsole
读取一个事件实际上可能会尝试读取多个原始事件,如果没有可用的未过滤原始事件,这将导致该函数阻塞。函数
ReadConsoleInput
没有这个问题,因为它直接处理原始事件并且不过滤任何事件。

这是我的程序,它使用上面提到的功能,并且完全按照您的要求进行操作。

#define WIN32_LEAN_AND_MEAN

#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

void DoDefaultTask()
{
    printf( "Doing default task.\n" );
}

void DoTask1()
{
    printf( "Doing task #1.\n" );
}

void DoTask2()
{
    printf( "Doing task #2.\n" );
}

int main(void)
{
    INPUT_RECORD ir;
    HANDLE hConInput = GetStdHandle(STD_INPUT_HANDLE);
    DWORD dwReadCount;
    bool should_prompt = true, quit = false;

    while ( !quit )
    {
        //prompt user for input
        if ( should_prompt )
        {
            printf( "Please select an option: " );
            should_prompt = false;
        }

        //flush output
        fflush( stdout );

        switch ( WaitForSingleObject( hConInput, 10000 ) )
        {
        case WAIT_OBJECT_0:

            //attempt to read input
            if ( !ReadConsoleInput( hConInput, &ir, 1, &dwReadCount ) || dwReadCount != 1 )
            {
                fprintf( stderr, "Unexpected input error!\n" );
                exit( EXIT_FAILURE );
            }

            //only handle key-down events
            if ( ir.EventType != KEY_EVENT || !ir.Event.KeyEvent.bKeyDown )
                break;

            //echo output, if character is printable
            if ( isprint( (unsigned char)ir.Event.KeyEvent.uChar.AsciiChar ) )
            {
                printf( "%c", ir.Event.KeyEvent.uChar.AsciiChar );
            }

            printf( "\n" );

            switch ( ir.Event.KeyEvent.uChar.AsciiChar )
            {
            case '1':
                DoTask1();
                break;
            case '2':
                DoTask2();
                break;
            case 'q':
                printf( "Quitting program...\n" );
                quit = true;
                break;
            default:
                printf( "Unknown command.\n" );
            }

            should_prompt = true;

            break;

        case WAIT_TIMEOUT:
            printf( "Timeout!\n" );
            DoDefaultTask();
            should_prompt = true;
            break;

        default:
            fprintf( stderr, "unexpected error!" );
            exit( EXIT_FAILURE );
        }
    }

    return 0;
}

该程序具有以下行为:

Please select an option: 1
Doing task #1.
Please select an option: 2
Doing task #2.
Please select an option: 3
Unknown command.
Please select an option: 4
Unknown command.
Please select an option: Timeout!
Doing default task.
Please select an option: Timeout!
Doing default task.
Please select an option: 1
Doing task #1.
Please select an option: 3
Unknown command.
Please select an option: 2
Doing task #2.
Please select an option: 1
Doing task #1.
Please select an option: Timeout!
Doing default task.
Please select an option: q
Quitting program...
© www.soinside.com 2019 - 2024. All rights reserved.