九.流

内容参考:

C++文件读写详解(ofstream,ifstream,fstream)_c++ 文件读写-CSDN博客

一. 概述

分类:

在程序设计中,用于输入/输出的流是必不可少的。C++中,依照用途不同,流可以被划分位三种:

  • 标准IO流:内存与标准输入、输出设备间的通信,一般是控制台。
  • 文件IO流:内存与外部文件之间的通信。
  • 字符串IO流:内存变量与字符串流的字符串数组之间的消息传递。

类关系图:

可以看出:

  • ios:所有流的基类,保存流的状态并处理错误。
  • 标准IO流:
    • istream:输入流。C+为该类创建了cin对象,并重载了>>操作符,用于输入。
    • ostream:输出流。C++为该类创建了cout对象,并重载了<<操作符,用于输出。
    • iostream:输入输出流。C++为该类创建了cerr对象,是导出程序错误消息的地方,只允许向屏幕设备写数据。
  • 文件IO流:
    • ifstream:文件输入流类。
    • ofstream:文件输出流类。
    • fstream:文件输入、输出流类。
  • 字符串IO流:串流提供了两套类,一套基于C类字符串char*编写,定义于头文件strstream;一套基于std::string编写,定义于头文件sstream。C++委员会推荐使用后者。
    • 基于std::string编写的:
      • istringstream:串输入流
      • ostringstream:串输出流
      • stringstream:串输入、输出流
    • 基于char*编写的:
      • istrstream:串输入流
      • ostrstream:串输出流
      • strstream:串输入、输出流
  • 缓冲区:包括streambuffilebufstringbuf。流与控制台、文件、字符串之间的读写并不是直接进行的,而是通过一个缓冲区,内容先经过缓冲区,然后缓冲区在一定条件下将内容再传递出去。

二. 文件流

如上文所述,文件流包括三种:

  • ifstream:文件输入流类。
  • ofstream:文件输出流类。
  • fstream:文件输入、输出流类。

这里主要记录fstream流的内容。

头文件:

#include <fstream>

1. 打开文件

成员函数。

函数原型:

void open ( const char * filename,
            ios_base::openmode mode = ios_base::in | ios_base::out );
 
void open(const wchar_t *_Filename,
        ios_base::openmode mode= ios_base::in | ios_base::out,
        int prot = ios_base::_Openprot);
  • filename:文件名,也就是文件的地址。

  • mode:打开文件的方式,定义于所有流的基类ios中,有如下几种方式:

    ios::in 为输入(读)而打开文件
    ios::out 为输出(写)而打开文件
    ios::ate 初始位置:文件尾
    ios::app 所有输出附加在文件末尾
    ios::trunc 如果文件已存在则先删除该文件
    ios::binary 二进制方式

    这些方式之间可以组合使用,使用运算符进行组合,例如:

    #include <fstream>
    ofstream out;
    out.open("Hello.txt", ios::in|ios::out|ios::binary)                 //根据自己需要进行适当的选取
    
  • prot:打开文件的属性,一般很少用到,在流基类ios中定义:

    0 普通文件,打开操作
    1 只读文件
    2 隐含文件
    4 系统文件

注意:

文件相关的流在构造函数中调用了open()函数,因此可以直接使用流对象进行文件的操作。例如:

ofstream out("hello.txt", ios::out);
ifstream in("hello.txt", ios::in);
fstream io("hello.txt", ios::in|ios::out);

判断文件是否打开成功:

成员函数is_open()

#include <fstream>
fstream stream;

stream.open("demo.txt");
// 判断文件是否打开成功
if (!stream.is_open()) {
	cout << "文件打开失败!" << endl;
	system("pause");
	exit(-1);
}

2. 关闭文件

函数:close()

注意:

  • 在使用流读取文件时,该流不能用于读取其余文件,该文件也无法被其余进程访问。在对文件的读写操作结束之后,我们必须要关闭流,从而将缓存中的数据释放出来并关闭文件。
  • 文件相关的流为了避免程序员忘记关闭流,在析构函数中调用了close()函数。

3. 文本文件的读写

3.1 写

文件流类重载了流插入运算符<<,通过流对象和<<运算符,即可写文件。

#include <fstream>
fstream stream;
stream.open("hello.txt", ios::in);
stream << "hello\n";
stream << "world!";
stream.close();

3.2 读

使用流运算符读:

和写类似,读也需要流输出运算符>>

#include <iostream.h>
#include <fstream.h>
#include <string>

int main () {
    std::string buffer;
    ifstream in("test.txt");
    if (! in.is_open())
    { cout << "Error opening file"; exit (1); }
    while (!in.eof() )
    {
        in >> buffer;
        std::cout << buffer << endl;
    }
    return 0;
}

输出:

//结果 在屏幕上输出
 This is a line.
 This is another line

这里我们使用了一个新的成员函数eof(),它是从类ios中继承过来的。当到达文件末尾时,返回true

终止条件:满足以下任一条件,即终止本次读取:

  • 遇到空格。
  • 遇到换行符。
  • 文本结束。

按行来读:

通过成员函数getline(),可以一次读取一行。

该函数定义于istream类中,由于存在继承关系,ifstream继承了该成员函数,该函数再istream中的原型如下:

istream& getline (char* s, streamsize n );
istream& getline (char* s, streamsize n, char delim );
  • 参数:

    • s:用于承载读取的当前行内容。

    • n:每次读取的最大字符数(包含结束标记符)。

    • delim:截至符,即使这一行没有读完,也没有达到最大的字符数,只要遇到这一个字符,就会停止读取。

  • 终止条件:根据用户传入的参数决定,以下四条满足任意一条,结束当前一次读取:

    • 当前行读取完毕。
    • 当前次读取的字符数量已经达到最大。
    • 遇到了用户设置的截至符。
    • 文件已结束。

EG:

#include <fstream>
fstream io;

io.open("text.txt");
char msg[256];		
io.getline(msg, 256);

3.3 校验读写状态

文件流类中有一些成员函数用于校验流的状态:

  • bad():如果在读写过程中出错,返回 true 。例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。
  • fail():除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,例如当想要读入一个整数,而获得了一个字母的时候。
  • eof():如果读文件到达文件末尾,返回true。
  • good():这是最通用的:如果调用以上任何一个函数返回true 的话,此函数返回 false 。

3.4 流指针

在使用流对象读写文件时,至少存在一个流指针,指向文件中的内容。这些指针如下:

  • ifstream:类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。
  • ofstream:类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。
  • fstream,:类似 iostream, 同时继承了get 和 put两个指针。

获取指针:

tellg()tellp()

这两个成员函数不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前get 流指针的位置 (用tellg) 或 put 流指针的位置(用tellp).

设置指针位置:

seekg()seekp()
这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:

  •   seekg ( pos_type position );
      seekp ( pos_type position );
    

    参数:

    position:流指针改变指向后的绝对位置。要求传入的参数类型必须与函数 tellg 和tellp 的返回值类型相同。

    使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置。要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。

  •   seekg ( off_type offset, seekdir direction );
      seekp ( off_type offset, seekdir direction );
    

    参数:

    • offset:偏移量。

    • direction:开始计算偏移的位置:

      • ios::beg:从流开始位置计算的位移。

      • ios::cur:从流指针当前位置开始计算的唯一。

      • ios::end:从流末尾处开始计算的位移。

注意:
流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,因为文本模式的文件中某些特殊字符可能被修改。由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对tellg 或 tellp 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。

EG:使用流指针来获取一个二进制文件的大小:

#include <iostream.h>
#include <fstream.h>

const char * filename = "test.txt";

int main () {
    long l,m;
    ifstream in(filename, ios::in|ios::binary);
    l = in.tellg();
    in.seekg (0, ios::end);
    m = in.tellg();
    in.close();
    cout << "size of " << filename;
    cout << " is " << (m-l) << " bytes.\n";
    return 0;
}

//结果:
size of example.txt is 40 bytes.

4. 二进制文件的读写

在二进制文件中,依旧可以使用<<>>以及函数getline()来进行输入和输出,但这需要我们对二进制进行转化,这相当麻烦。

文件流包含两个为顺序读写数据而特殊设计的成员函数:

  •   write ( char * buffer, streamsize size );
    
    • 来源:是ostream 的一个成员函数,被ofstream所继承。由于存在继承链ostream->iostream->fstream,因此fstream也继承了该成员函数。
    • 参数:
      • buffer:要写入的数据。
      • size:要写入的字符数。
  •   read ( char * buffer, streamsize size );
    
    • 来源:是istream的一个成员函数,被ifstream 所继承。由于存在继承链istream->iostream->fstream。因此fstream也继承了该成员函数。

EG:

// reading binary file
#include <iostream>
#include <fstream.h>

const char * filename = "test.txt";

int main () {
    char * buffer;
    long size;
    ifstream in (filename, ios::in|ios::binary|ios::ate);
    size = in.tellg();
    in.seekg (0, ios::beg);
    buffer = new char [size];
    in.read (buffer, size);
    in.close();

    cout << "the complete file is in a buffer";

    delete[] buffer;
    return 0;
}
//运行结果:
The complete file is in a buffer

5. 缓存与同步

当我们使用流对文件进行操作时,流并没有直接接触到文件,而是通过一个streambuff类型的缓存(buff)和文件进行交互。这个缓存实际上是一块内存空间,它的原理是:

  1. 一个流读/写一个文件n个字符。
  2. n个字符先进入该流的缓存(buff)中。
  3. 缓存(buff)排放(flush)时,它里面的数据会被真正写入文件或读出。这一步被称之为同步(synchronization)。同步会发生的情况如下:
    • 当文件被关闭时:在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。
    • 当缓存buffer 满时:缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。
    • 控制符明确指明:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flushendl
    • 明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败。

三. 字符流

posted @ 2024-02-17 23:31  BinaryPrinter  阅读(10)  评论(0编辑  收藏  举报