【问题标题】:Why does scanf appear to skip input?为什么 scanf 似乎跳过输入?
【发布时间】:2012-06-11 17:29:14
【问题描述】:

我对以下程序中 scanf 的行为感到困惑。 scanf 似乎输入了一次,然后不再输入,直到打印出一个字符流。

下面是一个C程序

#include<stdio.h>
int main()
{
    int i, j=0;

    do
    {
        ++j;
        scanf("%d", &i);
        printf("\n\n%d %d\n\n", i, j);
    }
    while((i!=8) && (j<10));  

    printf("\nJ = %d\n", j);
    return 0;
}

在这里,直到我输入任何整数程序都可以正常工作,但是当输入一个字符时,它会继续打印 i 的最后输入值并且永远不会停止(直到循环退出时 j 为 10),以便 scanf 接受下一个输入.

output::  
1    <-----1st input
1 1
2    <---- 2nd input
2 2
a    <---- character input
2 3  
2 4
2 5
2 6
2 7
2 8
2 9
2 10

J = 10  

同样的事情也在 c++ 中发生。

#include<iostream>
using namespace std;
int main()
{
    int i, j=0;

    do
    {
        ++j;
        cin>>i;
        cout<<i<<" "<<j<<"\n";
    }
    while((i!=8) && (j<10));

    cout<<"\nj = "<<j<<"\n";
}   


output of c++ program ::  
1     <-----1st input
1 1
2     <-----2nd input
2 2
a    <------ character input
0 3
0 4
0 5
0 6
0 7
0 8
0 9
0 10

j = 10  

c++ 中唯一的变化是打印 0 而不是最后一个值。

我知道程序需要整数值,但我想知道当输入字符代替整数时会发生什么? 以上所有发生的原因是什么?

【问题讨论】:

标签: c++ c io scanf cout


【解决方案1】:

当您输入a 时,cin &gt;&gt; i 无法读取它,因为i 的类型是int,其中一个字符无法读取。这意味着,a 将永远保留在流中。

现在为什么i 打印0 是另一回事。实际上它可以打印任何东西。一旦尝试读取失败,i 的内容就不会被定义。 scanf 也会发生类似的情况。

正确的写法:

do
{
    ++j;
    if (!(cin>>i)) 
    {
       //handle error, maybe you want to break the loop here?
    }
    cout<<i<<" "<<j<<"\n";
}
while((i!=8) && (j<10));

或者简单地说(如果你想在发生错误时退出循环):

int i = 0, j = 0;
while((i!=8) && (j<10) && ( cin >> i) )
{
    ++j;
    cout<<i<<" "<<j<<"\n";
}

【讨论】:

  • 这是否正确取决于您要如何处理输入错误。
  • "The content of i is not defined once the attempt to read fails" 对于cin &gt;&gt; i,在C++11中定义
【解决方案2】:

您永远不能期望您的用户输入有效的内容。最佳做法是read the input into a string and try to convert it to integerIf the input is not an integer,你可以给用户一个错误信息。

【讨论】:

    【解决方案3】:

    如果scanf 在输入流中发现与转换说明符不匹配的字符,它会停止转换并将违规字符留在输入流中。

    有几种方法可以解决这个问题。一种是将所有内容都读取为文本(使用%s%[ 转换说明符或fgets),然后使用atoistrtol 进行转换(我的首选方法)。

    或者,您可以检查scanf的返回值;它将指示成功转换的次数。因此,如果scanf("%d", &amp;i); 等于 0,那么您就知道输入流中有错误字符。您可以使用getchar() 使用它,然后重试。

    【讨论】:

      【解决方案4】:

      问题是当你输入一个不属于预期类型的​​输入时(由%d指定为scanf,int类型为cin&gt;&gt;i;指定,输入流不高级,这导致两个操作尝试从完全相同的错误输入中提取相同类型的数据(这次也同样失败),因此您永远不会要求其他输入。

      为确保不会发生这种情况,您需要检查两个操作的返回值(阅读手册了解每个操作如何报告错误)。如果发生错误确实(例如当您输入字符时),您将需要清除错误,使用无效输入并重试。我发现在 C++ 中使用std::gtline() 而不是int 甚至std::string 以交互方式获取用户输入时读取整行会更好,因此您会进入您所经历的这个“无限”循环。

      【讨论】:

        【解决方案5】:

        您忽略了返回值。查看手册中关于scanf(3) 的内容:

        RETURN VALUE
        These functions return the number of input items successfully matched and assigned, which can be fewer than provided for, or even zero in the event of an early matching failure.

        匹配整数失败。

        【讨论】:

          【解决方案6】:

          对于scanf,您需要检查其返回值以查看输入的转换是否有效。 scanf 将返回成功扫描的元素数。如果转换不起作用,它会留下输入,您可以尝试不同的扫描,或者只是报告错误。例如:

          if (scanf("%d", &i) != 1) {
              char buf[512];
              fgets(buf, sizeof(buf), stdin);
              printf("error in line %d: got %s", j, buf);
              return 0;
          }
          

          在您的程序中,由于输入是单独存在的,因此您的循环会重复尝试读取相同的输入。

          在 C++ 中,您使用 fail 方法检查失败,但输入流失败状态是粘性的。所以它不会让你在不清除错误状态的情况下进一步扫描。

              std::cin >> i;
              if (std::cin.fail()) {
                  std::string buf;
                  std::cin.clear();
                  std::getline(cin, buf);
                  std::cout
                       << "error in line " << j
                       << ": got " << buf
                       << std::endl;
                  return 0;
              }
          

          在您的程序中,由于您从未清除故障状态,因此循环在故障状态下使用cin 重复,因此它只是报告故障而不做任何事情。

          在这两种情况下,如果您先读取输入行,然后尝试解析输入行,您可能会发现使用输入更容易或更可靠。在伪代码中:

          while read_a_line succeeds
              parse_a_line
          

          在 C 中,读取一行的关键在于,如果它比缓冲区长,则需要检查这一点并将多个 fgets 调用结果连接在一起以形成一行。而且,要解析一行,您可以使用sscanf,它类似于scanf,但适用于字符串。

              if (sscanf(buf, "%d", &i) != 1) {
                  printf("error in line %d: got %s", j, buf);
                  return 0;
              }
          

          在 C++ 中,对于格式化输入的快速低级解析,我也更喜欢sscanf。但是,如果您想使用流方法,您可以将string 缓冲区转换为istringstream 以扫描输入。

              std::getline(cin, buf);
              if (std::cin.fail()) {
                  break;
              }
              std::istringstream buf_in(buf);
              buf_in >> i;
              if (buf_in.fail()) {
                  std::cout << "error in line " << j
                       << ": got " << buf
                       << std::endl;
                  return 0;
              }
          

          【讨论】:

            【解决方案7】:

            您可以检查scanf 的返回值以确定是否正确解析了一个整数(返回应该=1)。如果失败,您可以选择:通知用户错误并终止,或者通过读取带有scanf("%s" ...) 的下一个令牌(可能带有警告)来恢复。

            【讨论】:

              猜你喜欢
              • 2012-05-21
              • 2021-11-13
              • 2017-08-12
              • 2021-11-07
              • 1970-01-01
              • 1970-01-01
              • 2011-08-30
              相关资源
              最近更新 更多