在C++中,对文件的操作是通过stream的子类fstream(file stream)来实现的,所以,要用这种方式操作文件,就必须加入头文件fstream.h

C++ 通过以下几个类支持文件的输入输出:
- ofstream: 写操作(输出)的文件类 (由ostream引申而来)
- ifstream: 读操作(输入)的文件类(由istream引申而来)
- fstream: 可同时读写操作的文件类 (由iostream引申而来)
缓存和同步(Buffers and Synchronization)
当对文件流进行操作的时候,它们与一个streambuf类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作用是流和物理文件的媒介。
例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存中。
当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。这个过程称为同步(synchronization),它会在以下任一情况下发生:
- 当文件被关闭时: 在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。
- 当缓存buffer满时: 缓存Buffers有一定的空间限制,当缓存满时,它会被自动同步。
- 控制符明确指明: 当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush 和 endl。
- 明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败。
在C++中,有一个stream这个类,所有的I/O都以这个"流"类为基础的,包括我们要认识的文件I/O,stream这个类有两个重要的运算符:
1、插入器(<<) 向流输出数据。比如说系统有一个默认的标准输出流(cout),一般情况下就是指的显示器,所以,cout<<"Write Stdout"<<'n';就表示把字符串"Write Stdout"和换行字符('n')输出到标准输出流。
2、析取器(>>) 从流中输入数据。比如说系统有一个默认的标准输入流(cin),一般情况下就是指的键盘,所以,cin>>x;就表示从标准输入流中读取一个指定类型(即变量x的类型)的数据。
打开文件
在fstream类中,有一个成员函数open(),就是用来打开文件的,其原型是:
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::app: 以追加的方式打开文件
ios::ate: 文件打开后定位到文件尾,
ios:app就包含有此属性
ios::binary: 以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文
ios::in: 文件以输入方式打开
ios::out: 文件以输出方式打开
ios::nocreate: 不建立文件,所以文件不存在时打开失败
ios::noreplace:不覆盖文件,所以打开文件时如果文件存在失败
ios::trunc: 如果文件存在,把文件长度设为0
access:打开文件的属性, 取值是:
0:普通文件,打开访问
1:只读文件
2:隐含文件
4:系统文件
这些标识符可以被组合使用,中间以"或"操作符(|)间隔。
例如,如果我们想要以二进制方式打开文件"example.bin" 来写入一些数据,我们可以通过以下方式调用成员函数open()来实现:
ofstream file; file.open("example.bin", ios::out | ios::app | ios::binary);
ofstream, ifstream 和 fstream所有这些类的成员函数open,都包含了一个默认打开文件的方式,这三个类的默认方式各不相同, 参数的默认方式:
ofstream ios::out | ios::trunc
ifstream ios::in
fstream ios::in | ios::out
只有当函数被调用时没有声明"打开文件方式"参数的情况下,默认值才会被采用。如果函数被调用时声明了任何参数,默认值将被完全改写,而不会与调用参数组合。
由于对类ofstream, ifstream 和 fstream 的对象所进行的第一个操作通常都是打开文件,这些类都有一个构造函数可以直接调用open函数,并拥有同样的参数。这样我们就可以通过以下方式进行与上面同样的打开文件的操作:ofstream file ("example.bin", ios::out | ios::app | ios::binary);
两种打开文件的方式都是正确的。
可以通过调用成员函数is_open()来检查一个文件是否已经被顺利的打开了:
bool is_open(); 返回值:true 文件已经被顺利打开,false 则相反。
关闭文件
当文件读写操作完成之后,我们必须将文件关闭以使文件重新变为可访问的。关闭文件需要调用成员函数close(),它负责释放缓存中的数据并关闭文件。
这个函数一旦被调用,原先的流对象(stream object)就可以被用来打开其它的文件了,这个文件也就可以重新被其它的进程(process)所访问了。
为防止流对象被销毁时还联系着打开的文件,析构函数(destructor)将会自动调用关闭函数close。
读写文件
读写文件分为文本文件和二进制文件的读取,对于文本文件的读取比较简单,用插入器和析取器就可以了;而对于二进制的读取就要复杂些。
1、文本文件的读写
类ofstream, ifstream 和fstream 是分别从ostream, istream 和iostream 中引申而来的。这就是为什么 fstream 的对象可以使用其父类的成员来访问数据。
一般来说,我们将使用这些类与同控制台交互同样的成员函数(cin 和 cout)来进行输入输出。
插入器 << 向文件写入
析取器 >> 从文件读取
2、二进制文件的读写
在二进制文件中,使用 << 和 >>,以及函数(如getline)输入和输出数据,没有什么实际意义,虽然它们是符合语法的。
①put()
put()函数向流写入一个字符,其原型是ofstream &put(char ch),使用也比较简单,如file1.put('c');就是向流写一个字符'c'。
②get()
get()函数比较灵活,有3种常用的重载形式:
第一种:就是和put()对应的形式:ifstream &get(char &ch);功能是从流中读取一个字符,结果保存在引用ch中,如果到文件尾,返回空字符。如file2.get(x);表示从文件中读取一个字符,并把读取的字符保存在x中。
第二种:重载形式的原型是: int get();这种形式是从流中返回一个字符,如果到达文件尾,返回EOF,如x=file2.get();和上例功能是一样的。
第三种:原型是:ifstream &get(char *buf,int num,char delim='/n');这种形式把字符读入由 buf 指向的数组,直到读入了 num 个字符或遇到了由 delim 指定的字符,如果没使用 delim 这个参数,将使用缺省值换行符'/n'。
例如: file2.get(str1,127,'A');//从文件中读取字符到字符串str1,当遇到字符'A'或读取了127个字符时终止。
③读写数据块
要读写二进制数据块,使用成员函数read()和write()成员函数,它们原型如下:
read(unsigned char *buf,int num);
write(const unsigned char *buf,int num);
read()从文件中读取 num 个字符到 buf 指向的缓存中,如果在还未读入 num 个字符时就到了文件尾,可以用成员函数 int gcount();来取得实际读取的字符数;而 write() 从buf 指向的缓存写 num 个字符到文件中,值得注意的是缓存的类型是 unsigned char *,有时可能需要类型转换。
状态标志符的验证
bad()
如果在读写过程中出错,返回 true 。例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。
fail()
除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,例如当想要读入一个整数,而获得了一个字母的时候。
eof()
如果读文件到达文件末尾,返回true。
good()
这是最通用的:如果调用以上任何一个函数返回true 的话,此函数返回 false 。
要想重置以上成员函数所检查的状态标志,你可以使用成员函数clear(),没有参数。
获得和设置流指针
我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针:
查询指针位置:ifstream::tellg() 和 ofstream::tellp()
这两个成员函数不用传入参数,返回pos_type类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前get流指针的位置(用tellg)(用于输入流 (ifstream) ,返回读指针 的当前位置)或put流指针的位置(用tellp)(用于输出流 (ofstream) ,返回写指针的当前位置)
移动指针位置:ifstream::seekg() 和 ofstream::seekp()
这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:istream_type& seekg(pos_type position); (用于输入流 (ifstream) ,移动读指针)
istream_type& seekp(pos_type position); (用于输出流 (ofstream) ,移动写指针)
使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置 。要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。
__istream_type& seekg(off_type offset, ios_base::seekdir);
__istream_type& seekp(off_type offset, ios_base::seekdir);
使用这个原型可以指定由参数direction决定的一个具体的指针开始计算的一个位移(offset)。
它们有两种使用方式(即重载):
方式一:移动到绝对位置
cpp
my_stream.seekg(position);
这会将指针移动到从文件开头算起的第 position 个字节处。position 通常是一个之前通过 tellg() 获得的值。
方式二:从特定基准点进行相对位移
cpp
my_stream.seekg(offset, direction);
offset: 你想要移动的字节数。可以是正数(向文件末尾方向移动)或负数(向文件开头方向移动)。
direction 它可以是:
ios::beg 从文件开头 (beginning) 开始。seekg(10, ios::beg) 意味着移动到第10个字节处。
ios::cur 从指针当前 (current) 位置开始。seekg(5, ios::cur) 意味着从现在的位置再向后移动5个字节。
ios::end 从文件末尾 (end) 开始。seekg(-10, ios::end) 意味着移动到距离文件末尾10个字节的位置。
流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,因为文本模式的文件中某些特殊字符可能被修改。由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对 tellg 或 tellp 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。
示例:
cpp
int main ()
{
//获得一个二进制文件的大小
long pos1, pos2;
// 1. 以二进制(ios::binary)和输入(ios::in)模式打开文件
// 必须是二进制模式,才能保证tellg返回的是准确的字节数
ifstream in("example.txt", ios::in|ios::binary);
// 2. 获取初始位置
// 文件刚打开,读指针在文件开头,所以 pos1 的值是 0
pos1 = in.tellg();
// 3. 移动读指针到文件末尾
// 从文件末尾(ios::end)开始,移动0个字节
in.seekg (0, ios::end);
// 4. 再次获取位置
// 此时读指针在文件末尾,tellg()返回的就是整个文件的总字节数
pos2 = in.tellg();
// 5. 关闭文件
in.close();
// 6. 计算并输出大小
// 文件大小 = 末尾位置 - 起始位置 (即 pos2 - 0)
cout << "file size is " << (pos2-pos1) << " bytes.\n";
return 0;
}