C++ fstream 详解

最近在写哈夫曼压缩,遇到了一个比较让人头疼的问题,那就是对文件的读写操作,尤其是以二进制的形式来读写,无奈C++Primer第五版上写的并不详细,很多让人困惑的地方没有涉及或者没有讲清楚。于是这几天我一直在网上看了很多这方面相关的文章,同时自己也在电脑上试了很多,觉得理解了不少,很多困惑也迎刃而解。

我们都知道,C语言里面对文件的操作是通过文件指针,以及一些相关的函数,那么C++中是如何对文件进行操作的呢?没错,就是通过 fstream 这个文件流来实现的。第一次听到文件流这个名字时,我也是一脸懵逼,总感觉这东西太抽象了,其实我们可以简单地把它理解成一个类,这样是不是清楚多了,当我们使用#include <fstream> 时,我们就可以使用其中的 ifstream,ofstream以及fstream 这三个类了,也就可以用这三个类来定义相应的对象了,例如:

ifstream fin();

这样我们就定义了一个叫做fin的对象了,它就和我们自己定义的其他对象一样,可以调用ifstream类中的一些函数,可以使用ifstream类中定义的一些操作符等等。那么我们定义完这个对象后,怎么和某个文件关联起来呢?毕竟我们最终是希望它能帮我们实现对文件的操作。没错,我们可以调用它的一个成员函数来实现,如下:

fin.open("test.txt");

open()这个成员函数重载了好几种形式,上面这种是最简单的,它中间的参数是一个常量字符指针,你可以填入所要打开文件的相对路径(比如和你的.cpp文件在同一目录下你就可以直接输入文件名,注意必须是完整的文件名,包含文件拓展名,否则找不到这个文件的),也可以填入绝对路径(比如 "D:\CodeBlocks\Myprojects\0407test\test.txt"),当然,你也可以填入一个指向文件名字符串的指针常量,或者内容等于文件名的string常量,例如:

const char * c = "test.txt";

fin.open(c);

或者

const string s = "test.txt";

fin.open(s); 

注意必须是指向常量的指针或string

如果不是const string 那么得这样用

string s = "test.txt";

fin.open(str.c_str()); 这里的c_str()是 string 的一个成员函数,作用就是变成一个const string ,其实也就相当于上面的了

另外,open()函数里还可以加上其他参数,例如打开的方式,等等。但是文件名是最基本的一个,如果其他参数未加,将使用缺省值,这个函数的声明,

void open ( const char * filename,  
            ios_base::openmode mode = ios_base::in | ios_base::out );  
  
void open(const wchar_t *_Filename,  
        ios_base::openmode mode= ios_base::in | ios_base::out,  
        int prot = ios_base::_Openprot); 

参数:

filename    操作文件名

mode        打开文件的方式

prot          打开文件的属性   //基本很少用到,在查看资料时,发现有两种方式

ios::in 为输入(读)而打开文件
ios::out 为输出(写)而打开文件
ios::ate 初始位置:文件尾
ios::app 所有输出附加在文件末尾
ios::trunc 如果文件已存在则先删除该文件
ios::binary 二进制方式

 

 

 

 

 

 

 

这些方式是能够进行组合使用的,以“或”运算(“|”)的方式,例如:

fin.open("test.txt", ios::in|ios::out|ios::binary) 

另外,这个类有一个构造函数允许我们在定义流对象的时候以文件名初始化,如下:

ifstream fin("test.txt");

ifstream fin("test.txt",ios::in);

const string s = "test.txt";

ifstream fin(s,ios::in);

ofstream,ftream和上面是类似的,就不详细展开了

接下来说说几个重要的函数,

一个是成员函数is_open(),可以判断文件是否正确打开,如果是,返回true,否则,返回false。

然后是getline()函数,这个函数是按行读取txt中的内容,示例如下

ifstream fin("test.txt",ios::in);
string s;
while(getline(fin,s))
        cout << s;//输出每一行

每次从fin指向的文件中读取一行,一行之中的所有字符都会被读入,包括空格。但是结尾的空格不读入,回车换行也不读入。

我们知道,所有输入/输出流对象(i/o streams objects)都有至少一个流指针:

  • ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。
  • ofstream, 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。
  • fstream, 类似 iostream, 同时继承了get 和 put

我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针

tellg() 和 tellp()
这两个成员函数不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前get 流指针的位置 (用tellg) 或 put 流指针的位置(用tellp).
seekg() 和seekp()
这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:
seekg ( pos_type position );
seekp ( pos_type position );
使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置。要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。
seekg ( off_type offset, seekdir direction );
seekp ( off_type offset, seekdir direction );
使用这个原型可以指定由参数direction决定的一个具体的指针开始计算的一个位移(offset)。它可以是:

ios::beg 从流开始位置计算的位移
ios::cur 从流指针当前位置开始计算的位移
ios::end 从流末尾处开始计算的位移

文本中的字符是从position = 0开始的,例如

//假设test.txt中的内容是HelloWorld
ifstream fin("test.txt",ios::in);
cout << fin.tellg();//输出0,流置针指向文本中的第一个字符,类似于数组的下标0

char c;
fin >> c;
fin.tellg();//输出为1,因为上面把fin的第一个字符赋值给了c,同时指针就会向后 移动一个字节(注意是以一个字节为单位移动)指向第二个字符

fin.seekg(0,ios::end);//输出10,注意最后一个字符d的下标是9,而ios::end指向的是最后一个字符的下一个位置

fin.seekg(10,ios::beg);//和上面一样,也到达了尾后的位置

//我们发现利用这个可以算出文件的大小

int m,n;
m = fin.seekg(0,ios::beg);
n =  fin.seekg(0,ios::end);
//那么n-m就是文件的所占的字节数

我们也可以从文件末尾出发,反向移动流指针,
fin.seekg(-10,ios::end);//回到了第一个字符

tellp()以及seekp()的用法类似

顺便讲一下 << 以及 >> 这两个操作符

由于ifstream以及ofstream分别继承于istream和ostream,所以他们也分别继承了相应的运算符,这就是为什么我们可以有下面这种操作的原因了:

ifstream fin("test.txt");

char c;

fin >> c;

 这其实和我们从控制台读取文件是一个道理,我们利用istream 中的对象cin 和其中的操作符 >> 来从控制台读取数据

这里我们用fin对象和 >> 操作符来从相应的文件中读取数据,相当于流的方向从控制台改变到文件中去了

不过这里应该要注意一下 >> 这个操作符的特点,有如下例子:

假如test.txt中的内容是Hello World

ifstream fin("test.txt");

string s;

fin >> s;

cout << s;

我们发现输出并不是Hello World,而是Hello,这是为什么呢?

几番试验后,我们发现,文件的每个空白之后, ">>" 操作符会停止读取内容, 直到遇到另一个>>操作符. 因为我们读取的每一行都被换行符分割开(是空白字符), ">>" 操作符只把这一行的内容读入变量。这就是这个代码也能正常工作的原因。如果你想把整行读入一个char数组, 我们没办法用">>"?操作符,因为每个单词之间的空格(空白字符)会中止文件的读取。为了验证,有如下示例:

char  c[20] ;

fin >> c;

我们想包含整个句子, 所以当我们想读取整行时,我们得用上面提到的getline()。这就是我们要做的:

fin.getline(c, 20); 

cout << c;

这是函数参数. 第一个参数显然是用来接受的char数组. 第二个参数是在遇到换行符之前,数组允许接受的最大元素数量

现在我们得到了想要的结果:Hello World。

或者这样用也行:

string s;

getline(fin,s);

cout << s;

注意这样操作后流指针会指向文件的尾后

 

接下来说说本文的重点了,那就是以二进制的形式读写文件

这里的重点就是两个函数的使用,分别是read()和write()函数

二进制文件会复杂一点, 但还是很简单的。 首先你要注意我们不再使用插入和提取操作符(译者注:<< 和 >> 操作符). 你可以这么做,但它不会用二进制方式读写。你必须使用read() 和write() 方法读取和写入二进制文件. 创建一个二进制文件, 看下一行。

ofstream fout("file.dat", ios::binary); 

这会以二进制方式打开文件, 而不是默认的ASCII模式。首先从写入文件开始。函数write() 有两个参数。 第一个是指向对象的char类型的指针, 第二个是对象的大小(译者注:字节数)。 为了说明,看例子:

int number = 30;

fout.write((char *)(&number), sizeof(number));

第一个参数写做"(char *)(&number)". 这是把一个整型变量转为char *指针。如果你不理解,可以立刻翻阅C++的书籍,如果有必要的话。

第二个参数写作"sizeof(number)". sizeof() 返回对象大小的字节数. 就是这样!

这样就写入了整个结构! 接下来是输入. 输入也很简单,因为read()函数的参数和 write()是完全一样的, 使用方法也相同。

ifstream fin("file.dat", ios::binary);

fin.read((char *)(&obj), sizeof(obj));

我不多解释用法, 因为它和write()是完全相同的。二进制文件比ASCII文件简单, 但有个缺点是无法用文本编辑器编辑。 接着, 我解释一下ifstream 和ofstream 对象的其他一些方法作为结束.

 

参考文章: 

http://blog.csdn.net/kingstar158/article/details/6859379

http://www.cnblogs.com/greatverve/archive/2012/10/29/cpp-io-binary.html 

http://blog.csdn.net/lightlater/article/details/6364931 

posted @ 2017-04-07 19:18  nullxjx  阅读(35841)  评论(0编辑  收藏  举报