流状态和输入验证
流状态
ios_base类包含几个状态标志,用于指示使用流时可能出现的各种情况:
Flag | 含义 |
---|---|
goodbit | 切正常 |
badbit | 发生了某种致命错误(例如程序试图读取文件末尾) |
eofbit | 流已到达文件的末尾 |
failbit | 发生了非致命错误(例如,当程序期望整数时用户输入了字母) |
虽然这些标志位于ios_base中,因为ios派生自ios_base,ios输入的输入比ios_base少,但它们通常通过ios访问(例如,作为std :: ios :: failbit)。
ios还提供了许多成员函数,以便于访问这些状态:
成员函数 | 含义 |
---|---|
good() | 如果设置了goodbit,则返回true(流是正常的) |
bad() | 如果设置了badbit,则返回true(发生致命错误) |
eof() | 如果设置了eofbit,则返回true(流位于文件的末尾) |
fail() | 如果设置了failbit,则返回true(发生非致命错误) |
clear() | 清除所有标志并将流恢复到goodbit状态 |
clear(state) | 清除所有标志并设置传入的状态标志 |
rdstate() | 返回当前设置的标志 |
setstate(state) | 设置传入的状态标志 |
最常处理的是failbit,它是在用户输入无效输入时设置的。例如,请考虑以下程序:
cout << "Enter your age: ";
int nAge;
cin >> nAge;
请注意,此程序期望用户输入整数。但是,如果用户输入非数字数据,例如“Alex”,则cin将无法向nAge提取任何内容,并且将设置failbit。
如果发生错误并且将流设置为除goodbit之外的任何其他内容,则将忽略该流上的进一步流操作。可以通过调用clear()函数清除此条件。
输入验证
输入验证是检查用户输入是否满足某些条件的过程。输入验证通常可以分为两种类型:字符串和数字。
使用字符串验证,我们接受所有用户输入作为字符串,然后接受或拒绝该字符串,具体取决于它是否格式正确。例如,如果我们要求用户输入电话号码,我们可能希望确保他们输入的数据有十位数。在大多数语言中(特别是像Perl和PHP这样的脚本语言),这是通过正则表达式完成的。但是,C ++没有内置的正则表达式支持(它应该与C ++的下一版本一起提供),所以通常通过检查字符串的每个字符来确保它符合某些条件。
通过数值验证,我们通常关注确保用户输入的数字在特定范围内(例如,在0到20之间)。但是,与字符串验证不同,用户可以输入完全不是数字的东西,我们也需要处理这些情况。
为了帮助我们,C ++提供了许多有用的功能,我们可以用它来确定特定字符是数字还是字母。以下函数存在于cctype头中:
函数 | 含义 |
---|---|
isalnum(int) | 如果参数是字母或数字,则返回非零值 |
isalpha(int) | 如果参数是字母,则返回非零值 |
iscntrl(int) | 如果参数是控制字符,则返回非零值 |
isdigit(int) | 如果参数是数字,则返回非零 |
isgraph(int) | 如果参数是非空白的可打印字符,则返回非零值 |
isprint(int) | 如果参数是可打印字符(包括空格),则返回非零值 |
ispunct(int) | 如果参数既不是字母数字也不是空格,则返回非零值 |
isspace(int) | 如果参数是空格,则返回非零值 |
isxdigit(int) | 如果参数是十六进制数字(0-9,af,AF),则返回非零值 |
字符串验证
让我们通过要求用户输入他们的名字来做一个简单的字符串验证。我们的验证标准是用户只输入字母字符或空格。如果遇到任何其他问题,输入将被拒绝。
当涉及可变长度输入时,验证字符串的最佳方法(除了使用正则表达式库之外)是逐步遍历字符串的每个字符并确保它符合验证标准。这正是我们在这里要做的。
#include <cctype>
#include <string>
#include <iostream>
using namespace std;
while (1)
{
// 获取用户名
cout << "Enter your name: ";
string strName;
getline(cin, strName); // 得到整行,包括空格
bool bRejected=false; // strName被拒绝了吗?
// 逐步浏览字符串中的每个字符,直到我们点击
// 字符串的结尾,或者我们拒绝了一个字符
for (unsigned int nIndex=0; nIndex < strName.length() && !bRejected; nIndex++)
{
// 如果当前字符是字母字符,那很好
if (isalpha(strName[nIndex]))
continue;
// 如果它是一个空间,那也没关系
if (strName[nIndex]==' ')
continue;
// O否则我们拒绝这个输入
bRejected = true;
}
// 如果已接受输入,则退出while循环
// 否则我们继续循环
if (!bRejected)
break;
}
请注意,这段代码并不完美:用户可以说他们的名字是“asf w jweo s di we ao”或者其他一些乱码,或者更糟糕的是,只是一堆空格。我们可以通过改进我们的验证标准来仅接受包含至少一个字符和至多一个空格的字符串来解决这个问题。
现在让我们看看另一个例子,我们将要求用户输入他们的电话号码。与用户的名称(可变长度,并且每个字符的验证标准相同)不同,电话号码是固定长度,但验证标准根据角色的位置而不同。因此,我们将采用不同的方法来验证我们的电话号码输入。在这种情况下,我们将编写一个函数,它将根据预定的模板检查用户的输入,以查看它是否匹配。该模板将按如下方式工作:
A #将匹配用户输入中的任何数字。
A @将匹配用户输入中的任何字母字符。
A _将匹配任何空格。
A ?会匹配任何东西。
否则,用户输入中的字符和模板必须完全匹配。
因此,如果我们要求函数匹配模板“(###)### - ####”,这意味着我们希望用户输入’(‘character,three numbers,a’)'字符,一个空格,三个数字,一个短划线和另外四个数字。如果这些事情中的任何一个不匹配,则输入将被拒绝。
这是代码:
bool InputMatches(string strUserInput, string strTemplate)
{
if (strTemplate.length() != strUserInput.length())
return false;
// 逐步执行用户输入以查看是否匹配
for (unsigned int nIndex=0; nIndex < strTemplate.length(); nIndex++)
{
switch (strTemplate[nIndex])
{
case '#': //数字匹配
if (!isdigit(strUserInput[nIndex]))
return false;
break;
case '_': // 空白匹配
if (!isspace(strUserInput[nIndex]))
return false;
break;
case '@': // 字幕匹配
if (!isalpha(strUserInput[nIndex]))
return false;
break;
case '?': // anything匹配
break;
default: //准确的字符匹配
if (strUserInput[nIndex] != strTemplate[nIndex])
return false;
}
}
return true;
}
int main()
{
string strValue;
while (1)
{
cout << "Enter a phone number (###) ###-####: ";
getline(cin, strValue); // 获得所有,包括空格
if (InputMatches(strValue, "(###) ###-####"))
break;
}
cout << "You entered: " << strValue << endl;
}
使用此函数,我们可以强制用户完全匹配我们的特定格式。但是,此函数仍然受到几个限制:如果是#,@,_和?在用户输入中是有效字符,此函数不起作用,因为这些符号具有特殊含义。此外,与正则表达式不同,没有模板符号表示“可以输入可变数量的字符”。因此,这样的模板不能用于确保用户输入由空格分隔的两个单词,因为它不能处理单词具有可变长度的事实。对于这些问题,非模板方法通常更合适。
数字验证
处理数字输入时,显而易见的方法是使用提取运算符将输入提取到数字类型。通过检查failbit,我们可以判断用户是否输入了数字。
让我们尝试这种方法:
int main()
{
int nAge;
while (1)
{
cout << "Enter your age: ";
cin >> nAge;
if (cin.fail()) // 没有提取
{
cin.clear(); // 状态位重置为goodbit所以我们可以使用ignore()
cin.ignore(32767, '\n'); // 清除流中的错误输入
continue; // t再试一次
}
if (nAge <= 0) // 保nAge是正数
continue;
break;
}
cout << "You entered: " << nAge << endl;
}
如果用户输入一个数字,cin.fail()将为false,我们将点击break语句,退出循环。如果用户输入以字母开头的输入,则cin.fail()将为true,我们将进入条件。
然而,还有一个我们还没有测试过的情况,那就是用户输入一个以数字开头但后面包含字母的字符串(例如“34abcd56”)。在这种情况下,起始数字(34)将被提取到nAge中,字符串的其余部分(“abcd56”)将保留在输入流中,并且不会设置failbit。这会导致两个潜在的问题:
1)如果您希望这是有效输入,您现在在流中有垃圾。
2)如果您不希望这是有效输入,则不会拒绝它(并且您的流中有垃圾)。
让我们解决第一个问题。这很简单:
int main()
{
int nAge;
while (1)
{
cout << "Enter your age: ";
cin >> nAge;
if (cin.fail()) //有提取
{
cin.clear(); // 将状态位重置为goodbit所以我们可以使用ignore()
cin.ignore(32767, '\n'); // 清除流中的错误输入
continue; // try again
}
cin.ignore(32767, '\n'); // 清除流中的任何其他输入
if (nAge <= 0) // 确保nAge是正数
continue;
break;
}
cout << "You entered: " << nAge << endl;
}
如果您不希望这样的输入有效,我们将不得不做一些额外的工作。幸运的是,之前的解决方案让我们走了一半。我们可以使用gcount()函数来确定忽略了多少个字符。如果我们的输入有效,gcount()应该返回1(被丢弃的换行符)。如果返回的值超过1,则用户输入的内容未正确提取,我们应该要求他们输入新内容。这是一个例子:
int main()
{
int nAge;
while (1)
{
cout << "Enter your age: ";
cin >> nAge;
if (cin.fail()) // 没有提取
{
cin.clear(); // 将状态位重置为goodbit所以我们可以使用ignore()
cin.ignore(32767, '\n'); // 清除流中的错误输入
continue; // try again
}
cin.ignore(32767, '\n'); // 除流中的任何其他输入
if (cin.gcount() > 1) // 如果我们清除了多个额外的character
continue; // 我们会认为此输入无效
if (nAge <= 0) // 确保nAge是正数
continue;
break;
}
cout << "You entered: " << nAge << endl;
}
数字验证为字符串
上面的例子只是为了得到一个简单的值而做了相当多的工作!处理数字输入的另一种方法是将其作为字符串读取,将其作为字符串处理,如果它通过验证,则将其转换为数字类型。以下程序使用该方法:
int main()
{
int nAge;
while (1)
{
cout << "Enter your age: ";
string strAge;
cin >> strAge;
// 检查以确保每个字符都是数字
bool bValid = true;
for (unsigned int nIndex=0; nIndex < strAge.length(); nIndex++)
if (!isdigit(strAge[nIndex]))
{
bValid = false;
break;
}
if (!bValid)
continue;
//此时,我们可以将某些内容转换为数字
// 所以我们将使用stringstream进行转换
stringstream strStream;
strStream << strAge;
strStream >> nAge;
if (nAge <= 0) // 确保nAge是正数
continue;
break;
}
cout << "You entered: " << nAge << endl;
}
这种方法是否比直接数字提取更多或更少取决于您的验证参数和限制。
如您所见,在C ++中进行输入验证是很多工作。幸运的是,许多此类任务(例如,将数字验证作为字符串)可以很容易地转换为可以在各种情况下重用的函数。