【问题标题】:C++ input failure: multiple inputs in a single statementC++ 输入失败:单个语句中有多个输入
【发布时间】:2015-03-24 23:19:07
【问题描述】:

我的 C++ 教授坚持认为,在检查输入故障时,必须为每个单独的输入使用单独的 while() 循环。他指出,以下检查在单个 cin 语句中收集的多个输入的方法将不起作用:

while (!good){
    cout << "Enter the length and the width of the rectangle: ";
    cin >> length >> width;
    if(!cin){
        cout << "Bad input, try again" << endl;
        cin.clear();
        cin.ignore(200, '\n');}
    else {
        good = true;}}

他提出的方法:

bool good = false;
while (!good){
    cout << "Enter the length rectangle: ";
    cin >> length;
    if(!cin){
        cout << "Bad input, try again" << endl;
        cin.clear();
        cin.ignore(200, '\n');}
    else {
        good = true;}}  
while (good){
    cout << "Enter the width rectangle: ";
    cin >> width;
    if(!cin){
        cout << "Bad input, try again" << endl;
        cin.clear();
        cin.ignore(200, '\n');}
    else {
        good = false;}}

我上面的方法似乎工作得很好。如果任何一个输入都输入了非数字字符,循环将提示输入新的输入并清除失败状态。

像我所做的那样检查输入失败是否只是被认为是错误的形式?我希望更清楚地了解为什么我的方法是错误的。如果我的方法有问题,有没有一种方法可以检查输入失败,而无需为每个用户输入使用单独的循环?我主要问是因为我正在编写一个程序,该程序涉及从文件中获取许多输入,并且单独检查每个变量似乎过于乏味。我在想一定有更好的方法。

【问题讨论】:

  • 您的教授需要了解这种称为 do-while 的新语法(只有大约 40 年的历史)。
  • 与希腊化名更接近:多注意,代码的两个版本都不会检查eof标志。
  • 您可能会发现C++ FAQ: How can I get std::cin to skip invalid input characters? 很有用(请参阅第二个代码示例。)
  • @remyabel:两种变体都已经这样做了。
  • @BenVoigt 你看链接了吗?我说的是减少代码的大小。

标签: c++


【解决方案1】:

两者都是“正确的”,因为它们从失败的输入中恢复并给用户另一个机会。

两者都只对交互式输入有用。您提到从文件中读取-实际上无法恢复。忽略一个损坏的字段只会导致下一个字段被消耗,并且解释与它在文件中的位置所指示的不同。从文件读取时最好的做法是输出尽可能好的解释(文件中发生错误的确切位置,例如行号和列号、预期的数据类型以及遇到的数据无效) .

在交互使用中,清空缓冲区再提示是很有用的。你的版本有个缺点是在正确输入长度后,如果用户粗手指宽度,他们不能只是重新输入错误的数据,但也必须再次输入长度。

然而,第二次编写循环是毫无意义的。相反,您应该编写一个辅助函数,例如:

template<typename T>
T prompt_for_value( const char* const prompt )
{
    T result;
    while (true) {
         std::cout << prompt << std::flush;
         if (std::cin >> result) return result;
         std::cout << "Bad input, try again" << std::endl;
    }
}

double width = prompt_for_value<double>("Enter the width in meters: ");
double height = prompt_for_value<double>("Enter the height: ");

请注意,代码总体上更短,并且避免了 good 变量的笨拙使用,该变量在原始代码中途颠倒了其含义。此外,调用站点现在非常干净,完全专注于重要信息——提示和输入的数据类型。

感谢 C++11 lambda 支持,现在也可以很容易地向辅助函数添加参数验证:

T prompt_for_value( const char* const prompt, std::function<bool(T)> validation = {} )
{
    T result;
    while (true) {
         std::cout << prompt << std::flush;
         if (std::cin >> result) {
             if (validation && !validation(result)) {
                 std::cout << "Input out of range, try again" << std::endl;
                 continue;
             }
             return result;
         }
         std::cout << "Bad input, try again" << std::endl;
    }
}

double width = prompt_for_value<double>("Enter the width in meters: ",
                                        [](int w) { return w >= 0; });
double height = prompt_for_value<double>("Enter the height: ",
                     [&width](int h) { return (h >= 0) && (h <= 10000.0 / width); }));

【讨论】:

  • @remyabel:原版也是。而且我明确说过,这种重试方法只对交互输入有用。我对cincout 进行了硬编码,以减少错误地将其用于文件输入的可能性。您阅读答案了吗?
【解决方案2】:

由于违反了代码中的don't-repeat-yourself (DRY) principle,您所指出的教授建议的方法是非常糟糕的形式。如果你想多次做基本相同的事情,你应该使用循环或函数以避免编写重复的逻辑。

您的方法涉及全有或全无检查,因此可能需要 用户 重复自己。应根据具体情况决定其可用性。

这两种方法都无法检测到输入行末尾的无关字符。这样的输入可能会被合理地解释为错误的——不管它是否恰好与后续的输入请求一起工作。

但是,正如您所展示的,全有或全无的方法实际上基本上是有效的。鉴于此,这是一个基于 iostream library 能够进行的基于自动异常检查的替代全有或全无实现。

   cin.exceptions( ~std::ios::goodbit );

   int length=0, width=0;

   for ( bool ok=false; !ok; )
      {
      try
         {

         cout << "Enter the length and the width of the rectangle: ";
         cin >> std::skipws >> length >> width;

         std::string s;
         std::getline( cin, s );
         s.erase( std::remove_if( s.begin(), s.end(), ::isspace ), s.end() );
         if ( !s.empty() ) throw std::runtime_error("too much input");

         ok = true;
         }
      catch( std::ios::failure const & e )
         {
         cout<<"\n"<<"bad input ["<<e.what()<<"], try again...\n";
         cin.clear();
         cin.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );
         }
      catch( std::runtime_error const & e )
         {
         cout<<"\n"<<e.what()<<", try again...\n";
         }
      }

   cout<<"\n"<<"length="<<length<<", width="<<width<<"\n";

输出:

Enter the length and the width of the rectangle: x

bad input [basic_ios::clear], try again...
Enter the length and the width of the rectangle: 1 x

bad input [basic_ios::clear], try again...
Enter the length and the width of the rectangle: 1 2 x

too much input, try again...
Enter the length and the width of the rectangle: 1 2 3

too much input, try again...
Enter the length and the width of the rectangle: 4 5

length=4, width=5

【讨论】:

  • 检查尾随字符是一个不错的主意,但异常处理增加了复杂性,没有任何好处。异常应该用于在调用堆栈中的几层之外启用集中式错误处理,而不是用于本地重试。
  • 使用 iostreams 库很复杂,而且至少有可能打开异常会使事情变得更简单——尤其是当您想要执行多个 I/O 操作时。使用异常让我更有信心,如果出现问题,我会知道它。我认为在这里使用异常而不是手动检查没有任何害处——当然执行速度不是问题。我的实现有更多的代码行,但主要是为了支持额外的错误条件检查。
  • 我想指出 iostreams 的异常模式,特别是因为对于基于文件的 I/O,一种全有或全无的方法可能会更加更简洁/更简单通过绕过手动错误检查。我猜想比较复杂的替代方法是忽略所有错误,直到您完成整个 I/O 序列并且然后询问库它是否真的有效。也许这是一种合理的方法,但对我来说它似乎不优雅且容易出错。
猜你喜欢
  • 2017-08-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-09
  • 1970-01-01
相关资源
最近更新 更多