九.流
内容参考:
一. 概述
分类:
在程序设计中,用于输入/输出的流是必不可少的。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
:串输入、输出流
- 基于
- 缓冲区:包括
streambuf
、filebuf
、stringbuf
。流与控制台、文件、字符串之间的读写并不是直接进行的,而是通过一个缓冲区,内容先经过缓冲区,然后缓冲区在一定条件下将内容再传递出去。
二. 文件流
如上文所述,文件流包括三种:
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)
和文件进行交互。这个缓存实际上是一块内存空间,它的原理是:
- 一个流读/写一个文件n个字符。
- n个字符先进入该流的
缓存(buff)
中。 - 当
缓存(buff)
被排放(flush)
时,它里面的数据会被真正写入文件或读出。这一步被称之为同步(synchronization)
。同步会发生的情况如下:当文件被关闭时
:在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。当缓存buffer 满时
:缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。控制符明确指明
:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush
和endl
。明确调用函数sync()
: 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败。