C++中的有关文件的设计,由于历史原因,着实不太好用,里面的坑很多。

一、常见文件打开方式

文件模式常量含义
常量 含义
ios_base::in 以读的方式打开文件
ios_base::out 以写的方式打开文件
ios_base::ate 打开文件,并移到文件尾
ios_base::app 追加到文件尾
ios_base::trunc 如果文件存在,则截断文件(删除原有内容)
ios_base::binary 二进制文件

这里面就有不少注意事项。

in 和 out

in和out是什么鬼?熟悉一些第三方库的同学肯定对read、write很了解,即便是C语言,也是用'r'和'w'来表示read和write,可这里为什么要搞个in和out?和我们常用的read、write有什么区别?

主要是针对的对象不同。

read和write,是针对文件本身而言,对文件读或者写;而in和out,是针对C++文件流而言,从文件流中流入和流出。文件流,位于你编写的程序中,所以,某种程度也可以将这个in和out理解成针对你的程序而言。

流出,是指字节流出你的程序。既然流出去,肯定要有接收方,否则流出的字节就会丢失掉。接收方就是存放于磁盘中的文件(虽然可以重定向,但简单考虑,就特定指文件),字节从程序中流出,写入到文件中去。所以,此时需要文件以写的方式打开。

流入,类似于流出,是指将字节从文件中流入程序的文件流。这时候,以读的方式打开文件,读取其中的字节,流入进程序。

还有一些注意事项,文章后面部分介绍。

二 、多版本I/O流

自从发现窄字符char不够用后,经过一系列演变,最后C++提供了宽字符wchar_t(有关窄字符和宽字符,这里不具体介绍)。相应的,处理这些字符的流也就需要同时支持者两个版本,于是有了cout-wcout、cin-wcin、fstream-wfstream等等。

那么,这些流的使用方式有什么不同呢?回答之前,先来看看这些流的源码定义:

可以看到,istream和wistream都是模板basic_istream的实例化,只是模板参数不同,一个是窄字符char,一个是宽字符wchat_t。所以这两个流的用法完全可以看做是同一个,其他的也类似。

三、文件流

注意,想使用文件流,需要包含头文件:#include <fstream>

1、fstream(wfstream)

都是basic_fstream实例化,所以看一下basic_fstream的声明:

原来,继承自basic_iostream

2、ifstream

继承自basic_istream。

3、ofstream

继承自basic_fstream。

所以,ofstream、ifstream和fstream之间没啥直接关系

ofstream,输出流,将程序中字节输出写到文件中;ifstream,输入流,从文件中读取字节输入到程序中;fstream,既可以输出也可以输入。

总结一下文件流与属性的特征(摘自《C++ Primer》):

1、只可以对ofstream或fstream对象设定out模式。
2、只可以对ifstream或fstream对象设定in模式。
3、只有当out也被设定时才可设定trunc模式。
4、只要trunc没被设定,就可以设定app模式。在app模式下,即使没有显式指定out模式,文件也总是以输出方式被打开。
5、默认情况下,即使我们没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作。
6、ate和 binary模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。

四、文件流与文件绑定方式

有两种方法:

1、在创建对象时,直接指定文件名和打开方式来绑定;

例如:D盘下有一个文本文件"123.txt",内容如下:

现在想打开文件,读取其中内容,并打印出来,可以这么做:

using namespace std;
    ifstream fIn("D:\\123.txt");  //只有文件名,没有打开方式
    if (fIn.is_open())
    {
        char ch;
        while (fIn.get(ch))
            cout << ch;
        fIn.close();
    }
    cout << endl;

执行结果:

嗯?有点不对是不是?为什么只有文件名没有打开方式?看一下basic_ifstream的构造函数就能够了解(构造函数的版本很多,这里只截与上例中匹配的那个。)

很容易看出来,第二个参数打开方式已有默认值ios_base::in,所以可以不指定。

2、利用open函数

通样是要实现上面的功能,还可以用下面的代码:

using namespace std;
    ifstream fIn;  //这里不带任何参数
    fIn.open("D:\\123.txt");  //用open函数,也只是给了文件名,未指定打开方式
    if (fIn.is_open())
    {
        char ch;
        while (fIn.get(ch))
            cout << ch;
        fIn.close();
    }
    cout << endl;

执行结果:

这里同样没有指定打开方式,也没有问题,因为open函数也给了默认值:

ofstream与文件的关联也类似于此,此处不赘述,但是来看一个比较有意思的现象。

假如我想往123.txt文件写入一行字符:I Love C++ more。怎么做呢?

前面有介绍,往文件中写,就是将文件流中的字节流出到文件中,所以应该用ofstream类,并且指定打开方式为ios_base::out。但有时候需要有点黑客精神,如果非要传入ios_base::in,会有什么情况发生?

在开始之前,有一点需要注意,打开一个文件,我们定位在的是文件首,如果想在文件最后增加新内容,则需要定位到文件尾,可以用前面表格中的ios_base::app。

因为后面经常要使用读取文件内容并打印的功能,故将其封装成一个名为ReadFile的函数,代码如下:

void ReadFile(const std::string& strFileName)
{
    std::ifstream fIn(strFileName);
    if (fIn.is_open())  //打开文件成功
    {
        char ch;
        while (fIn.get(ch))
            cout << ch;
        fIn.close();
    }
}

往文件中追加内容,并打印追加后的文件内容:

using namespace std;
    std::string strFileName("D:\\123.txt");
    ofstream fOut;
    fOut.open(strFileName, ios_base::in | ios::app);  //注意,这里传的是in,而不是out
    if (fOut.is_open())
    {
        fOut << "I Lova C++ more" << endl;
        fOut.close();
    }
    ReadFile(strFileName);

执行结果:

为什么???明明没有指定out属性啊,为什么能够往文件中写内容?

难道问题出在open函数上?如果直接在构造是指定文件名和打开方式,结果还是如此(读者可以自己尝试)。

why???

欲知答案,查看源码!

我们看一下basic_ofstream(ofstream从它继承的)类中open函数的源码:

看到没?不用去管_Filebuffer是啥,open函数调用了_Filebuffer对象的open函数,传入的第二个参数,除了basic_ofstream对象的open函数中的_Mode外,还'按位与'了一个ios_base::out,答案就在这里!

那构造函数呢?同样,也去看一下源码:

也是一样!

所以,对于ofstream类,不管你创建对象或者调用open函数时有没有指定ios_base::out属性,都会默认加上该属性!

而对于ifstream类,则会默认加上ios_base::in属性!(可以自己查看源码,此处不赘述。)

但是,fstream类,不会帮你加上任何属性!

五、文件操作

1、追加内容、截断内容

回顾一下我们在open函数那里是如何将"I Love C++ More"加到文件中的,为了方便阅读,将代码粘贴如下:

using namespace std;
    std::string strFileName("D:\\123.txt");
    ofstream fOut;
    fOut.open(strFileName, ios_base::in | ios::app);  //注意,这里传的是in,而不是out
    if (fOut.is_open())
    {
        fOut << "I Lova C++ more" << endl;
        fOut.close();
    }
    ReadFile(strFileName);

open函数中,属性我们加的是in和app,前面有提到,oftream类open函数的源码中,会默认加上ios_base::out,所以这里输出流打开文件的方式其实是:ios_base::out|ios_base::in|ios_base::app,in和out是读写方式打开,而app,则是append,增加的意思。

想要在文件末尾添加内容,必须加上app属性,如果不加上呢?我们可以试一下在不加上app属性的情况下,增加一行文字:What do you love,代码如下:

using namespace std;
    std::string strFileName("D:\\123.txt");
    std::ofstream fOut(strFileName, std::ios_base::out);  //这里没有app属性
    if (fOut.is_open())
    {
        fOut << "What do you love" << endl;
        fOut.close();
    }
    ReadFile(strFileName);

执行结果如下:

什么情况?还有两句呢?

仔细看最开始的那些文件打开方式,除了我们用到的in、out、app外,还有trunc、ate和binary,binary是二进制,本篇不讨论。ate将在下一节介绍,truc是什么意思呢?

ios_base::truc:截断文件,即加上这个属性,在打开文件的时候,会将原有的内容全部清除掉。

这里,又有一个巨大的坑!

有ios_base::out属性,则默认有ios_base::truc属性。要想去除其中的ios_base::truc属性,则必须加上ios_base:app属性或者ios_base::in属性。

是不是很绕?仔细分析一下,其实很容易理解。

ofstream,不管调用的时候有没有指定ios_base::out属性,类实现里都会默认加上,所以输出流操作文件,必须加上app或者in属性,才不会截断原来的文件内容;

ifstream,类实现部分会默认帮你加上ios_base::in属性,所以在使用输出流的时候,即便不手动加上app或者in属性,也不会截断原来的文件内容;

fstream流,不会默认加上任何属性,所以当手动指定ios_base::out属性后,必须加上in属性或者app属性(都加上当然没问题),才不会截断原来的文件内容。

以上关于这三个类属性的介绍,读者可自行验证,下面给出一些参考代码,未穷举以上所有情况,只是列了其中部分:

using namespace std;
    std::string strFileName("D:\\123.txt");
    cout << "----------------文件原始内容:" << endl;
    ReadFile(strFileName);  //打印123.txt中内容

//这里用ofstream类,加上in属性,不会截断文件内容
    ofstream fOut(strFileName, ios_base::in); 
    if (fOut.is_open())
        fOut.close();
    cout << "----------------ofstream,文件内容:" << endl;
    ReadFile(strFileName);  //打印123.txt中内容

//这里用ifstream类,仅有out属性,但类实现中会默认加上in属性,不会截断
    ifstream fIn(strFileName, ios_base::out);
    if (fIn.is_open())
        fIn.close();
    cout << "----------------ifstream,文件内容:" << endl;
    ReadFile(strFileName);  //打印123.txt中内容

//这里用fstream类,仅指定out属性,会截断文件内容
    fstream fFile(strFileName, ios_base::out);
    if (fFile.is_open())
        fFile.close();
    cout << "----------------fstream,文件内容:" << endl;
    ReadFile(strFileName);  //打印123.txt中内容

执行结果如下:

弄清楚截断特性后,追加文件内容比较简单,在避免截断的前提下,加上app属性即可。代码如下:

using namespace std;
    std::string strFileName("D:\\123.txt");
    cout << "----------------文件原始内容:" << endl;
    ReadFile(strFileName);  //打印123.txt中内容

//这里用fstream类,仅指定out属性,会截断文件内容
    fstream fFile(strFileName, ios_base::out | ios_base::app);
    if (fFile.is_open())
    {
        fFile << "What do you love?" << endl;
        fFile.close();
    }    
    cout << "----------------追加后,文件内容:" << endl;
    ReadFile(strFileName);  //打印123.txt中内容

执行结果为:

这里用的是fstream类,ofstream类也是一样。

2、查看文件大小

首先看两个函数:

tellg():

返回输入流的文件位置,g-get,既然是输入流,则只有ifstream和fstream才有该成员函数;

using namespace std;
    std::string strFileName("D:\\123.txt");
    ReadFile(strFileName);
    ifstream fIn(strFileName, ios_base::in);  //仅有ios_base::in属性
    if (fIn.is_open())
    {
        std::streamoff nSize = fIn.tellg();
        cout << nSize << endl;
        fIn.close();
    }

执行结果为:

为什么是0?

因为打开文件后,文件位置定位在的是未见头。文件打开方式中有一个ate,即 at end,会将文件指针移至文件尾,上面代码改成如下:

using namespace std;
    std::string strFileName("D:\\123.txt");
    ReadFile(strFileName);
    ifstream fIn(strFileName, ios_base::in | ios_base::ate);  //这里加上ate属性
    if (fIn.is_open())
    {
        std::streamoff nSize = fIn.tellg();
        cout << nSize << endl;
        fIn.close();
    }

执行结果为:

仔细一数,加上空格,好像只有28个字符,为什么是32呢?

因为有两个换行,而windows系统,用回车和换行两个字符来表示换行,所以28+4=32。

tellp():

返回输出流的文件位置,p-put。既然是输出流,则只有ofstream和fstream才有该成员函数。用法与tellp类似,注意上一节介绍,有out属性时,默认会截断文件,为避免这种情况,需要加上app属性或者in属性。

我们用ofstream类,加上app属性,代码如下:

using namespace std;
    std::string strFileName("D:\\123.txt");
    cout << "----------------文件原始内容:" << endl;
    ReadFile(strFileName);  //打印123.txt中内容

//测试tellp
    ofstream fOut(strFileName, ios_base::out | ios_base::app);  //加上app属性
    if (fOut.is_open())
    {
        cout << fOut.tellp() << endl;
        fOut.close();
    }

执行结果为:

0?难道和前面一样,加上ate属性,代码如下:

using namespace std;
    std::string strFileName("D:\\123.txt");
    cout << "----------------文件原始内容:" << endl;
    ReadFile(strFileName);  //打印123.txt中内容

//测试tellp
    ofstream fOut(strFileName, ios_base::out | ios_base::app | ios_base::ate);  //加上app和ate属性
    if (fOut.is_open())
    {
        cout << fOut.tellp() << endl;
        fOut.close();
    }

执行结果为:

结果正确。

还有关于seekp和seekg的内容,以及修改文件内容、在文件中间插入内容等,本篇不予介绍。

有关随机存取,并不是C++文件流的强项,C++实际使用中用到的并不多。这里主要介绍文件流的简单使用方式,以及平常使用中的一些坑,后面如若有时间,再专门写一篇有关seekp和seekg的文章。

C++基础(四) 文件流 I/O流 文件指针 tellg tellp seekp seekg相关推荐

  1. 流的定位tellg() / tellp()、seekg() / seekp()

    获取流位置 – tellg() / tellp() 可以用于获取输入 / 输出流位置 (pos_type 类型 ) – 两个方法可能会失败,此时返回 pos_type(-1) **tellp()**返 ...

  2. 【C#进阶四】详细总结C#中的文件和I/O流之文件和目录(File 、 FileInfo、Directory、DirectoryInfo和Path)

    文章目录 1 Flie类 1.1常用属性.方法 1.2 代码示例(详细) 2 FileInfo 2.1 常用属性.方法 2.2 代码示例 3 Directory 类 3.1 常用方法和属性: 3.2 ...

  3. java IO流基础 万字详解(从拷贝文件到模拟上传头像)

    目录 一.前言: 二.IO流简介: 1.什么是IO流? 2.IO流能干什么? 3.IO流的分类: 4.IO流体系: 三.字符流读写文件: 1.普通字符流读取文件: 前言: ①以单个字符读取: 代码演示 ...

  4. 零基础学Python(第十八章 文件IO流操作)

    本套学习内容共计[22]个章节,每个章节都会有对应的从0-1的学习过程详细讲解,希望可以给更多的人提供帮助. 开发环境:[Win10] 开发工具:[Visual Studio 2019] 本章内容为: ...

  5. 【java基础,IO合集】文件流、高级流(缓冲流、对象流)、 序列化接口java.io.Serrializable

    目录 文件流 1 块读写的文件复制操作 2 文件输出流-覆盖模式 3 文件输出流-追加模式 4 文件输出流-读取文本数据 高级流 1  流连接示意图 2 缓冲流 2.1 使用缓冲流完成文件复制操作 2 ...

  6. Java基础 Stream流方法引用异常文件

    Stream流 引例 需求:按照下面要求完成集合的创建和遍历 创建一个集合,存储多个字符串元素 1. 把所有以"曹"开头的元素存储到新集合中 2. 把曹开头,长度为3的元素存储到新 ...

  7. java基础实现水果超市系统(数组+集合+对象持久化(io流存储到txt文件))

    java基础实现水果超市系统(数组+集合+对象持久化(io流存储到txt文件)) Fruit类 package com.zr.fruitSupermarket;/*** 水果* @author ZR* ...

  8. JAVA mac系统io文件流_Java IO流基础1--IO的分类体系与文件流

    什么是IO流 Java中的IO 了解什么是IO流之前,要先知道什么是IO.IO,就是in和out(即输入和输出),指应用程序和外部设备之间的数据传递,常见的外部设备包括文件.管道.网络连接等. 流的概 ...

  9. C#,核心基础算法——文件处理的基础功能,文本文件与文本流编码问题的终极解决方案之源程序

    一.文件后缀那点事 一般的文件都有个后缀,比如:demo.cs ,cs 代表了 csharp ,也就是 c#.这个文件一般是一个  文本格式 C# 的源代码(源程序)文件. 那么 demo.cs 还可 ...

最新文章

  1. java中setid(),Java Process.setId方法代碼示例
  2. debain mariadb10配置root
  3. poj 1252 Euro Efficiency (01背包变形)
  4. 3(1)-字符缓冲流
  5. 华为副总裁回应应用删除用户图片;美国拟允许华为参与 5G 标准建设;Firefox 76.0 发布​ | 极客头条...
  6. 集成ueditor后显示html问题处理
  7. Office 程序默认打开方式
  8. iOS延时执行的几种方法
  9. 中国节能装备与产品市场“十四五”规划及2035年远景目标建议报2022-2028年
  10. 苹果x屏幕多少钱_xsmax闪屏,苹果xsmax换屏幕多少钱
  11. 《Python渗透测试编程技术:方法与实践》:信息的利用(进阶)
  12. 关于transition过渡的详解
  13. 做了三年数据分析,给你的几点建议
  14. java web 开发问题总结 1 原创-胡志广
  15. A `Concatenate` layer requires inputs with matching shapes except for the concat axis.
  16. 架构设计--软件工程
  17. python批量改文件名,截取原文件名的一部分
  18. 27年,IE时代终落幕
  19. 【Rust日报】 2019-03-04
  20. 【老狼推荐】Instruments 用户指南

热门文章

  1. GoDaddy SSL证书制作和安装
  2. 5000比特量子计算机,量子计算机平台正式发布:拥有5000量子比特
  3. 【R语言数据科学】:(九)数据清洗技巧之数据表连接大全
  4. Unity3D RectTransform中文教程详细用法分析
  5. 引文信件和地址的写法!
  6. 我的第六个项目:实现一个任意图片下载器
  7. 《写给大家看的设计书(第3版)》
  8. 射频和无线技术入门--射频行为--2
  9. 国有企业如何建立现代企业制度
  10. 【暑假实践总结】大学生暑假社会实践-关于社区人口老龄化的实践报告