fstream
读取文本文件完整代码
首先,展示一下使用fstream读取文本文件的基本代码。
1 #include <string.h> //strerror函数 2 #include <errno.h> //errno错误代码变量 3 #include <fstream> 4 #include <iostream> 5 #include <string> 6 #include <sstream> 7 8 int main(int argc, char * argv[]) 9 { 10 std::fstream fs(argv[1]); 11 if (!fs) 12 { 13 std::cout << "无法打开文件(" << argv[1] << "):" << strerror(errno) << std::endl; 14 return -1; 15 } 16 std::string line; 17 while (std::getline(fs, line)) 18 { 19 if (line.empty()) 20 continue; 21 //处理每一行文本...... 22 } 23 24 return 0; 25 }
一般来说,正常思维逻辑是打开文件、判断文件打开是否成功、循环读取每一行数据、处理每一行数据(也可能不是按行处理)。
每一步具体的实现如上面的代码,看上去很简单。
打开文件
在C++11之前,fstream类有两种构造函数原型:
1 fstream(); 2 explicit fstream (const char* filename, ios_base::openmode mode = ios_base::in | ios_base::out);
第一个为默认构造函数,第二个为接受一个const char *的常量C字符串参数的文件名,以及打开模式参数;
在C++11之后,fstream类增加了两个构造函数:
1 //默认构造 2 fstream(); 3 //初始构造 4 explicit fstream (const char* filename, ios_base::openmode mode = ios_base::in | ios_base::out); 5 explicit fstream (const string& filename, ios_base::openmode mode = ios_base::in | ios_base::out); 6 //拷贝构造 7 fstream (const fstream&) = delete; //deleted, 没有拷贝构造函数 8 //move构造 9 fstream (fstream&& x); //从x直接获得文件缓冲区对象(filebuf)
值得注意的是,终于增加了使用std::string为文件名参数的构造函数,即第五行的构造函数。
当我们使用默认构造函数构造了fstream类之后,可以使用open函数来打开文件。
同样,C++11之后,open函数也增加了一个支持std::string文件名参数的函数:
1 void open (const char* filename, ios_base::openmode mode = ios_base::in | ios_base::out); 2 void open (const string& filename, ios_base::openmode mode = ios_base::in | ios_base::out);
所以,如果想使用支持std::string参数的新增函数时,需要使用-std=c++11的编译指示(g++)。
判断文件打开是否成功
判断文件是否打开,这里使用了"!fs",不知道读者是否理解,这里我展开讲解一下。
一般来说,判断一个文件是否打开成功都是要判断一个bool型的状态值,按理说fstream类应该具有这样的函数。
翻阅一下fstream的函数列表,我们会看到:
bool is_open() const; //C++11 bool is_open(); //C++98
所以,我们完全可以通过fs.is_open来判断文件是否正确打开了。但是,例子并没有这样做,而是使用了“!fs”。fs是一个类对象,如果一个类对象可以使用一个操作符来操作,那意味着该类必然要进行运算符的重载,也就是说,在std::fstream类中一定有一个类似于:bool operator!()const的函数重载定义。
感谢eclipse的跳转功能,否则,我们得花大力气去寻找这段定义的代码。在basic_ios.h头文件124~126行定义了该重载叹号(!)运算符函数:(下面的代码是109~126行)
109 //@{ 110 /** 111 * @brief The quick-and-easy status check. 112 * 113 * This allows you to write constructs such as 114 * <code>if (!a_stream) ...</code> and <code>while (a_stream) ...</code> 115 */ 116 #if __cplusplus >= 201103L 117 explicit operator bool() const 118 { return !this->fail(); } 119 #else 120 operator void*() const 121 { return this->fail() ? 0 : const_cast<basic_ios*>(this); } 122 #endif 123 124 bool 125 operator!() const 126 { return this->fail(); }
之所以把109~126行的代码贴出来,是我们额外发现了一些东西,即,为什么我们在代码中可以使用if(!fs)、while(fs)的形式来做判断和循环了。
在C++11之前,我们可以使用while(fs)来进行循环,是因为120~121行,编译器可以将fs隐式转换为void*指针,如果fail()返回true,即得到一个NULL指针,否则,是实际的fs对象的地址。有人会说干啥不直接调用fail(),可能作者觉得更酷吧!由于都是inline的,所以,酷并不会影响性能。为什么说是酷,因为我认为这样的封装(操作符重载)对于流对象来说,不是“自然而然”的语义,反而让人感觉很奇怪。
std::fstream::fail()
fail到底是什么意思?fail函数在basic_ios.h文件中的定义如下:
193 /** 194 * @brief Fast error checking. 195 * @return True if either the badbit or the failbit is set. 196 * 197 * Checking the badbit in fail() is historical practice. 198 * Note that other iostate flags may also be set. 199 */ 200 bool 201 fail() const 202 { return (this->rdstate() & (badbit | failbit)) != 0; }
该函数检查badbit和failbit标志位。rdstate函数返回当前的streambuf状态值,与badbit和failbit比较后,如果为0,就说明一切正常,如果不为零,说明badbit或者/和failbit被设置了,此时fail()函数便返回true。
继续跟踪,我们发现如下代码:
basic_ios.h: 129 /** 130 * @brief Returns the error state of the stream buffer. 131 * @return A bit pattern (well, isn't everything?) 132 * 133 * See std::ios_base::iostate for the possible bit values. Most 134 * users will call one of the interpreting wrappers, e.g., good(). 135 */ 136 iostate 137 rdstate() const 138 { return _M_streambuf_state; }
ios_base.h: 386 // 27.4.2.1.3 Type ios_base::iostate 387 /** 388 * @brief This is a bitmask type. 389 * 390 * @c @a _Ios_Iostate is implementation-defined, but it is valid to 391 * perform bitwise operations on these values and expect the Right 392 * Thing to happen. Defined objects of type iostate are: 393 * - badbit 394 * - eofbit 395 * - failbit 396 * - goodbit 397 */ 398 typedef _Ios_Iostate iostate; 399 400 /// Indicates a loss of integrity in an input or output sequence (such 401 /// as an irrecoverable read error from a file). 402 static const iostate badbit = _S_badbit; 403 404 /// Indicates that an input operation reached the end of an input sequence. 405 static const iostate eofbit = _S_eofbit; 406 407 /// Indicates that an input operation failed to read the expected 408 /// characters, or that an output operation failed to generate the 409 /// desired characters. 410 static const iostate failbit = _S_failbit; 411 412 /// Indicates all is well. 413 static const iostate goodbit = _S_goodbit;
ios_base.h: 153 enum _Ios_Iostate 154 { 155 _S_goodbit = 0, 156 _S_badbit = 1L << 0, 157 _S_eofbit = 1L << 1, 158 _S_failbit = 1L << 2, 159 _S_ios_iostate_end = 1L << 16, 160 _S_ios_iostate_max = __INT_MAX__, 161 _S_ios_iostate_min = ~__INT_MAX__ 162 };
iostate是一个枚举类型,rdstate函数将返回上面四种枚举值之一,fail()函数将当前的枚举值与failbit和badbit比较,得出结果,所以,函数fail检查了两个流缓冲区状态位。
下面的表格来自于www.cplusplus.com网站对四种状态位的解释,以及相关的检查函数:
iostate value (member constants) | indicates | functions to check state flags | ||||
---|---|---|---|---|---|---|
good() | eof() | fail() | bad() | rdstate() | ||
goodbit | No errors (zero value iostate) | true | false | false | false | goodbit |
eofbit | End-of-File reached on input operation | false | true | false | false | eofbit |
failbit | Logical error on i/o operation | false | false | true | false | failbit |
badbit | Read/writing error on i/o operation | false | false | true | true | badbit |
从该网站对标志位的具体描述可知,failbit一般来说是程序在操作流的过程中发生了内部的逻辑错误时被设置,badbit是在处理流io时发生错误,即,缓冲区与文件可能存在完整性不一致时被设置。
未完,待续
设置eofbit的时机