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)indicatesfunctions 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的时机

 

posted on 2018-03-14 19:48  帅老  阅读(1008)  评论(0编辑  收藏  举报