如何使用c++中file stream


  在c++中引入了stream,一开始接触这个的时候感觉无法正确的理解stream的用法,在写项目的时候要用到读写文件,慢慢理解了stream的一些基本的用法。

  • overview

首先我们来看一下fstream的基本信息:


  可以看到fstream是从iostream中继承来的,那么iostream的方法fstream,我们再来看看c++中提供的I/O类:


图中表明了各个类的关系,我们主要来看看fstream中的内容,fstream有4个类:

  • ifstream
  • fstream
  • ofstream
  • filebuf

其中前三个类中ifstreamofstream的意思从字面上很好理解:

  • ifstream将文件作为输入的源
  • ofstream将文件作为输出destination
  • fstream在定义文件流对象是可以自己设置为in或者out

定义了文件流对象后,每个文件流对象维护自己的filebuf,filebuf是与文件关联的缓冲区,filebuf对象在其内部是通过操作一个中间的输入/输出缓冲区,该文件内容的改变会与其同步(在对文件操作完后显示的调用sync或者文件关闭之后)

  • 不同I/O类型的关系

  • 从概念上来说,不同的设备和不同的char(charwchar)的size对I/O操作没有影响。例如:

我们可以用cin从控制台、文件、string中获取输入;
    同理也可以用cout向不同控制台、文件、string中输出

c++抹平了这些是通过继承实现的,ifstreamistringstream是从istream中继承来的,那么继承保证我们在使用ifstreamistringstream时好像在使用istream(cin)一样。

  • I/O类不允许拷贝和赋值(将拷贝构造和赋值构造声明为私有方法并不予以实现)

  • fstream

我们来看一个简单的示例程序:

    #include <iostream>
    #include <fstream>
    int main(int argc, char **argv)
    {
        std::ofstream my_file;
        my_file.open("example.txt");
        my_file << "Contents from ofstream.\n"
        my_file.close()
        return 0;
    } 

以上的代码会往example.txt文件(若当前路径下存在则直接写入,不存在则会创建对应的文件)中写入我们要写的内容,和cout向屏幕输出一样,只是这里屏幕变成了文件而已。
从以上的代码入手,我们来一步一步看看文件流的用法。

  • 打开文件

开文件就是我们需要将文件与文件对象关联,一个打开的文件在程序中的呈现方式是以文件流对象存在的,对流对象的任何操作都会应用到物理文件上,打开一个文件的动作是:
open(filename, mode)

其中filename是文件名,类型是字符串,mode是一个可选的参数,可以从以下组合中选择:

fstream mode

以上的每个可选参数可以通过位操作的|来组合,例如我们想将文件以二进制的形式打开,并向其尾巴部添加内容:
    std::ofstream my_file("example.txt", ios::out | ios::app | ios::binary);

每一个fstream都有自己的默认模式:

default mode

通过以上的表格,ifstreamofstream即使在定义的时候,其mode参数默认为in或者out,对于fstream,默认的mode被应用只在fstreammode参数没有传入任何值的时候才会组合ios::inios::out,但是只要mode中有一个参数被选择,那么默认的参数就会被重写,不考虑先前的组合的mode

'fstream'分别定义了三个构造函数会自动的调用open()函数,我们可以这样来定义一个fstream对象:

    std::fstream my_file("example.txt", ios::out | ios::app | ios::binary);

但是打开文件可能会出现打不开的异常情况,标准库提供了一个函数is_open()去检测文件是否打开成功:

    if(my_file.is_open()){
        /*open is successful, proceed with output*/
    }
  • 关闭文件

就像我们开水龙头必然要关水龙头一样,我们打开文件必须要关闭文件,文件一直打开会消耗系统资源。在我们完成了对我文件的输入或输出操作后关闭文件后,对应的文件资源变得可用。
  my_file.close();
以上的close()调用将会刷新与之相关的缓冲区并将文件关闭。在关闭之后其原先的文件流对象可以绑定其他文件了,并且该文件可以被其他进程打开。值得注意的是,在实际的编写代码的过程中我们经常会忘记调用close(),但是在文件流对象被析构的时候与之关联的文件也会被关闭,但是我认为自己主动去关闭文件是个好习惯,最好不要把这个任务交给编译器。

  • 文本文件

在打开文件的的模式中不加入ios::binary的打开方式是以文本的方式去打开文件,在这里存在一个问题是,对于格式化输出(\n\t等转移字符)的文本,会有一个转换的过程,这里的行为就像cout向屏幕输出一样。

    #include <iostream>
    #include <fstream>
    int main(int argc, char **argv)
    {
        std::ofstream my_file("example.txt");
        if(my_file.is_open()){
            my_file << "This is a line.\n";
            my_file << "This is another line.\n";
        }else{
            std::cout <<"Unable to open file";
        }
        return 0;
    }

文件的内容如下:
example.txt:

This is a line.
This is another line.

从文件中输入的行为类似与我们从cin中获取输入的行为类似,这里不举例。

  • 检测打开文件的文件流对象的状态

标准库提供了一系列成员函数去检测文件流对象的状态。

bad()

对文件的读写操作失败时返回true,例如,当我们试图去向一个未打开的文件执行写入操作或者向文件写入时文件空间不足的时候会返回true

fail()

行为和bad()类似,但是在在读写文件内容,内容格式的错误的时候也会返回true,例如我们去读一个整数的时候,但是一个字母被读入的时候,这时候'fail()'会返回true

eof()

读文件到文件尾的时候返回true

good()

在以上介绍的所有状态中只要有一个返回truegood()就返回false,注意good()bad()不是相反的操作,相对于bad(),good()检测的状态更多。
成员函数clear()用于重置以上的状态。

  • 获取和设置文件流对象的位置

所有的流对象都会在其内部至少维护一个指示读写位置的动作。
ifstreamistream一样维护一个内部的get,来获取当前文件的读入的位置,ofstream反之亦然,而fstream维护了一个getput的动作。
对于当前文件流读写的位置可以通过以下的成员函数获取或者修改。

tellg() && tellp()

这两个函数返回一个类型为streampos的值,这个类型的值表示当前的位置,其中get position(tellg()返回)或表示put position(tellp()返回)

seekg() && seekp()

以上函数允许改变当前的getput position,其函数调用被重载:
seekg(position)
seekp(position)
以上两个函数改变了stream pointerposition的绝对位置(从文件的开始位置算起),其参数的类型是streampos

另外两个重载函数的原型为;
seekg(offset, direction)
seekp(offset, direction)
以上是改变stream position的相对位置,以direction为参考对象作偏移,其中direction的类型是seekdir,是一个枚举类型,其值有以下的值可选:


| ios::beg | offset counted from the beginning of the stream|
| ios::cur | offset counted from the curent position       |
| ios::end | offset counted from the end of the stream     |

    /**********************************
    * @Brief: Obtaining the file size
    * @CreatedTime:29/5/16
    **********************************/
    #include <iostream>
    #include <fstream>
    int main(int argc, char **argv)
    {
        std::streampos begin, end;
        std::ifstream my_file("example.bin", ios::binary);
        begin = my_file.tellg()  // get the beginning position
        my_file.seekg(0, ios::end);  // set the stream pointer to end position
        end = my_file.tellg();  // get the end position
        my_file.close();  // close the opening file
        std::cout << "Size is: " << (end - begin) << "bytes.\n";  // calculate the file size
        return 0;
    }

在以上代码中我们注意到有streampos类型的值,streampos类型是专门为缓冲区和stream pointerposition使用的,具有相同类型的值可以做减法,也可以转换为整型。

  • 二进制文件

二进制的文件不同于文本文件,文本文件读写其内容时有其格式,例如我们可以利用getline获取每一行的内容,但是首先对于二进制文件来说getline()来读取二进制文件效率不高,还有就是二进制的数据可能不是按照行的格式存储。
  file stream为二进制的数据设计了两个成员函数读写二进制文件,read()write()是读写二进制文件的操作,其中write()ostream的成员函数,fstreamostream中继承得到这个函数;read()函数是istream的成员函数,ifstream从其继承这个函数; fstream的对象同时拥有这两个函数,其中readwrite函数的原型如下:
write(memory_block, size)
read(memory_block, size)
read()write()中的memory_block是一个字节数组(char*),表示要读入或者写入的字节块,其中size是一个整型表示读取或者要写入的字节数。

    #include <iostream>
    #include <fstream>
    int main(int argc, char **argv)
    {
        std::streampos size;
        char *memory_block;
        std::ifstream file("example.txt", ios::in | ios::bnary | ios::ate);
        if(file.is_open()){
            size = file.tellg();
            memory_block = new char[size];
            file.seekg(0, ios::beg);
            file.read(memory_block, size);
            file.close();
            std::cout << "the entire file is in memory";
            delete[] memory_block;
        }else{
            std::cout << "unable to open the file";
       }
        return 0;
    }

通过以上的程序我们将文件的内容放入内存中(char数组),由于无法事先确定文件的大小,所以我们需要动态分配这个数组的大小,一开始我们打开文件的时候我们将position指针放在了文件内容的末尾,当调用tellg的时候我们可以可以得到文件内容的大小。在分配了整个数组后,我们将positon指针放回到文件开始,将文件读入。

  • 缓冲和同步

当我们使用流对象时,在file stream和物理文件之间存在一个类型为streambuf的缓冲对象,例如,我们有一个ofstream的对象,我们调用put的时候每次会向这个中间的buffer插入一个字节,而不是直接插入到与之关联的文件中。

当缓冲区刷新的时候,缓冲区中的内容会写入到与之关联的文件中,当以下事件发生时,缓冲区会刷新:
**文件关闭: **在文件关闭之前,缓冲区中还没有刷新的数据会同步,待处理的数据写入文件或先从文件中读取。
**缓冲区溢出: **当缓冲区溢出的时候会自动同步。
**手动同步: **显式的执行flush或者endl会刷新缓冲区。
**调用成员函数同步: ** 对应的文件流对象调用sync()的方法就可以同步缓存区,当同步失败的时候返回-1,同步成功返回0.


相关资料:
Input/output with files
C++ Primer 5th


Keep focus and have fun

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • https://nodejs.org/api/documentation.html 工具模块 Assert 测试 ...
    KeKeMars阅读 6,297评论 0 6
  • 转自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的猫阅读 2,272评论 0 22
  • C/C++输入输出流总结 前两天写C++实习作业,突然发现I/O是那么的陌生,打了好长时间的文件都没有打开,今天终...
    LuckTime阅读 1,706评论 0 6
  • c++文件操作详解 C++ 通过以下几个类支持文件的输入输出: ofstream: 写操作(输出)的文件类 (由o...
    鲍陈飞阅读 1,771评论 0 2