2009-02-13 70 views
7

我的兄弟最近开始学习C++。他告诉我他在尝试验证简单程序中的输入时遇到的问题。他有一个文本菜单,用户输入一个整数choice,如果他们输入了一个无效的选择,他们会被要求再次输入(while while循环)。但是,如果用户输入的是字符串而不是int,则代码会中断。 我读了计算器的各种问题,并告诉他的线沿线的重写一行代码:用cin在C++中进行输入验证的最佳方式是什么?

#include<iostream> 
using namespace std; 

int main() 
{ 
    int a; 
    do 
    { 
    cout<<"\nEnter a number:" 
    cin>>a; 
     if(cin.fail()) 
     { 
      //Clear the fail state. 
      cin.clear(); 
      //Ignore the rest of the wrong user input, till the end of the line. 
      cin.ignore(std::numeric_limits<std::streamsize>::max(),\ 
                '\n'); 
     } 
    }while(true); 
    return 0; 
} 

虽然这工作确定,我也尝试了一些其他的想法:
1.使用try catch块。它没有工作。我认为这是因为由于输入错误而不会引发异常。 2.我试过if(! cin){//Do Something}哪个也没用。我还没有想出这一个。
3.第三,我尝试输入一个固定长度的字符串,然后解析它。我会用atoi()。这个标准是否符合标准和便携?我应该写我自己的解析函数吗?
4.如果编写一个使用cin的类,但动态地执行这种错误检测,可能通过在运行时确定输入变量的类型,是否会有太多开销?它甚至有可能吗?

我想知道做这种检查的最佳方法是什么,最佳实践是什么?

我想补充一点,虽然我并不是新手写C++代码,但我是新手,需要编写符合标准的良好代码。我试图去清除不好的习惯并学习正确的习惯。如果回答者给出了详细的解释,我将非常感激。

编辑:我看到litb回答了我之前的一个编辑。我将在这里发布代码以供参考。

#include<iostream> 
using namespace std; 

int main() 
{ 
    int a; 
    bool inputCompletionFlag = true; 
    do 
    { 
    cout<<"\nEnter a number:" 
    cin>>a; 
     if(cin.fail()) 
     { 
      //Clear the fail state. 
      cin.clear(); 
      //Ignore the rest of the wrong user input, till the end of the line. 
      cin.ignore(std::numeric_limits<std::streamsize>::max(),\ 
                '\n'); 
     } 
     else 
     { 
      inputCompletionFlag = false; 
     } 
    }while(!inputCompletionFlag); 
    return 0; 
} 

此代码失败上像 “1asdsdf” 输入。我不知道如何解决这个问题,但是litb已经发布了一个很好的答案。 :)

回答

14

这里是代码,你可以用它来确保你也拒绝之类的东西

42crap 

凡非数字字符跟随数。如果您阅读整行,然后解析并正确执行操作,则可能需要您更改程序的工作方式。如果你的程序直到现在都从不同的地方读取你的号码,那么你必须把一个中心位置分析一行输入,然后决定行动。但也许这是一件好事 - 这样你就可以通过具有分开的事情,这样增加了代码的可读性: NPUT - P rocessing - Ø本安输出

无论如何,这里是你如何拒绝上面的数字 - 非数字。读取一行成一个字符串,然后用stringstream解析它:

std::string getline() { 
    std::string str; 
    std::getline(std::cin, str); 
    return str; 
} 

int choice; 
std::istringstream iss(getline()); 
iss >> choice >> std::ws; 
if(iss.fail() || !iss.eof()) { 
    // handle failure 
} 

它吃所有尾随空白。当它读取字符串流的文件结尾时,读取整数或尾随空白,然后它设置eof位,然后我们检查它。如果首先读取任何整数失败,那么失败或错误位将被设置。

这个答案的早期版本使用std::cin直接 - 但std::ws不会连接到终端std::cin能够很好地配合(它会阻止而不是等待用户输入的东西),所以我们使用stringstream用于读取整数。


回答大家的一些问题:

问: 1.使用try catch块。它没有工作。我认为这是因为由于输入错误而不会引发异常。

答:好了,你可以告诉流时,你读的东西抛出异常。您可以使用istream::exceptions功能,请您说出哪一种错误的,你想有一个异常抛出:

iss.exceptions(ios_base::failbit); 

我也从来没有使用它。如果你在std::cin上这样做,你将不得不记得为依赖它的其他读者恢复标志而不是抛出。找到它更容易使用功能失败,不好要求流的状态。我试过if(!cin){ //Do Something }也没有用。我还没有想出这一个。

答案:这可能来自于你给它类似“42crap”的事实。对于流,在对整数进行提取时,这是完全有效的输入。

问题: 3.第三,我尝试输入一个固定长度的字符串,然后解析它。我会用atoi()。这个标准是否符合标准和便携?我应该写我自己的解析函数吗?

答案: atoi符合标准。但是当你想要检查错误时,这并不好。没有错误检查,由它来完成,而不是其他功能。如果你有一个字符串,并想检查它是否包含一个数字,那么就像在上面的初始代码中那样。

有类C函数可以直接从C字符串中读取。它们的存在允许与旧的旧版代码进行交互并编写快速执行的代码。人们应该避免在程序中使用它们,因为它们工作的水平较低,需要使用原始的裸指针。就其性质而言,它们不能被增强以使用用户定义的类型。具体来说,这里讨论了函数“strtol”(string-to-long),它基本上是atoi,具有错误检查功能并能够与其他基(例如hex)一起工作。

问题: 4.如果我写的使用CIN一类,而是通过确定在运行时输入变量的类型动态地进行错误检测的这种,也许,将它有太多的开销?它甚至有可能吗?

答:一般情况下,你不需要太在意这里的开销(如果你的意思是运行时的开销)。但它具体取决于你在哪里使用该课程。如果你正在编写一个高性能的系统来处理输入并且需要整个系统都很高,那么这个问题将非常重要。但是,如果您需要从终端或文件读取输入,您已经看到了这种情况:等待用户输入内容需要很长的时间,因此您不需要在此处注意运行时间成本规模。

如果你的意思是代码开销 - 它取决于代码的实现方式。您需要扫描您读取的字符串 - 无论它是否包含数字,无论是否包含任意字符串。根据你想要扫描的内容(也许你有一个“日期”输入,或者一个“时间”输入格式,查看boost.date_time),你的代码可能变得非常复杂。对于简单的事情,如数字之间的分类或不,我认为你可以逃脱少量的代码。

+0

谢谢你的详细解释litb,但我抬起头,发现Boost不是标准库。鉴于此,如果我可以或者应该坚持提升,那么推出自己的代码会更好吗? – batbrat 2009-02-13 16:44:33

3

我会做什么是双重的:首先,尝试验证输入,并使用正则表达式提取数据,如果输入有点不重要。即使输入只是一系列数字,它也可能非常有用。

然后,我喜欢使用boost::lexical_ cast,如果输入无法转换,则可能引发bad_lexical_ cast异常。

在您的例子:

std::string in_str; 
cin >> in_str; 

// optionally, test if it conforms to a regular expression, in case the input is complex 

// Convert to int? this will throw bad_lexical_cast if cannot be converted. 
int my_int = boost::lexical_cast<int>(in_str); 
12

这是我做C,但它可能适用于C++为好。

以字符串形式输入所有内容。

然后,只有这样,解析字符串到你需要的。编写自己的代码有时候比尝试弯曲别人的意愿更好。

+0

感谢您回复Pax。我曾想过你在说什么,但想知道这是否是正确的做法。感谢你的回答。 – batbrat 2009-02-13 16:45:29

2

忘记使用格式化输入(>>操作符)直接在实际代码中。您将始终需要使用std :: getline或类似语言读取原始文本,然后使用您自己的输入分析例程(可能使用>>操作符)来解析输入。

4
  • 为了得到exceptions with iostreams您需要为流设置适当的异常标志。
  • 我会用get_line以获取输入的整条生产线,然后处理它相应的 - 使用lexical_cast的,正则表达式(例如Boost RegexBoost Xpressive,用Boost Spirit解析它,或者只是使用某种适当的逻辑
+0

感谢您的回答。我想知道是否有一个标准库相当于这里提到的增强备选方案。 – batbrat 2009-02-13 13:39:53

2

如何有关各种方法的组合:

  1. 缠碍使用std::getline(std::cin, strObj)std::cin输入其中strObjstd::string对象。

  2. 使用boost::lexical_caststrObj执行词法翻译到最大宽度(例如,unsigned long long或类似的东西)

  3. 使用boost::numeric_cast铸整数下降到预期的范围的任一个符号或无符号整数。

你可以只取出输入与std::getline,然后调用boost::lexical_cast到合适的窄整数类型以及不同的地方你想赶上错误。三步法有利于接受任何整数数据,然后分别捕获缩小的错误。

1

我同意Pax,最简单的方法是将所有内容读取为字符串,然后使用TryParse验证输入。如果格式正确,则继续,否则只是通知用户并在循环中继续使用。

+0

TryParse是一个.NET特定的习惯用法。基于标准的便携式C++不能使用它。 – 2009-02-13 16:20:38

+0

我在想那个harper。谢谢澄清。谢谢你回答Rekreativc – batbrat 2009-02-13 16:42:32

1

尚未提及的一件事是,在使用从流中获取某些东西的变量之前,测试cin >>操作是否工作通常很重要。

该示例与您的示例类似,但会进行该测试。

#include <iostream> 
#include <limits> 
using namespace std; 
int main() 
{ 
    while (true) 
    { 
     cout << "Enter a number: " << flush; 
     int n; 
     if (cin >> n) 
     { 
     // do something with n 
     cout << "Got " << n << endl; 
     } 
     else 
     { 
     cout << "Error! Ignoring..." << endl; 
     cin.clear(); 
     cin.ignore(numeric_limits<streamsize>::max(), '\n'); 
     } 
    } 
    return 0; 
} 

这将使用通常的操作符>>语义;它会先跳过空格,然后尝试读取尽可能多的数字,然后停止。所以“42crap”会给你42,然后跳过“废话”。如果这不是你想要的,那么我同意以前的答案,你应该将它读入一个字符串,然后验证它(可能使用正则表达式 - 但这可能是一个简单的数字序列矫枉过正)。

相关问题