❥关于C++之流状态(stream state)

Posted itzyjr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了❥关于C++之流状态(stream state)相关的知识,希望对你有一定的参考价值。

cin或cout对象包含一个描述流状态(stream state)的数据成员(从ios_base类那里继承的)。流状态(被定义为iostate类型,而iostate是一种bitmask类型)由3个ios_base元素组成:eofbitbadbitfailbit,其中每个元素都是一位,可以是1(置位)0(清除)。当cin操作到达文件末尾时,它将置位eofbit;当cin操作未能读取到预期的字符时,它将置位failbit。I/O失败(如试图读取不可访问的文件或试图写入写保护的磁盘),也可能将failbit置位为1。在一些无法诊断的失败破坏流时,badbit元素将被置位(实现没有必要就哪些情况下置位failbit,哪些情况下置位badbit达成一致)。当全部3个状态位都置为0时,说明一切顺利。程序可以检查流状态,并使用这种信息来决定下一步做什么。下表列出了这些位和一些报告或改变流状态的ios_base方法。


ios_base::iostate类: 

public member type
std::ios_base::iostate

表示流错误状态标志的位掩码类型。
所有流对象在内部保留有关对象状态的信息。可通过调用成员函数basic_ios::rdstate来检索或通过调用basic_ios::setstate来置位。
这些函数传递和检索的值可以是以下成员常量的任何有效组合(使用布尔或运算符“|”):

标志描述
eofbit对输入流执行提取操作时到达文件结尾。
failbit由于与操作本身的内部逻辑相关的错误,导致上次输入操作失败。
badbit由于流缓冲区上的输入/输出操作失败而导致的错误。
goodbit没有错。表示没有上述所有内容(值为0)。

ios_base::iostate类型的对象,可以包含以下状态标志成员常量的任意组合:

iostate值描述good()eof()fail()bad()rdstate()
goodbit无错误(0值)truefalsefalsefalsegoodbit
eofbit输入操作达到文件结尾falsetruefalsefalseeofbit
failbitI/O操作出现逻辑错误falsefalsetruefalsefailbit
badbitI/O操作上的读/写错误falsefalsetruetruebadbit

这些常量在ios_base类中定义为公共成员。因此,它们可以直接被称为ios_base成员(比如ios_base::badbit),也可以使用它们的任何子类或实例化对象,比如ios::eofbit或cin.goodbit。

C++强有力的保证:如果抛出异常,流中不会有任何更改。


源码:

 

 通过源码,可以清晰的看到这几个状态位的定义,也看到了对它们的清晰注释。

std::cout << "goodbit = " << std::ios_base::goodbit << "\\n";
std::cout << "failbit = " << std::ios_base::failbit << "\\n";
std::cout << "badbit = " << std::ios_base::badbit << "\\n";
std::cout << "eofbit = " << std::ios_base::eofbit << "\\n";
打印输出:
0
4
1
2

在默认情况下枚举类型都是int型的。但是可以在定义枚举型时制定特定的类型,例如:
enum weekday:longsun,mon,tue,wed,thu,fri,sat;这样枚举类型就是long型了。

 就源码中_S_failbit = 1L << 2来说:它是一个int型,占4字节,4bytes=4×8bit=32位,1L=00000000 00000000 00000000 00000001,左移运算符,左移2位,最高位去除,最低位补0,得到00......100=4。

二进制表示4个状态位的默认值
goodbitfailbitbadbiteofbit
000100001010
位与:failbit(100)&badbit(001)&eofbit(010) = 0b000
位或:failbit(100)|badbit(001)|eofbit(010) = 0b111

它们的二进制位的有效位“1”分布在第1、第2、第3的位置,互不影响,为的就是置位逻辑操作。


掩码:

掩码是一串二进制代码对目标字段进行位与运算,屏蔽当前的输入位。将源码与掩码经过按位运算或逻辑运算得出新的操作数。其中要用到按位运算如OR运算和AND运算。用于如将ASCII码中大写字母改作小写字母。
如A的ASCII码值为65= (01000001),a的ASCII码值为97=(01100001),要想把大写字母A转化为小写字母只需要将A的ASCII码与(00100000)进行或运算就可以得到小写字母a:

A(65) = (01000001)
                 <OR>
掩码  = (00100000)
结果  = (01100001) = a(97)

设置状态:

表中的两种方法——clear()和setstate()很相似。它们都重置状态,但采取的方式不同。clear()方法将状态设置为它的参数。因此,下面的调用将使用默认参数0,这将清除全部3个状态位(eofbit、badbit和failbit):clear();

同样,下面的调用将状态eofbit置位;也就是说,eofbit将被置位,另外两个状态位被清除:clear(eofbit);

而setstate()方法只影响其参数中已设置的位。因此,下面的调用将置位eofbit,而不会影响其他位:setstate(eofbit); 它的行为等同于clear(rdstate() | eofbit); 

为什么需要重新设置流状态呢?对于程序员来说,最常见的理由是,在输入不匹配或到达文件尾时,需要使用不带参数的clear()重新打开输入。这样做是否有意义,取决于程序要执行的任务。稍后将介绍一些例子。setstate()的主要用途是为输入和输出函数提供一种修改状态的途径。例如,如果num是一个int,则下面的调用将可能导致operator >> (int &)使用setstate()置位failbit或eofbit:cin >> num;// read an int

iostate exceptions() const; 返回流的当前异常掩码。
void exceptions (iostate except); 为流设置新的异常掩码,并清除流的错误状态标志(就像调用了成员clear()一样)。参数except:由要设置(badbit、eofbit和/或failbit)或设置为goodbit(或0)的错误状态标志位组合而成的成员类型iostate的位掩码值。(C++基本保证:如果抛出异常,则流处于有效状态。)

异常掩码是由所有流对象保留的一个内部值,指定在设置时引发异常的状态标志——成员类型failure或某些派生类型。此掩码是成员类型iostate的对象,iostate是由成员常量的任意组合形成的值。

默认情况下,所有流都有goodbit(由于设置了错误状态标志,它们不会引发异常)。

ios_base::failure类: 

public member class
std::ios_base::failure
class failure;
这个嵌入式类继承自system_error,并作为标准输入/输出库元素引发的异常的基类

class ios_base::failure : public system_error 
public:
  explicit failure(const string& msg, const error_code& ec = io_errc::stream);
  explicit failure(const char*   msg, const error_code& ec = io_errc::stream);

这些错误通常被归类为iostream_category(如果它们与库的操作有关)或system_category(如果错误是由系统引起的)。尽管具体细节是由实现定义的。
库实现可以使用io_errc类型的值来可移植地识别iostream_category的错误条件。

#include <iostream>// std::cerr
#include <fstream>// std::ifstream
int main() 
	std::ifstream file;
	file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	try 
		file.open("test.txt");
		while (!file.eof())
			file.get();// 从流中提取单个字符
		file.close();
	 catch (std::ifstream::failure e) 
		std::cerr << "Exception opening/reading/closing file\\n";
	
	return 0;

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使得能够指定多位。例如,如果badbit或eofbit随后被置位,下面的语句将引发异常:

cin.exceptions(badbit | eofbit);
// cinexcp.cpp -- having cin throw an exception
#include <iostream>
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;
Enter numbers:|1 2 3 a 4<Enter>
basic_ios::clear: iostream error
O! the horror!
Last value entered = 0
Sum = 6

这就是如何在接受输入时使用异常。然而,应该使用它们吗?这取决于具体情况。就这个例子而言,答案是否定的。异常用于捕获不正常的意外情况,但这个例子将输入错误作为一种退出循环的方式。然而,让这个程序在badbit被置位时引发异常可能是合理的,因为这种情况是意外的。如果程序被设计成从一个数据文件中读取数据,直到到达文件尾,则在failbit被置位时引发异常也是合理的,因为这表明数据文件出现了问题。

failbit:如果输入操作未能读取预期的字符或输出操作没有写入预期的字符,则设置为1。
badbit:如果流被破坏,则设置为1。
eofbit:如果到达文件尾,则设置为1。

由于要提取的是int类型值,如果输入字符“a”就满足“输出操作没有写入预期字符”并且其他成员位都不满足异常条件,即有且仅满足设置状态位failbit。

如果将上述代码cin.exceptions(ios_base::failbit);这行代码中的failbit改为其他状态位badbit或eofbit或goodbit,或干脆注释掉这行代码,程序运行结果都是如下:

Enter numbers:|1 2 3 a 4<Enter>
Last value entered = 0
Sum = 6

即,虽有异常,但捕获不到异常。提取到字母a时,(cin>>input)==false了,不抛异常。

cin.exceptions(argbit) 的意思是:只有cin出现异常致使设置了参数状态位argbit,才抛出异常。

如果注释掉cin.exceptions(ios_base::failbit);就相当于默认行为,而exceptions()的默认设置为goodbit。对于“不符合预期的字符”这种情况,除了置位failbit外其他状态位是不可能设置的。所以对于此例子,要想能抛出(如果能抛出的话)异常,就必须设置exceptions并且参数必须是状态位failbit。

流状态的影响:

只有在流状态良好(所有的位都被清除)的情况下,下面的测试才返回true:

while (cin >> input)

如果测试失败,可以使用其他成员函数来判断可能的原因。比如判断是否是因为提取到达文件尾而设置了状态位:

while (cin >> input) 
    sum += input;

if (cin.eof())
    cout << "Loop terminated because EOF encountered\\n";

设置流状态位有一个非常重要的后果:流将对后面的输入或输出关闭,直到位被清除。

while (cin >> input) 
    sum += input;

cout << "Now enter a new number:";
cin >> input; ←←← won't work!!!

修正:1.调用cin.clear();将流状态重置为良好;2.清空输入缓存。

while (cin >> input) 
    sum += input;

cout << "Now enter a new number:";
cin.clear();// reset stream state
while (cin.get() != '\\n')
    continue;// get rid rest of line
cin >> input;// will work now

如果希望程序在流状态位被置位后能够读取后面的输入,就必须将流状态重置为良好。

现在,假设循环是由于到达文件尾或者由于硬件故障而终止的,则上述处理错误输入的代码将毫无意义。可以使用fail()方法检测假设是否正确,来修复问题。由于历史原因,fail()在failbit或badbit被置位时返回true,因此代码必须排除后一种情况。下面是这么做的一个例子:

while (cin >> input) 
    sum += input;

...
if (cin.fail() && !cin.bad()) // failed because of mismatch input
    cin.clear();// reset stream state
    while (!isspace(cin.get()))
        continue;// get rid of bad input
 else 
    // badbit发生系统级的错误,如不可恢复的读写错误。通常情况下一旦badbit被置位,流就无法再使用了
    cout << "I cannot go on!\\n";
    exit(1);

cout << "Now enter a new number:";
cin >> input;// will work now
    

检测到EOF后,cin将两位(eofbitfailbit)都设置为1。 

badbit 表示发生系统级的错误,如不可恢复的读写错误。通常情况下一旦badbit被置位,流就无法再使用了。

goodbit 被置位表示流未发生错误。如果badbit failbit 和eofbit 任何一个被置位,则检查流状态的条件会失败。

以上是关于❥关于C++之流状态(stream state)的主要内容,如果未能解决你的问题,请参考以下文章

Spark Streaming源码解读之流数据不断接收全生命周期彻底研究和思考

Java8之流Stream使用,附上例子

201902161119_《Node.js之流(Stream)一二事》

Kafka Streams State Store

第10课:Spark Streaming源码解读之流数据不断接收全生命周期彻底研究和思考

Flutter Child State 未根据更改的父状态更新:FlutterFirestore Stream