如何使用c++中file stream

如何使用c++中file stream_第1张图片

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

  • overview

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

如何使用c++中file stream_第2张图片

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

如何使用c++中file stream_第3张图片

图中表明了各个类的关系,我们主要来看看 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 
    #include 
    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是一个可选的参数,可以从以下组合中选择:

如何使用c++中file stream_第4张图片
fstream mode

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

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

如何使用c++中file stream_第5张图片
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 
    #include 
    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 
    #include 
    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 
    #include 
    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

你可能感兴趣的:(如何使用c++中file stream)