小心为上:注意C++ fstream给你设下的陷阱

  1. 云栖社区>
  2. 博客>
  3. 正文

小心为上:注意C++ fstream给你设下的陷阱

华仔爱技术 2010-10-24 11:37:00 浏览1654
展开阅读全文

 

                      小心为上:注意C++ fstream给你设下的陷阱  

       

透过名字看本质:到底什么是stream

 stream的定义

stream的中文翻译为“流”,不是很好理解,我们来看英文关于stream的定义,比较常见的有两个:

1.      A stream is an abstraction that represents a device on which input and output operations are performed.

2.      A stream is a "stream of data" in which character sequences "flow."

 

英文看起来比较累,总结一下,提炼如下几个关键点:

1.      abstraction that represents a device:代表一个设备;

2.      stream of data:数据流,隐含了FIFO的一个特性;

3.      character sequences flow:字符序列在其中流动,而不是二进制在其中流动;

 

多说无益,还是看图说话:

 

 

注:上图中暗含一个玄机:stream的数据是设备数据的一个子集,因为stream只是代表一个设备,而并不是完全等于一个设备。

 

关公战秦琼: stream vs buffer

一个容易和stream混淆的概念就是大家常见的bufferbuffer的定义为(参见WIKI Data Buffer):A buffer is a region of memory used to temporarily hold data while it is being moved from one place to another.

英文看起来也比较累,还是总结一下:

1.      region of memory:一块内存区域,因此就隐含了随机访问和二进制的操作方式;

2.      one place to another:数据移动过程中使用

 

经过对两个术语定义的分析总结,相信大家都明白了这两者之间的差异了,其实这就是一个关公战秦琼的例子,因为它们两个实际上并不是一个竞争关系。

汇总对比点如下:

对比点

stream

buffer

作用

代表一个设备

数据临时存储

访问方式

FIFO

随机访问

数据内容

字符流

二进制

当然,并不是说streambuffer就毫无关系了,stream为了提高性能,实现的时候就用到了buffer

小心为上:注意fstream设下的陷阱

2.1  charwchar_t的操作

当你使用<<输出的时候,任何字符都可以输出;但当使用>>进行输入的时候,开头的空白字符缺省情况下是跳过的。

 

例如:假设文件中有这么一个字符串“       test”,则使用>>读入的时候,直接就读到了字母‘t’,而不会是空格。

避免这个陷阱的招数:

1.      清除skipws标志:unsetf(ios::skipws)

2.      使用get()函数

2.2  指针类型的操作

1.      char*

char*类型的数据输出时会将字符串全部输出,但输入的时候你千万要注意,输入的时候默认会将前面的空白字符全部去掉;而且输入时默认是以空格来作为分隔符的,例如:“This is a test C-string.”可以全部输出,但如果文件中有这么一串字符串,那么用<<是无法全部读入的,只能读入“This”“is”“a”“test”“C-string.

避免这个陷阱的招数:

1.      如果想改变默认去掉头部空白字符的操作方式,请参考skipws

2.      如果想改变以空格作为结束符的操作方式,对不起,用<<是没有办法的,只能用istream& istream::get (char* str, streamsize count, char delim)或者

istream& istream::getline (char* str, streamsize count, char delim)

 

2.      void*

直接输出指针地址。

 

3.      其它指针

不管是指向int/double等标准数据类型,还是自定义的struct/class类型,都是输出指针指向的地址,而不是输出指针指向的对象。

2.3  eoffail

2.3.1  eof

eof从字面意思来看,当然是end of file,用于表明当前已经到了文件末尾,不能再读了。

但这里有一个很迷惑的陷阱:只要遇到结束符,流就会将状态置为EOF,而不管置位前的操作是否成功。

例如,使用getline函数读取文件的最后一行,如果这一行是因为遇到了EOF而结束的,那么getline操作是成功的,但eof还是会置位。

 

因此,不能在调用函数后通过eof来判断函数调用是否读到文件末尾了,而应该直接判断调用本身是否成功,具体样例请看fail

2.3.2  fail

导致fail标志位置位的有如下常见的情况:

1.      文件不存在;

2.      文件不能创建;

3.      eof标志位置位;

4.      非法的格式,例如当你期望数字的时候,而文件里面却是字母

 

注意第三种情况,在文件eof的时候也会同时置fail,所以,循环读取文件的时候,要将faileof结合起来使用:在循环判断中使用failfail失败后再使用eof

错误的用法:

1:    std::ifstream file("test.txt");

2:    std::string word;

3:    double value;

4:    while ( true ) {

5:      // A word and a double value were both read successfully

6         file >> word >> value;

7         if( file.eof() )

8             break;

9:    }

 

正确的用法

1:    std::ifstream file("test.txt");

2:    std::string word;

3:    double value;

4:    while (file >> word >> value) {

5:      // A word and a double value were both read successfully

6:    }

7:    if (!file.eof()) throw std::runtime_error("Invalid data from file");

 

2.4  fstreambinary打开方式

文件流的打开方式中有一个binary,从字面意思来看,应该是按照二进制打开文件,然后进行二进制读写。

然而这样理解的话,就陷入了C++的陷阱:binary实际上和二进制读写没有关系,binary只是为了告诉系统是否将不同操作系统间特定的字符替换,最典型的是换行符,在windows上是/r/n,而在Unix类系统上是/n,如果加了这个binary标志,流就不会自动替换。

 

C++fstream流如何进行二进制读写呢?其实很简单:只需要调用get/readput/write即可。也就是说:是否是二进制读写和文件打开方式无关,而是和调用函数有关。如果使用<<>>,则就是按照字符流进行读写;如果使用get/readput/write,则按照二进制读写。

 

前面说过流是character sequences flow,那为什么这里又说fstream能够按照二进制读写呢?由于没有研究过相关的实现代码,因此这里无法给出分析,个人推断应该是流的内部实现将字符流转换为二进制了。

 

网友评论

登录后评论
0/500
评论
华仔爱技术
+ 关注