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

简介:

 

                      小心为上:注意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能够按照二进制读写呢?由于没有研究过相关的实现代码,因此这里无法给出分析,个人推断应该是流的内部实现将字符流转换为二进制了。

 

相关文章
|
存储 缓存 程序员
C++ 文件读写:探索 ofstream, ifstream 和 fstream 的奥秘
C++ 文件读写:探索 ofstream, ifstream 和 fstream 的奥秘
66 0
|
8月前
|
存储 算法 定位技术
c++游戏制作指南(四):c++实现数据的存储和读取(输入流fstream)
c++游戏制作指南(四):c++实现数据的存储和读取(输入流fstream)
246 0
|
数据安全/隐私保护 C++ iOS开发
c++中fstream类对文本文件的操作
#include #include #include using namespace std; //追加方式写文件 void writefile() { fstream f("try.
1027 0
|
C++
C++中fstream文件写入读出代码
按陈良乔的书写测试的。 我觉得先写入,再读出比较好, 所以修改了一下执行顺序。。。。 代码: #include #include #include using namespace std; int main(int argc, char *argv[]) { ...
948 0
|
19天前
|
存储 C++ 容器
C++入门指南:string类文档详细解析(非常经典,建议收藏)
C++入门指南:string类文档详细解析(非常经典,建议收藏)
31 0
|
19天前
|
存储 编译器 C语言
C++入门: 类和对象笔记总结(上)
C++入门: 类和对象笔记总结(上)
30 0
|
21天前
|
算法 编译器 C语言
【C++ 异常】C++ 标准库异常类及其应用
【C++ 异常】C++ 标准库异常类及其应用
16 0
|
9天前
|
存储 算法 C语言
【C++初阶】8. STL初阶 + String类
【C++初阶】8. STL初阶 + String类
45 1
|
9天前
|
C语言 C++
【C++初阶】9. string类的模拟实现
【C++初阶】9. string类的模拟实现
36 1
|
17天前
|
存储 安全 编译器
【C++】类的六大默认成员函数及其特性(万字详解)
【C++】类的六大默认成员函数及其特性(万字详解)
32 3