乐趣区

关于c++:c中ifstream及ofstream超详细说明

前文说过,ifstream 是继承于 istream,ofstream 是继承于 ostream,fstream 是继承于 iostream 类,而他们应用的缓冲区类是 filebuf。

对于这些类之间的关系,有趣味能够去查看我之前的文章:
c++ 规范输入输出流关系梳理

1. filebuf 类介绍

filebuf 类又比 stringbuf 类要简单一点,毕竟是对文件进行读写,首先在它的成员变量中找到了这样一条申明:

__file_type         _M_file;

_M_file 就是它外面的文件操作对象,那么探索一下 __file_type 到底是什么类型呢,通过跟代码发现它其实是一个类模板 __basic_file 的 char 实例,而 __basic_file 是围绕一个 FILE类型的指针来进行操作的,而 FILE这个类型大家其实就很相熟啦,它是 c 语言规范库外面操作文件的规范类型,个别是叫做文件指针,所以从这里就能够看出 filebuf 最终其实还是基于 c 语言的 fopen 等这一套文件操作函数去进行解决的。当然啦,在 linux 外面 fopen 其实又是基于它的零碎调用 open 函数来进行的,这个咱们晓得就好,就不再做开展啦。

当看到 fopen 的调用时,其实心田是挺惊喜的,因为 fstream 这一套货色以前也常常应用,然而呢,从来不晓得它的底层到底是怎么实现的,这次总算是晓得了一点。

1.1 filebuf 类构造函数和析构函数

首先看一下 filebuf 的构造函数原型,如下:

// 这里申明了一个无参构造函数
basic_filebuf();
#if __cplusplus >= 201103L
      basic_filebuf(const basic_filebuf&) = delete;
// 这里申明了一个入参为右值援用的挪动构造函数,所谓右值援用,它容许内容从一个 filebuf 对象挪动到另外一个 filebuf 对象,挪动实现当前,源 filebuf 对象将被销毁,不能再被应用,它没有产生任何拷贝的动作
      basic_filebuf(basic_filebuf&&);
#endif
#if __cplusplus >= 201103L
      basic_filebuf& operator=(const basic_filebuf&) = delete;
// 同上,这里申明了一个挪动赋值函数
      basic_filebuf& operator=(basic_filebuf&&);
#endif
// 析构函数敞开文件
virtual
      ~basic_filebuf()
      {this->close(); }

小贴士:=delete 是 c ++11 的用法,在 c ++11 以前要避免类对象被拷贝和赋值,就要把拷贝构造函数和赋值函数申明为 private 的,到了 c ++11 就间接应用 =delete 来进行申明,它意味着这个函数不能被调用,否则编译就会报错。

从下面咱们能够看到 filebuf 类是不容许拷贝和赋值的,否则可能会产生多个对象操作同一个 IO 缓冲,就会导致诸多的问题,它只容许进行挪动,这样能保障任何时候同一个 IO 缓冲只有一个对象能够操作。

应用例子如下:

#include <fstream>
using namespace std;

int main()
{
    filebuf buf;
    filebuf buf2(move(buf));// 挪动构造函数,std::move 用于生成一个绑定在 buf 上的 filebuf&&
    filebuf buf3 = move(buf);// 挪动赋值函数
    return 0;
}

1.2 open 函数

函数原型如下:

//is_open 用于判断文件是否是关上状态,返回 true 则示意文件是关上状态,否则就是敞开状态      
bool
      is_open() const throw()
      {return _M_file.is_open(); }
//__s 示意文件名,__mode 示意文件打开方式
      __filebuf_type*
      open(const char* __s, ios_base::openmode __mode);
#if __cplusplus >= 201103L
// 同上,只是文件名应用了 string 示意而已
      __filebuf_type*
      open(const std::string& __s, ios_base::openmode __mode)
      {return open(__s.c_str(), __mode); }
#endif
// 敞开文件
      __filebuf_type*
      close();

应用案例如下:

#include <fstream>
#include <iostream>
using namespace std;

int main()
{
    filebuf buf;
    if (buf.open("aaa.txt", ios_base::in) == nullptr )
    {
        cout << "file open failed" << endl;
        return -1;
    }
    if (buf.is_open())
    {cout << "file is opened" << endl;}
    else
        cout << "file is closed" << endl;
    
    buf.close();// 能够调用也能够不调用,析构函数会主动敞开

    return 0;
}

还有其余函数这里就不多做介绍了,实践上来讲,咱们并不会间接应用 filebuf,因为它只是一个工具人,是幕后奉献者,是藏于暗中滴,大多数时候,咱们都是间接应用 ifstream 和 ofstream。

2. ifstream 类

2.1 构造函数和析构函数

ifstream 的构造函数除了默认无参构造函数以外,还基于 filebuf 的 open 函数申明了另外两个构造函数,fstream 头文件中原型如下:

// 默认的无参构造函数
basic_ifstream() : __istream_type(), _M_filebuf()
      {this->init(&_M_filebuf); }   
// 基于 filebuf 的 open 函数,申明了两个有参构造函数,默认是从文件读取数据
explicit
      basic_ifstream(const char* __s, ios_base::openmode __mode = ios_base::in)
      : __istream_type(), _M_filebuf()
      {this->init(&_M_filebuf);
    this->open(__s, __mode);
      }

#if __cplusplus >= 201103L
      explicit
      basic_ifstream(const std::string& __s,
             ios_base::openmode __mode = ios_base::in)
      : __istream_type(), _M_filebuf()
      {this->init(&_M_filebuf);
    this->open(__s, __mode);
      }
    // 同样的,拷贝构造函数不容许应用
      basic_ifstream(const basic_ifstream&) = delete;

      basic_ifstream(basic_ifstream&& __rhs)
      : __istream_type(std::move(__rhs)),
      _M_filebuf(std::move(__rhs._M_filebuf))
      {__istream_type::set_rdbuf(&_M_filebuf); }
#endif
      ~basic_ifstream()
      { }
#if __cplusplus >= 201103L
      basic_ifstream&
      operator=(const basic_ifstream&) = delete;

      basic_ifstream&
      operator=(basic_ifstream&& __rhs)
      {__istream_type::operator=(std::move(__rhs));
    _M_filebuf = std::move(__rhs._M_filebuf);
    return *this;
      }

ifstream 的拷贝构造函数和赋值函数也是间接被禁用的,那么再调用有参的构造函数后,默认的文件就被关上了,无需再次调用 open 函数,能够看到它的析构函数是什么都没有做的,所以 ifstream 须要显式的调用 close 函数,如果不显式调用的话,filebuf 对象也会主动调用析构函数敞开文件,但如果 filebuf 调用 close 失败,就没方法晓得以后流的状态了。

2.2 swap 和 rdbuf 函数

函数原型如下:

// 替换两个 ifstream
void swap(basic_ifstream& __rhs);
// 返回一个指向以后 filebuf 缓冲区的指针
__filebuf_type*
      rdbuf() const;

一个应用案例如下:

#include <fstream>
#include <iostream>
#include <string.h>
using namespace std;

int main()
{char szIn[32] = {0};
    char szIn2[32] = {0};
    ifstream in("aaa.txt", ios_base::in);//aaa.txt 内容:0123456789
    ifstream in2("file.txt");// 应用默认打开方式, file.txt 内容 abcdefg 带换行符
    cout << "file aaa.txt size is" << in.rdbuf()->in_avail() << endl;
    in.read(szIn, 3);
    in2.read(szIn2, 3);
    cout << "in's content is "<< szIn << endl <<"in2's conteng is" << szIn2 <<endl;
    in.swap(in2);
    memset(szIn, 0x00, sizeof(szIn));
    memset(szIn2, 0x00, sizeof(szIn2));
    in.read(szIn, sizeof(szIn));
    in2.read(szIn2, sizeof(szIn2));
    cout << "in's content is "<< szIn << endl <<"in2's conteng is" << szIn2 <<endl;
    
    in.close();
    in2.close();

    return 0;
}

in_avail 是 streambuf 类外面的一个函数,用于返回以后缓冲区长度。

编译后执行后果如下:

[root@mylinux ~]# ./a.out 
file aaa.txt size is 10
in's content is 012
in2's conteng is abc
in's content is defg

in2's conteng is 3456789
[root@mylinux ~]#

通过后果能够看到 swap 函数并不会重置以后读的地位,根本是依照原样替换过去的。

2.3 open 和 close 函数

函数原型如下:

      bool
      is_open();
      bool
      is_open() const;
      void
      open(const char* __s, ios_base::openmode __mode = ios_base::in);
      void
      open(const std::string& __s, ios_base::openmode __mode = ios_base::in);
      void
      close()
      {if (!_M_filebuf.close())
      this->setstate(ios_base::failbit);
      }

open 函数和 is_open 函数用法与 filebuf 类基本一致,这里不再多说,次要是 close,咱们能够看到它调用失败当前,会将以后流状态置为失败,所以规范的做法,还是显式的调用一下 close 函数比拟好。

2.4 ofstream 类和 fstream 类

ofstream 用于往文件写入数据,除了结构和调用 open 函数的时候,默认的关上模式是 ios_base::out,其余所有函数应用都与 ifstream 截然不同,且用法也是一样的,包含 fstream 的函数用法,也是一样的,只是 fstream 默认关上模式是 ios_base::in | ios_base::out,其余函数的用法这里不再多说。

总之,咱们要记住,如果要从文件读取数据,那么应用 ifstream,如果要将数据写到文件,那么应用 ofstream,如果既要读又要写,那么应用 fstream。

3. stream 流关上模式

前文说到文件打开方式 ios_base::in 和 ios_base::out,实际上流打开方式不只这两种,上面就一一阐明一下:

流关上模式 阐明
ios_base::app 以追加形式关上文件,文件关上当前以后指针地位间接指到缓冲区最初面,总是从文件开端写入
ios_base::ate 关上时文件指针地位指向文件开端,但能够手动扭转指针地位,进而从以后地位开始写入
ios_base::binary 文件关上后在二进制模式下进行读写,依据我的察看,须要与 ios_base::in 或者 ios_base::out 联结应用,否则关上失败
ios_base::in 以只读形式关上文件,若文件存在则默认从最开始读取,如果文件不存在,则文件会关上失败
ios_base::out 以只写形式关上文件,若文件存在,则清空文件内容,若文件不存在,则创立一个空文件
ios_base::trunc 每次关上文件都间接清空文件内容,个别用于写入

在应用的时候,以 ’|’ 分隔,例如:ios_base::in|ios_base::out

上面列几种不同场景下应用时的组合,如下:

以上场景都是针对文本文件进行读写的,如果想以同样的形式操作二进制文件,则在每个场景前面追加一个 ios_base::binary 即可。

这里在应用 ifstream 的时候要留神,不论是构造函数关上还是手动调用 open 函数关上文件,它在咱们指定的文件模式上会另外追加一个 ios_base::in,如下:

void
      open(const char* __s, ios_base::openmode __mode = ios_base::in)
      {if (!_M_filebuf.open(__s, __mode | ios_base::in))
      this->setstate(ios_base::failbit);
    else
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // 409. Closing an fstream should clear error state
      this->clear();}

同理,ofstream 则会默认追加一个ios_base::out,但 fstream 则没有默认追加,不指定就默认ios_base::in|ios_base::out,如果指定了模式,则以指定的为准。

不晓得大家有没有发现一点,这里咱们始终没有说到 ios_base::ate 的应用场景,是认为我始终想不进去有用这个的必要性,惟一的场景是想关上文件的时候指针指向开端,而后续又可能从其余地位进行读写,就应用它,但在应用 ofstream 的时候如果指定了 ios_base::ate, 它又会因为默认追加的ios_base::out 而清空文件,这时惟一的办法是应用 fstream 类,并指定模式为ios_base::ate|ios_base::in|ios_base::out,这样才既没有清空文件,也满足了咱们的应用场景。

退出移动版