【问题标题】:Wrong Inputs will cause the program to exit错误的输入会导致程序退出
【发布时间】:2012-04-30 06:14:04
【问题描述】:

看这段代码:

#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”并按回车,它会在提示用户重试之前导致程序退出。我知道输入是错误的,它们不在它们的类型中,但为什么会导致退出?以及如何避免这种退出?

【问题讨论】:

  • @CppLearner 是标签语句again: cout &lt;&lt; "Please Type this: ABC456 7.8 9 XYZ\n";的标签
  • @CppLearner:它是指示要使用的 goto 语句的标签。亲爱的,请参见第 8 行....
  • 谢谢。首先,我希望 &lt;string&gt; 包含在标题中。让我执行......很难阅读代码在同一行。谢谢。
  • 您要求将 7.8(浮点数)作为第二项,但尝试将其读入 nint。这可能不是幸福的秘诀(因为x 得到 0.8,s 得到 9;而 XYZ 的空白或“X”读作t)。
  • 因为空白和 X 都不与 y 或 Y 相同,所以读入 t 的字符不满足循环条件。当对这样的事情感到困惑时,首先要做的是打印出关键控制值,在本例中为t。如果您在t (cout &lt;&lt; "t = [" &lt;&lt; t &lt;&lt; "]" &lt;&lt; endl;) 中打印该值,您会立即明白为什么goto 没有被执行。或者在调试器中单步执行代码并以这种方式检查值。这种技术是调试的基础。

标签: c++


【解决方案1】:

改变

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,并且由于它不是yY,因此循环退出。

【讨论】:

  • 这不是答案。 OP 想知道程序退出的原因。他故意换了类型。
  • @shiplu.mokadd.im:正在更新答案。
  • @codaddict 我只明白一件事,如果输入不仅仅是要存储的变量,那么程序将完全退出。但是为什么会这样呢?
  • @Stranger:您需要了解用户输入的内容以及在变量中读取的内容。读取每个变量后,您需要了解输入缓冲区中剩下的内容。
  • @codaddict 所以看来我应该在输入后擦除左缓冲区。有没有办法做到这一点?
【解决方案2】:

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 部分被读入floatx。现在下一个输入9 再次分配给s,因此s 的最终值是字符串“9”。现在X 的第一个字符被输入到下一个cin 分配给t。要确认只需在输入t 后插入另一个调试行cout &lt;&lt; "\nt = " &lt;&lt; t;。因此if 为假,t 的值分配给“X”,因此程序退出。

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

【讨论】:

    【解决方案3】:

    回答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 &gt;&gt;,而是因为cin 不处于失败状态并且缓冲区为空并且缓冲区重新映射的源是控制台。在返回之前等待'\n'的是控制台,而不是cin。

    cin &gt;&gt; n 被调用时...

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

    所有这些机制都使得 - 如果您键入的内容超出要求 - 缓冲区内容仍然可用于下一个 &gt;&gt; 调用,无论它是否是另一个程序行。

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

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

    这样,无论输入什么都被丢弃,下一个cin&gt;&gt; 找到一个没有数据的缓冲区(只是'\n',被修剪为“开始空间”),从而导致控制台进入队列再次编辑模式。

    【讨论】:

    • -1:有输入输出交互交错的概念,cincout就是这样链接在一起的。见basic_istream::tie()。除了不正确之外,这个答案也是漫不经心且不清楚的。
    • @Potatoswatter:在谈论哲学时,一切都是“漫无边际的”:重点不是在 Touring 意义上是正确的,而是在效果上是正确的。 cin 和 cout OP使用的方式彼此不同步。 OP 明确表示他想知道“为什么”而不是“什么”。并且要解释“为什么”,您必须只关注 OP 选择的方面。我尊重你的意见,但我认为你只是有偏见。
    • cincout 彼此同步,因为对 cin 的任何操作都会自动刷新 cout。 (这在您的步骤序列中缺失。)您支持一个不正确的断言,即库使用与库的设计意图无关的发明哲学做什么。正确的哲学论点会得到参考文献的支持,而你没有。
    • "彼此同步,因为对 cin 的任何操作都会自动刷新 cout"。不:这使 cout 与 cin 同步,但反之亦然:cin 不会被 cout“刷新”(如何??)。无论 cout 是什么,超出的输入都保留在 cin 中。这就是重点。如果你没有得到它,那可能是错误的,只有在没有其他人得到它之后。你告诉我做错误的断言,但你告诉我这样做也是一样的。关于“图书馆设计意图......”,请至少让自己成为一名图书馆设计师!
    • "问题的答案是 cin 进入错误状态" 不,在这种特殊情况下 cin 不会出错。数据按预期以不同方式分布,让“尾部”可用于下一个输入,该“尾部”不会因中间输出的存在而被刷新,并且被认为是值“否”的有效答案。我不想进入这样设计的原因(无论如何,您在实现中的贡献不是“设计”,规范已经存在),但这就是重点。
    【解决方案4】:

    当流提取操作符&gt;&gt; 遇到无效输入时,它会将流置于不再提取输入的模式。

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

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

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

    如需更深入的了解,请参阅 Semantics of flags on basic_ios(我确信本网站上的一些问题与此完全相同)。

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 为什么? sync 在输入流上没有标准化行为。
        • 在输入 t 之前使用 cin.sync() 可以解决问题,谢谢。
        • 一般来说不是。输入流上的sync 具有实现定义的行为。预计它通常是无操作的。
        • 所以我会等待另一个答案,如果没有答案,那么这个答案将被接受。那么避免这个问题的一般技巧是什么?
        • sync会清空输入缓冲区,调用这个函数会清空错误的输入以避免再次读取。
        【解决方案7】:

        一旦流检测到错误,它就处于错误状态,并且所有 进一步的输入尝试将是无操作的。访问由 a 读取的变量 流而不首先测试读取是否成功是未定义的 行为,所以至少在理论上,你的程序可以做任何事情。 (在 练习,如果未初始化的变量是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;
        }
        

        【讨论】:

        • 感谢您的回复,但这是一个漫长的技巧!顺便说一句,再次感谢,它也可以提供帮助;)
        • @Stranger 正确处理输入通常相当复杂,因为您必须能够处理用户输入的任何内容。用户输入的内容可能非常难以预测。
        猜你喜欢
        • 1970-01-01
        • 2020-07-12
        • 1970-01-01
        • 2021-12-11
        • 1970-01-01
        • 2011-02-08
        • 1970-01-01
        • 1970-01-01
        • 2020-04-09
        相关资源
        最近更新 更多