为什么scanf似乎跳过输入?

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

下面是C程序

#include 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 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 。 这意味着,永远留在流中。

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

写这个的正确方法:

 do { ++j; if (!(cin>>i)) { //handle error, maybe you want to break the loop here? } cout< 

或者只是这个(如果你想在出现错误时退出循环):

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

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

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

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

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

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

为确保不会发生这种情况,您需要检查两个操作的返回值(请阅读手册,了解每个操作的报告错误)。 如果确实发生了错误(如输入字符时),则需要清除错误,使用无效输入并重试。 我发现在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的返回值以确定是否已正确解析整数(return should = 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方法检查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; }