5

我对以下程序中 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 而不是最后一个值。

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

4

7 回答 7

6

当您输入 时a,则cin >> i无法读取它,因为 的类型iint无法读取字符的。这意味着,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";
}
于 2012-06-07T16:10:15.453 回答
5

如果scanf在输入流中看到与转换说明符不匹配的字符,它将停止转换并将有问题的字符留在输入流中。

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

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

于 2012-06-07T16:12:29.820 回答
3

你永远不能期望你的用户输入有效的东西。最佳做法是将输入读入字符串并尝试将其转换为整数如果输入不是整数,你可以给用户一个错误信息。

于 2012-06-07T16:11:50.693 回答
3

问题是,当您输入不属于预期类型的​​输入(由%dfor scanf 和 for 指定的int类型)时cin>>i;,输入流不高级,这导致两个操作都试图从完全相同的不正确的输入(这次也失败了),因此您永远不会要求其他输入。

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

于 2012-06-07T16:12:47.353 回答
1

您忽略了返回值。查看手册中的内容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.

它无法匹配整数。

于 2012-06-07T16:12:49.973 回答
1

对于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缓冲区转换为 aistringstream来扫描输入。

    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;
    }
于 2012-06-07T16:13:31.347 回答
1

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

于 2012-06-07T16:13:35.747 回答