13-流状态
17.3.2 流状态
我们来进一步看看不适当的输入会造成什么后果。cin或cout对象包含一个描述流状态(stream state)的数据成员(从ios_base类那里继承的)。流状态(被定义为iostate类型,而iostate是一种bitmask类型)由3个ios_base元素组成:eofbit、badbit或failbit,其中每个元素都是一位,可以是1(设置)或0(清除)。当cin操作到达文件末尾时,它将设置eofbit;当cin操作未能读取到预期的字符时(像前一个例子那样),它将设置failbit。I/O失败(如试图读取不可访问的文件或试图写入开启写保护的磁盘),也可能将failbit设置为1。在一些无法诊断的失败破坏流时,badbit元素将被设置(实现没有必要就哪些情况下设置failbit,哪些情况下设置badbit达成一致)。当全部3个状态位都设置为0时,说明一切顺利。程序可以检查流状态,并使用这种信息来决定下一步做什么。表17.4列出了这些位和一些报告或改变流状态的ios_base方法。
| 成 员 | 描 述 | | :----- | :----- | :----- | :----- | | eofbit | 如果到达文件尾,则设置为1 | | badbit | 如果流被破坏,则设置为1;例如,文件读取错误 | | failbit | 如果输入操作未能读取预期的字符或输出操作没有写入预期的字符,则设置为1 | | goodbit | 另一种表示0的方法 | | good() | 如果流可以使用(所有的位都被清除),则返回true | | eof() | 如果eofbit被设置,则返回true | | bad() | 如果badbit被设置,则返回true | | fail() | 如果badbit或failbit被设置,则返回true | | rdstate() | 返回流状态 | | exceptions() | 返回一个位掩码,指出哪些标记导致异常被引发 | | exceptions(isostate ex) | 设置哪些状态将导致clear()引发异常;例如,如果ex是eofbit,则如果eofbit被设置,clear()将引发异常 | | clear(iostate s) | 将流状态设置为s;s的默认值为0(goodbit);如果(restate()& exceptions())! = 0,则引发异常basic_ios::failure | | setstate(iostate s) | 调用clear(rdstate() | s)。这将设置与s中设置的位对应的流状态位,其他流状态位保持不变 |
1.设置状态
表17.4中的两种方法——clear()和setstate()很相似。它们都重置状态,但采取的方式不同。clear()方法将状态设置为它的参数。因此,下面的调用将使用默认参数0,这将清除全部3个状态位(eofbit、badbit和failbit):
clear();
同样,下面的调用将状态设置为eofbit;也就是说,eofbit将被设置,另外两个状态位被清除:
clear(eofbit);
而setstate()方法只影响其参数中已设置的位。因此,下面的调用将设置eofbit,而不会影响其他位:
setstate(eofbit);
因此,如果failbit被设置,则仍将被设置。
为什么需要重新设置流状态呢?对于程序员来说,最常见的理由是,在输入不匹配或到达文件尾时,需要使用不带参数的clear()重新打开输入。这样做是否有意义,取决于程序要执行的任务。稍后将介绍一些例子。setstate()的主要用途是为输入和输出函数提供一种修改状态的途径。例如,如果num是一个int,则下面的调用将可能导致operator >> (int &)使用setstate()设置failbit或eofbit:
cin >> num; // read an int
2.I/O和异常
假设某个输入函数设置了eofbit,这是否会导致异常被引发呢?在默认情况下,答案是否定的。但可以使用exceptions()方法来控制异常如何被处理。
首先,介绍一些背景知识。exceptions()方法返回一个位字段,它包含3位,分别对应于eofbit、failbit和badbit。修改流状态涉及clear()或setstate(),这都将使用clear()。修改流状态后,clear()方法将当前的流状态与exceptions()返回的值进行比较。如果在返回值中某一位被设置,而当前状态中的对应位也被设置,则clear()将引发ios_base::failure异常。如果两个值都设置了badbit,将发生这种情况。如果exceptions()返回goodbit,则不会引发任何异常。ios_base::failure异常类是从std::exception类派生而来的,因此包含一个what()方法。
exceptions()的默认设置为goodbit,也就是说,没有引发异常。但重载的exceptions(iostate)函数使得能够控制其行为:
cin.exceptions(badbit); // setting badbit causes exception to be thrown
位运算符OR(在附录E讨论)使得能够指定多位。例如,如果badbit或eofbit随后被设置,下面的语句将引发异常:
cin.exceptions(badbit | eofbit);
程序清单17.12对程序清单17.11进行了修改,以便程序能够在failbit被设置时引发并捕获异常。
程序清单17.12 cinexcp.cpp
// cinexcp.cpp -- having cin throw an exception
#include <iostream>
#include <exception>
int main()
{
using namespace std;
// have failbit cause an exception to be thrown
cin.exceptions(ios_base::failbit);
cout << "Enter numbers: ";
int sum = 0;
int input;
try {
while (cin >> input)
{
sum += input;
}
} catch(ios_base::failure & bf)
{
cout << bf.what() << endl;
cout << "O! the horror!\n";
}
cout << "Last value entered = " << input << endl;
cout << "Sum = " << sum << endl;
return 0;
}
程序清单17.12中程序的运行情况如下,其中的what()消息取决于实现:
Enter numbers: 20 30 40 pi 6
ios_base failure in clear
O! the horror!
Last value entered = 40.00
Sum = 90.00
这就是如何在接受输入时使用异常。然而,应该使用它们吗?这取决于具体情况。就这个例子而言,答案是否定的。异常用于捕获不正常的意外情况,但这个例子将输入错误作为一种退出循环的方式。然而,让这个程序在badbit位被设置时引发异常可能是合理的,因为这种情况是意外的。如果程序被设计成从一个数据文件中读取数据,直到到达文件尾,则在failbit位被设置时引发异常也是合理的,因为这表明数据文件出现了问题。
3.流状态的影响
只有在流状态良好(所有的位都被清除)的情况下,下面的测试才返回true:
while (cin >> input)
如果测试失败,可以使用表17.4中的成员函数来判断可能的原因。例如,可以将程序清单17.11中的核心部分修改成这样:
while (cin >> input)
{
sum += input;
}
if (cin.eof())
cout << "Loop terminated because EOF encountered\n";
设置流状态位有一个非常重要的后果:流将对后面的输入或输出关闭,直到位被清除。例如,下面的代码不可行:
while (cin >> input)
{
sum += input;
}
cout << "Last value entered = " << input << endl;
cout << "Sum = " << sum << endl;
cout << "Now enter a new number: ";
cin >> input; // won't work
如果希望程序在流状态位被设置后能够读取后面的输入,就必须将流状态重置为良好。这可以通过调用clear()方法来实现:
while (cin >> input)
{
sum += input;
}
cout << "Last value entered = " << input << endl;
cout << "Sum = " << sum << endl;
cout << "Now enter a new number: ";
cin.clear(); // reset stream state
while (!isspace(cin.get()))
continue; // get rid of bad input
cin >> input; // will work now
注意,这还不足以重新设置流状态。导致输入循环终止的不匹配输入仍留在输入队列中,程序必须跳过它。一种方法是一直读取字符,直到到达空白为止。isspace()函数(参见第6章)是一个cctype函数,它在参数是空白字符时返回true。另一种方法是,丢弃行中的剩余部分,而不仅仅是下一个单词:
while (cin.get() != '\n')
continue; // get rid rest of line
这个例子假设循环由于不恰当的输入而终止。现在,假设循环是由于到达文件尾或者由于硬件故障而终止的,则处理错误输入的新代码将毫无意义。可以使用fail()方法检测假设是否正确,来修复问题。由于历史原因,fail()在failbit或eofbit被设置时返回true,因此代码必须排除后一种情况。下面是一个排除这种情况的例子:
while (cin >> input)
{
sum += input;
}
cout << "Last value entered = " << input << endl;
cout << "Sum = " << sum << endl;
if (cin.fail() && !cin.eof() ) // failed because of mismatched input
{
cin.clear(); // reset stream state
while (!isspace(cin.get()))
continue; // get rid of bad input
}
else // else bail out
{
cout << "I cannot go on!\n";
exit(1);
}
cout << "Now enter a new number: ";
cin >> input; // will work now