c++进阶---IO类详解(二)--文件流的详解

迷南。 2022-07-12 15:22 347阅读 0赞

前言

在上一篇博客,我们已经主要介绍了IO类是什么和对标准输入流cin做了比较详细的介绍,这篇博客我们就来开始学习另外一类IO类:ifstream 、oftream、fstream。我们称之为文件流,主要是对系统的文件进行一些读写之类的操作。下面我们就对文件流进行详细的讲述。

1、流的分类总结

  1. ofstream :文件的写操作,主要是从内存写入存储设备(如磁盘),它是继承istream
  2. ifstream :文件的读操作,主要是从存储设备中读取数据到内存,它是继承了ostream
  3. fstream :读写操作,对打开的文件可进行读写操作,它是继承了iostream类。

2、打开文件

打开文件作为我们对文件操作一个最基本操作,它主要就是把我们的类对象和一个文件相关联起来,这样这个被打开的文件就可以用那个类对象表示,之后我们对类对象所做的输入和输出操作其实都是对那个文件所做的操作。

在每个文件流的类中都定义了一个打开文件的成员函数:open,函数原型为:

  1. void open(const char* filename,ios_base::openmode mode);
  2. 参数的含义:
  3. filename:  要打开的文件名
  4.   mode:    要打开文件的方式

其中mode定义在之前说的所有IO的基类:ios类中,它包括如下几种方式:

  1. ios::app:   以追加的方式打开文件
  2.   ios::ate:   文件打开后定位到文件尾,ios:app就包含有此属性
  3.   ios::binary: 以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文
  4.   ios::in:    文件以输入方式打开(文件数据输入到内存)(ifstream对象默认方式就是这个)
  5.   ios::out:   文件以输出方式打开(内存数据输出到文件)(ofstream对象默认的打开方式)
  6.   ios::nocreate 不建立文件,所以文件不存在时打开失败
  7.   ios::noreplace:不覆盖文件,所以打开文件时如果文件存在失败
  8.   ios::trunc:  如果文件存在,把文件长度设为0

其中,mode的参数可以不止一个,但是两个参数之间必须要用操作符:“|”隔开,例如:

  1. //下面是声明一个ofstream对象,往text.txt文件中输入内容,并且是在文件的末尾追加内容,不清空原有的内容,
  2. //具体声明如下
  3. ofstream out
  4. out.open("text.txt",ios::out|ios::app)

或者

  1. //这个声明方式是调用了ofstream有参构造函数,该构造函数会自动调用open函数。
  2. ofstream out("text.txt",ios::out|ios::app);

例外,我们之前说的三种文件流类都有自己默认的打开方式,具体的内容如下:

  1. ofstream默认打开方式:ios::out | ios::trunc
  2. ifstream默认打开方式: ios::in
  3. fstream默认打开方式: ios::in | ios::out

我们要对文件进行操作,那么第一个操作当然是要把我们需要操作的文件打开,所以每次我们调用完open之后,都要去判断一下我们的文件是否打开,你可以直接使用如下两种方式判断:

  1. ofstream out;
  2. out.open("text.txt",ios::out|ios::app);
  3. if(!out){
  4. cout<<"文件打开失败"<<endl;
  5. }
  6. //这个方式和我们之前使用:while(cin>>a)的原理一样,都是因为流的条件状态:badbit、failbit、eofbit
  7. //三者中只要有一个被置位,那么我们的条件都是返回false的,而文件打开失败,failbit会被置位。

或者

  1. ofstream out;
  2. out.open("text.txt",ios::out|ios::app);
  3. if(!out.is_open()){
  4. cout<<"文件打开失败"<<endl;
  5. }
  6. //你可以通过调用成员函数is_open()来检查一个文件是否已经被顺利的打开了:bool is_open();
  7. //它返回一个布尔(bool)值,为真(true)代表文件已经被顺利打开,假( false )则相反。

3、关闭文件

当我们完成了对文件的操作后,我们就需要调用函数close来关闭我们的文件流,close函数的作用其实就是清空该类对象的在缓存中的内容并且关闭该类对象和那个文件的关联关系。为了防止一个类对象被销毁后,还和某个文件保留着关联关系,所以文件流类的析构函数都会自动调用close函数。

4、文件读写操作

对于文件读写操作要分两种:

  1. 文本文件的读取
  2. 二进制文件的读取

(1)文本文件的读写

使用操作符:“<<”往文本文件中写入数据,使用操作符:“>>”从文本文件中读取数据,具体的代码示例如下:

  1. /************************************************************/
  2. /* 程序作者:Willam */
  3. /* 程序完成时间:2017/3/5 */
  4. /* 有任何问题请联系:2930526477@qq.com */
  5. /************************************************************/
  6. //@尽量写出完美的程序
  7. #include<iostream>
  8. #include<fstream>
  9. #include<string>
  10. using namespace std;
  11. //往文本文件中写入数据。
  12. void write_content_file() {
  13. //如果text.txt文件不存在,系统会自动新建一个text.txt文件
  14. //如果text.txt文件存在了,系统会先把文件清空,除非我们设置了
  15. //打开文件的属性:ios::app,那么系统就会在文件的末尾追加内容
  16. ofstream fout("text.txt");
  17. if (!fout) { cerr << "文件打开失败" << endl; return; }
  18. //插入一个字符串
  19. fout << "hello , i am fine." << endl;
  20. //fout其实就是一个指针,此时,fout它指向了字符串“hello , i am fine.”的末尾。
  21. //所以当我们希望往文件中插入新的数据时,它就会直接在最后追加。
  22. int num = 1;
  23. fout << num << endl;
  24. fout.close();
  25. }
  26. void read_content_file() {
  27. //把刚才的内容读取出来
  28. ifstream fin("text.txt");
  29. //以读文件为目的打开文件,最容易出现问题
  30. if (!fin) { cerr << "文件打开失败" << endl; return; }
  31. //读取一行的方法。
  32. string str;
  33. getline(fin, str);
  34. cout << str << endl;
  35. //其实,fin也是一个指针,现在它指向了文件中存放上一个函数变量num的内存空间
  36. //的首地址。
  37. int num;
  38. fin >> num;
  39. cout << num << endl;
  40. fin.close();
  41. }
  42. int main() {
  43. write_content_file();
  44. read_content_file();
  45. system("pause");
  46. return 0;
  47. }

输出结果:
这里写图片描述 这里写图片描述

(2)二进制文件的读写

对于二进制文件的读写,我们必须强调的一点就是打开文件属性必须添加:ios::binary,其实就是告诉系统,我要按照二进制格式进行读写文件了。

具体代码示例如下:

  1. /************************************************************/
  2. /* 程序作者:Willam */
  3. /* 程序完成时间:2017/3/5 */
  4. /* 有任何问题请联系:2930526477@qq.com */
  5. /************************************************************/
  6. //@尽量写出完美的程序
  7. #include<iostream>
  8. #include<fstream>
  9. #include<string>
  10. using namespace std;
  11. //往二进制文件中写入数据。
  12. void write_content_file() {
  13. ofstream fout("text.bat", ios::binary);
  14. if (!fout) { cerr << "文件打开失败" << endl; return; }
  15. int num = 1;
  16. string str = "Hello word";
  17. //记住,write有两个参数,第一个为字符数组参数,第二个需要输入到文件的数据的
  18. //大小
  19. fout.write((char*)&num, sizeof(int));
  20. fout.write(str.c_str(),sizeof(char)*(str.size()));
  21. fout.close();
  22. }
  23. //读取二进制文件中的数据。
  24. void read_content_file() {
  25. ifstream fin("text.bat", ios::binary);
  26. if (!fin) { cerr << "文件打开失败" << endl; return; }
  27. int num;
  28. char buf[256] = {
  29. 0};
  30. //记住,read有两个参数,第一个为字符数组参数,第二个需要输入到文件的数据的
  31. //大小
  32. fin.read((char*)&num, sizeof(int));
  33. fin.read(buf, sizeof(char)*256);
  34. cout << num << endl;
  35. cout << buf << endl;
  36. fin.close();
  37. }
  38. int main() {
  39. write_content_file();
  40. read_content_file();
  41. system("pause");
  42. return 0;
  43. }

输出结果为:
这里写图片描述这里写图片描述

首先,我们要记住我们是以二进制流方式打开和读取文件的,所以我们的在text.bat中看到hello word前面会有四个空白符,因为1的是int型,它占用了四个字节,而且我们是以ASCII编码的,所以0是空白符,1是辩题的开始。

由于文本文件本质上也是磁盘上的一个个二进制编码,因此,读写二进制文件的代码同样可以读写文本文件,在文件类型不是很明确的读写操作中,直接使用二进制读写比较可取。

当然,我们有时候需要遍历整个文件,然后,对文件内容进行操作,我们通过函数eof()来判断文件是否到达文件末尾了,如果到达文件尾返回非0值,否则返回0。原型是int eof();下面一个例子来演示如果遍历整个文件:

代码如下:

  1. /************************************************************/
  2. /* 程序作者:Willam */
  3. /* 程序完成时间:2017/3/5 */
  4. /* 有任何问题请联系:2930526477@qq.com */
  5. /************************************************************/
  6. //@尽量写出完美的程序
  7. #include<iostream>
  8. #include<fstream>
  9. #include<string>
  10. using namespace std;
  11. //将一个文件的内容复制到另外一个文件
  12. void copy_file_content() {
  13. ifstream fin("text.txt");
  14. ofstream fout("text1.txt");
  15. if(!fin || !fout) { cerr << "文件打开失败" << endl; return; }
  16. while (!fin.eof()) {
  17. char buf[256] = { 0 };
  18. fin.read(buf, sizeof(char) * 256);
  19. fout.write(buf, sizeof(char) * 256);
  20. }
  21. }
  22. int main() {
  23. copy_file_content();
  24. system("pause");
  25. return 0;
  26. }

结果为:
这里写图片描述

5、文件定位

c++文件系统管理着两个与文件相关联的指针

  1. 读指针:说明下一次读在文件中的位置
  2. 写指针:说明下一次写在文件的位置,

每次执行了读或写操作后,这两个指针的值都会自动变化,另外,c++还提供了修改这两个指针值的函数分别是:

  • seekg(),设置下次读的位置
  • seekp(),设置下一次写的位置

这两个函数的原型如下:

  1. istream &seekg(streamoff offset,seek_dir origin);
  2. ostream &seekp(streamoff offset,seek_dir origin);

其中,offset代表的是我们希望把指针移到的距离的大小,origin代表起点是哪里,它的取值有三种如下:

  1. ios::beg:  文件开头
  2. ios::cur:  文件当前位置
  3. ios::end:  文件结尾

下面,我们举两个例子来说明上面两个函数的使用方式:

  1. file1.seekg(1234,ios::cur); //把文件的读指针从当前位置向后移1234个字节file2.seekp(1234,ios::beg); //把文件的写指针从文件开头向后移1234个字节

其中,我们可以通过文件的定位来计算文件的大小,但是在介绍如何计算文件大小前,我们需要在介绍两个函数:

  1. 对读操作:seekg()与tellg() //函数tellg()返回读指针当前的值
  2. 对写操作:seekp()与tellp() //函数tellp()返回写指针当前的值

所以下面我们就介绍如何计算文件的大小:

  1. /************************************************************/
  2. /* 程序作者:Willam */
  3. /* 程序完成时间:2017/3/5 */
  4. /* 有任何问题请联系:2930526477@qq.com */
  5. /************************************************************/
  6. //@尽量写出完美的程序
  7. #include<iostream>
  8. #include<fstream>
  9. #include<string>
  10. using namespace std;
  11. void filesize() {
  12. ifstream fin("text.txt");
  13. if (!fin) { cerr << "文件打开失败" << endl; return; }
  14. fin.seekg(0, ios::beg);
  15. streampos sp = fin.tellg();
  16. cout << "tellg起点为:" << sp
  17. << ",所以当其定位到文件末尾的指针时,其值就是文件的大小,如下列程序所示"
  18. << endl;
  19. fin.seekg(0, ios::end); //基地址为文件结束处,偏移地址为0,于是指针定位在文件结束处
  20. sp = fin.tellg(); //sp为定位指针,因为它在文件结束处,所以也就是文件的大小
  21. cout << "file size:" << sp <<"字节"<< endl;
  22. }
  23. int main() {
  24. filesize();
  25. system("pause");
  26. return 0;
  27. }

输出:
这里写图片描述这里写图片描述

发表评论

表情:
评论列表 (有 0 条评论,347人围观)

还没有评论,来说两句吧...

相关阅读