最近在写哈夫曼压缩,遇到了一个比较让人头疼的问题,那就是对文件的读写操作,尤其是以二进制的形式来读写,无奈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(,ios::end);//输出10,注意最后一个字符d的下标是9,而ios::end指向的是最后一个字符的下一个位置 fin.seekg(,ios::beg);//和上面一样,也到达了尾后的位置 //我们发现利用这个可以算出文件的大小 int m,n;
m = fin.seekg(,ios::beg);
n = fin.seekg(,ios::end);
//那么n-m就是文件的所占的字节数 我们也可以从文件末尾出发,反向移动流指针,
fin.seekg(-,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

C++ fstream 详解的更多相关文章

  1. C++文件读写详解(ofstream,ifstream,fstream)

    C++文件读写详解(ofstream,ifstream,fstream) 这里主要是讨论fstream的内容: #include <fstream> ofstream //文件写操作 内存 ...

  2. delphi 资源文件详解

    delphi资源文件详解 一.引子: 现在的Windows应用程序几乎都使用图标.图片.光标.声音等,我们称它们为资源(Resource).最简单的使用资源的办法是把这些资源的源文件打入软件包,以方便 ...

  3. MD5加密详解

    MD5加密详解 引言: 我在百度百科上查找到了关于MD5的介绍,我从中摘要一些重要信息: Message Digest Algorithm MD5(中文名为信息摘要算法第五版)为计算机安全领域广泛使用 ...

  4. C++通过jsoncpp类库读写JSON文件-json用法详解

    介绍: JSON 是常用的数据的一种格式,各个语言或多或少都会用的JSON格式. JSON是一个轻量级的数据定义格式,比起XML易学易用,而扩展功能不比XML差多少,用之进行数据交换是一个很好的选择. ...

  5. 18、标准IO库详解及实例

    标准IO库是由Dennis Ritchie于1975年左右编写的,它是Mike Lestbain写的可移植IO库的主要修改版本,2010年以后, 标准IO库几乎没有进行什么修改.标准IO库处理了很多细 ...

  6. 孙鑫视频VC++深入详解学习笔记

    孙鑫视频VC++深入详解学习笔记 VC++深入详解学习笔记 Lesson1: Windows程序运行原理及程序编写流程 Lesson2: 掌握C++基本语法 Lesson3: MFC框架程序剖析 Le ...

  7. sift代码实现详解

    1.创建高斯金字塔第-1组 1.1.将源图片转成灰度图 void ConvertToGray(const Mat& src, Mat& dst) { cv::Size size = s ...

  8. OpenCV-Mat结构详解

    前面博客中Mat函数谈到一些理解,但是理解的比较浅显,下面谈谈通道,行列等意义: Mat的常见属性 opencv中type类型· CV_<bit_depth>(S|U|F)C<num ...

  9. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

随机推荐

  1. spring中对象的注入方式

    平常的java开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,spring提出了依赖注入的思想,即依赖类不由程 ...

  2. .Net软件开发面试技巧

    2016.11.20日,我们毕业了!到了大家各奔东西的日子了,有留在家里的,有另求出路的,有留在哈尔滨的,有去北京的!去北京的一共有11个同学,我就是这11个人里的一个! 大学刚毕业的时候,在济南上班 ...

  3. Linux 命令--查看物理CPU个数、核数、逻辑CPU个数

    # 总核数 = 物理CPU个数 X 每颗物理CPU的核数 # 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数 # 查看物理CPU个数 cat /proc/cpuinfo| ...

  4. C语言中NULL的定义

    用C语言编程不能不说指针,说道指针又不能不提NULL,那么NULL究竟是个什么东西呢? C语言中又定义,定义如下: #undef NULL #if defined(__cplusplus) #defi ...

  5. 剑指offer编程题Java实现——面试题12相关题大数的加法、减法、乘法问题的实现

    用字符串或者数组表示大数是一种很简单有效的表示方式.在打印1到最大的n为数的问题上采用的是使用数组表示大数的方式.在相关题实现任意两个整数的加法.减法.乘法的实现中,采用字符串对大数进行表示,不过在具 ...

  6. 开始了大概三四天的Rails学习之路

    最近因为一位极光推送朋友,我开始了大概三四天的Rails学习之路,最终达到的水平是可以比较轻松地做出大部分功能,然后自我感觉可以自如地按照Rails的设计思想去思考.由于编程的日益流行,我结识了越来越 ...

  7. Visual Studio 20周年软件趋势随想

    从2002年开始,.net让开发人员能快速构建和部署应用程序,便捷的开发windows和web服务器应用,同时著名的hacker Miguel de Icaza ,Miguel 为了GNOME项目启动 ...

  8. 摆脱printf的噩梦

    众所周知,printf是一个方便.直观.易写.变长参数的打印函数,但它有一个致命的缺陷,如下的语句将导致程序出现严重的运行时错误: printf("%s", 1); 然后程序中断, ...

  9. 使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱

    1.当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的某一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释来精确配置. 2.示例 ...

  10. C — 对C语言的认识

    有趣的C语言代码 看一下这段代码输出的是什么 #include <stdio.h> int main() { ; printf("%d\n", printf(" ...