C + + 提供了以下类来执行字符到文件的输出和输入:
ofstream
: 写入文件的Stream类ifstream
: 从文件中读取的Stream类fstream
: 包含读和写的Stream类
这些类直接或间接派生自 iststream 和 ostream 类。我们以前使用的: cin 是类 iststream 的对象,cout 是类 ostream 的对象。因此,我们已经使用了与文件流相关的类。实际上,我们可以像使用 cin 和 cout 一样使用文件流,唯一的区别是我们必须将这些流与物理文件关联起来。让我们看一个例子:
// 基本文件操作
#include
#include
using namespace std;
int main () {
ofstream myfile;
myfile.open ("example.txt");
myfile << "Writing this to a file.\n";
myfile.close();
return 0;
}
这段代码创建了一个名为 example.txt 的文件,并按照与 cout 相同的方式向其中插入了一个句子,但是使用了文件流(file stream) myfile。
让我们一步一步来:
通常对这些类中的一个对象执行的第一个操作是将其关联到一个真正的文件。此过程称为打开文件。一个打开的文件在一个程序中由一个流表示(例如,这些类中的一个对象; 在前面的例子中,这是 myfile) ,对这个流对象执行的任何输入或输出操作都将应用到与它相关联的物理文件。
为了用一个流对象打开一个文件,我们使用它的成员函数 open:
open (filename, mode);
其中 filename 是一个字符串,表示要打开的文件的名称,mode 是一个可选参数,由以下标志组合而成:
ios::in |
打开输入操作 |
ios::out |
打开输出操作 |
ios::binary |
以二进制模式打开 |
ios::ate |
设置文件末尾的初始位置。 如果未设置此标志,则初始位置为文件的开头。 |
ios::app |
所有输出操作都在文件末尾执行,将内容追加(appending)到文件的当前内容。 |
ios::trunc |
如果文件已经为输出操作打开并且已经存在,则删除其以前的内容并用新的内容替换。 |
所有这些标志都可以使用位运算符 OR (|)组合。例如,如果我们想要在二进制模式下打开 example.bin 文件来添加数据,我们可以通过以下对成员函数 open 的调用来实现:
ofstream myfile;
myfile.open ("example.bin", ios::out | ios::app | ios::binary);
oftream、 ifstream 和 fstream 类的每个打开成员函数都有一个默认模式,如果文件在没有第二个参数的情况下打开,则使用该模式:
class | default mode parameter |
---|---|
ofstream |
ios::out |
ifstream |
ios::in |
fstream |
ios::in | ios::out |
由于在文件流上执行的第一个任务通常是打开一个文件,因此这三个类包含一个构造函数,它自动调用 open 成员函数,并且具有与这个成员完全相同的参数。因此,我们还可以声明上一个 myfile 对象,并在上一个示例中执行相同的打开操作,方法是:
ofstream myfile ("example.bin", ios::out | ios::app | ios::binary);
要检查文件流是否成功打开了文件,可以通过调用成员函数 is _ open 来完成。如果流对象确实与打开的文件关联,则此成员函数返回一个 bool 值 true,否则返回 false:
if (myfile.is_open()) { /* ok, proceed with output */ }
当我们完成对文件的输入和输出操作时,我们将关闭它,以便通知操作系统并使其资源再次可用。为此,我们调用流的成员函数 close。这个成员函数将刷新相关的缓冲区并关闭文件:
myfile.close();
文本文件(Text file)流是那些在其开放模式中未包含ios::binary
标志的文件流。这些文件旨在存储文本,因此输入或输出到这些文件的所有值都会经历一些格式转换,这些格式转换不一定对应于它们的文本二进制值。
对文本文件的写操作的执行方式与使用 cout 时相同:
// writing on a text file
#include
#include
using namespace std;
int main () {
ofstream myfile ("example.txt");
if (myfile.is_open())
{
myfile << "This is a line.\n";
myfile << "This is another line.\n";
myfile.close();
}
else cout << "Unable to open file";
return 0;
}
执行后:
// 会生成 example.txt 文件, 内容是: This is a line. This is another line.
从文件中读取数据也可以按照与 cin 相同的方式执行:
// reading a text file
#include
#include
#include
using namespace std;
int main () {
string line;
ifstream myfile ("example.txt");
if (myfile.is_open())
{
while ( getline (myfile,line) ) // getline() 是按行读取
{
cout << line << '\n';
}
myfile.close();
}
else cout << "Unable to open file";
return 0;
}
输出结果:
This is a line. This is another line.
最后一个示例读取一个文本文件并将其内容打印到屏幕上。我们已经创建了一个 while 循环,它使用 getline 逐行读取文件。getline 返回的值是对流对象本身的引用,如果流已经准备好进行更多操作,则以布尔表达式(如在这个 while-loop 中)进行计算时,该值为 true; 如果已经到达文件末尾或者发生了其他错误,则为 false。
下列成员函数用于检查流的特定状态(它们都返回 bool 值) :
bad():
fail():
eof():
good():
成员函数 clear ()可用于重置状态标志。
所有 i/o 流对象都保持至少一个内部位置:
这些内部流位置指向流中执行下一个读写操作的位置。可以使用下列成员函数观察和修改这些位置:
tellg() 和 tellp():
这两个没有参数的成员函数返回一个成员类型
streampos
,这是一个表示当前 get位置(在 tellg 情况下)或 put位置(在 tellp 情况下)的类型。
seekg() 和 seekp():
这些函数允许更改 get 和 put 位置的定位。这两个函数都用两个不同的原型重载。第一种形式是:
seekg ( position );
seekp ( position );
streampos
,它与 tell 和 tellp 函数返回的类型相同。这些函数的另一种形式是:
seekg ( offset, direction );
seekp ( offset, direction );
使用这个原型,get 或 put 位置被设置为相对于由参数方向确定的某个特定点的偏移值(offset value)。偏移量(offset
)为是streamoff类型
。方向(direction
)是 Seekdir 类型,它是一个枚举类型,用于确定从哪里开始计算偏移,并且可以采用以下任何值:
ios::beg |
从流开始计算偏移量 |
ios::cur |
从当前位置计算偏移量 |
ios::end |
从流的末端计算偏移量 |
下面的示例使用刚才的成员函数来获取文件的大小:
// obtaining file size
#include
#include
using namespace std;
int main () {
streampos begin,end;
ifstream myfile ("example.bin", ios::binary);
begin = myfile.tellg();
myfile.seekg (0, ios::end);
end = myfile.tellg();
myfile.close();
cout << "size is: " << (end-begin) << " bytes.\n";
return 0;
}
输出结果:
size is: 40 bytes. // 假设 example.bin 是一个 40 byte的文件
注意我们用于变量 start 和 end 的类型:
streampos size;
streampos
是用于缓冲区和文件定位的特定类型,也是 file.tellg ()返回的类型。可以从同一类型的其他值中安全地减去此类型的值,还可以转换为大到足以包含文件大小的整数类型。
这些流定位函数使用两种特殊的类型: streampos
和streamoff
。这些成员类型同样被定义在stream类中:
Type | Member type | Description |
---|---|---|
streampos |
ios::pos_type |
Defined as fpos .It can be converted to/from streamoff and can be added or subtracted values of these types. |
streamoff |
ios::off_type |
It is an alias of one of the fundamental integral types (such as int or long long ). |
上面的每个成员类型都是它的非成员等价物的别名(它们是完全相同的类型)。使用哪一个并不重要。成员类型更为通用,因为它们在所有流对象上都是相同的(即使在使用特殊字符类型的流上也是如此) ,但由于历史原因,非成员类型在现有代码中被广泛使用。
对于二进制文件,使用提取和插入操作符(< < 和 > >)以及 getline 等函数读写数据效率不高,因为我们不需要格式化任何数据,而且数据可能不是按行格式化的。
文件流包括两个成员函数,专门设计用于顺序读写二进制数据: write
和 read
。第一个函数(write)是 ostream 的成员函数(由 ofstream 继承)。Read 是 istream 的成员函数(由 ifstream 继承)。类 fstream 的对象两者都有。他们的原型是:
write ( memory_block, size );
read ( memory_block, size );
Memory _ block 的类型为 char * (指向 char 的指针) ,表示存储读数据元素的字节数组的地址或要写入的数据元素的地址。Size 参数是一个整数值,指定要 读取/写入 内存块的字符数。
// 读取整个二进制文件
#include
#include
using namespace std;
int main () {
streampos size;
char * memblock;
ifstream file ("example.bin", ios::in|ios::binary|ios::ate);
if (file.is_open())
{
size = file.tellg();
memblock = new char [size];
file.seekg (0, ios::beg);
file.read (memblock, size);
file.close();
cout << "the entire file content is in memory";
delete[] memblock;
}
else cout << "Unable to open file";
return 0;
}
在这个例子中,整个文件被读取并存储在一个内存块中:
首先,文件打开时带有 ios: : ate 标志,这意味着 get 指针将定位在文件的末尾。这样,当我们调用 成员 tellg ()时,我们将直接获得文件的大小。
一旦我们获得了文件的大小,我们请求分配一个足够大的内存块来容纳整个文件:
memblock = new char[size];
在这之后,我们继续在文件的开头设置 get 位置(之前我们在末尾用这个指针打开了文件) ,然后我们读取整个文件,最后关闭它:
file.seekg (0, ios::beg);
file.read (memblock, size);
file.close();
此时,我们可以使用从文件中获得的数据进行操作了...
当我们使用文件流操作时,这些文件流与一个 streambuf
类型的内部缓冲对象相关联。这个缓冲区对象可以表示一个内存块,作为流和物理文件之间的中介。例如,对于ofstream,每次调用成员函数 put (写入单个字符)时,字符可以插入到这个中间缓冲区中,而不是直接写入与流相关联的物理文件。
操作系统还可以定义用于读写文件的其他缓冲层。
当刷新缓冲区时,包含的所有数据都写入物理介质(如果它是输出流)。这个过程被称为同步,在下列任何情况下都会发生:
flush
和 endl
.。