我哥哥最近开始学习C ++。他告诉我在尝试在简单程序中验证输入时遇到的问题。他有一个文本菜单,用户输入一个整数choice
,如果他们输入了一个无效的选项,他们会被要求再次输入(循环时)。但是,如果用户输入的是字符串而不是int,则代码会中断。
我在stackoverflow上阅读了各种问题,并告诉他按照以下方式重写他的代码:
#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块。它没用。我认为这是因为输入错误不会引发异常。
我试过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发布了一个很好的答案。 :)
答案 0 :(得分:17)
以下代码可用于确保您还拒绝
等内容42crap
非数字字符跟随数字。如果您阅读整行,然后解析它并适当地执行操作,则可能需要您更改程序的工作方式。如果您的程序从不同的地方读取您的号码,那么您必须放置一个解析一行输入的中心位置,并决定该操作。但也许这也是一件好事 - 所以你可以通过分离事物来提高代码的可读性:我输入 - P rocessing - O 强>安输出
无论如何,这里是你如何拒绝上面的数字 - 非数字。将一行读入字符串,然后使用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-bit,然后检查它。如果它首先无法读取任何整数,那么将设置失败或坏位。
此答案的早期版本直接使用std::cin
- 但std::ws
无法与连接到终端的std::cin
一起使用(它会阻止等待用户输入内容),所以我们使用stringstream
来读取整数。
回答你的一些问题:
问题: 1.使用try catch块。它没用。我认为这是因为输入错误不会引发异常。
答案: 嗯,您可以告诉流在您阅读内容时抛出异常。您使用istream::exceptions
函数,告诉您要引发异常的错误类型:
iss.exceptions(ios_base::failbit);
我从未使用它。如果你在std::cin
上执行此操作,则必须记住为其他依赖它而不是投掷的读者恢复标记。找到方法更容易使用函数失败,坏来询问流的状态。
问题: 2.我尝试了if(!cin){ //Do Something }
,但也无效。我还没想出来。
答案: 这可能来自于你给它类似“42crap”的事实。对于流,在提取整数时,这是完全有效的输入。
问题: 3.第三,我尝试输入固定长度的字符串然后解析它。我会用atoi()。这个标准是否合规且便携?我应该编写自己的解析函数吗?
答案: atoi符合标准。但是当你想要检查错误时,它并不好。没有错误检查,由它完成,而不是其他功能。如果你有一个字符串,并想检查它是否包含一个数字,那么就像在上面的初始代码中那样做。
有类似C的函数可以直接从C字符串中读取。它们的存在是为了允许与旧的遗留代码进行交互并编写快速执行的代码。人们应该在程序中避免它们,因为它们的工作水平较低,需要使用原始的裸指针。就其本质而言,它们无法增强以使用用户定义的类型。具体来说,这谈到了函数“strtol”(字符串到长),它基本上是atoi,具有错误检查和与其他基础(例如hex)一起工作的能力。
问题: 4.如果我编写了一个使用cin的类,但是动态地执行这种错误检测,也许通过在运行时确定输入变量的类型,它是否会有太多的开销?它甚至可能吗?
答案: 通常,您不需要过多关注开销(如果您的意思是运行时开销)。但它具体取决于您使用该类的位置。如果您正在编写一个处理输入并且需要高整数的高性能系统,那么这个问题将非常重要。但是如果你需要从终端或文件中读取输入,你已经看到了这归结为:等待用户输入的东西需要很长时间,你现在不再需要观察运行时成本规模。
如果你的意思是代码开销 - 那么它取决于代码的实现方式。您需要扫描您读取的字符串 - 是否包含数字,是否包含任意字符串。根据您要扫描的内容(也许您有“日期”输入或“时间”输入格式。请查看boost.date_time
),您的代码可能会变得任意复杂。对于简单的事情,比如在数字与否之间进行分类,我认为你可以通过少量的代码来逃避。
答案 1 :(得分:12)
这就是我用C做的,但它也可能适用于C ++。
以字符串形式输入所有内容。
然后,只有这样,才能将字符串解析为您需要的内容。有时候编写自己的代码比尝试根据自己的意愿弯曲别人更好。
答案 2 :(得分:4)
答案 3 :(得分: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);
答案 4 :(得分:2)
忘记在实际代码中直接使用格式化输入(&gt;&gt;运算符)。您将始终需要使用std :: getline或类似内容读取原始文本,然后使用您自己的输入解析例程(可能使用&gt;&gt;运算符)来解析输入。
答案 5 :(得分:2)
各种方法的组合如何:
使用std::cin
来抓取std::getline(std::cin, strObj)
的输入,其中strObj
是std::string
个对象。
使用boost::lexical_cast
执行从strObj
到最大宽度的有符号或无符号整数的词汇翻译(例如,unsigned long long
或类似的东西)
使用boost::numeric_cast
将整数转换为预期范围。
您可以使用std::getline
获取输入,然后根据您想要捕获错误的位置,将boost::lexical_cast
调用到适当的窄整数类型。三步法有利于接受任何整数数据,然后分别捕捉缩小误差。
答案 6 :(得分:1)
我同意Pax,最简单的方法是将所有内容都读为字符串,然后使用TryParse来验证输入。如果格式正确,则继续,其他只需通知用户并在循环中使用continue。
答案 7 :(得分:1)
尚未提及的一件事是,通常重要的是要测试cin&gt;&gt;在使用可能从流中获得某些内容的变量之前,操作已经完成。
此示例与您的示例类似,但进行了此测试。
#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;
}
这将使用通常的运算符&gt;&gt;语义;它将首先跳过空格,然后尝试读取尽可能多的数字,然后停止。所以“42crap”会给你42然后跳过“垃圾”。如果那不是你想要的,那么我同意之前的答案,你应该把它读成一个字符串,然后验证它(也许使用正则表达式 - 但对于一个简单的数字序列来说可能有点过分了。)