为什么 scanf 似乎跳过输入

Why does scanf appear to skip input?

本文关键字:输入 scanf 为什么      更新时间:2023-10-16

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

下面在 C 程序中

#include<stdio.h>
int main()
{
    int i, j=0;
    do
    {
        ++j;
        scanf("%d", &i);
        printf("nn%d %dnn", i, j);
    }
    while((i!=8) && (j<10));  
    printf("nJ = %dn", 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 而不是最后一个值。

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

当您输入 a 时,cin >> 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";
}
如果

scanf在输入流中看到与转化说明符不匹配的字符,则会停止转换并将违规字符保留在输入流中。

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

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

您永远不能期望您的用户输入有效的内容。最佳做法是将输入读入字符串并尝试将其转换为整数。如果输入不是整数,则可以向用户提供错误消息。

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

为了确保不会发生这种情况,您需要检查两个操作的返回值(阅读手册以了解每个操作如何报告错误)。 如果确实发生错误(如输入字符时),则需要清除错误,使用无效输入,然后重试。 我发现C++,在交互式地从用户那里获取输入时,使用 std::gtline() 而不是int甚至std::string来阅读整行更好,因此您可以进入您体验到的这个"无限"循环。

您忽略了返回值。请参阅手册中关于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.

它无法匹配整数。

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

对于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;
    }