【问题标题】:How can I prevent scanf() to wait forever for an input character?如何防止 scanf() 永远等待输入字符?
【发布时间】:2014-01-17 23:38:43
【问题描述】:

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

  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() 后面的分号,这是一个明显的错误。

【问题讨论】:

  • 我认为没有标准的方法可以实现这一点 - 即超时。您的目标是哪个平台?
  • 你在什么平台上。如果您使用的是 linux,您可能会 pollselect 来查看 STDIN_FILENO 上是否发生了什么
  • 开始?您正在 while 条件下测试它(以 ; 结束,因此删除它),所以您不需要它(而是“继续”),也不需要 if。 — 要进行更多“交互式”输入,您可以查看 ncurses 库;有一些关于 SO 你可能感兴趣的文献,例如here;也许你可以安排一些超时时间——但当然你的代码将取决于那个不属于标准库的库。
  • 你还没有初始化command所以第一个循环是未定义的。另外,请在 while 循环结束时删除 ;,否则您的程序根本不会循环

标签: c scanf


【解决方案1】:

尝试使用 select() 函数。然后您可以等待 10 秒钟,直到您可以从标准输入读取而不会阻塞。如果 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() 并不是我的朋友,但这是个人品味问题)。

【讨论】:

  • 这里有一个类似的问题和一个例子 - stackoverflow.com/questions/7226603/timeout-function
  • 非常感谢罗纳德和格雷姆。但是,我尝试了 Graeme 建议的示例,但命令窗口将永远显示“无法读取您的输入”,并且两个显示之间没有延迟。所以我想知道select()函数的配置和它的初始化是否应该注意其他的事情,比如FD_ZEROFD_SET。非常感谢。
  • 谢谢罗纳德,非常感谢您的代码。我刚刚尝试了您的代码,但 num_readable 的值始终为 -1,我无法使用键盘输入任何内容。我也找不到头文件unistd.h,所以我不能使用read()函数。我在 Windows 7 中使用 Microsoft Visual C++ 2010 Express。
  • 好的,我错过了你关于使用 Windows 的说明。我不是 Windows 专家,但 read() 并没有做一些特别的事情。基本上,您需要一个从 stdin 读取而不会阻塞的函数,如果它无法读取其缓冲区中适合的字符数。我认为它在 Windows 中称为 ReadFile() 。您可能想要使用 FormatMessage() 而不是 strerror()。
  • 嗨罗纳德,很抱歉再次打扰您。问题是在"Enter Command:"之后不能输入任何字符,而且select()函数的返回值总是-1,屏幕会显示:"Error in选择:没有错误”。你对这个问题有什么想法吗?
【解决方案2】:

这是一个如何使用 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,应该可以类似地工作。

【讨论】:

  • WSAPoll 仅适用于套接字。
【解决方案3】:

但问题是程序会一直陷在scanf()函数的那一行等待输入字符,

删除while 之后的分号。

【讨论】:

  • 您对分号的看法完全正确。但是,这如何解决如果用户不输入任何内容,程序将在 10 秒后执行默认任务的问题?
  • 这只是第一个问题。
【解决方案4】:

试试闹钟(3)

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

【讨论】:

  • 我想知道哪个库是函数alarm(),谢谢。
【解决方案5】:

正如其他人所说,实现真正异步 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);

【讨论】:

  • 我想知道getline(...)里面的函数是哪个库,第三个参数fp是什么意思,谢谢。
  • 再一次,这直接来自man getline:“标准 C 库 (libc, -lc)”。 #include &lt;stdio.h&gt; fp 是您正在读取的文件指针,例如标准输入或其他内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-30
  • 1970-01-01
相关资源
最近更新 更多