6

看看这段代码:

#include <iostream>
using namespace std;
int main()
{
    string s;
    int n;
    float x;
again:
    cout << "Please Type this: ABC456 7.8 9 XYZ\n";
    cin >> s >> n >> x >> s;
    cout << "\nDo you want to try Again(y/n)? ";
    char t;
    cin >> t;
    if (t=='y' || t=='Y')
        goto again;
    return 0;
}

只需尝试键入“ABC456 7.8 9 XYZ”并按回车,将导致程序退出,然后提示用户重试。我知道输入是错误的,它们不在它们的类型中,但为什么会导致退出?以及如何避免这种退出?

4

7 回答 7

7

改变

cin >> s >> n >> x >> s;

cin >> s >> x >> n >> s;

当您7.8作为第二个输入输入时,您将它收集在一个整数变量而不是浮点变量中。因此,当您输入时:

ABC456 7.8 9 XYZ

s获取ABC456n获取7(因为它是 int 类型并且输入缓冲区仍然.8 9 XYZ\n在其中)。下一个n得到.8,最后s得到"9"。现在输入缓冲区已经XYZ\n在其中了。下次当您将输入读入t以获取用户的选择时,X会被读入t并且因为它不是yor Y,所以循环退出。

于 2012-04-30T06:18:47.350 回答
3

在后面输入调试行cin

cout << "s = " << s << "   n = " << n << "   x = " << x;  

并运行

Please Type this: ABC456 7.8 9 XYZ
ABC456 7.8 9 XYZ
s = 9   n = 7   x = 0.8

很明显,第一个 ABC456 被读入字符串s。下一个是整数,因此只有7被读入n0.8部分被读入float x9现在再次分配下一个输入s,因此最终值为s字符串“9”。现在 的第一个字符X被送入下一个cin它被分配到的位置tcout << "\nt = " << t;要确认,只需在输入后插入另一条调试行t。因此,分配给“X”if的值是假的,t因此程序退出。

如果您输入ABC456 7.8 9 YZ,程序将再次要求输入,因为现在t将有“​​Y”。

于 2012-04-30T06:28:58.430 回答
2

当流提取运算符>>遇到无效输入时,它会将流置于不再提取输入的模式。

您可以使用布尔测试(例如 )检测此模式if ( cin ),并使用 重置它cin.clear()。这样做之后,无效的输入仍保留在cin的输入缓冲区中,因此您需要以某种方式对其进行处理,例如 by ignore

更好的是,提取运算符返回cin,因此您可以在提取时进行测试:

if ( ! ( cin >> s >> n >> x >> s ) ) {
    cout << "You fail horribly!\n";
    cin.clear();
    cin.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );
}

如需更深入的了解,请参阅basic_ios 上的标志语义(我确信此站点上的一些问题与此完全相同)。

于 2012-04-30T06:38:52.727 回答
2

回答https://stackoverflow.com/a/10379322/924727说明会发生什么。关于为什么,我们必须深入一点哲学。

C++ 流模型不考虑“人机交互”:它是一种通用转换器,可将几乎无限的字符序列转换为以空格分隔的“值”列表,然后再转换为提供的“变量”。

没有“对话的输入和输出交错”的概念。如果您将输入写入文本文件,例如myinput.txt(取消正确输入)

ABC456 9 7.8 XYZ
Y
ABC456 5 6.7 XYZ
N

并从命令提示符运行您的程序,例如

   myprogram < myinput.txt

您的程序将运行......并且不需要“暂停”即可查看输出,因为没有人坐在那里查看并回答它。

程序暂停等待用户输入不是因为cin >>,而是因为cin没有处于失败状态并且缓冲区为空并且缓冲区重新映射的源是控制台。在返回之前等待'\n'的是控制台,而不是cin。

什么时候cin >> n叫...

  • operator>>函数被调用,它...
  • 从流语言环境中获取 num_get facet 并调用它的 get 函数...
  • 重复调用流缓冲区sbumpc以获取数字并计算数值。
  • 如果缓冲区有内容,它只是一个接一个地返回它的字符。当没有更多字符存在时(或者如果它是空的)......
  • 缓冲区要求操作系统从低级文件中读取。
  • 如果文件是控制台,则调用控制台内部行编辑器:
  • 这使得控制台卡住让用户按下字符和一些控件(例如退格),直到按下 Enter 时
  • 控制台行编辑器将该行返回给操作系统,这将使输入 CON 文件的内容可用...
  • 这是由填充自身的缓冲区读取的(在将读取的字符传递给 cvt 语言环境方面,但这是一个细节之后)。
  • 现在做自己的回报展开。

所有这些机制都使得 - 如果您键入的内容超过要求 - 缓冲区内容仍然可用于下一次>>调用,独立于它是否是另一个程序行。

正确的“更安全”的解析需要,在读取输入后,要清除流状态并忽略以下内容直到下一个'\n'. 这通常是通过

cin.clear(); 
cin.ignore(numeric_limits<std::streamsize>::max(), '\n');

这样,任何输入的内容都会被丢弃,接下来会cin>>找到一个没有数据的缓冲区(只是'\n', 被修剪为“开始空间”),从而导致控制台再次进入行编辑模式。

于 2012-04-30T08:14:42.800 回答
1

程序在尝试将错误的数据类型放在一起时遇到问题或异常。也许您想查看 cin 上的 >> 运算符的文档,查找当它遇到错误的数据类型输入时它会做什么的详细信息,并查看 cin>> 行和您的输入以查找可能存在的任何地方发生,因此您可以验证输入是否正确处理

于 2012-04-30T06:18:55.260 回答
1

当你调用 cin.clear() 时,你应该同时调用 cin.sync()。

于 2012-04-30T07:01:11.040 回答
1

一旦流检测到错误,它就处于错误状态,并且所有进一步的输入尝试都将是无操作的。在没有首先测试读取是否成功的情况下访问流读取的变量是未定义的行为,因此至少在理论上,您的程序可以做任何事情。(实际上,如果未初始化变量的类型为char,那么您所冒的风险就是一个随机值。)

读取面向行的输入时,最好的解决方案是使用 std::getline. 然后使用输入的字符串构造一个 std::istringstream来解析行。这使输入流处于良好状态,并且已经为下一个输入同步。我会使用类似的东西:

void
getInput()
{
    std::string line;
    std::cout << "Please type this: ABC456 7.8 9 XYZ" << std::endl;
    std::getline( std::cin, line );
    if ( ! std::cin ) {
        //  Very unexpected error... 
        throw SomethingSerious();
    }
    std::string s;
    int n;
    float f;
    std::istringstream toParse( line );
    toParse >> s >> f >> n >> s;
    if ( ! toParse ) {
        //  Input incorrect...
    }
}

bool
tryAgain()
{
    std::cout << "Do you want to try again (y/n)? ";
    std::string line;
    std::getline( std::cin, line );
    return line.size() == 1 && (line[0] == 'y' || line[0] == 'Y');
        //  But I'd be more tolerant with regards to spaces...
}

bool
oneInput()
{
    getInput();
    return tryAgain();
}

int
main()
{
    while ( oneInput() ) {
    }
    return 0;
}
于 2012-04-30T08:10:06.230 回答