C++提供了以下到/从文件的类执行输出和输入字符:
- ofstream:写文件的类;
- ifstream:读取文件类
- fstream:读/写文件的类。
这些类都是直接或间接来自类istream和ostream。我们已经使用的对象类型:sin是类istream的对象,cout是类ostream类对象。因此,我们已经使用与文件流相关的类。事实上,我们也可以一样使用文件流,我们已经习惯了使用cin和cout,那我们不得不联想到这些流与物理文件的唯一区别。让我们看一个例子:
// basic file operations
#include <iostream>
#include <fstream>
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一样插入一句话,但使用文件流来代替。
下面一步一步看:
打开文件
在这些类对象上执行的第一个操作是将它与实际文件关联。这个过程被称为打开一个文件。一个打开的文件是代表在程序流(即,一个对象的一个类;在前面的例子中,这是myfile)和任何输入或输出操作在这个流对象实行相关的物理文件。
为了打开一个流对象的文件,我们使用它的成员函数打开:
open (filename, mode);
其中filename文件名是一个字符串,该字符串表示要打开的文件的名称,模式是一个可选参数,具有下列标志的组合:
这些标志可以组合使用按位运算符OR(|)。例如,如果我们想以二进制模式打开文件example.bin并添加数据,我们可以通过下面的调用成员函数open实现:
ofstream myfile;
myfile.open ("example.bin", ios::out | ios::app | ios::binary);
各个类ofstream,ifstream和fstream的open成员函数的参数有默认使用方式,如果文件没有第二个参数:
对于ifstream和ofstream类,ios::in和 ios::out自动填充,即使不包括他们的一个模式传递第二个参数到open成员函数(包括标志位)。
对于fstream,默认值是如果调用函数没有指定任何值仅使用模型参数。如果函数被调用,那么默认参数被覆盖。
以二进制模式打开的文件流执行输入和输出操作独立于其他任何格式。非二进制文件是文本文件 text files,一些变化是可能出现由于格式化一些特殊字符(如换行符和回车字符)。
由于在文件流上执行的第一个任务通常是打开一个文件,这三个类包括一个构造函数,它自动调用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();
一旦这个成员函数被调用,流对象可以被重新用来打开另一个文件,并且该文件可以再次被其他进程打开。
如果想要消灭一个已经处于打来状态文件的对象,那么析构函数自动调用成员函数close来使的文件关闭。
文本文件
文本文件流是那些 有ios::binary二进制标志但不包括在他们的开放模式中。这些文件被设计用来存储文本,因此输入或输出的所有值都会有一些格式转换,这不一定对应于它们的二进制值。
对文本文件的写操作是以同样的方式进行,我们使用cout:
// writing on a text file
#include <iostream>
#include <fstream>
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;
}
结果为:
[file example.txt]
This is a line.
This is another line.
从文件中读取也可以用与cin相同的方式执行:
// reading a text file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main () {
string line;
ifstream myfile ("example.txt");
if (myfile.is_open())
{
while ( getline (myfile,line) )
{
cout << line << '\n';
}
myfile.close();
}
else cout << "Unable to open file";
return 0;
}
最后一个示例读取文本文件并在屏幕上打印其内容。我们已经创建了一个while循环使用getline读取文件的每一行。返回值的函数是参考流对象本身,当得到的这个布尔表达式(在这个while循环中)是true时那么意味着可以操作,如果为false则文件到达末尾或者发生其他的错误。
检查状态标志
下面的成员函数可以用于检查流的特定状态(他们都返回一个布尔值):
bad()
如果读写操作发生错误的时候返回true。例如,如果我们想写一个文件而无法打开或者没有足够的空间去写。
Fail()
和bad()相同的情况时,返回的也是true,除此之外当有错误发生的时候也满足,例如当我们想要读取一个整数的时候却读取到了一个字符。
Eof()
如果读取的文件到达了末尾的时候返回true。
Good()
这是使用最为广泛的一种状态标识位:上述任何一种状态发生的时候都返回true,否则返回false。注意的是good与bad并非相反的操作(good具有更为广泛的状态检查)。
获取并放置流定位
所有的 I/O流对象内部保持至少一个内部位置:
ifstream,像istream保持内部get position使得该元素下一个输入操作能被读。
ofstream,像ostream保持内部put position使得下一个元素可写。
最后,fstream,具有et和put的position,如iostream。
这些内部流位置指向下一个读或写操作执行的流中的位置。这些位置可以使用以下成员函数进行观察和修改:
tellg() and tellp()
这两个成员函数没有参数,返回值的类型是streampos,这是一种代表当前得到的位置get position(在tellg的情况下)或放的位置put position(在tellp的情况)。
seekg() and seekp()
这些函数允许更改get 和put positions。这两个函数重载两个不同的原型。第一种形式是:
seekg ( position );
seekp ( position );
使用此原型,流指针将更改为绝对位置(从文件的开始计数)。此参数的类型是streampos,同类型的函数tellg和tellp返回。
另一种形式是:
seekg ( offset, direction );
seekp ( offset, direction );
使用此原型, get 或put position设置为相对于参数方向确定的某些特定点的偏移值。偏streamoff型。和方向seekdir型,这是一个枚举类型决定点,偏移进行计数,并可以采取下列任何值:
下面的示例使用我们刚才看到的成员函数来获取文件的大小:
// obtaining file size
#include <iostream>
#include <fstream>
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;
}
注意我们使用的begin与end变量的类型:
streampos size;
streampos是一种用于缓冲和文件定位和返回类型file.tellg()的特定类型。此类型的值可以安全地从同类型的其他值中减去,也可以转换为足够大的整数类型,以包含文件的大小。
这些流定位功能使用两种特殊类型:streampos和streamoff。这些类型也被定义为流类的成员类型:
上面的每个成员类型都是非成员等价的别名(它们是完全相同的类型)。使用哪一个没有什么影响。成员类型 member types 更通用,因为它们在所有流对象上都是相同的(即使在使用外来类型的字符的流),但因为历史原因非成员类型被广泛用于现有代码。
二进制文件
对于二进制文件,读取和写数据的提取和插入运算符(< <和> >)函数如getline是没有效率的,因为我们不需要任何的数据格式,数据也不可能以行分开。
文件流包括两个专门设计用来读取和写入二进制数据的成员函数:write和read。第一个(write)是ostream成员函数(继承ofstream)。read是一个成员函数(继承了istream ifstream)。fstream对象有两类。他们的原型是:
write ( memory_block, size );
read ( memory_block, size );
这里memory_block是char*类型(char指针),并代表读取数据元素字节数组的存储地址,或从哪里要写入的数据元素。大小size参数是一个整数值,指定要读取或写入内存块的字符数。
// reading an entire binary file
#include <iostream>
#include <fstream>
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标识符标识文件打开,这意味着得到指针将定位在文件的末尾。这样,当我们调用成员tellg(),我们将直接获得该文件的大小。
一旦我们获得了文件的大小,我们要求分配足够大的内存块来保存整个文件:
memblock = new char[size];
在这之后,我们开始在文件的开头设置get位置(记住我们在结尾用这个指针打开文件),然后我们读取整个文件,最后关闭它:
file.seekg (0, ios::beg);
file.read (memblock, size);
file.close();
在这一点上,我们可以操作从文件中获得的数据。但我们的程序只是使得文件的内容在内存中,然后完成。
缓存和同步
当我们使用文件流,都要和streambuf内部缓冲区对象相关联。这个缓冲对象可能代表作为流和物理文件之间的中介的内存块。例如,ofstream,每次成员函数put(一个字符写入)被调用,这个字符也许被插在这中间的缓冲区而不是直接写入物理文件与流相关的。
操作系统还可以定义用于读取和写入文件的其他缓冲层。
当缓冲区刷新时,包含在它中的所有数据都写入物理介质(如果它是输出流)。此过程称为同步,并在下列任何情况下发生:
(1)当文件关闭时:在关闭文件之前,所有尚未刷新的缓冲区被同步,所有挂起的数据被写入或读取到物理介质中。
(2)缓冲区已满时:缓冲区具有一定大小。当缓冲区已满时,它会自动同步。
(3)明确地,用操纵器:当某些操纵器上使用的流,一个显式的同步发生。这些操纵器为:flush和endl。
(4)明确地,用成员函数sync():调用流的成员函数sync()会立即同步。如果流没有关联的缓冲区或在失败的情况下这个函数返回一个-1的int值。否则(如果流缓冲区成功同步)返回0。