我对以下程序中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而不是最后一个值。
我知道程序需要整数值,但是我想知道输入字符代替整数时会发生什么? 上面发生的一切是什么原因?
答案 0 :(得分:6)
当您输入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";
}
答案 1 :(得分:5)
如果scanf
在输入流中看到与转换说明符不匹配的字符,它将停止转换并在输入流中保留有问题的字符。
有几种方法可以解决这个问题。一种是将所有内容作为文本阅读(使用scanf
使用%s
或%[
转换说明符或fgets
),然后使用atoi
或strtol
进行转换(我的首选方法)。
或者,您可以检查scanf
的返回值;它将指示成功转换的次数。因此,如果scanf("%d", &i);
等于0,那么您就知道输入流中存在错误的字符。您可以使用getchar()
使用它,然后重试。
答案 2 :(得分:2)
您永远不会指望您的用户输入有效的内容。最佳做法是read the input into a string and try to convert it to integer。 If the input is not an integer,您可以向用户发送错误消息。
答案 3 :(得分:2)
问题是,当您输入的输入不是预期类型(由%d
为scanf指定,而int
类型为cin>>i;
时,输入流不是高级的,这导致两个操作都试图从完全相同的错误输入中提取相同类型的数据(这次也失败了),因此你永远不会要求另一个输入。
为确保不会发生这种情况,您需要检查两个操作的返回值(请阅读手册,了解每个操作的报告错误)。如果发生 错误(如输入字符时),则需要清除错误,使用无效输入并重试。我发现在C ++中使用std::gtline()
而不是int
甚至是std::string
从交互式用户输入时读取整行更好,这样您就可以进入这种“无限”循环
答案 4 :(得分: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.
无法匹配整数。
答案 5 :(得分: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
缓冲区转换为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;
}
答案 6 :(得分:1)
您可以检查scanf
的返回值,以确定是否已正确解析整数(return = 1)。如果失败,您可以选择:要么通知用户错误并终止,要么通过使用scanf("%s" ...)
读取下一个令牌,或者发出警告来恢复。